[PATCH 0/3] elf: allow dl_iterate_phdr() to be called concurrently
Caleb Sander Mateos
csander@purestorage.com
Tue Sep 23 18:32:51 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.
Add support for (recursive) reader-writer locks in rtld, using
pthread_rwlock internally. 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) ()
Caleb Sander Mateos (3):
mach: remove unused rtld_lock macros
sysdeps: add rtld_rwlock_recursive
elf: allow dl_iterate_phdr() to be called concurrently
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 | 5 ++++-
sysdeps/generic/libc-lock.h | 5 +++++
sysdeps/mach/libc-lock.h | 10 +++++++---
sysdeps/nptl/dl-mutex.c | 24 ++++++++++++++++++++++++
sysdeps/nptl/dl-tls_init_tp.c | 9 +++++++++
sysdeps/nptl/libc-lockP.h | 26 ++++++++++++++++++++++++++
11 files changed, 84 insertions(+), 13 deletions(-)
--
2.43.0
More information about the Libc-alpha
mailing list