aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorJohannes Stoelp <johannes.stoelp@gmail.com>2024-04-06 23:17:02 +0200
committerJohannes Stoelp <johannes.stoelp@gmail.com>2024-04-06 23:17:02 +0200
commit6d2292f2431ec9405b8789c28c81e8e711c15b2d (patch)
tree31cd471b7e3443af75fdddec619c51ad2db6c199 /src
parent3de8cfbc28ff4f7619c20d5552e03ca96aab4b01 (diff)
downloadnotes-6d2292f2431ec9405b8789c28c81e8e711c15b2d.tar.gz
notes-6d2292f2431ec9405b8789c28c81e8e711c15b2d.zip
ld.so: add RTLD_LOCAL, RTLD_DEEPBIND notes + example
Diffstat (limited to 'src')
-rw-r--r--src/development/ld.so.md131
-rw-r--r--src/development/ldso/deepbind/.gitignore2
-rw-r--r--src/development/ldso/deepbind/Makefile19
-rw-r--r--src/development/ldso/deepbind/lib.c23
-rw-r--r--src/development/ldso/deepbind/main.c40
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;
+}