Red Zone Memory Allocator under Mach 3.0

Tero Kivinen <kivinen@hut.fi>,

Helsinki University of Technology

Thursday, April 9, 1992

Abstract

This article describes an implementation of Redzal, a red zone memory allocator using virtual memory features of mach 3.0. This malloc replacement is neither efficient nor portable, and its current implementation uses huge amounts of memory, but it serves excellently its primary purpose: it can detect bugs.

1.0 Why is a red zone memory allocator needed?

1.1 Overwriting data

One of the most common errors in C-programs is illegal use of allocated memory. For example, a program allocates a buffer for an input stream and then reads data to it. If the data does not fit to the allocated area and the program does not check this, the read can overwrite important data located after the buffer. When this data is later used, random errors can occur at places that are not related to the place where the actual error occurred. Had the error been detected right away, fixing it would have taken only a few seconds - now it can take hours. To help debugging this, we need a good debugging malloc.

1.2 Reading out of bounds data

Older debugging malloc packages check after every malloc operation that nobody has overwritten data between allocated areas. This will detect the errors described above, but it will not detect illegal reads outside the allocated area.

The following example was found in the GNU C-compiler (gcc) by this malloc implementation. When compiling itself gcc generates from a machine description file several C-source files, which form the machine depended part of the compiler. One of these generators reads a line from the machine description file and checks if the last character in line is '*'. Unfortunately, it didn't check if line could be empty, so when it did "if (line [ strlen( line ) - 1 ] == '*')" it tested a character in front of the allocated area. This character could have been '*' and so this generator could have generated erroneous code, later causing gcc to generate invalid code. Finding this kind of bugs without a red zone memory allocator would be very difficult.

1.3 Other errors

Other types of malloc-related errors are memory leaks, accessing uninitialised, freed or reallocated data. This implementation will detect all accesses to freed or reallocated data, and offers some help to find memory leaks.

2.0 What is a red zone memory allocator?

A red zone memory allocator (RZMA) creates an area called the red zone around legal data, and detects reads or writes to it. Because allocation routines are often used to allocate arrays of data, the size of the red zone should be at least the size of one element of the allocated array. In this way the RZMA will detect common "off by one" errors when the program tries to accesses elements beyond or before the allocated array. In this implementation, the size of the red zone is at least one page (4 kB) and its can be configured by the user when the program is started.

When the program accesses memory outside the allocated data, the allocator will need a method to find the block the program tried to access. This type of illegal access could also have been a random pointer dereference and no allocated block was involved. This problem is solved in this implementation by creating an empty zone between allocated blocks (which are surrounded by the red zone). So if the red zone is accessed the RZMA displays the nearest data block, and if the empty zone is accessed then it was probably just a random access, so no block is displayed. By creating larger empty zones the RZMA can identify more random accesses, but it will waste much more virtual address space.

In case of large programs (gcc, zsh, etc.) it will run out of 32-bit virtual memory quite soon, if larger empty zone or red zone is used. Every malloc will need virtual address space of 2 * red zone size + real data size (rounded up to page size) + empty zone size. This means that the allocator can do about 125 000 mallocs when using 4 kB zones, but when using 64 kB zones there can be only 10 000 allocated blocks, assuming normal 32 bit address space. Zsh, for example, uses about 5 000 blocks just to start up, and when loading the command line history etc., its size can easily be doubled.

Picture 1. Virtual address space of an RZMA process.

The virtual address space of the RZMA task is shown in picture 1. The addresses shown there are for typical mach task, that is run under unix emulation. For clarity only two allocated blocks are shown. For real applications there usually are much more data areas, and their size could be anything from 1 page to size of the virtual memory.

3.0 Implementation of the Redzal (Red Zone Allocator)

3.1 Initialisation

The Redzal can be configured by xmalloc_config() function. This function allows a programmer to set the sizes of the red zone, empty zone and the reserved area. This function is also used to set the error detection mode of Redzal. In current implementation the Redzal can be in two different mode, XMALLOC_CHECK_BOTTOM and XMALLOC_CHECK_TOP. In future we are implementing two more modes. More about the modes of the Redzal in chapter 6.1.

Error detection mode can only be set before any calls to xmalloc function. When xmalloc is called first time it will initialise itself using current mode, after that the mode cannot be changed anymore.

In the initialisation the Redzal will then try to locate the largest contiguous area of available virtual memory. Redzal will leave some empty space called Reserved area in the beginnign of this located space, and after the reserved area it will allocate enough space for its own tables. The rest of the area is used for the allocated blocks.

3.2 Exceptions

If the xmalloc_debugger variable is false, will create a separate exception thread for the current thread. Otherwise the debugger is supposed to handle exceptions. When an exception is raised, the exception thread calls xmalloc_fault() to print information about exception. If the exception was EXC_BAD_ACCESS, xmalloc_fault() routine will wait for 60 seconds to give the programmer time to start a debugger and attach to the task. After 60 seconds, the program will abort and dump core. If the exception is not EXC_BAD_ACCESS it is forwarded to the original exception handler (normally the unix server).

