Next: , Previous: Example 2, Up: Examples


16.3 Detecting use of free memory

This next example illustrates how the mpatrol library is able to check to see if anything has been written into free memory. The test is located in tests/fail/test4.c and simply writes a single byte into free memory.

     23  /*
     24   * Allocates a block of 16 bytes and then immediately frees it.  A
     25   * NULL character is written into the middle of the freed memory.
     26   */
     
     
     29  #include "mpatrol.h"
     
     
     32  int main(void)
     33  {
     34      char *p;
     
     36      if (p = (char *) malloc(16))
     37      {
     38          free(p);
     39          p[8] = '\0';
     40      }
     41      return EXIT_SUCCESS;
     42  }

The following output was produced as part of mpatrol.log. Note that this test was run using the same MPATROL_OPTIONS settings as the last example, but make sure that PRESERVE is not set.

     ERROR: [FRDCOR]: freed allocation 0x08062F54 has memory corruption at 0x08062F5C
             0x08062F5C  00555555 55555555                    .UUUUUUU
     
         0x08062F54 (16 bytes) {free:52:0} [main|test4.c|38]
             0x08049456 main+70
             0x4007C9CB __libc_start_main+255
             0x08049381 _start+33

The library was able to detect that something had been written into free memory and could report on the memory allocation that was overwritten. However, these checks are only performed whenever a function in the mpatrol library is called if the CHECK option is used, or at the end of program execution. In the example above, the code which wrote into free memory could have been miles away from where the library detected the error since we were not using the CHECK option. However, adding CHECK=- to the MPATROL_OPTIONS environment variable doesn't really help much since the next mpatrol function that is called is the one to terminate the library anyway.

Note that using the CHECK option is equivalent to calling __mp_check() when each mpatrol library function is called, or at the range and frequency specified in the values passed to the CHECK option. If you suspect that heap corruption is occurring in a part of your code where there is a large gap between mpatrol library calls, you can try to narrow the problem down by adding a few calls to __mp_check().

On platforms that support memory protection, the library also supports the PAGEALLOC option. This option instructs the library to force every single memory allocation to have a size which is a multiple of the system page size. Although the library still stores the original requested size, it effectively means that no two memory allocations occupy the same page of memory. It can then use page protection (which only operates on pages of memory) to protect all free memory from being read from or written to, and uses similar features to install a page of overflow buffer on either side of the allocation.

However, if the requested size for the memory allocation was not a multiple of the page size this means that there will still be unused space left over in the allocated pages. This problem is solved by turning the unused space into overflow buffers that will be checked in the normal way. The positioning of the allocation within its pages is also important. If you want to check for illegal reads from the borders of the memory allocation, unless it fits exactly into its pages then there is a chance that a program could illegally read the right-most overflow buffer if the allocation was left-aligned, or vice-versa. Two settings therefore exist for the PAGEALLOC option: LOWER and UPPER. They refer to the placement of every memory allocation within its constituent pages.

The following diagram illustrates the PAGEALLOC option. In the diagram, the system page size is assumed to be 16 bytes (very unlikely, but will serve for this example) and each character represents 1 byte.

     x = allocated memory
     o = overflow buffer (filled with the overflow byte)
     . = overflow buffer page (read and write protected)
     
     PAGEALLOC=LOWER, allocation size is 16 bytes or
     PAGEALLOC=UPPER, allocation size is 16 bytes:
         ................xxxxxxxxxxxxxxxx................
     
     PAGEALLOC=LOWER, allocation size is 8 bytes:
         ................xxxxxxxxoooooooo................
     
     PAGEALLOC=UPPER, allocation size is 8 bytes:
         ................ooooooooxxxxxxxx................

In our original example, if the PAGEALLOC=LOWER option is added to the MPATROL_OPTIONS environment variable then the following error will be produced instead of the original error.

     ERROR: [ILLMEM]: illegal memory access at address 0x081C6008
         0x081C6000 (16 bytes) {free:52:0} [main|test4.c|38]
             0x08049456 main+70
             0x4007C9CB __libc_start_main+255
             0x08049381 _start+33
     
         call stack
             0x0804945F main+79
             0x4007C9CB __libc_start_main+255
             0x08049381 _start+33

On systems that support memory protection, the mpatrol library has a built-in signal handler which catches illegal memory accesses and terminates the program. In the above case, the freed memory was made write-protected and so could not be written to. The underlying virtual memory system in the operating system noticed this and signaled this to the library immediately after it happened.

Along with the details of the freed memory allocation that was being written to, the library also attempts to display the function call stack for the location in the program that caused the illegal memory access, although this can be quite unreliable. A better solution would be to run the program in a debugger to catch the illegal memory access.

Note that the PAGEALLOC option also modifies the behaviour of the NOFREE and PRESERVE options when used together. The memory allocation being freed will always be made write-protected when the PRESERVE option is used, otherwise it will also be made read-protected to prevent further accesses.

Note also that the PAGEALLOC=UPPER option is potentially much less efficient at catching illegal memory accesses than the PAGEALLOC=LOWER option. This is due to alignment requirements, since an allocation of 1 byte requiring an alignment of 16 bytes cannot be placed at the very end of a page of size 4096 bytes. The following diagram illustrates this, using the same page size as the last diagram.

     x = allocated memory
     o = overflow buffer (filled with the overflow byte)
     . = overflow buffer page (read and write protected)
     
     PAGEALLOC=UPPER, allocation size is 16 bytes, alignment is 8 bytes:
         ................xxxxxxxxxxxxxxxx................
     
     PAGEALLOC=UPPER, allocation size is 3 bytes, alignment is 1 byte:
         ................oooooooooooooxxx................
     
     PAGEALLOC=UPPER, allocation size is 3 bytes, alignment is 8 bytes:
         ................ooooooooxxxooooo................

Everything is OK until the last allocation, where the alignment requirement means that there must be two overflow buffers. This slows down program execution since the library must check an additional overflow buffer, and also means that the program would have to read six bytes beyond the end of the allocation before the illegal memory access would be detected.