Controlling the level of checking

Updated: April 19, 2023

You can use environment variables or the mallopt() function to enable extra checks within the librcheck library.

If you decide to use mallopt(), you have to modify your application to enable the additional checks. Using environment variables lets you specify options that go into effect from the time the program runs. If your program does a lot of allocations before main(), setting options using mallopt() may be too late. In such cases, it's better to use environment variables.

The prototype of mallopt() is:

int mallopt ( int cmd, 
              intptr_t value );

The arguments are:

The command you want to use. The options used to enable additional checks in the library are:

We'll look at some of the other commands later in this chapter.

A value corresponding to the command used. For these particular commands, the value argument can be:
  • 0 to disable the checking (the default for these commands)
  • 1 to enable it

For information about all the commands, see the entry for mallopt() in the QNX Neutrino C Library Reference. Let's look at the commands that control the additional checks:

Turn on (or off) boundary checking for memory and string operations.

Environment variable: MALLOC_CKACCESS

This helps to detect buffer overruns and underruns that are a result of memory or string operations. When on, each pointer operand to a memory or string operation is checked to see if it's a heap buffer. If it is, the size of the heap buffer is checked, and the information is used to ensure that no assignments are made beyond the bounds of the heap buffer. If an attempt is made that would assign past the buffer boundary, a diagnostic warning message is printed.

Here's how you can use this option to find an overrun error:

char *p;
int opt;
opt = 1;
mallopt(MALLOC_CKACCESS, opt);
p = malloc(strlen("hello"));
strcpy(p, "hello, there!");  /* a warning is generated here */

The following illustrates how access checking can trap a reference through a stale pointer:


char *p;
int opt;
opt = 1;
mallopt(MALLOC_CKACCESS, opt);
p = malloc(30);
strcpy(p, "hello, there!");
Turn on (or off) fill-area boundary checking that validates that the program hasn't overrun the user-requested size of a heap buffer.

Environment variable: MALLOC_CKBOUNDS

It does this by applying a guard code check when the buffer is released or when it's resized. The guard code check works by filling any excess space available at the end of the heap buffer with a pattern of bytes. When the buffer is released or resized, the trailing portion is checked to see if the pattern is still present. If not, a diagnostic warning message is printed.

The effect of turning on fill-area boundary checking is a little different than enabling other checks: the checking is performed only on memory buffers allocated after the check was enabled, and not on memory buffers allocated earlier.

Here's how you can catch an overrun with the fill-area boundary checking option:

int *foo, *p, i, opt;
opt = 1;
mallopt(MALLOC_CKBOUNDS, opt);
foo = (int *)malloc(10*4);
for (p = foo, i = 12; i > 0; p++, i--)
    *p = 89;
free(foo);  /* a warning is generated here */
Enable (or disable) full chain checking. This option is expensive and should be considered as a last resort when some code is badly corrupting the heap and otherwise escapes the detection of boundary checking or fill-area boundary checking.

Environment variable: MALLOC_CKCHAIN

This kind of corruption can occur under a number of circumstances, particularly when they're related to direct pointer assignments. In this case, the fault may occur before a check such as fill-area boundary checking can be applied. There are also circumstances in which both fill-area boundary checking and the normal attempts to check the headers of neighboring buffer fail to detect the source of the problem. This may happen if the buffer that's overrun is the first or last buffer associated with a block or arena. It may also happen when the allocator chooses to satisfy some requests, particularly those for large buffers, with a buffer that exactly fits the program's requested size.

Full-chain checking traverses the entire set of allocation chains for all arenas and blocks in the heap every time a memory operation (including allocation requests) is performed. This lets the developer narrow down the search for a source of corruption to the nearest memory operation.