3.3 Allocation

Due to limitations of the hardware virtual memory system in the conventional machines Redzal can only detect either top or bottom violations of allocated block, not both. This is because the hardware will only allow permissions to be set on a per page basis and not per byte. Redzal has to allocate at least one page of memory with read and write permissions for the data, and another pages around the data pages, for the red zones with no access permission. Because users can allocate blocks of any size Redzal cannot check both the top and bottom limits of the blocks simultaneously. If it is configured to detect reads and writes before the start of the block, it will align the block so that it will start from the beginning of the page (see picture 2). This mode is called XMALLOC_CHECK_BOTTOM. When checking the end of the block, it will align the block so that it will end at the page boundary (see picture 3). This mode is called XMALLOC_CHECK_TOP.

Redzal can currently only be configured to one of these two modes. The default value is to select randomly either XMALLOC_CHECK_TOP or XMALLOC_CHECK_BOTTOM.

3.4 Freeing data

When data is freed the pages used for it are deallocated, and the memory is immediately returned to the operating system removing it from the address space of the user program. Information about the freed block will still be in the data block table, and the virtual memory area used by the block is left reserved. Redzal will store the address of the call to free(), so it can print it if the program tries to access a freed block.

3.5 Reallocating data

Redzal will always allocate a new block for reallocated data and move the data there. After that, the old block is freed, so Redzal will detect all accesses to the old location of data.


3.6 Illegal access

When Redzal detects an illegal operation it will display the following information:

xmalloc: Accessed freed data

xmalloc: code = 1 (1) [Invalid access]

xmalloc: Information about accessed data:

xmalloc: Object start address = 0x402000 (4202496)

xmalloc: Object size = 0x202 (514)

xmalloc: Mallocated from = 0x10040 (-)

xmalloc: Freed from = 0x1044f (-)

xmalloc: Accessed address = 0x402002 (4202498)

xmalloc: Called from = 0x10899 (-)

You have 60 seconds time to attach this process before abort

This contains enough information for the programmer to find the bug and fix it. If more information is needed the programmer can use a debugger and attach to the task.

4.0 Special features

4.1 Changing the protection of block

The programmer can change the protection of any allocated block with xmalloc_setopt(). Available permissions are XMALLOC_PERM_RW (the default value), XMALLOC_PERM_RO (for read only data) and XMALLOC_PERM_NA (no access). xmalloc_getopt() can be used to test permissions of a block. This feature can be used to detect illegal modifications or reading of data in the following way.

If the programmer knows that somewhere in the program some data is overwritten, the programmer can remove write permissions from the overwritten block and allow writing to the block only in the proper places. If the program tries to modify the block outside those explicit places this will be detected.

4.2 Permanent and temporary blocks

When a block is created it is marked as temporary by default. The programmer can change the state of the block to permanent by xmalloc_setopt using the flavour XMALLOC_PERMANENT, and back to temporary by the flavour XMALLOC_TEMPORARY. If a block is permanent it is illegal to free it. With xmalloc_dump() the programmer can print information about all temporary blocks or about all blocks. This feature can be used to find memory leaks.

4.3 Malloc breakpoint

Redzal has one special user settable breakpoint (xmalloc_break_if_touched) that can be set to contain start of any allocated block. When Redzal does any operations (malloc, free, realloc, etc.) involving this block, it calls the function xmalloc_break(). This function will print an informative message telling what the program tried to do and then continue normally. By setting normal a breakpoint in this function the programmer can break every time the program does something to a specific block. To use xmalloc_break_if_touched the programmer runs the program normally until he can find the address of the offending block (either by knowing it beforehand from previous executions or by detecting the allocation of the block), then by setting the offending address to the xmalloc_break_if_touched variable and setting breakpoint to xmalloc_break(). Redzal will break first when block is allocated, next when it is freed, resized etc.

5.0 Compatibility

By default Redzal is not fully compatible with normal malloc. It does not align blocks correctly to a double word boundary when checking top, because it aligns blocks strictly to the top of a page in this mode. This usually does not cause problems, because normally the size of the block is aligned properly for the data in it. However, in some cases programmers combine different kinds of data (e.g. a structure and a string) and that will cause trouble if the machine needs proper alignment. This can be avoided by setting the variable xmalloc_align to true. Redzal will then align all blocks correctly, although it cannot then detect all errors when checking the top. This is because there may be a small gap between the end of the data and the red zone.

When allocating 0 bytes of memory, Redzal will print a warning and allocate only the red zones. If program tries to access this block, Redzal will trap this.

Old use of free(), realloc(), free(), realloc() combination to pack data is not supported, because this feature should not be used. Read the manual page of the malloc for more information about this "feature".

6.0 Interface functions

6.1 xmalloc_config

xmalloc_err_t xmalloc_config(xmalloc_check_t type,

vm_size_t red_zone_size,

vm_size_t empty_zone_size,

vm_size_t reserved_zone_size)

Configure Redzal. Type can be only set before any calls to malloc. Legal values are:

