[PATCH RESEND 3/3] elf: allow dl_iterate_phdr() to be called concurrently
Caleb Sander Mateos
csander@purestorage.com
Tue Sep 23 19:27:04 GMT 2025
dl_iterate_phdr() internally acquires the dl_load_write_lock mutex,
which means it will block if called concurrently on a second thread.
This mutual exclusion is surprising for a function that's read-only in
spirit. And it can cause deadlocks in applications calling it from
multiple places if the user-supplied callback acquires a nested mutex.
For example, an application compiled with ThreadSanitizer that uses
dl_iterate_phdr() to build a symbol table can deadlock if TSAN detects a
race condition concurrently. This is because any memory access inside
the dl_iterate_phdr() callback may be intercepted by TSAN and block on a
TSAN-internal semaphore. At the same time, TSAN's race reporting may
already hold the semaphore and call into dl_iterate_phdr() to symbolize
the stacktrace, blocking on dl_load_write_lock.
Change dl_load_write_lock from a mutex to a reader-writer lock to allow
concurrent calls to dl_iterate_phdr(). _dl_add_to_namespace_list() and
_dl_close_worker() acquire a write lock as they modify the loaded object
list, while __dl_iterate_phdr() acquires a read lock as it only reads
from the list.
Example backtraces from threads in the dl_iterate_phdr-TSAN deadlock:
0 0x00000000043dacee in __sanitizer::FutexWait(__sanitizer::atomic_uint32_t*, unsigned int) ()
1 0x00000000043dc23a in __sanitizer::Semaphore::Wait() ()
2 0x000000000446cf90 in __tsan::TraceSwitchPartImpl(__tsan::ThreadState*) ()
3 0x000000000446eadd in void __tsan::RestartMemoryAccessRange<true>(__tsan::ThreadState*, unsigned long, unsigned long, unsigned long) ()
4 0x000000000440d7ab in __interceptor_strcmp ()
5 0x00000000068bb33f in elf_add (state=0x7f8270031000, filename=filename@entry=0x7f8270777de0 "/lib64/libresolv.so.2", descriptor=42, memory=memory@entry=0x0, memory_size=memory_size@entry=0, base_address=140198202634240, error_callback=0x4c3abe2 <backtrace_error_cb(void*, char const*, int)>, data=0x7f822a965e60, fileline_fn=0x7f822a965b48, found_sym=0x7f822a965d34, found_dwarf=0x7f822a965b54, fileline_entry=0x0, exe=0, debuginfo=0, with_buildid_data=0x0, with_buildid_size=0, so_filename=0x7f8270777de0 "/lib64/libresolv.so.2") at ./tpc/backtrace/elf.c:4264
6 0x00000000068bd405 in phdr_callback (info=0x7f822a965be0, size=<optimized out>, pdata=0x7f822a965cd0) at ./tpc/backtrace/elf.c:4896
7 0x000000000440cbe2 in dl_iterate_phdr_cb(__sanitizer::__sanitizer_dl_phdr_info*, unsigned long, void*) ()
8 0x00007f826fb56874 in __GI___dl_iterate_phdr (callback=0x440caf0 <dl_iterate_phdr_cb(__sanitizer::__sanitizer_dl_phdr_info*, unsigned long, void*)>, data=0x7f822a965c88) at dl-iteratephdr.c:75
9 0x000000000440ca52 in __interceptor_dl_iterate_phdr ()
10 0x00000000068ba9d2 in backtrace_initialize (state=state@entry=0x7f8270031000, filename=filename@entry=0x30c2219 <.L.str.189> "/proc/self/exe", descriptor=<optimized out>, error_callback=error_callback@entry=0x4c3abe2 <backtrace_error_cb(void*, char const*, int)>, data=data@entry=0x7f822a965e60, fileline_fn=fileline_fn@entry=0x7f822a965db8) at ./tpc/backtrace/elf.c:4940
0 futex_wait (private=0, expected=2, futex_word=0x7f827093a9b0 <_rtld_local+2480>) at ../sysdeps/nptl/futex-internal.h:146
1 __GI___lll_lock_wait (futex=futex@entry=0x7f827093a9b0 <_rtld_local+2480>, private=0) at lowlevellock.c:50
2 0x00007f826fa8d5ad in lll_mutex_lock_optimized (mutex=0x7f827093a9b0 <_rtld_local+2480>) at pthread_mutex_lock.c:49
3 ___pthread_mutex_lock (mutex=0x7f827093a9b0 <_rtld_local+2480>) at pthread_mutex_lock.c:129
4 0x00007f826fb5671c in __GI___dl_iterate_phdr (callback=0x43e6ae0 <__sanitizer::dl_iterate_phdr_cb(dl_phdr_info*, unsigned long, void*)>, data=0x7f822a8c34f0) at dl-iteratephdr.c:40
5 0x000000000440c9cd in __interceptor_dl_iterate_phdr ()
6 0x00000000043e6acd in __sanitizer::ListOfModules::init() ()
7 0x00000000043f0645 in __sanitizer::Symbolizer::FindModuleForAddress(unsigned long) ()
8 0x00000000043f083b in __sanitizer::Symbolizer::SymbolizeData(unsigned long, __sanitizer::DataInfo*) ()
9 0x0000000004482486 in __tsan::SymbolizeData(unsigned long) ()
10 0x000000000447d457 in __tsan::ScopedReportBase::AddLocation(unsigned long, unsigned long) ()
11 0x000000000447f82f in __tsan::ReportRace(__tsan::ThreadState*, __tsan::RawShadow*, __tsan::Shadow, __tsan::Shadow, unsigned long) ()
Signed-off-by: Caleb Sander Mateos <csander@purestorage.com>
---
elf/dl-close.c | 4 ++--
elf/dl-iteratephdr.c | 6 +++---
elf/dl-object.c | 4 ++--
elf/dl-support.c | 2 +-
elf/rtld.c | 2 +-
sysdeps/generic/ldsodefs.h | 2 +-
6 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/elf/dl-close.c b/elf/dl-close.c
index 83e4f012b2..11cffffb9b 100644
--- a/elf/dl-close.c
+++ b/elf/dl-close.c
@@ -482,11 +482,11 @@ _dl_close_worker (struct link_map *map, bool force)
/* Protects global and module specitic TLS state. */
__rtld_lock_lock_recursive (GL(dl_load_tls_lock));
/* We modify the list of loaded objects. */
- __rtld_lock_lock_recursive (GL(dl_load_write_lock));
+ __rtld_rwlock_wrlock_recursive (GL(dl_load_write_lock));
/* Check each element of the search list to see if all references to
it are gone. */
for (unsigned int i = first_loaded; i < nloaded; ++i)
{
@@ -704,11 +704,11 @@ _dl_close_worker (struct link_map *map, bool force)
free (imap);
}
}
- __rtld_lock_unlock_recursive (GL(dl_load_write_lock));
+ __rtld_rwlock_unlock_recursive (GL(dl_load_write_lock));
/* If we removed any object which uses TLS bump the generation counter. */
if (any_tls)
{
size_t newgen = GL(dl_tls_generation) + 1;
diff --git a/elf/dl-iteratephdr.c b/elf/dl-iteratephdr.c
index e83e7e2a0c..dfe029acd1 100644
--- a/elf/dl-iteratephdr.c
+++ b/elf/dl-iteratephdr.c
@@ -22,11 +22,11 @@
#include <libc-lock.h>
static void
cancel_handler (void *arg __attribute__((unused)))
{
- __rtld_lock_unlock_recursive (GL(dl_load_write_lock));
+ __rtld_rwlock_unlock_recursive (GL(dl_load_write_lock));
}
int
__dl_iterate_phdr (int (*callback) (struct dl_phdr_info *info,
size_t size, void *data), void *data)
@@ -34,11 +34,11 @@ __dl_iterate_phdr (int (*callback) (struct dl_phdr_info *info,
struct link_map *l;
struct dl_phdr_info info;
int ret = 0;
/* Make sure nobody modifies the list of loaded objects. */
- __rtld_lock_lock_recursive (GL(dl_load_write_lock));
+ __rtld_rwlock_rdlock_recursive (GL(dl_load_write_lock));
__libc_cleanup_push (cancel_handler, NULL);
/* We have to determine the namespace of the caller since this determines
which namespace is reported. */
size_t nloaded = GL(dl_ns)[0]._ns_nloaded;
@@ -76,11 +76,11 @@ __dl_iterate_phdr (int (*callback) (struct dl_phdr_info *info,
break;
}
/* Release the lock. */
__libc_cleanup_pop (0);
- __rtld_lock_unlock_recursive (GL(dl_load_write_lock));
+ __rtld_rwlock_unlock_recursive (GL(dl_load_write_lock));
return ret;
}
hidden_def (__dl_iterate_phdr)
diff --git a/elf/dl-object.c b/elf/dl-object.c
index 51d3704edc..0f5133a207 100644
--- a/elf/dl-object.c
+++ b/elf/dl-object.c
@@ -28,11 +28,11 @@
/* Add the new link_map NEW to the end of the namespace list. */
void
_dl_add_to_namespace_list (struct link_map *new, Lmid_t nsid)
{
/* We modify the list of loaded objects. */
- __rtld_lock_lock_recursive (GL(dl_load_write_lock));
+ __rtld_rwlock_wrlock_recursive (GL(dl_load_write_lock));
if (GL(dl_ns)[nsid]._ns_loaded != NULL)
{
struct link_map *l = GL(dl_ns)[nsid]._ns_loaded;
while (l->l_next != NULL)
@@ -45,11 +45,11 @@ _dl_add_to_namespace_list (struct link_map *new, Lmid_t nsid)
GL(dl_ns)[nsid]._ns_loaded = new;
++GL(dl_ns)[nsid]._ns_nloaded;
new->l_serial = GL(dl_load_adds);
++GL(dl_load_adds);
- __rtld_lock_unlock_recursive (GL(dl_load_write_lock));
+ __rtld_rwlock_unlock_recursive (GL(dl_load_write_lock));
}
/* Allocate a `struct link_map' for a new object being loaded,
and enter it into the _dl_loaded list. */
diff --git a/elf/dl-support.c b/elf/dl-support.c
index bbef3ab6b6..edf033839c 100644
--- a/elf/dl-support.c
+++ b/elf/dl-support.c
@@ -207,11 +207,11 @@ struct link_map *_dl_sysinfo_map;
At this time it is not anymore a problem to modify the tables. */
__rtld_lock_define_initialized_recursive (, _dl_load_lock)
/* This lock is used to keep __dl_iterate_phdr from inspecting the
list of loaded objects while an object is added to or removed from
that list. */
-__rtld_lock_define_initialized_recursive (, _dl_load_write_lock)
+__rtld_rwlock_define_initialized_recursive (, _dl_load_write_lock)
/* This lock protects global and module specific TLS related data.
E.g. it is held in dlopen and dlclose when GL(dl_tls_generation),
GL(dl_tls_max_dtv_idx) or GL(dl_tls_dtv_slotinfo_list) are
accessed and when TLS related relocations are processed for a
module. It was introduced to keep pthread_create accessing TLS
diff --git a/elf/rtld.c b/elf/rtld.c
index 753ce6690b..60e6421a58 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -323,11 +323,11 @@ struct rtld_global _rtld_global =
/* Generally the default presumption without further information is an
* executable stack but this is not true for all platforms. */
._dl_stack_prot_flags = DEFAULT_STACK_PROT_PERMS,
#ifdef _LIBC_REENTRANT
._dl_load_lock = _RTLD_LOCK_RECURSIVE_INITIALIZER,
- ._dl_load_write_lock = _RTLD_LOCK_RECURSIVE_INITIALIZER,
+ ._dl_load_write_lock = _RTLD_RWLOCK_RECURSIVE_INITIALIZER,
._dl_load_tls_lock = _RTLD_LOCK_RECURSIVE_INITIALIZER,
#endif
._dl_nns = 1,
._dl_ns =
{
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
index f36e64ddba..6e78e18882 100644
--- a/sysdeps/generic/ldsodefs.h
+++ b/sysdeps/generic/ldsodefs.h
@@ -368,11 +368,11 @@ struct rtld_global
At this time it is not anymore a problem to modify the tables. */
__rtld_lock_define_recursive (EXTERN, _dl_load_lock)
/* This lock is used to keep __dl_iterate_phdr from inspecting the
list of loaded objects while an object is added to or removed
from that list. */
- __rtld_lock_define_recursive (EXTERN, _dl_load_write_lock)
+ __rtld_rwlock_define_recursive (EXTERN, _dl_load_write_lock)
/* This lock protects global and module specific TLS related data.
E.g. it is held in dlopen and dlclose when GL(dl_tls_generation),
GL(dl_tls_max_dtv_idx) or GL(dl_tls_dtv_slotinfo_list) are
accessed and when TLS related relocations are processed for a
module. It was introduced to keep pthread_create accessing TLS
--
2.43.0
More information about the Libc-alpha
mailing list