Dangers of using dlsym() with RTLD_NEXT
2016-05-07 14:32
369 查看
Background
There are times when you want to wrap a library function in order to provide some additional functionality. A common example of this is wrapping the standard library’smalloc()and
free()so that you can easily track memory allocations in your program. While there are several techniques for wrapping library functions, one well-knownmethod is using
dlsym()with
RTLD_NEXTto locate the wrapped function’s address so that you can correctly forward calls to it.
Problem
So what can go wrong? Let’s look at an example:LibWrap.h
void* memAlloc(size_t s); // Allocate a memory block of size 's' bytes. void memDel(void* p); // Free the block of memory pointed to by 'p'. |
#define _GNU_SOURCE #include <dlfcn.h> #include "LibWrap.h" static void* malloc(size_t s) { // Wrapper for standard library's 'malloc'. // The 'static' keyword forces all calls to malloc() in this file to resolve // to this functions. void* (*origMalloc)(size_t) = dlsym(RTLD_NEXT,"malloc"); return origMalloc(s); } static void free(void* p) { // Wrapper for standard library's 'free'. // The 'static' keyword forces all calls to free() in this file to resolve // to this functions. void (*origFree)(void*) = dlsym(RTLD_NEXT,"free"); origFree(p); } void* memAlloc(size_t s) { return malloc(s); // Call the malloc() wrapper. } void memDel(void* p) { free(p); // Call the free() wrapper. } |
#include <malloc.h> #include "LibWrap.h" int main() { struct mallinfo beforeMalloc = mallinfo(); printf("Bytes allocated before malloc: %d\n",beforeMalloc.uordblks); void* p = memAlloc(57); struct mallinfo afterMalloc = mallinfo(); printf("Bytes allocated after malloc: %d\n",afterMalloc.uordblks); memDel(p); struct mallinfo afterFree = mallinfo(); printf("Bytes allocated after free: %d\n",afterFree.uordblks); return 0; } |
LibWrap.cinto a shared library:
$ gcc -Wall -Werror -fPIC -shared -o libWrap.so LibWrap.c
Next compile
Main.cand link it against the
libWrap.sothat we just created:
$ gcc -Wall -Werror -o Main Main.c ./libWrap.so -ldl
Time to run the program!
$ ./Main Bytes allocated before malloc: 0 Bytes allocated after malloc: 80 Bytes allocated after free: 0
So far, so good. No surprises. We allocated a bunch of memory and then freed it. The statistics returned by
mallinfo()confirm this.
Out of curiosity, let’s look at
lddoutput for the application binary we created.
$ ldd Main linux-vdso.so.1 => (0x00007fff1b1fe000) ./libWrap.so (0x00007fe7d2755000) libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fe7d2542000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe7d217c000) /lib64/ld-linux-x86-64.so.2 (0x00007fe7d2959000)
The lddoutput is from Ubuntu 14.04.1 LTS for x86-64. Your output may differ. |
libWrap.sowith respect to
libc.so.6:
libWrap.socomes before
libc.so.6. Remember this. It will be important later.
Now for fun, let’s re-compile
Main.cwith
libc.so.6explicitly specified on the command-line and comingbefore
libWrap.so:
$ gcc -Wall -Werror -o Main Main.c /lib/x86_64-linux-gnu/libc.so.6 ./libWrap.so -ldl
Re-run:
$ ./Main Bytes allocated before malloc: 0 Bytes allocated after malloc: 80 Bytes allocated after free: 80
Uh oh, why are we leaking memory all of a sudden? We de-allocate everything we allocate, so why the memory leak?
It turns out that the leak is occurring because we are not actually forwarding
malloc()and
free()calls to
libc.so.6‘s implementations. Instead, we are forwarding them to
malloc()and
free()inside
ld-linux-x86-64.so.2!
“What are you talking about?!” you might be asking.
Well, it just so happens that
ld-linux-x86-64.so.2, which is the dynamic linker/loader, has its own copy of
malloc()and
free(). Why? Because
ld-linuxhas to allocate memory from the heapbefore it loads
libc.so.6. But the version of
malloc/freethat
ld-linuxhas does not actually free memory!
See elf/dl-minimal.cin glibc source code for ld-linux‘s malloc/freeimplementation. |
libWrap.soforward calls to
ld-linuxinstead of
libc? The answer comes down to how
dlsym()searches for symbols when
RTLD_NEXTis specified. Here’s the relevant excerpt from the
dlsym(3)man page:
[RTLD_NEXT] will find the next occurrence of a function in the search order after the current library. This allows one to provide a wrapper around a function in another shared library.
— dlsym(3)
To understand this better, take a look at
lddoutput for the new
Mainbinary:
$ ldd Main linux-vdso.so.1 => (0x00007fffe1da0000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f32c2e91000) ./libWrap.so (0x00007f32c2c8f000) libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f32c2a8a000) /lib64/ld-linux-x86-64.so.2 (0x00007f32c3267000)
Unlike
earlier,
libWrap.socomes after
libc.so.6. So when
dlsym()is called inside
libWrap.soto search for functions, it skips
libc.so.6since it precedes
libWrap.soin the search order list. That means the searches continue through to
ld-linux-x86-64.so.2where they find linker/loader’s
malloc/freeand return pointers to those functions. And so,
libWrap.soends up forwading calls to
ld-linuxinstead of
libc!
Exercise to the reader: Verify thatmalloc/freecalls are getting forwarded to ld-linuxinstead of libcby stepping through Mainwith GDB. |
command to build our application and then encountered a memory leak due to weird library linking order caused by said command. Isn’t this whole thing a silly contrived scenario?
The answer is unfortunately no. At OptumSoft, we recently encountered this very same memory leak with a binary compiled using the standard
./configure && makeon x86-64 Ubuntu 14.04.1 LTS. For reasons we don’t understand, the linking order for
the binary was such that using
dlsym()with
RTLD_NEXTto lookup
malloc/freeresulted in pointers to implementations inside
ld-linux. It took a ton of effort and invaluable help from
Mozilla’s rr tool to root-cause the issue. After the whole ordeal, we decided to write a blog post about this strange behavior in case someone else encounters it in the future.
Solution
If you finddlsym()with
RTLD_NEXTreturning pointers to
malloc/freeinside
ld-linux, what can you do?
For starters, you need to detect that a function address indeed does belong to
ld-linuxusing
dladdr():
void* func = dlsym(RTLD_NEXT,"malloc"); Dl_info dlInfo; if(!dladdr(func,&dlInfo)) { // dladdr() failed. } if(strstr(dlInfo.dli_fname,"ld-linux")) { // 'malloc' is inside linker/loader. } |
ld-linux, you need to decide what to do next. Unfortunately, there is no straightforward way to continue searching for the same function name in all other libraries. But if you know the name
of a specific library in which the function exists (e.g. libc), you can use
dlopen()and
dlsym()to fetch the desired pointer:
void* handle = dlopen("libc.so.6",RTLD_LAZY); // NOTE: libc.so.6 may *not* exist on Alpha and IA-64 architectures. if(!handle) { // dlopen() failed. } void* func = dlsym(handle,"free"); if(!func) { // Bad! 'free' was not found inside libc. } |
dlopen‘ing a library to replace malloc/freeis generally frowned upon. Use at your own risk. |
Summary
One can usedlsym()with
RTLD_NEXTto implement wrappers around
malloc()and
free().
Due to unexpected linking behavior,
dlsym()when using
RTLD_NEXTcan return pointers to
malloc/freeimplementations inside
ld-linux(dynamic linker/loader). Using
ld-linux‘s
malloc/freefor general heap allocations leads to memory leaks because that particular version of
free()doesn’t actually release memory.
You can check if an address returned by
dlsym()belongs to
ld-linuxvia
dladdr(). You can also lookup a function in a specific library using
dlopen()and
dlsym().
References:
1. http://optumsoft.com/dangers-of-using-dlsym-with-rtld_next/
相关文章推荐
- Linux上安装配置使用Tomcat说明文档和JDK环境变量配置
- ClassLoader如何加载class
- CMS
- 剑指offer 替换字符串中的空格
- Android开发学习之路-图片颜色获取器开发(1)
- 第43讲项目1—— 由键盘到文件
- 交叉排列
- 无人驾驶!国产操作系统上的最高黑科技!
- [置顶] android框架
- Python之解析json
- 高考后的重生
- Eclipse构建maven web项目
- 进入BrowsermainRunner::Run处理消息
- Linux/Unix inode、vnode、dentry、file、进程表、文件表(上)
- 第六届山东省ACM竞赛 C题 Game!
- 浅析android应用增量升级
- Android 框架
- 任天堂将在2017年发布新主机Nintendo NX
- 请说出作用域public,private,protected,以及不写时的区别
- 自定义滑动菜单SlidingMenu