Skip to content

jamesodhunt/wrapalloc

Repository files navigation

https://travis-ci.org/jamesodhunt/wrapalloc.svg?branch=master Coverity Scan Build Status Donate via flattr Donate via Paypal

wrapalloc is a tool that intercepts calls to the libc memory management routines. This allows it:

  • perform checks on:
    • overruns (where the calling program writes beyond the end of the memory it has allocated).
    • underruns (where the calling program writes before the start of the memory it has allocated).
  • display memory management statistics.
  • arrange for memory to be initialised with a particular bit pattern.
  • arrange for memory to be returned to the system after first overwriting it with a particular bit pattern.

wrapalloc is similar to dmalloc(1) and electric-fence(efence(3)), but it's lighter-weight (and less clever).

When the user allocates a block of say 16 bytes of memory like this:

char *p = malloc (16);

... what actually happens is:

+---------+------------+--------+-------------+
| MCB     | pre-buffer | memory | post-buffer |
+---------+------------+--------+-------------+
 .memory               0 ..... 15
    |     ^            ^                      ^
    |     |            |                      |
    +-----|------------^                      |
          |            |                      |
 .begin --+            p                      |
                                              |
 .end ----------------------------------------+

 .request_size ------->{________}

 .total_size
     |
+----+
V
{_____________________________________________}

The user is returned the address of a block of the size they specified. However, we also allocate a pre- and post- buffer or "margin" either side and immediately adjacent to the chunk of space requested.

Immediately before the pre-buffer is the MCB or "MemoryCtlBlock", a structure used for recording information about the allocation and buffers/margins. The MCB has a number of elements useful for tracking the original allocation including:

  • .memory which points to the memory block returned to the user.
  • .begin which points to the start of the pre-buffer.
  • .end which points to the end of the post-buffer.
  • .request_size which records the size of the block of memory the user requested.
  • .total_size which records the actual amount of memory allocated (requested size + pre-buffer size + post-buffer size + MCB).

If the program either overruns or underruns such that either the pre- or post-buffers get modified, this is detected both when realloc(3) is called, and when free(3) is called.

The size of the pre- and post-buffers are configurable using environment variables, either independently:

export WRAP_ALLOC_PRE_BORDER=<size>
export WRAP_ALLOC_POST_BORDER=<size>

... or together:

export WRAP_ALLOC_BORDER=<size>

Where "<size>" is a value in bytes. Note that the smaller the border sizes, the lower the likelihood of detecting buffer over- and under-runs.

Notes:

The library is built using a linker trick [1] to redirect (interpose) all calls to the standard memory allocation routines with the internal library versions.

Variables are used to modify the behaviour of this library. Note that internally, those variables are queried as late as possible since loading all recognised environment variables at library load time is NOT useful since it precludes the caller from being able to modify the behaviour (since the caller will run after library load time).

See the man page for the complete list of available environment variables to control the behaviour of this library.

  • Won't work correctly for threaded apps (due to simplistic setting of 'caller').
  • Although the code handles it, the library cannot be built to use the dynamic linker rather than the linker trick since the dynamic linker routines allocate memory, resulting in recursive execution followed by a crash.

Make sure you disable all glibc/glib memory checkers by setting the following prior to running the application you want wrapalloc to check:

# disable (e)glibc checking
export MALLOC_CHECK_=0

# disable glib checking
export G_DEBUG=gc-friendly,resident-modules
export G_SLICE=always-malloc

Run the test program with LD_PRELOAD to force a write before the buffer the test program allocates:

$ (LD_PRELOAD=$PWD/src/.libs/libwrapalloc.so WRAP_ALLOC_LOGFILE=wrap-alloc.log ./tests/test_wrapalloc underrun)
$ cat wrap-alloc.log
ERROR: underrun - expected fill byte 0x0 got 0x55 (1 byte before
beginning of user memory 0x7fbc1145f058 of size 6)
ERROR: damaged pre-border:
INFO: caught SIGSEGV - aborting

Run the test program with LD_PRELOAD to force a write beyond the buffer the test program allocates:

$ (LD_PRELOAD=$PWD/src/.libs/libwrapalloc.so WRAP_ALLOC_LOGFILE=wrap-alloc.log ./tests/test_wrapalloc overrun)
$ cat wrap-alloc.log
ERROR: overrun - expected fill byte 0x0 got 0x4f (1 byte beyond end of
user memory 0x7ff19373c058 of size 6)
ERROR: damaged post-border:
INFO: caught SIGSEGV - aborting

By increasing the debug level, you can get a dump of the post buffer to see exactly how the program has overwritten the memory:

