From 60b7e5744ec0c16567ec1e84ca2ff57e261c199b Mon Sep 17 00:00:00 2001 From: Arjun Shankar Date: Thu, 10 Jul 2025 18:00:46 +0200 Subject: [PATCH] Extend struct r_debug to support multiple namespaces (RHEL-101986) Several patches related to this fix were applied in: 72524e00c33366b778a7a570ab360d821dedc7df "Prevented `ld.so` from asserting and crashing during audited library loads." The remaining are applied in this commit. Resolves: RHEL-101986 --- glibc-RHEL-101986-1.patch | 148 +++++++++++++++++++++ glibc-RHEL-101986-2.patch | 264 ++++++++++++++++++++++++++++++++++++++ glibc.spec | 7 +- 3 files changed, 418 insertions(+), 1 deletion(-) create mode 100644 glibc-RHEL-101986-1.patch create mode 100644 glibc-RHEL-101986-2.patch diff --git a/glibc-RHEL-101986-1.patch b/glibc-RHEL-101986-1.patch new file mode 100644 index 0000000..b1fdce9 --- /dev/null +++ b/glibc-RHEL-101986-1.patch @@ -0,0 +1,148 @@ +commit 8329939a37f483a16013dd8af8303cbcb86d92cb +Author: Florian Weimer +Date: Fri Jul 4 21:46:16 2025 +0200 + + elf: Introduce _dl_debug_change_state + + It combines updating r_state with the debugger notification. + + The second change to _dl_open introduces an additional debugger + notification for dlmopen, but debuggers are expected to ignore it. + + Reviewed-by: H.J. Lu + +diff --git a/elf/dl-close.c b/elf/dl-close.c +index 236d89f67f3bf410..fa3974afba798073 100644 +--- a/elf/dl-close.c ++++ b/elf/dl-close.c +@@ -472,8 +472,7 @@ _dl_close_worker (struct link_map *map, bool force) + /* Notify the debugger we are about to remove some loaded objects. + LA_ACT_DELETE has already been signalled above for !unload_any. */ + struct r_debug *r = _dl_debug_update (nsid); +- r->r_state = RT_DELETE; +- _dl_debug_state (); ++ _dl_debug_change_state (r, RT_DELETE); + LIBC_PROBE (unmap_start, 2, nsid, r); + + if (unload_global) +@@ -762,8 +761,7 @@ _dl_close_worker (struct link_map *map, bool force) + __rtld_lock_unlock_recursive (GL(dl_load_tls_lock)); + + /* Notify the debugger those objects are finalized and gone. */ +- r->r_state = RT_CONSISTENT; +- _dl_debug_state (); ++ _dl_debug_change_state (r, RT_CONSISTENT); + LIBC_PROBE (unmap_complete, 2, nsid, r); + + #ifdef SHARED +diff --git a/elf/dl-debug.c b/elf/dl-debug.c +index 649386d5a6b885ed..f840a1b92292968d 100644 +--- a/elf/dl-debug.c ++++ b/elf/dl-debug.c +@@ -67,6 +67,13 @@ _dl_debug_update (Lmid_t ns) + return &r->base; + } + ++void ++_dl_debug_change_state (struct r_debug *r, int state) ++{ ++ atomic_store_release (&r->r_state, state); ++ _dl_debug_state (); ++} ++ + /* Initialize _r_debug_extended for the namespace NS. LDBASE is the + run-time load address of the dynamic linker, to be put in + _r_debug_extended.r_ldbase. Return the address of _r_debug. */ +diff --git a/elf/dl-load.c b/elf/dl-load.c +index 6714807946b60188..c118db811d8899f6 100644 +--- a/elf/dl-load.c ++++ b/elf/dl-load.c +@@ -946,8 +946,7 @@ _dl_notify_new_object (int mode, Lmid_t nsid, struct link_map *l) + /* Notify the debugger we have added some objects. We need to + call _dl_debug_initialize in a static program in case dynamic + linking has not been used before. */ +- r->r_state = RT_ADD; +- _dl_debug_state (); ++ _dl_debug_change_state (r, RT_ADD); + LIBC_PROBE (map_start, 2, nsid, r); + } + else +diff --git a/elf/dl-open.c b/elf/dl-open.c +index 1e61e402455da666..df6aa55a8842ee62 100644 +--- a/elf/dl-open.c ++++ b/elf/dl-open.c +@@ -787,8 +787,7 @@ dl_open_worker (void *a) + #ifdef SHARED + bool was_not_consistent = r->r_state != RT_CONSISTENT; + #endif +- r->r_state = RT_CONSISTENT; +- _dl_debug_state (); ++ _dl_debug_change_state (r, RT_CONSISTENT); + LIBC_PROBE (map_complete, 3, nsid, r, args->map); + + #ifdef SHARED +@@ -866,7 +865,7 @@ no more namespaces available for dlmopen()")); + } + + GL(dl_ns)[nsid].libc_map = NULL; +- _dl_debug_update (nsid)->r_state = RT_CONSISTENT; ++ _dl_debug_change_state (_dl_debug_update (nsid), RT_CONSISTENT); + } + /* Never allow loading a DSO in a namespace which is empty. Such + direct placements is only causing problems. Also don't allow +diff --git a/elf/rtld.c b/elf/rtld.c +index cd233174c9d944b2..0fe9986e4c7ed830 100644 +--- a/elf/rtld.c ++++ b/elf/rtld.c +@@ -1842,8 +1842,7 @@ dl_main (const ElfW(Phdr) *phdr, + #endif + + /* We start adding objects. */ +- r->r_state = RT_ADD; +- _dl_debug_state (); ++ _dl_debug_change_state (r, RT_ADD); + LIBC_PROBE (init_start, 2, LM_ID_BASE, r); + + /* Auditing checkpoint: we are ready to signal that the initial map +@@ -2527,8 +2526,7 @@ dl_main (const ElfW(Phdr) *phdr, + /* Notify the debugger all new objects are now ready to go. We must re-get + the address since by now the variable might be in another object. */ + r = _dl_debug_update (LM_ID_BASE); +- r->r_state = RT_CONSISTENT; +- _dl_debug_state (); ++ _dl_debug_change_state (r, RT_CONSISTENT); + LIBC_PROBE (init_complete, 2, LM_ID_BASE, r); + + #ifdef SHARED +diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h +index 21dbe2d21ed8e605..371c32dd79c3ea2b 100644 +--- a/sysdeps/generic/ldsodefs.h ++++ b/sysdeps/generic/ldsodefs.h +@@ -1129,8 +1129,14 @@ extern void _dl_debug_state (void); + rtld_hidden_proto (_dl_debug_state) + + /* Initialize `struct r_debug_extended' for the namespace NS. LDBASE +- is the run-time load address of the dynamic linker, to be put in the +- `r_ldbase' member. Return the address of the structure. */ ++ is the run-time load address of the dynamic linker, to be put in ++ the `r_ldbase' member. ++ ++ Return the address of the r_debug structure for the namespace. ++ This is not merely a convenience or optimization, but it is ++ necessary for the LIBC_PROBE Systemtap/debugger probes to work ++ reliably: direct variable access can create probes that tools ++ cannot consume. */ + extern struct r_debug *_dl_debug_initialize (ElfW(Addr) ldbase, Lmid_t ns) + attribute_hidden; + +@@ -1138,6 +1144,10 @@ extern struct r_debug *_dl_debug_initialize (ElfW(Addr) ldbase, Lmid_t ns) + of the namespace NS. */ + extern struct r_debug *_dl_debug_update (Lmid_t ns) attribute_hidden; + ++/* Update R->r_state to STATE and notify the debugger by calling ++ _dl_debug_state. */ ++void _dl_debug_change_state (struct r_debug *r, int state) attribute_hidden; ++ + /* Initialize the basic data structure for the search paths. SOURCE + is either "LD_LIBRARY_PATH" or "--library-path". + GLIBC_HWCAPS_PREPEND adds additional glibc-hwcaps subdirectories to diff --git a/glibc-RHEL-101986-2.patch b/glibc-RHEL-101986-2.patch new file mode 100644 index 0000000..d382cd6 --- /dev/null +++ b/glibc-RHEL-101986-2.patch @@ -0,0 +1,264 @@ +commit ea85e7d55087075376a29261e722e4fae14ecbe7 +Author: Florian Weimer +Date: Fri Jul 4 21:46:30 2025 +0200 + + elf: Restore support for _r_debug interpositions and copy relocations + + The changes in commit a93d9e03a31ec14405cb3a09aa95413b67067380 + ("Extend struct r_debug to support multiple namespaces [BZ #15971]") + break the dyninst dynamic instrumentation tool. It brings its + own definition of _r_debug (rather than a declaration). + + Furthermore, it turns out it is rather hard to use the proposed + handshake for accessing _r_debug via DT_DEBUG. If applications want + to access _r_debug, they can do so directly if the relevant code has + been built as PIC. To protect against harm from accidental copy + relocations due to linker relaxations, this commit restores copy + relocation support by adjusting both copies if interposition or + copy relocations are in play. Therefore, it is possible to + use a hidden reference in ld.so to access _r_debug. + + Only perform the copy relocation initialization if libc has been + loaded. Otherwise, the ld.so search scope can be empty, and the + lookup of the _r_debug symbol mail fail. + + Reviewed-by: H.J. Lu + +Conflicts: + elf/rtld.c: Adjust for prelink. + +diff --git a/elf/Makefile b/elf/Makefile +index 721f254d121118c0..3eac746d21042ec9 100644 +--- a/elf/Makefile ++++ b/elf/Makefile +@@ -404,6 +404,8 @@ tests += \ + tst-dlmopen1 \ + tst-dlmopen3 \ + tst-dlmopen4 \ ++ tst-dlmopen4-nonpic \ ++ tst-dlmopen4-pic \ + tst-dlopen-auditdup \ + tst-dlopen-constructor-null \ + tst-dlopen-self \ +@@ -1986,6 +1988,13 @@ $(objpfx)tst-dlmopen3.out: $(objpfx)tst-dlmopen1mod.so + + $(objpfx)tst-dlmopen4.out: $(objpfx)tst-dlmopen1mod.so + ++CFLAGS-tst-dlmopen4-pic.c += -fPIC ++$(objpfx)tst-dlmopen4-pic.out: $(objpfx)tst-dlmopen1mod.so ++ ++CFLAGS-tst-dlmopen4-nonpic.c += -fno-pie ++tst-dlmopen4-nonpic-no-pie = yes ++$(objpfx)tst-dlmopen4-nonpic.out: $(objpfx)tst-dlmopen1mod.so ++ + $(objpfx)tst-audit1.out: $(objpfx)tst-auditmod1.so + tst-audit1-ENV = LD_AUDIT=$(objpfx)tst-auditmod1.so + +diff --git a/elf/dl-debug-symbols.S b/elf/dl-debug-symbols.S +index 28456ab1f237ea87..629b0c0c6b2cd9e1 100644 +--- a/elf/dl-debug-symbols.S ++++ b/elf/dl-debug-symbols.S +@@ -38,3 +38,4 @@ + _r_debug: + _r_debug_extended: + .zero R_DEBUG_EXTENDED_SIZE ++rtld_hidden_def (_r_debug) +diff --git a/elf/dl-debug.c b/elf/dl-debug.c +index f840a1b92292968d..4388a04cdf828898 100644 +--- a/elf/dl-debug.c ++++ b/elf/dl-debug.c +@@ -16,6 +16,7 @@ + License along with the GNU C Library; if not, see + . */ + ++#include + #include + + +@@ -37,6 +38,37 @@ extern const int verify_link_map_members[(VERIFY_MEMBER (l_addr) + to LM_ID_BASE + 1. See elf/dl-debug-symbols.S. */ + struct r_debug_extended _r_debug_array[DL_NNS - 1]; + ++/* If not null, pointer to the _r_debug in the main executable. */ ++static struct r_debug *_r_debug_main; ++ ++void ++_dl_debug_post_relocate (struct link_map *main_map) ++{ ++ /* Perform a full symbol search in all objects, to maintain ++ compatibility if interposed _r_debug definitions. The lookup ++ cannot fail because there is a definition in ld.so, and this ++ function is only called if the ld.so search scope is not empty. */ ++ const ElfW(Sym) *sym = NULL; ++ lookup_t result =_dl_lookup_symbol_x ("_r_debug", main_map, &sym, ++ main_map->l_scope, NULL, 0, 0, NULL); ++ if (sym->st_size >= sizeof (struct r_debug)) ++ { ++ struct r_debug *main_r_debug = DL_SYMBOL_ADDRESS (result, sym); ++ if (main_r_debug != &_r_debug_extended.base) ++ { ++ /* The extended version of the struct is not available in ++ the main executable because a copy relocation has been ++ used. r_map etc. have already been copied as part of the ++ copy relocation processing. */ ++ main_r_debug->r_version = 1; ++ ++ /* Record that dual updates of the initial link map are ++ required. */ ++ _r_debug_main = main_r_debug; ++ } ++ } ++} ++ + /* Return the r_debug object for the namespace NS. */ + static inline struct r_debug_extended * + get_rdebug (Lmid_t ns) +@@ -71,6 +103,11 @@ void + _dl_debug_change_state (struct r_debug *r, int state) + { + atomic_store_release (&r->r_state, state); ++#ifdef SHARED ++ if (r == &_r_debug_extended.base && _r_debug_main != NULL) ++ /* Update the copy-relocation of _r_debug. */ ++ atomic_store_release (&_r_debug_main->r_state, state); ++#endif + _dl_debug_state (); + } + +@@ -103,7 +140,9 @@ _dl_debug_initialize (ElfW(Addr) ldbase, Lmid_t ns) + if (ns - 1 == LM_ID_BASE) + { + atomic_store_release (&_r_debug_extended.r_next, r); +- /* Now there are multiple namespaces. */ ++ /* Now there are multiple namespaces. Note that this ++ deliberately does not update the copy in the main ++ executable (if it exists). */ + atomic_store_release (&_r_debug_extended.base.r_version, 2); + } + else +@@ -116,8 +155,15 @@ _dl_debug_initialize (ElfW(Addr) ldbase, Lmid_t ns) + } + + if (r->base.r_map == NULL) +- atomic_store_release (&r->base.r_map, +- (void *) GL(dl_ns)[ns]._ns_loaded); ++ { ++ struct link_map_public *l = (void *) GL(dl_ns)[ns]._ns_loaded; ++ atomic_store_release (&r->base.r_map, l); ++#ifdef SHARED ++ if (ns == LM_ID_BASE && _r_debug_main != NULL) ++ /* Update the copy-relocation of _r_debug. */ ++ atomic_store_release (&_r_debug_main->r_map, l); ++#endif ++ } + + return &r->base; + } +diff --git a/elf/rtld.c b/elf/rtld.c +index 0fe9986e4c7ed830..dac827e249b2fe14 100644 +--- a/elf/rtld.c ++++ b/elf/rtld.c +@@ -2395,6 +2395,9 @@ dl_main (const ElfW(Phdr) *phdr, + /* Likewise for the locking implementation. */ + __rtld_mutex_init (); + ++ /* Update copy-relocated _r_debug if necessary. */ ++ _dl_debug_post_relocate (main_map); ++ + /* Mark all the objects so we know they have been already relocated. */ + for (struct link_map *l = main_map; l != NULL; l = l->l_next) + { +@@ -2505,6 +2508,9 @@ dl_main (const ElfW(Phdr) *phdr, + + __rtld_mutex_init (); + __rtld_malloc_init_real (main_map); ++ ++ /* Update copy-relocated _r_debug if necessary. */ ++ _dl_debug_post_relocate (main_map); + } + + /* All ld.so initialization is complete. Apply RELRO. */ +diff --git a/elf/tst-dlmopen4-nonpic.c b/elf/tst-dlmopen4-nonpic.c +new file mode 100644 +index 0000000000000000..ad4e40995337f4f9 +--- /dev/null ++++ b/elf/tst-dlmopen4-nonpic.c +@@ -0,0 +1,2 @@ ++#define BUILD_FOR_NONPIC ++#include "tst-dlmopen4.c" +diff --git a/elf/tst-dlmopen4-pic.c b/elf/tst-dlmopen4-pic.c +new file mode 100644 +index 0000000000000000..919fa85c2579fb5d +--- /dev/null ++++ b/elf/tst-dlmopen4-pic.c +@@ -0,0 +1,2 @@ ++#define BUILD_FOR_PIC ++#include "tst-dlmopen4.c" +diff --git a/elf/tst-dlmopen4.c b/elf/tst-dlmopen4.c +index 3fe150e50bc259f0..633addf41978cee8 100644 +--- a/elf/tst-dlmopen4.c ++++ b/elf/tst-dlmopen4.c +@@ -53,6 +53,15 @@ do_test (void) + TEST_COMPARE (debug->base.r_version, 1); + TEST_VERIFY_EXIT (debug->r_next == NULL); + ++#ifdef BUILD_FOR_PIC ++ /* In a PIC build, using _r_debug directly should give us the same ++ object. */ ++ TEST_VERIFY (&_r_debug == &debug->base); ++#endif ++#ifdef BUILD_FOR_NONPIC ++ TEST_COMPARE (_r_debug.r_version, 1); ++#endif ++ + void *h = xdlmopen (LM_ID_NEWLM, "$ORIGIN/tst-dlmopen1mod.so", + RTLD_LAZY); + +@@ -64,6 +73,19 @@ do_test (void) + const char *name = basename (debug->r_next->base.r_map->l_name); + TEST_COMPARE_STRING (name, "tst-dlmopen1mod.so"); + ++#ifdef BUILD_FOR_NONPIC ++ /* If a copy relocation is used, it must be at version 1. */ ++ if (&_r_debug != &debug->base) ++ { ++ TEST_COMPARE (_r_debug.r_version, 1); ++ TEST_COMPARE ((uintptr_t) _r_debug.r_map, ++ (uintptr_t) debug->base.r_map); ++ TEST_COMPARE (_r_debug.r_brk, debug->base.r_brk); ++ TEST_COMPARE (_r_debug.r_state, debug->base.r_state); ++ TEST_COMPARE (_r_debug.r_ldbase, debug->base.r_ldbase); ++ } ++#endif ++ + xdlclose (h); + + return 0; +diff --git a/include/link.h b/include/link.h +index 0cf130ddb8af2e89..bafac6c9628b183c 100644 +--- a/include/link.h ++++ b/include/link.h +@@ -366,6 +366,8 @@ struct auditstate + dynamic linker. */ + extern struct r_debug_extended _r_debug_extended attribute_hidden; + ++rtld_hidden_proto (_r_debug) ++ + #if __ELF_NATIVE_CLASS == 32 + # define symbind symbind32 + # define LA_SYMBIND "la_symbind32" +diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h +index 371c32dd79c3ea2b..484893c2928db8e7 100644 +--- a/sysdeps/generic/ldsodefs.h ++++ b/sysdeps/generic/ldsodefs.h +@@ -1140,6 +1140,10 @@ rtld_hidden_proto (_dl_debug_state) + extern struct r_debug *_dl_debug_initialize (ElfW(Addr) ldbase, Lmid_t ns) + attribute_hidden; + ++/* This is called after relocation processing to handle a potential ++ copy relocation for _r_debug. */ ++void _dl_debug_post_relocate (struct link_map *main_map) attribute_hidden; ++ + /* Update the `r_map' member and return the address of `struct r_debug' + of the namespace NS. */ + extern struct r_debug *_dl_debug_update (Lmid_t ns) attribute_hidden; diff --git a/glibc.spec b/glibc.spec index 01bb43e..c8d5513 100644 --- a/glibc.spec +++ b/glibc.spec @@ -157,7 +157,7 @@ end \ Summary: The GNU libc libraries Name: glibc Version: %{glibcversion} -Release: 214%{?dist} +Release: 215%{?dist} # In general, GPLv2+ is used by programs, LGPLv2+ is used for # libraries. @@ -1300,6 +1300,8 @@ Patch990: glibc-RHEL-49549-6.patch Patch991: glibc-RHEL-49549-7.patch Patch992: glibc-RHEL-49549-8.patch Patch993: glibc-RHEL-49549-9.patch +Patch994: glibc-RHEL-101986-1.patch +Patch995: glibc-RHEL-101986-2.patch ############################################################################## # Continued list of core "glibc" package information: @@ -3297,6 +3299,9 @@ update_gconv_modules_cache () %endif %changelog +* Thu Jul 10 2025 Arjun Shankar - 2.34-215 +- Extend struct r_debug to support multiple namespaces (RHEL-101986) + * Wed Jul 09 2025 Arjun Shankar - 2.34-214 - Signal la_objopen for ld.so with dlmopen (RHEL-49549)