Next: , Previous: General errors, Up: Using mpatrol

7.4 Overwrites and underwrites

Once a block of memory has been allocated, it is imperative that the program does not attempt to write any data past the end of the block or write any data just before the beginning of the block. Even writing a single byte just beyond the end of an allocation or just before the beginning of an allocation can cause havoc. This is because most malloc libraries store the details of the allocated block in the first few words before the beginning of the block, such as its size and a pointer to the next block. The mpatrol library does not do this, so a program which failed using the normal malloc library and worked when the mpatrol library was linked in is a possible candidate for turning on overflow buffers.

Such memory corruption can be extremely difficult to pinpoint as it is unlikely to show itself until the next call is made to the malloc library, or if the internal malloc library blocks were not overwritten, the next time the data is read from the block that was overwritten. If the former is the case then the next library call will cause an internal error or a crash, but only when the memory block that was affected is referenced. This is likely to disappear when using the mpatrol library since it keeps its internal structures separate, and write-protects them on systems that support memory protection.

In order to identify such errors, it is possible to place special buffers1 on either side of every memory allocation, and these will be pre-filled with a specified byte. Before every mpatrol library call, the library will check the integrity of every such overflow buffer in order to check for a memory underwrite or overwrite. Depending on the number of allocations and size of these buffers, this can take a noticable amount of time (which is why overflow buffers are disabled by default), but can mean that these errors get noticed sooner. The option which governs this is OFLOWSIZE. The byte with which they get pre-filled can be changed with OFLOWBYTE. Depending on what gets written, it might only be possible to see such errors when a different size of buffer or a different pre-fill byte is used.

Note that you may wish to change the OFLOWBYTE from its default value on your system so that the contents can be filled with values that are least likely to be used at run-time. For example, ensuring that the pointer representation of the value can never be a valid pointer, or that the floating point representation will always be invalid. These values will vary across operating systems and processor architectures, but may also vary depending on the datatypes that you will be expecting to store in the memory allocations.

A worse situation can occur when it is only reads from memory that overflow or underflow; i.e. with the faulty code reading just before or just past a memory allocation. These cannot be detected by overflow buffers as it is not possible using conventional means to interrupt every single read from memory. However, on systems with virtual memory, it is possible to use the memory protection feature to provide an alternative to overflow buffers, although at the added expense of increased memory usage.

The PAGEALLOC option turns on this feature and automatically rounds up the size of every memory allocation to a multiple of the system page size. It also rounds up the size of every overflow buffer to a multiple of the system page size so that every memory allocation occupies its own set of pages of virtual memory and no two memory allocations occupy the same page of virtual memory. The overflow buffers are then read and write protected so that any memory accesses to them will generate an error2. Following on from the previous section, the PAGEALLOC option also causes free memory to be read and write protected as well since that will also occupy non-overlapping virtual memory pages.

The remaining memory that is left over within an allocation's pages is effectively turned into traditional overflow buffers, being pre-filled with the overflow byte and checked periodically by the mpatrol library to ensure that nothing has written into them. However, because of this remaining memory, the library has a choice of where to place the memory allocation within its pages. If it places the allocation at the very beginning then it will catch memory underwrites, but if it places the allocation at the very end then it will catch memory overwrites. Such a choice can be controlled at run-time by supplying an argument to the PAGEALLOC option. If PAGEALLOC=LOWER is used then every allocation will be placed at the very beginning of its pages and if PAGEALLOC=UPPER is used then the placement will be at the very end of its pages. This is probably better explained in Example 3 (see Example 3) where the problems with PAGEALLOC=UPPER and alignment are also discussed.

Obviously, there are still some deficiencies when using PAGEALLOC since it can use up a huge amount of memory (especially with NOFREE) and the overflow buffers within an allocation's pages can still be read without causing an immediate error. Both of these deficiencies can be overcome by using the OFLOWWATCH option to install software watch points instead of overflow buffers, but there are still very few systems that support software watch points at the moment, and it can slow a program's execution speed down by a factor of around 10,000. The reason for this is that software watch points instruct the operating system to check every read from and write to memory, which means that it has to single-step through a process checking every instruction before it is executed. However, this is a very thorough way of checking for overflows and is unlikely to miss anything, although there may be problems with misaligned memory accesses when using watch points (see Virtual memory).

Note that from release 1.1.0 of mpatrol, the library comes with replacement functions for many memory operation functions, such as memset() and memcpy(). These new functions provide additional checks to ensure that if a memory operation is being performed on a memory block, the operation will not read or write before or beyond the boundaries of that block.

Normally, if an error is discovered in the call to such functions, the mpatrol library will report the error but prevent the operation from being performed before continuing execution. If the error was that the range of memory being operated on overflowed the boundaries of an existing memory allocation then the ALLOWOFLOW option can be used to turn the error into a warning and force the operation to continue. This behaviour can be desirable in certain cases where third-party libraries are being used that make such calls but the end result does not overflow the allocation boundary.

From release 1.3.3 of mpatrol, the library comes with functions that interface to the -fcheck-memory-usage option of the GNU compiler. This option instructs the compiler to place error-checking calls before each read or write to memory. The functions that are called then check to ensure that the memory access does not overflow a heap memory allocation or access free memory. This can be a very useful way to go through your code looking for errors with a fine tooth-comb, but be aware that it does slow down execution by a large factor. It also only affects functions that were compiled with this option, so if the problem lies in a function that was not recompiled with -fcheck-memory-usage then it won't do much good.

To conclude, if you suspect your program has a piece of code which is performing illegal memory underwrites or overwrites to a memory allocation you turn on the CHECK=- option and you should use each of the following options in sequence, but only if your system supports them. If all else fails and you are using the GNU compiler then you could try recompiling some or all of your code with the -fcheck-memory-usage option.



[1] Commonly known as overflow buffers or fence posts.

[2] This is a feature that was first used by Electric Fence (see Related software) to track down memory corruption.