$ (LD_PRELOAD=$PWD/src/.libs/libwrapalloc.so WRAP_ALLOC_DEBUG=3 WRAP_ALLOC_LOGFILE=wrap-alloc.log WRAP_ALLOC_BORDER=8 ./tests/test_wrapalloc overrun)
$ cat wrap-alloc.log
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=1036:func=wa_init:DEBUG: --------------------
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=1038:func=wa_init:DEBUG: wrap-alloc version 0.1
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=1039:func=wa_init:DEBUG:
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=1041:func=wa_init:DEBUG: build date: Jun  1 2015 at 20:17:22
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=1043:func=wa_init:DEBUG: build type:
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=1050:func=wa_init:DEBUG:   - mmap
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=1061:func=wa_init:DEBUG: functions:
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=1063:func=wa_init:DEBUG:   __wa_wrap_malloc=0x7fedd9787cbf
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=1064:func=wa_init:DEBUG:   __real_malloc=0x7fedd9786310
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=1066:func=wa_init:DEBUG:   __wa_wrap_calloc=0x7fedd9787d55
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=1067:func=wa_init:DEBUG:   __real_calloc=0x7fedd978632a
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=1069:func=wa_init:DEBUG:   __wa_wrap_realloc=0x7fedd9787ded
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=1070:func=wa_init:DEBUG:   __real_realloc=0x7fedd978634f
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=1079:func=wa_init:DEBUG:   __wa_wrap_free=0x7fedd9787ff5
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=1080:func=wa_init:DEBUG:   __real_free=0x7fedd9786374
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=1082:func=wa_init:DEBUG:   __cyg_profile_func_enter=0x7fedd94cfd40
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=1083:func=wa_init:DEBUG:   __cyg_profile_func_exit=0x7fedd94cfd40
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=1085:func=wa_init:DEBUG:   malloc=0x7fedd9786310
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=1086:func=wa_init:DEBUG:   calloc=0x7fedd978632a
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=1087:func=wa_init:DEBUG:   realloc=0x7fedd978634f
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=1088:func=wa_init:DEBUG:   free=0x7fedd9786374
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=1091:func=wa_init:DEBUG:    dlopen=0x7fedd91b7030
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=1092:func=wa_init:DEBUG:    dlsym=0x7fedd91b7100
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=1093:func=wa_init:DEBUG:    dlerror=0x7fedd91b7370
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=1094:func=wa_init:DEBUG:    dlclose=0x7fedd91b70a0
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=1098:func=wa_init:DEBUG:    mmap=0x7fedd94bb720
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=1099:func=wa_init:DEBUG:    munmap=0x7fedd94bb750
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=1102:func=wa_init:DEBUG: option settings:
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=1104:func=wa_init:DEBUG:   free: enabled
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=1107:func=wa_init:DEBUG:   pre buffer size: 8
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=1109:func=wa_init:DEBUG:   post buffer size: 8
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=1112:func=wa_init:DEBUG:   pre buffer fill byte: 0x0
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=1114:func=wa_init:DEBUG:   post buffer fill byte: 0x0
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=1115:func=wa_init:DEBUG:   alloc byte: 0x0
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=1116:func=wa_init:DEBUG:   free byte: 0x0
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=1117:func=wa_init:DEBUG:   debug: 0x3
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=1118:func=wa_init:DEBUG: --------------------
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=811:func=__wa_wrap_malloc:DEBUG: caller requested allocation of 6 bytes
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=638:func=__wa_new_mem_block:DEBUG: __wa_new_mem_block called with size=6
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=592:func=wa_show_ctl_block:DEBUG: MemoryCtlBlock=0x7fedd9bac000
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=593:func=wa_show_ctl_block:DEBUG:   pre_border_size=8
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=594:func=wa_show_ctl_block:DEBUG:   post_border_size=8
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=595:func=wa_show_ctl_block:DEBUG:   eye_catcher='WACTLBK'
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=596:func=wa_show_ctl_block:DEBUG:   memory=0x7fedd9bac060
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=597:func=wa_show_ctl_block:DEBUG:   begin=0x7fedd9bac058
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=598:func=wa_show_ctl_block:DEBUG:   end=0x7fedd9bac06e
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=599:func=wa_show_ctl_block:DEBUG:   request_size=6
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=600:func=wa_show_ctl_block:DEBUG:   total_size=110
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=608:func=wa_show_ctl_block:DEBUG:   call_time=1433187315.274992508
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=737:func=__wa_new_mem_block:DEBUG: filling user buffer with alloc byte value 0x0
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=741:func=__wa_new_mem_block:DEBUG: returning m->memory=0x7fedd9bac060
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=947:func=__wa_wrap_free:DEBUG: caller requested freeing of 6 bytes
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=568:func=wa_check_ctl_block:ERROR: overrun - expected fill byte 0x0 got 0x4f (1 byte beyond end of user memory 0x7fedd9bac060 of size 6)
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=570:func=wa_check_ctl_block:ERROR: damaged post-border:
wrap-alloc:pid=5960:ppid=2928:file=wa_util.c:line=473:func=wa_tohex:DEBUG: 000000: 4f00 0000 0000 0000                      O.......
wrap-alloc:pid=5960:ppid=2928:file=wrap_alloc.c:line=1334:func=wa_abort:INFO: caught SIGSEGV - aborting

Notes:

  • The penultimate line above shows the bogus write - the test_wrapalloc program writes a "O" when requested to perform an over-run.
  • The default buffer has been changed to only 8 bytes using the WRAP_ALLOC_BORDER variable to make the example above clearer.

Here's an example of running "sleep 1" via LD_PRELOAD:

$ (LD_PRELOAD=$PWD/src/.libs/libwrapalloc.so WRAP_ALLOC_LOGFILE=wrap-alloc.log /bin/sleep 1)
[1]The linker trick is "ld -Wl,--wrap=malloc,--wrap=calloc,--wrap=free ...".

wrapalloc was written by James Hunt <[email protected]>.