GDB, LD_PRELOAD and libc

OK, this is meant to be a short post, just a couple of tips regarding debugging of code you would typically run as such:
LD_PRELOAD=./somedlib.so ./yourbin
If you're reading this your're probably well aware about what LD_PRELOAD is for, but just in case you don´t let me explain real quick. LD_PRELOAD is an environment variable that allows you to specify the path to a certain shared object (shared library), that library will then be loaded before any other library in the LD_LIBRARY_PATH. That includes libc.so. Doing so allows you, amongst other things, to "intercept" calls to the libc. For example, I'm currently building a memory profiler - a la valgrind, although far less features, but with the goal of minimizing runtime performance impact for the target binary. To build this profiler, I've had to intercept malloc, calloc, realloc, free, etc, because I must account for all memory allocations. So I created my own malloc() (and others) which wraps the call to libc's malloc and performs accounting in the meantime. The way to achieve this is by using LD_PRELOAD. By loading the shared object which contains the code of my custom malloc, I can make sure that when the target binary calls malloc(), it's my malloc that gets executed, and not libc's.

Now, that's great, but we need to make sure the code in our shared object is kosher before releasing it, don't we? Which means a lot of debugging. In certain scenarios, like the memory profiler, if your shared library isn't perfect you will surely segfault. You really can't fill up your code with printf's, you really can't, and definitely not as your only means to debugging. OK, strictly speaking the truth is in the code, if you _really_ understand what you're doing debugging would be needlees - this is basically Linus' rationale and the reason there is no kernel debugger in Linux - but we all know it's not always the case :-)
So enter gdb. How would we debug something like this with gdb? If you're not intercepting the libc or doing anything too fancy you can probably get away with:

# gdb
# (gdb) set environment LD_PRELOAD=/path/to/sobject/yourobject.so
# (gdb) file /path/to/binary/yourbin
# (gdb) r

However, this might not be a good idea in more complex cases. The reason is gdb will not call your code directly, what gdb really does is equivalent to:

LD_PRELOAD=/path/to/sobject/yourobject.so bash -c '/path/to/binary/yourbin'

So an undebugged or partially flawed yourobject.so library could potentially break bash. Well, not bash itself, just bash when running with yourobject.so preloaded. So, what I recommend you do is set an execution wrapper for gdb, that way you will not impose the LD_PRELOAD environment variable on the gdb shell environment. Summing up, do it this way:

# (gdb) set exec-wrapper env 'LD_PRELOAD=/path/to/sobject/yourobject.so'
# (gdb) file /path/to/binary/yourbin
# (gdb) r

You should now be able to debug accordingly. Enjoy your crashes, and happy debugging! :P

SECURITY NOTE: Many of you are probably wondering about the security risks intercepting the libc could pose. Well, although it is fun to play around with it, and you might manage mess around with some userspace stuff, the potential damage is limited by the fact that the loader will ignore LD_PRELOAD directives if the effective UID is different to the real UID, this complicates privilege escalation, naturally.

Written on January 16, 2013