Wednesday, February 18, 2009

Overloading library function

Sometimes during testing (especially while testing maintenance update), I come to a problem how to test the reaction of the program to an unusual function return value (e.g. special error), data, etc. The problem is, that I need to test the unmodified program binary and that it is almost impossible to set up a situation when the error "naturally" occurs.

If the function is in a shared library, there is a simple way to force the program to use another function instead of the one from the library. The program will call the new function, which will return the specific value (unusual error I need to test).

The magic "tool" is LD_PRELOAD variable. When the program is loaded to memory, libraries specified in LD_PRELOAD are loaded before libraries needed by the program. If I have function strdup() in my custom library (loaded by LD_PRELOAD), it will be used instead of strdup() from standard C library.

So for (very simplified) example, let's say I need to test program prog correctly checks whether strdup() returns NULL (I assume that prog segfaults on NULL access): I'll write simple library libexploit which will only contains my new strdup().

exploit.c:

char *strdup(const char *s)
{
      return NULL;
}

Makefile:

all: libexploit.so.1.0

clean:
      rm -f libexploit.so.1.0 *.o

libexploit.so.1.0: exploit.o
      gcc -shared -Wl,-soname,libexploit.so.1 -o libexploit.so.1.0 exploit.o

exploit.o: exploit.c
      gcc -Wall -fPIC -c exploit.c

Now when I make the simple library, I'll get result libexploit.so.1.0 which I'll preload to the program:

LD_PRELOAD="`pwd`/libexploit.so.1.0" prog

And it is all - the program got NULL from all calls to strdup().

Simple, right? There is one catch, however. Or two.

First problem is that the original function is replaced by the new one, so you cannot call the original one (not even from your "new" function).

Second problem is that even the original library will start using your function. So if the library is using the overloaded function, it uses the new one. This may result in unexpected library behavior.

Both these problems are not (AFAIK) solvable in a easy way. The only way I've found out so far is the re-factoring of the original library in following steps:

  1. Rename the original function - e.g. from strdup() to __strdup().
  2. Replace all calls of function strdup() with calls of function __strdup() in the library source - this will ensure that the library code will always call the correct function.
  3. Create new function strdup() which will contain the testing code (and can also call the original __strdup() function).
  4. Compile the library and load it with LD_PRELOAD as described above.

Now, that's finally all.

(This text has also been published on openSUSE wiki)

No comments:

Post a Comment