diff options
author | Johannes Stoelp <johannes.stoelp@gmail.com> | 2024-04-06 23:17:02 +0200 |
---|---|---|
committer | Johannes Stoelp <johannes.stoelp@gmail.com> | 2024-04-06 23:17:02 +0200 |
commit | 6d2292f2431ec9405b8789c28c81e8e711c15b2d (patch) | |
tree | 31cd471b7e3443af75fdddec619c51ad2db6c199 | |
parent | 3de8cfbc28ff4f7619c20d5552e03ca96aab4b01 (diff) | |
download | notes-6d2292f2431ec9405b8789c28c81e8e711c15b2d.tar.gz notes-6d2292f2431ec9405b8789c28c81e8e711c15b2d.zip |
ld.so: add RTLD_LOCAL, RTLD_DEEPBIND notes + example
-rw-r--r-- | src/development/ld.so.md | 131 | ||||
-rw-r--r-- | src/development/ldso/deepbind/.gitignore | 2 | ||||
-rw-r--r-- | src/development/ldso/deepbind/Makefile | 19 | ||||
-rw-r--r-- | src/development/ldso/deepbind/lib.c | 23 | ||||
-rw-r--r-- | src/development/ldso/deepbind/main.c | 40 |
5 files changed, 208 insertions, 7 deletions
diff --git a/src/development/ld.so.md b/src/development/ld.so.md index 67ff6e9..9b03c45 100644 --- a/src/development/ld.so.md +++ b/src/development/ld.so.md @@ -30,7 +30,7 @@ dlopen("libbar.so", RTLD_LAZY); Libraries specified in `LD_PRELOAD` are loaded from `left-to-right` but initialized from `right-to-left`. -```markdown +``` > ldd ./main >> libc.so.6 => /usr/lib/libc.so.6 @@ -48,7 +48,7 @@ The preload order determines: For the example listed above the resulting `link map` will look like the following: -```makrdown +``` +------+ +------+ +------+ +------+ | main | -> | liba | -> | libb | -> | libc | +------+ +------+ +------+ +------+ @@ -56,7 +56,7 @@ following: This can be seen when running with `LD_DEBUG=files`: -```makrdown +``` > LD_DEBUG=files LD_PRELOAD=liba.so:libb.so ./main # load order (-> determines link map) >> file=liba.so [0]; generating link map @@ -74,7 +74,7 @@ To verify the `link map` order we let `ld.so` resolve the `memcpy(3)` libc symbol (used in _main_) dynamically, while enabling `LD_DEBUG=symbols,bindings` to see the resolving in action. -```makrdown +``` > LD_DEBUG=symbols,bindings LD_PRELOAD=liba.so:libb.so ./main >> symbol=memcpy; lookup in file=./main [0] >> symbol=memcpy; lookup in file=<path>/liba.so [0] @@ -83,6 +83,122 @@ to see the resolving in action. >> binding file ./main [0] to /usr/lib/libc.so.6 [0]: normal symbol `memcpy' [GLIBC_2.14] ``` +## `RTLD_LOCAL` and `RTLD_DEEPBIND` +As shown in the `LD_PRELOAD` section above, when the dynamic linker resolves +symbol relocations, it walks the link map and until the first object provides +the requested symbol. + +When libraries are loaded dynamically during runtime with `dlopen(3)`, one can +control the visibility of the symbols for the loaded library. The following two +flags control this visibility. +- `RTLD_LOCAL` the symbols of the library (and its dependencies) are not + visible in the global symbol scope and therefore do not participate in global + symbol resolution from other libraries (default). +- `RTLD_GLOBAL` the symbols of the library are visible in the global symbol + scope. + +Additionally to the visibility one can use the `RTLD_DEEPBIND` flag to define +the lookup order when resolving symbols of the loaded library. With deep +binding, the symbols of the loaded library (and its dependencies) are searched +first before the global scope is searched. Without deep binding, the order is +reversed and the global space is searched first, which is the default. + +The sources in [ldso/deepbind][src-deepbind] give a minimal example, which can +be used to experiment with the different flags and investigate their behavior. +``` +main +|-> explicitly link against liblink.so +|-> dlopen(libdeep.so, RTLD_LOCAL | RTLD_DEEPBIND) +`-> dlopen(libnodp.so, RTLD_LOCAL) +``` + +The following snippets are taken from `LD_DEBUG` to demonstrate the +`RLTD_LOCAL` and `RTLD_DEEPBIND` flags. +```ini +# dlopen("libdeep.so", RTLD_LOCAL | RTLD_DEEPBIND) +# scopes visible to libdeep.so, where scope [0] is the local one. +object=./libdeep.so [0] + scope 0: ./libdeep.so /usr/lib/libc.so.6 /lib64/ld-linux-x86-64.so.2 + scope 1: ./main ./libprel.so ./liblink.so /usr/lib/libc.so.6 /lib64/ld-linux-x86-64.so.2 + +# main: dlsym(handle:libdeep.so, "test") +symbol=test; lookup in file=./libdeep.so [0] +binding file ./libdeep.so [0] to ./libdeep.so [0]: normal symbol `test' + +# libdeep.so: dlsym(RTLD_NEXT, "next_libdeep") +symbol=next_libdeep; lookup in file=/usr/lib/libc.so.6 [0] +symbol=next_libdeep; lookup in file=/lib64/ld-linux-x86-64.so.2 [0] +./libdeep.so: error: symbol lookup error: undefined symbol: next_libdeep (fatal) + +# libdeep.so: dlsym(RTLD_DEFAULT, "default_libdeep") +# first search local scope (DEEPBIND) +symbol=default_libdeep; lookup in file=./libdeep.so [0] +symbol=default_libdeep; lookup in file=/usr/lib/libc.so.6 [0] +symbol=default_libdeep; lookup in file=/lib64/ld-linux-x86-64.so.2 [0] +symbol=default_libdeep; lookup in file=./main [0] +symbol=default_libdeep; lookup in file=./libprel.so [0] +symbol=default_libdeep; lookup in file=./liblink.so [0] +symbol=default_libdeep; lookup in file=/usr/lib/libc.so.6 [0] +symbol=default_libdeep; lookup in file=/lib64/ld-linux-x86-64.so.2 [0] +./libdeep.so: error: symbol lookup error: undefined symbol: default_libdeep (fatal) + +# main: dlsym(handle:libdeep.so, "libdeep_main") +symbol=libdeep_main; lookup in file=./libdeep.so [0] +symbol=libdeep_main; lookup in file=/usr/lib/libc.so.6 [0] +symbol=libdeep_main; lookup in file=/lib64/ld-linux-x86-64.so.2 [0] +./libdeep.so: error: symbol lookup error: undefined symbol: libdeep_main (fatal) +``` + +The following snippets are taken from `LD_DEBUG` to demonstrate the +`RLTD_LOCAL` flag _without_ the `RTLD_DEEPBIND` flag. +```ini +# dlopen("libdeep.so", RTLD_LOCAL) +# scopes visible to libnodp.so, where scope [0] is the global one. +object=./libnodp.so [0] + scope 0: ./main ./libprel.so ./liblink.so /usr/lib/libc.so.6 /lib64/ld-linux-x86-64.so.2 + scope 1: ./libnodp.so /usr/lib/libc.so.6 /lib64/ld-linux-x86-64.so.2 + +# main: dlsym(handle:libnodp.so, "test") +symbol=test; lookup in file=./libnodp.so [0] +binding file ./libnodp.so [0] to ./libnodp.so [0]: normal symbol `test' + +# libnodp.so: dlsym(RTLD_NEXT, "next_libnodp") +symbol=next_libnodp; lookup in file=/usr/lib/libc.so.6 [0] +symbol=next_libnodp; lookup in file=/lib64/ld-linux-x86-64.so.2 [0] +./libnodp.so: error: symbol lookup error: undefined symbol: next_libnodp (fatal) + +# libnodp.so: dlsym(RTLD_DEFAULT, "default_libnodp") +# first search global scope (no DEEPBIND) +symbol=default_libnodp; lookup in file=./main [0] +symbol=default_libnodp; lookup in file=./libprel.so [0] +symbol=default_libnodp; lookup in file=./liblink.so [0] +symbol=default_libnodp; lookup in file=/usr/lib/libc.so.6 [0] +symbol=default_libnodp; lookup in file=/lib64/ld-linux-x86-64.so.2 [0] +symbol=default_libnodp; lookup in file=./libnodp.so [0] +symbol=default_libnodp; lookup in file=/usr/lib/libc.so.6 [0] +symbol=default_libnodp; lookup in file=/lib64/ld-linux-x86-64.so.2 [0] +./libnodp.so: error: symbol lookup error: undefined symbol: default_libnodp (fatal) + +# main: dlsym(handle:libnodp.so, "libnodp_main") +symbol=libnodp_main; lookup in file=./libnodp.so [0] +symbol=libnodp_main; lookup in file=/usr/lib/libc.so.6 [0] +symbol=libnodp_main; lookup in file=/lib64/ld-linux-x86-64.so.2 [0] +./libnodp.so: error: symbol lookup error: undefined symbol: libnodp_main (fatal) +``` + +The following is a global lookup from the main application, since +`lib{deep,nodp}.so` were loaded with `RTLD_LOCAL`, they are not visible in the +global symbol scope. +```ini +# main: dlsym(RTLD_DEFAULT, "default_main") +symbol=default_main; lookup in file=./main [0] +symbol=default_main; lookup in file=./libprel.so [0] +symbol=default_main; lookup in file=./liblink.so [0] +symbol=default_main; lookup in file=/usr/lib/libc.so.6 [0] +symbol=default_main; lookup in file=/lib64/ld-linux-x86-64.so.2 [0] +./main: error: symbol lookup error: undefined symbol: default_main (fatal) +``` + ## Dynamic Linking (x86_64) Dynamic linking basically works via one indirect jump. It uses a combination of function trampolines (`.plt` section) and a function pointer table (`.got.plt` @@ -90,7 +206,7 @@ section). On the first call the trampoline sets up some metadata and then jumps to the `ld.so` runtime resolve function, which in turn patches the table with the correct function pointer. -```makrdown +``` .plt ....... procedure linkage table, contains function trampolines, usually located in code segment (rx permission) .got.plt ... global offset table for .plt, holds the function pointer table @@ -98,7 +214,7 @@ correct function pointer. Using `radare2` we can analyze this in more detail: -```makrdown +``` [0x00401040]> pd 4 @ section..got.plt ;-- section..got.plt: ;-- .got.plt: ; [22] -rw- section size 32 named .got.plt @@ -135,7 +251,7 @@ Using `radare2` we can analyze this in more detail: relocation index pushed by the `puts` trampoline. - The relocation entry at index `0` tells the resolve function which symbol to search for and where to put the function pointer: - ```makrdown + ``` > readelf -r <main> >> Relocation section '.rela.plt' at offset 0x4b8 contains 1 entry: >> Offset Info Type Sym. Value Sym. Name + Addend @@ -143,3 +259,4 @@ Using `radare2` we can analyze this in more detail: ``` As we can see the offset from relocation at index `0` points to `GOT[3]`. +[src-deepbind]: https://github.com/johannst/notes/tree/master/src/development/ldso/deepbind diff --git a/src/development/ldso/deepbind/.gitignore b/src/development/ldso/deepbind/.gitignore new file mode 100644 index 0000000..a80e8fd --- /dev/null +++ b/src/development/ldso/deepbind/.gitignore @@ -0,0 +1,2 @@ +*.so +main diff --git a/src/development/ldso/deepbind/Makefile b/src/development/ldso/deepbind/Makefile new file mode 100644 index 0000000..6fc4eb1 --- /dev/null +++ b/src/development/ldso/deepbind/Makefile @@ -0,0 +1,19 @@ +run: build + LD_PRELOAD=./libprel.so ./main + +debug: build + #LD_DEBUG_OUTPUT=ldso + LD_DEBUG=scopes,symbols,bindings LD_PRELOAD=./libprel.so ./main + +build: + gcc -g -o libprel.so lib.c -DNAME=\"prel\" -fPIC -shared + gcc -g -o libdeep.so lib.c -DNAME=\"deep\" -fPIC -shared + gcc -g -o libnodp.so lib.c -DNAME=\"nodp\" -fPIC -shared + gcc -g -o liblink.so lib.c -DNAME=\"link\" -fPIC -shared + gcc -g -o main main.c ./liblink.so -ldl + +fmt: + clang-format -i *.c + +clean: + $(RM) *.so main diff --git a/src/development/ldso/deepbind/lib.c b/src/development/ldso/deepbind/lib.c new file mode 100644 index 0000000..e18a4ce --- /dev/null +++ b/src/development/ldso/deepbind/lib.c @@ -0,0 +1,23 @@ +#define _GNU_SOURCE +#include <dlfcn.h> +#include <stdio.h> + +#ifndef NAME +#define NAME "" +#endif + +void test() { + puts(NAME ":test()"); + + // Lookup next symbol from the libraries scope, which will search only in the + // libraries LOCAL scope, starting from the next object after the current one. + (void)dlsym(RTLD_NEXT, "next_lib" NAME); + + // Global lookup from the libraries scope, which will search libraries in the + // GLOBAL scope and the libraries LOCAL scope. The order in which the scopes + // are searched depends on whether the library was loaded (a) with DEEPBIND or + // (b) not. whether this library was loaded with DEEPBIND). In the first case, + // the LOCAL scope is searched first, where in the latter, the GLOBAL scope is + // searched first. + (void)dlsym(RTLD_DEFAULT, "default_lib" NAME); +} diff --git a/src/development/ldso/deepbind/main.c b/src/development/ldso/deepbind/main.c new file mode 100644 index 0000000..6d93520 --- /dev/null +++ b/src/development/ldso/deepbind/main.c @@ -0,0 +1,40 @@ +#include <dlfcn.h> +#include <stdio.h> + +int main() { + puts("-- deep --"); + // Load library into its own LOCAL scope with DEEPBINDING, meaning that + // symbols will first resolve to the library + its dependencies first before + // considering global symbols. + void* h1 = dlopen("./libdeep.so", RTLD_LAZY | RTLD_LOCAL | RTLD_DEEPBIND); + if (h1) { + // Lookup symbol in the libraries LOCAL scope (library + its own + // dependencies). + void (*test_fn)() = dlsym(h1, "test"); + test_fn(); + + // Lookup non-existing symbol in the libraries LOCAL scope (library + its + // own dependencies). + (void)dlsym(h1, "libdeep_main"); + } + + puts("-- nodp --"); + // Load library into its own LOCAL scope. + void* h2 = dlopen("./libnodp.so", RTLD_LOCAL | RTLD_LAZY); + if (h2) { + // Lookup symbol in the libraries LOCAL scope (library + its own + // dependencies). + void (*test_fn)() = dlsym(h2, "test"); + test_fn(); + + // Lookup non-existing symbol in the libraries LOCAL scope (library + its + // own dependencies). + (void)dlsym(h2, "libnodp_main"); + } + + puts("-- main --"); + // Lookup non-existing symbol in the GLOBAL scope. + (void)dlsym(RTLD_DEFAULT, "default_main"); + + return 0; +} |