XMALLOC_CHECK_BOTTOM: Will catch all accesses below an allocated block.

XMALLOC_CHECK_TOP: Will catch all access beyond the end of a block. If xmalloc_align is true, Redzal cannot catch all errors, but will be compatible with standard malloc.

XMALLOC_CHECK_BOTH_READ, XMALLOC_CHECK_BOTH_WRITE: Not yet implemented. Will be implemented by disallowing access to the data, trapping all accesses or only writes to the block, and single stepping over the offending instruction.

The last two settings are not real modes, but only affects how the real mode is selected.

XMALLOC_CHECK_RANDOM: Randomly select either check bottom or top. It will print which one it selected to stderr.

XMALLOC_CHECK_ENVIRONORRAND: Either use values in the XMALLOC environment variable or same as XMALLOC_CHECK_RANDOM. The XMALLOC environment variable has following syntax:

"<type>,<red>,<empty>,<reserved>,<alignment>"

Any of the fields can be empty. <Type> is a string setting Redzal mode. Possible values are: BR = Both read, BW = Both write, B = Bottom, T = Top and R = Random. <Red>, <empty> and <reserved> are numbers telling sizes of red, empty and reserved zone. Those numbers can have either 'k' or 'M' after them meaning 'kilobyte' or 'megabyte'. The <alignment> field can have the value 'A' if strict alignment is needed. Default value is "R,4k,4k,8M,"

The red_zone, empty_zone and reserved_zone arguments will set the sizes of the red, empty and reserved zone respectively. The environment always overrides the settings with this call if type is set to XMALLOC_CHECK_ENVIRONORRAND.

6.2 libmalloc: malloc, free, calloc, realloc, strdup

void *malloc(vm_size_t size)

void free(void *p)

void *calloc(vm_size_t n, vm_size_t size)

void *realloc(void *p, vm_size_t size)

char *strdup(const char *s)

These functions are the replacements for those in libc.

6.3 libxmalloc: xmalloc, xfree, xcalloc, xrealloc, xstrdup

Because many programs already have some kind of checked malloc or using the default names might cause trouble, the names of the replacement functions are changed so that all they have 'x' prefix. If program has its own xmalloc functions they must be removed and replaced with those in this library, because it is no use to know that this block was allocated from the function called xmalloc, which would be the case if you only linked program with libmalloc. The another way to compile program is by setting CFLAGS = -Dmalloc=xmalloc -Dfree=xfree -Dcalloc=xcalloc -Drealloc=xrealloc, and linking it with this library.

6.4 libymalloc: ymalloc, yfree, ycalloc, yrealloc, ystrdup

Same as libxmalloc, but 'y' prefix.

6.5 xmalloc_setopt and xmalloc_getopt

void xmalloc_setopt(void *p,

xmalloc_operation_t operation)

boolean_t xmalloc_getopt(void *p,

xmalloc_operation_t operation)

Set and get options for the blocks. Legal values for operation are:

XMALLOC_PERM_RW: Set the permissions to read / write, or return true if the block has read / write permissions.

XMALLOC_PERM_RO: Set the permissions to read only, or return true if the block has read only permission.

XMALLOC_PERM_NA: Remove all permissions from the block, or return true if the block has no permissions.

XMALLOC_PERMANENT: Mark the block as permanent, or return true if the block is permanent.

XMALLOC_TEMPORARY: Mark the block as temporary, or return true if the block is temporary.

6.6 xmalloc_dump

void xmalloc_dump(boolean_t all)

Dump information about blocks. If <all> is true then print all blocks, otherwise print only temporary blocks.

6.7 xmalloc_break

This function is only for setting breakpoint to it. It is called when xmalloc_break_if_touched variable matches the current block address.

7.0 Interface variables

7.1 xmalloc_align

boolean_t xmalloc_align = FALSE

If true, then all blocks are aligned properly even if checking top.

7.2 xmalloc_debugger

boolean_t xmalloc_debugger = FALSE

If true, no exception handler is installed. Used previously under debugger, but now gdb can handle multiple threads correctly, so this variable has no use anymore.

7.3 xmalloc_break_if_touched

vm_address_t xmalloc_break_if_touched

If this variable is set then all addresses passed to RZMA will be checked against this. Function xmalloc_break() will be called if match is found.

8.0 Future enhancements

The Redzal could be more efficient if it had its own external memory manager. With this it could lower its memory usage to a more user-friendly level. An EMM could cache a fixed amount of pages for allocated data, and pack all other data to its own memory. From there the default pager can page them to disk efficiently. Because most of the allocated blocks are much smaller than one page, memory requirements would be about 100 times smaller than now.

Another feature that has been planned is to implement checking of both ends of block in one run by protecting all blocks, and enabling permissions only during single stepping over offending instructions. This would slow down execution of the program, but it also offers other new features like storing last usage of a block (address and time), so memory leaks can be more easily detected.

RZMA could also detect that the first access to a block must be a write. With this it could detect programs that assume that malloc will return zeroed data.