How to store cyber security data using MMAP in C

In the previous article, I described the importance of having fast access to a memory that is also being automatically persisted to the file system to use more space than the actual RAM size and, at the same time, to avoid data loss, when the program is being restarted or killed. This functionality can be for big data, where you more or less randomly access them, achieved using the system function called MMAP. It needs to be properly instructed with flags and provided with the descriptor to the already created large file with its size being a multiple of the memory page. Before continuing, please refer to the previous article for further clarification.

The best thing about the return value of the MMAP is that it is really a pointer to an allocated memory, where you can store any type of object like structures. In the cyber security area, you usually want to have data organized into structures that are time and dimension specific. What do I mean by that? Well, when we are monitoring user behavior, we usually want to have the data organized into “chunks” by the time the user action takes place. The “chunk” can be 10 seconds or 5 minutes long based on the types of analysis we want to perform. On top of that, each “chunk” is specific to the dimension, which in our case can be the user name or other possible dimensions somehow related to the identity of the user and their actions. These “chunks” are then usually organized into larger blocks due to performance. Let me explain it more.

The data, that come from monitoring the activity of an entity like the user, are usually sequential from the time perspective. Imagine, that in the time zero, there are a few documents describing the user behavior, then in the time 5 seconds after time zero there are user activities etc. There are usually gaps, but they are not big. Analysis then watches the data from subsequent times and performs aggregations like sum, mean spike and so on. In order for the analysis to be really fast, the sequential data need to be located in the memory one after the other based on their time. If the analysis should jump from one block of the memory to another with completely different offsets, the performance would be lost just on the hopping between memory offsets. That is why each “chunk” must be stored in larger segments, which can be seen as arrays of sequential “chunks” (meaning, the first “chunk” represents data from some time zero, the second “chunk” data from time 5 after zero, the third “chunk” data from time 10 etc.).

The following sample shows this idea in a very simple way. Each “chunk” is simply an array of characters with some maximum length. This array can be filled with IDs of user activities separated by commas. Then every segment, which is the structure actually being stored in the memory, is just an array of “chunks” with some metadata (start time of the segment in this case):

It is always better to define fixed size of arrays, if possible. There are many security implications for having dynamic arrays, especially when their size is dependent on user input. The same applies for strings, that is why I always use the snprintf function in the samples. Another important thing is having the numeric types with fixed size (uint64_t instead of long int) to avoid compatibility issues.

Now, to put it all together, let us take the sample from the previous article and extend it with the idea of segments being stored in MMAP. The file, which the memory is mirrored to, should be big enough to store 3 segments (i. e. larger than sizeof(struct segment) * 3). Look at the following modified sample:

There should be additional checks for every function to see if the opening of the file, MMAP allocation etc. was successful, but for the purposes of the sample I decided to avoid them. The memory obtained from MMAP is cast to an array of segments (struct segment *). Then we are able to read the address of each segment (segment2 = &segments[1];), print its data and modify them. The sample inserts the first three “chunks” to the chunk array using snprintf (snprintf(segment1->user_chunks[0] …). On the first run, the application prints following output:

Now, the first segment starts at ‘1650968627’.

The segment was modified and automatically synchronized to the map file. So after the second run, you get the modified value printed to the console’s standard output. The data are the first three modified “chunks” with user activity IDs separated by commas inserted in the previous run:

Now, the first segment starts at ‘1650968671’.

Please, feel free to modify the sample to insert more chunks or modify other segments. You can work with them as with normal structs and since they are automatically mirrored to the file, you do not have to worry about their deallocation. In some of the following articles, I would like to focus on more possibilities that come with MMAP, like, for instance, using MMAP in Python for data science tasks.

Leave a Reply

Your email address will not be published.