NetBSD Problem Report #59751
From www@netbsd.org Sat Nov 8 19:51:35 2025
Return-Path: <www@netbsd.org>
Received: from mail.netbsd.org (mail.netbsd.org [199.233.217.200])
(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
key-exchange X25519 server-signature RSA-PSS (2048 bits)
client-signature RSA-PSS (2048 bits))
(Client CN "mail.NetBSD.org", Issuer "mail.NetBSD.org CA" (not verified))
by mollari.NetBSD.org (Postfix) with ESMTPS id 1E0221A923C
for <gnats-bugs@gnats.NetBSD.org>; Sat, 8 Nov 2025 19:51:35 +0000 (UTC)
Message-Id: <20251108195133.B9D321A923E@mollari.NetBSD.org>
Date: Sat, 8 Nov 2025 19:51:33 +0000 (UTC)
From: netbsd@kazlauskas.me
Reply-To: netbsd@kazlauskas.me
To: gnats-bugs@NetBSD.org
Subject: dlclose is not MT-safe depending on the libraries unloaded
X-Send-Pr-Version: www-1.0
>Number: 59751
>Category: lib
>Synopsis: dlclose is not MT-safe depending on the libraries unloaded
>Confidential: no
>Severity: non-critical
>Priority: low
>Responsible: riastradh
>State: open
>Class: sw-bug
>Submitter-Id: net
>Arrival-Date: Sat Nov 08 19:55:00 +0000 2025
>Closed-Date:
>Last-Modified: Wed Jul 22 05:25:02 +0000 2026
>Originator: Simonas K.
>Release: 10.1
>Organization:
N/A
>Environment:
NetBSD 10.1 NetBSD 10.1 (GENERIC) #0: Mon Dec 16 13:08:11 UTC 2024 mkrepro@mkrepro.NetBSD.org:/usr/src/sys/arch/amd64/compile/GENERIC amd64
>Description:
It looks like unloading dynamically loaded libraries via `dlclose` can result in applications SIGSEGV'ing with all sorts of stack traces such as:
```
* thread #1, stop reason = signal SIGSEGV
* frame #0: 0x000075c01b8419e0
frame #1: 0x00007f7eaa205e04 ld.elf_so`_rtld_call_fini_function + 135
frame #2: 0x00007f7eaa2061bd ld.elf_so`_rtld_unload_object.part.0 + 700
frame #3: 0x00007f7eaa2074b2 ld.elf_so`dlclose + 233
...
```
```
* frame #0: 0x00007f7f03a07fab ld.elf_so`_rtld_symlook_obj + 619
frame #1: 0x00007f7f03a083ea ld.elf_so`_rtld_symlook_list + 185
frame #2: 0x00007f7f03a0889f ld.elf_so`_rtld_symlook_default + 530
frame #3: 0x00007f7f03a08d4a ld.elf_so`_rtld_find_plt_symdef + 221
frame #4: 0x00007f7f03a00bc0 ld.elf_so`_rtld_bind + 75
frame #5: 0x00007f7f03a0082d ld.elf_so`_rtld_bind_start + 29
...
```
```
* frame #0: 0x000073c4aaa10e6f libgcc_s.so.1`__deregister_frame_info_bases + 70
frame #1: 0x000073c4aa446705 libc.so.12`__do_global_dtors_aux + 85
frame #2: 0x000073c4aa584919 libc.so.12`_fini + 9
frame #3: 0x00007f7e90805e04 ld.elf_so`_rtld_call_fini_function + 135
frame #4: 0x00007f7e9080623d ld.elf_so`_rtld_unload_object.part.0 + 828
frame #5: 0x00007f7e908074b2 ld.elf_so`dlclose + 233
...
```
etc. Not unloading the libraries (i.e. commenting out `dlclose`) makes the SIGSEGVs go away. It seems like this may be related to the fact that the library loaded/unloaded has dtors. In my case the code being tested is a Rust library over dl* primitives (rust_libloading) and the dynamic library used is not especially interesting (e.g. it has just a few pure functions and don't do risky stuff as spawning threads.)
>How-To-Repeat:
* git clone https://github.com/nagisa/rust_libloading
* cargo test
* observe sigsegv (make sure to run on a multi-core system.)
You can comment out the two occurrences of `dlclose` in `src/os/unix/mod.rs` to check that it is indeed `dlclose` that's having a poor interaction with *something*. You can also comment out the code involving `dlerror` to rule out any potential MT-unsafety related issues.
The code for library being loaded and unloaded is available at `src/test_helpers.rs` which can be independently compiled with `rustc src/test_helpers.rs -o test_helpers.so`.
>Fix:
A number of other system library/linker/loader implementations transparently refuse to unload libraries that e.g. register thread locals. An example of this is MacOS. I'm semi-confident that nothing of the sort is happening in `rust_libloading`s tests though and the shared object is extremely straighforward, so something deeper is going on here.
---
P.S. it would be nice if `dlerror` was made MT-safe (by e.g. maintaining a thread-local error buffer.)
P.P.S. I'm just forwarding a bug report made against rust_libloading after some initial investigation. I'm happy to help with brainstorming possible causes.
>Release-Note:
>Audit-Trail:
From: "Taylor R Campbell" <riastradh@netbsd.org>
To: gnats-bugs@gnats.NetBSD.org
Cc:
Subject: PR/59751 CVS commit: src
Date: Sun, 23 Nov 2025 22:01:14 +0000
Module Name: src
Committed By: riastradh
Date: Sun Nov 23 22:01:14 UTC 2025
Modified Files:
src/distrib/sets/lists/debug: mi
src/distrib/sets/lists/tests: mi
src/tests/libexec/ld.elf_so: Makefile
Added Files:
src/tests/libexec/ld.elf_so: t_dlclose_thread.c
Log Message:
ld.elf_so(1): Test concurrent dlopen/dlclose.
PR lib/59751: dlclose is not MT-safe depending on the libraries
unloaded
To generate a diff of this commit:
cvs rdiff -u -r1.490 -r1.491 src/distrib/sets/lists/debug/mi
cvs rdiff -u -r1.1400 -r1.1401 src/distrib/sets/lists/tests/mi
cvs rdiff -u -r1.28 -r1.29 src/tests/libexec/ld.elf_so/Makefile
cvs rdiff -u -r0 -r1.1 src/tests/libexec/ld.elf_so/t_dlclose_thread.c
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
Responsible-Changed-From-To: lib-bug-people->riastradh
Responsible-Changed-By: riastradh@NetBSD.org
Responsible-Changed-When: Sun, 23 Nov 2025 22:30:02 +0000
Responsible-Changed-Why:
mine
State-Changed-From-To: open->analyzed
State-Changed-By: riastradh@NetBSD.org
State-Changed-When: Sun, 23 Nov 2025 22:30:02 +0000
State-Changed-Why:
The main culprit is that while dlopen and dlclose are mostly serialized
by the rtld exclusive lock, they drop that lock to call constructors
and destructors -- and when that happens, they can get interleaved and
observe troublesome intermediate states.
I added a test case for this, and drafted a patch to address this by
serializing the constructor and destructor calls and deferring
unmapping/freeing objects until any concurrent dlclose activity on them
has completed -- could stand some more review and testing before I
commit:
https://mollari.NetBSD.org/~riastradh/tmp/20251122/pr59751-dlcloserace-v2.patch
https://mollari.NetBSD.org/~riastradh/tmp/20251122/pr59751-dlcloserace-v2.diff
From: "Martin Husemann" <martin@netbsd.org>
To: gnats-bugs@gnats.NetBSD.org
Cc:
Subject: PR/59751 CVS commit: [netbsd-11] src
Date: Thu, 18 Dec 2025 18:03:34 +0000
Module Name: src
Committed By: martin
Date: Thu Dec 18 18:03:33 UTC 2025
Modified Files:
src/distrib/sets/lists/debug [netbsd-11]: mi
src/distrib/sets/lists/tests [netbsd-11]: mi
src/tests/libexec/ld.elf_so [netbsd-11]: Makefile
Added Files:
src/tests/libexec/ld.elf_so [netbsd-11]: t_dlclose_thread.c
Log Message:
Pull up following revision(s) (requested by riastradh in ticket #127):
distrib/sets/lists/tests/mi: revision 1.1401
tests/libexec/ld.elf_so/t_dlclose_thread.c: revision 1.1
distrib/sets/lists/debug/mi: sort + revision 1.491
tests/libexec/ld.elf_so/Makefile: revision 1.29
ld.elf_so(1): Test concurrent dlopen/dlclose.
PR lib/59751: dlclose is not MT-safe depending on the libraries
unloaded
To generate a diff of this commit:
cvs rdiff -u -r1.485.2.3 -r1.485.2.4 src/distrib/sets/lists/debug/mi
cvs rdiff -u -r1.1387.2.2 -r1.1387.2.3 src/distrib/sets/lists/tests/mi
cvs rdiff -u -r1.28 -r1.28.2.1 src/tests/libexec/ld.elf_so/Makefile
cvs rdiff -u -r0 -r1.1.2.2 src/tests/libexec/ld.elf_so/t_dlclose_thread.c
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
From: "Taylor R Campbell" <riastradh@netbsd.org>
To: gnats-bugs@gnats.NetBSD.org
Cc:
Subject: PR/59751 CVS commit: src/tests/libexec/ld.elf_so
Date: Thu, 25 Jun 2026 03:39:05 +0000
Module Name: src
Committed By: riastradh
Date: Thu Jun 25 03:39:05 UTC 2026
Modified Files:
src/tests/libexec/ld.elf_so: t_dlclose_thread.c
Log Message:
ld.elf_so(1): Run concurrent dlopen/dlclose test a few more seconds.
More likely to provoke the problem this way. Still not 100% reliable
because the problem is a race condition, but better than having the
test unexpectedly pass half the time.
Also set a timeout of 20sec, since I've seen the test get into an
infinite loop sometimes and it's now supposed to complete in 5sec +
epsilon.
PR lib/59751: dlclose is not MT-safe depending on the libraries unloaded
To generate a diff of this commit:
cvs rdiff -u -r1.1 -r1.2 src/tests/libexec/ld.elf_so/t_dlclose_thread.c
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
From: "Taylor R Campbell" <riastradh@netbsd.org>
To: gnats-bugs@gnats.NetBSD.org
Cc:
Subject: PR/59751 CVS commit: src
Date: Wed, 1 Jul 2026 19:29:58 +0000
Module Name: src
Committed By: riastradh
Date: Wed Jul 1 19:29:58 UTC 2026
Modified Files:
src/libexec/ld.elf_so: load.c reloc.c rtld.c rtld.h search.c
src/tests/libexec/ld.elf_so: t_dlclose_thread.c
src/usr.bin/ldd: ldd.c ldd_elfxx.c
Log Message:
ld.elf_so(1): Resolve several races in dlopen/dlclose.
This is difficult because, although rtld generally has a single
exclusive lock, i.e., generally runs single-threaded itself, it can't
hold this lock while calling constructors/destructors (init/fini or
ifunc) -- if it did, then, for example, lazy symbol binding that
happens during the constructor/destructor would deadlock against
itself.
And whenever rtld drops the lock to call constructors/destructors,
any objects it is working on, during dlopen or dlclose, might have
been concurrently closed and invalidated by the time it gets the lock
again.
The key point is that anywhere we pass a sigset_t *mask parameter
during dlopen or dlclose, we might release the rtld lock to sleep and
then reacquire the lock. And anywhere we might release and reacquire
the lock, any objects we hold may be invalidated -- unless we hold some
reference to prevent invalidation. And any object we find in the list
of objects might be undergoing dlclose, waiting for destructors to
finish, so we have to be prepared to release and reacquire the lock to
wait until the destructors are done -- and then start over at the top.
Here is a (probably nonexhaustive) list of races that I encountered:
1. If thread A dlcloses foo.so, bringing the reference count to zero,
and drops the rtld lock to call destructors, thread B dlopening
foo.so can acquire a new reference and return it -- but then
thread A will proceed to unmap and free foo.so while thread B
thinks it has a good reference.
=> Resolution: Make dlopen refuse to acquire new references to
objects with reference count zero -- instead, drop the rtld
lock to wait until the object has been dlclosed, and then start
over the lookup.
Caveat: This means that the dlopen logic can drop the lock, and
the rtld state can change, while dlopen is gathering references to
objects, so we can't simply see whether new objects were added to
the end of _rtld_objlist to find which ones need to be relocated.
So...
2. If thread A dlopens foo.so and sleeps to wait for a previous
dlclose to finish, and thread B concurrently dlopens bar.so and
finishes relocating it before thread A continues, thread A might
try to relocate all of the new objects since it started --
including the ones that thread B just loaded and already
relocated.
=> Resolution: Instead of checking whether _rtld_objtail has
changed, store:
(a) a global count of the number of objects pending relocation
(_rtld_objrelocpending), so we can cheaply test whether
there are any before iterating over the list; and
(b) a per-object flag of whether it has been relocated yet
(obj->relocated),
so dlopen can check _rtld_objrelocpending to see whether it
needs to do any relocations, and _rtld_relocate_objects can
check _all_ objects and only relocate the ones that have yet to
be relocated.
Also, since _rtld_load_needed_objects in one thread may be
interleaved with _rtld_call_init_functions in another thread,
make sure to have _rtld_call_init_functions skip the objects
that have yet to be relocated -- they can't be part of the DAG
of dependencies of the objecteing dlopened (or else we would
have passed through _rtld_relocate_objects), and if they
haven't been relocated then the init/fini function pointers are
unusable.
We could alternatively store a queue of objects to be
relocated, so _rtld_relocate_objects need not iterate over all
objects when loading one or two objects into a process with
zillions of existing ones, but this was quicker to implement
for now.
3. If thread A is dlopening an object and loading dependencies, it goes
through the list of _all_ objects' dependencies, even those that
aren't relevant to the current dlopen. In so doing, it may start
loading the dependencies of some object foo.so that thread B wants
to dlclose. If foo.so depends on bar.so, and bar.so is concurrently
closed by thread C, thread A may wait for that to happen so it can
create a new incarnation of bar.so. While thread A waits, thread B
might unmap and free foo.so, leading to use-after-free in thread A
when it finally wakes up.
=> Resolution: While a thread is loading an object's dependencies,
make dlclose wait for that to finish before unmapping and
freeing the object. New reference count obj->neededrefcount
for this -- having it separate from obj->refcount obviates the
need for logic to GC newly-unreferenced objects in
_rtld_load_needed_objects. New state obj->neededwaiter to
record a queue of waiters in dlclose. (XXX Maybe that should
be a global queue? dlclose can reasonably wake up when any
objects are no now-unreferenced and no longer being loaded.)
4. If thread A is dlclosing foo.so which depends on bar.so, and
thread B is dlclosing bar.so but has dropped the rtld lock to run
bar.so's destructors, thread A might get to the garbage-collection
phase at the end of _rtld_unload_object -- and unmap and free
bar.so before thread B has finished, because bar.so's reference
count is zero and so it looks like garbage to _rtld_unmap_object.
=> Resolution: Store a flag obj->dlclosing, set when thread A's
_rtld_unload_object is dropping the lock to call destructors,
so thread B's _rtld_unload_object will not free and unmap obj
during garbage collection. This won't leak because thread B
will rerun the garbage collection anyway.
5. (a) If thread A is dlclosing foo.so which depends on baz.so, and
thread B is dlclosing bar.so which also depends on baz.so,
both threads may try to call baz.so's destructors, possibly
leading to double-destruction. And either thread might free
and unmap baz.so while the other one is still trying to call
the destructors.
(b) If thread A is dlopening foo.so and has dropped the lock to
run constructors, and thread B dlopens foo.so, thread B won't
run the constructors again (we already have a mechanism to
prevent that) -- but it might return before constructors have
finished running in thread A at all, and thus it may return a
handle to the caller for a library whose constructors haven't
finished initializing the library.
=> Resolution: New lock obj->initfinilock (implemented as a state
variable under the rtld exclusive lock with sleep/wake using
_lwp_park/unpark), taken across any calls to constructors or
destructors with the exclusive lock dropped, in order to
serialize calls to constructors and destructors even while the
rtld exclusive lock is dropped. This way:
(a) If thread A is running destructors for bar.so as part of
dlclose, thread B can skip garbage-collecting bar.so as
part of dlclose -- it won't leak anything because thread A
will eventually run garbage collection in
_rtld_unload_object too.
(b) If thread A is running constructors for foo.so, and thread
B dlopens foo.so, it can wait until the constructors have
finished in thread A before returning.
While here, sprinkle various reference count assertions.
PR lib/59751: dlclose is not MT-safe depending on the libraries
unloaded
To generate a diff of this commit:
cvs rdiff -u -r1.49 -r1.50 src/libexec/ld.elf_so/load.c
cvs rdiff -u -r1.120 -r1.121 src/libexec/ld.elf_so/reloc.c
cvs rdiff -u -r1.224 -r1.225 src/libexec/ld.elf_so/rtld.c
cvs rdiff -u -r1.155 -r1.156 src/libexec/ld.elf_so/rtld.h
cvs rdiff -u -r1.27 -r1.28 src/libexec/ld.elf_so/search.c
cvs rdiff -u -r1.2 -r1.3 src/tests/libexec/ld.elf_so/t_dlclose_thread.c
cvs rdiff -u -r1.28 -r1.29 src/usr.bin/ldd/ldd.c
cvs rdiff -u -r1.8 -r1.9 src/usr.bin/ldd/ldd_elfxx.c
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
From: "Taylor R Campbell" <riastradh@netbsd.org>
To: gnats-bugs@gnats.NetBSD.org
Cc:
Subject: PR/59751 CVS commit: src
Date: Wed, 1 Jul 2026 19:31:15 +0000
Module Name: src
Committed By: riastradh
Date: Wed Jul 1 19:31:15 UTC 2026
Modified Files:
src/libexec/ld.elf_so: load.c rtld.c
src/usr.bin/ldd: ldd.c
Log Message:
ld.elf_so(1): Bump _rtld_objgen when changing, not reading, objlist.
Prompted by:
PR lib/59751: dlclose is not MT-safe depending on the libraries
unloaded
To generate a diff of this commit:
cvs rdiff -u -r1.50 -r1.51 src/libexec/ld.elf_so/load.c
cvs rdiff -u -r1.225 -r1.226 src/libexec/ld.elf_so/rtld.c
cvs rdiff -u -r1.29 -r1.30 src/usr.bin/ldd/ldd.c
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
From: "Taylor R Campbell" <riastradh@netbsd.org>
To: gnats-bugs@gnats.NetBSD.org
Cc:
Subject: PR/59751 CVS commit: src/rescue
Date: Wed, 1 Jul 2026 22:41:53 +0000
Module Name: src
Committed By: riastradh
Date: Wed Jul 1 22:41:53 UTC 2026
Modified Files:
src/rescue: list.ldd
Log Message:
rescue/ldd: Add a couple new symbols for recent rtld changes.
Should fix:
# link rescue/rescue
...
/tmp/build/2026.07.01.20.39.44-i386/tools/lib/gcc/i486--netbsdelf/14.3.0/../../../../i486--netbsdelf/bin/ld: /tmp/build/2026.07.01.20.39.44-i386/obj/usr.bin/ldd/elf32/libldd_elf32.a(load.o): in function `_rtld_load_object':
load.c:(.text+0x23b): undefined reference to `_rtld_objgen'
/tmp/build/2026.07.01.20.39.44-i386/tools/lib/gcc/i486--netbsdelf/14.3.0/../../../../i486--netbsdelf/bin/ld: load.c:(.text+0x256): undefined reference to `_rtld_objrelocpending'
PR lib/59751: dlclose is not MT-safe depending on the libraries
unloaded
To generate a diff of this commit:
cvs rdiff -u -r1.7 -r1.8 src/rescue/list.ldd
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
From: "Martin Husemann" <martin@netbsd.org>
To: gnats-bugs@gnats.NetBSD.org
Cc:
Subject: PR/59751 CVS commit: [netbsd-11] src
Date: Fri, 3 Jul 2026 18:36:36 +0000
Module Name: src
Committed By: martin
Date: Fri Jul 3 18:36:36 UTC 2026
Modified Files:
src/libexec/ld.elf_so [netbsd-11]: load.c reloc.c rtld.c rtld.h
search.c
src/tests/libexec/ld.elf_so [netbsd-11]: t_dlclose_thread.c
src/usr.bin/ldd [netbsd-11]: ldd.c ldd_elfxx.c
Log Message:
Pull up following revision(s) (requested by riastradh in ticket #365):
tests/libexec/ld.elf_so/t_dlclose_thread.c: revision 1.2
tests/libexec/ld.elf_so/t_dlclose_thread.c: revision 1.3
usr.bin/ldd/ldd.c: revision 1.30
libexec/ld.elf_so/rtld.c: revision 1.225
libexec/ld.elf_so/rtld.c: revision 1.226
libexec/ld.elf_so/load.c: revision 1.50
libexec/ld.elf_so/load.c: revision 1.51
libexec/ld.elf_so/search.c: revision 1.28
libexec/ld.elf_so/rtld.h: revision 1.156
usr.bin/ldd/ldd.c: revision 1.29
usr.bin/ldd/ldd_elfxx.c: revision 1.9
libexec/ld.elf_so/reloc.c: revision 1.121
ld.elf_so(1): Run concurrent dlopen/dlclose test a few more seconds.
More likely to provoke the problem this way. Still not 100% reliable
because the problem is a race condition, but better than having the
test unexpectedly pass half the time.
Also set a timeout of 20sec, since I've seen the test get into an
infinite loop sometimes and it's now supposed to complete in 5sec +
epsilon.
PR lib/59751: dlclose is not MT-safe depending on the libraries unloaded
ld.elf_so(1): Resolve several races in dlopen/dlclose.
This is difficult because, although rtld generally has a single
exclusive lock, i.e., generally runs single-threaded itself, it can't
hold this lock while calling constructors/destructors (init/fini or
ifunc) -- if it did, then, for example, lazy symbol binding that
happens during the constructor/destructor would deadlock against
itself.
And whenever rtld drops the lock to call constructors/destructors,
any objects it is working on, during dlopen or dlclose, might have
been concurrently closed and invalidated by the time it gets the lock
again.
The key point is that anywhere we pass a sigset_t *mask parameter
during dlopen or dlclose, we might release the rtld lock to sleep and
then reacquire the lock. And anywhere we might release and reacquire
the lock, any objects we hold may be invalidated -- unless we hold some
reference to prevent invalidation. And any object we find in the list
of objects might be undergoing dlclose, waiting for destructors to
finish, so we have to be prepared to release and reacquire the lock to
wait until the destructors are done -- and then start over at the top.
Here is a (probably nonexhaustive) list of races that I encountered:
1. If thread A dlcloses foo.so, bringing the reference count to zero,
and drops the rtld lock to call destructors, thread B dlopening
foo.so can acquire a new reference and return it -- but then
thread A will proceed to unmap and free foo.so while thread B
thinks it has a good reference.
=> Resolution: Make dlopen refuse to acquire new references to
objects with reference count zero -- instead, drop the rtld
lock to wait until the object has been dlclosed, and then start
over the lookup.
Caveat: This means that the dlopen logic can drop the lock, and
the rtld state can change, while dlopen is gathering references to
objects, so we can't simply see whether new objects were added to
the end of _rtld_objlist to find which ones need to be relocated.
So...
2. If thread A dlopens foo.so and sleeps to wait for a previous
dlclose to finish, and thread B concurrently dlopens bar.so and
finishes relocating it before thread A continues, thread A might
try to relocate all of the new objects since it started --
including the ones that thread B just loaded and already
relocated.
=> Resolution: Instead of checking whether _rtld_objtail has
changed, store:
(a) a global count of the number of objects pending relocation
(_rtld_objrelocpending), so we can cheaply test whether
there are any before iterating over the list; and
(b) a per-object flag of whether it has been relocated yet
(obj->relocated),
so dlopen can check _rtld_objrelocpending to see whether it
needs to do any relocations, and _rtld_relocate_objects can
check _all_ objects and only relocate the ones that have yet to
be relocated.
Also, since _rtld_load_needed_objects in one thread may be
interleaved with _rtld_call_init_functions in another thread,
make sure to have _rtld_call_init_functions skip the objects
that have yet to be relocated -- they can't be part of the DAG
of dependencies of the objecteing dlopened (or else we would
have passed through _rtld_relocate_objects), and if they
haven't been relocated then the init/fini function pointers are
unusable.
We could alternatively store a queue of objects to be
relocated, so _rtld_relocate_objects need not iterate over all
objects when loading one or two objects into a process with
zillions of existing ones, but this was quicker to implement
for now.
3. If thread A is dlopening an object and loading dependencies, it goes
through the list of _all_ objects' dependencies, even those that
aren't relevant to the current dlopen. In so doing, it may start
loading the dependencies of some object foo.so that thread B wants
to dlclose. If foo.so depends on bar.so, and bar.so is concurrently
closed by thread C, thread A may wait for that to happen so it can
create a new incarnation of bar.so. While thread A waits, thread B
might unmap and free foo.so, leading to use-after-free in thread A
when it finally wakes up.
=> Resolution: While a thread is loading an object's dependencies,
make dlclose wait for that to finish before unmapping and
freeing the object. New reference count obj->neededrefcount
for this -- having it separate from obj->refcount obviates the
need for logic to GC newly-unreferenced objects in
_rtld_load_needed_objects. New state obj->neededwaiter to
record a queue of waiters in dlclose. (XXX Maybe that should
be a global queue? dlclose can reasonably wake up when any
objects are no now-unreferenced and no longer being loaded.)
4. If thread A is dlclosing foo.so which depends on bar.so, and
thread B is dlclosing bar.so but has dropped the rtld lock to run
bar.so's destructors, thread A might get to the garbage-collection
phase at the end of _rtld_unload_object -- and unmap and free
bar.so before thread B has finished, because bar.so's reference
count is zero and so it looks like garbage to _rtld_unmap_object.
=> Resolution: Store a flag obj->dlclosing, set when thread A's
_rtld_unload_object is dropping the lock to call destructors,
so thread B's _rtld_unload_object will not free and unmap obj
during garbage collection. This won't leak because thread B
will rerun the garbage collection anyway.
5. (a) If thread A is dlclosing foo.so which depends on baz.so, and
thread B is dlclosing bar.so which also depends on baz.so,
both threads may try to call baz.so's destructors, possibly
leading to double-destruction. And either thread might free
and unmap baz.so while the other one is still trying to call
the destructors.
(b) If thread A is dlopening foo.so and has dropped the lock to
run constructors, and thread B dlopens foo.so, thread B won't
run the constructors again (we already have a mechanism to
prevent that) -- but it might return before constructors have
finished running in thread A at all, and thus it may return a
handle to the caller for a library whose constructors haven't
finished initializing the library.
=> Resolution: New lock obj->initfinilock (implemented as a state
variable under the rtld exclusive lock with sleep/wake using
_lwp_park/unpark), taken across any calls to constructors or
destructors with the exclusive lock dropped, in order to
serialize calls to constructors and destructors even while the
rtld exclusive lock is dropped. This way:
(a) If thread A is running destructors for bar.so as part of
dlclose, thread B can skip garbage-collecting bar.so as
part of dlclose -- it won't leak anything because thread A
will eventually run garbage collection in
_rtld_unload_object too.
(b) If thread A is running constructors for foo.so, and thread
B dlopens foo.so, it can wait until the constructors have
finished in thread A before returning.
While here, sprinkle various reference count assertions.
PR lib/59751: dlclose is not MT-safe depending on the libraries
unloaded
ld.elf_so(1): Bump _rtld_objgen when changing, not reading, objlist.
Prompted by:
PR lib/59751: dlclose is not MT-safe depending on the libraries
unloaded
To generate a diff of this commit:
cvs rdiff -u -r1.49 -r1.49.10.1 src/libexec/ld.elf_so/load.c
cvs rdiff -u -r1.120 -r1.120.2.1 src/libexec/ld.elf_so/reloc.c
cvs rdiff -u -r1.221.2.2 -r1.221.2.3 src/libexec/ld.elf_so/rtld.c
cvs rdiff -u -r1.150.2.2 -r1.150.2.3 src/libexec/ld.elf_so/rtld.h
cvs rdiff -u -r1.27 -r1.27.10.1 src/libexec/ld.elf_so/search.c
cvs rdiff -u -r1.1.2.2 -r1.1.2.3 \
src/tests/libexec/ld.elf_so/t_dlclose_thread.c
cvs rdiff -u -r1.28 -r1.28.4.1 src/usr.bin/ldd/ldd.c
cvs rdiff -u -r1.8 -r1.8.6.1 src/usr.bin/ldd/ldd_elfxx.c
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
From: "Taylor R Campbell" <riastradh@netbsd.org>
To: gnats-bugs@gnats.NetBSD.org
Cc:
Subject: PR/59751 CVS commit: src
Date: Thu, 16 Jul 2026 02:50:11 +0000
Module Name: src
Committed By: riastradh
Date: Thu Jul 16 02:50:11 UTC 2026
Modified Files:
src/distrib/sets/lists/debug: shl.mi
src/distrib/sets/lists/tests: shl.mi
src/tests/libexec/ld.elf_so: Makefile t_dlclose_thread.c
Added Files:
src/tests/libexec/ld.elf_so/helper_recurdso: Makefile
h_helper_recurdso.c
src/tests/libexec/ld.elf_so/helper_recurdso2: Makefile
h_helper_recurdso2.c
Log Message:
t_dlclose_thread: Test recursive dlopen/dlclose too.
Simple test first:
- h_helper_recurdso dlopens h_helper_dso2
- h_helper_dso2 needs h_helper_dso1
When dlopening h_helper_recurdso, the initialization order must be:
ENTER h_helper_recurdso
-> h_helper_dso1
-> h_helper_dso2
LEAVE h_helper_recurdso
For the more complex test, we have the relations:
- h_helper_recurdso2 needs h_helper_recurdso
- h_helper_recurdso dlopens h_helper_dso2
- h_helper_dso2 needs h_helper_dso1
(And the finalization order must be in reverse.)
When dlopening h_helper_recurdso2, the initialization order,
therefore, must be:
ENTER h_helper_recurdso
-> h_helper_dso1
-> h_helper_dso2
LEAVE h_helper_recurdso
h_helper_recurdso2
PR lib/59751: dlclose is not MT-safe depending on the libraries
unloaded
(Really, this is about a followup bug in the fix for that issue,
which broke recursive dlopen/dlclose.)
To generate a diff of this commit:
cvs rdiff -u -r1.409 -r1.410 src/distrib/sets/lists/debug/shl.mi
cvs rdiff -u -r1.17 -r1.18 src/distrib/sets/lists/tests/shl.mi
cvs rdiff -u -r1.35 -r1.36 src/tests/libexec/ld.elf_so/Makefile
cvs rdiff -u -r1.3 -r1.4 src/tests/libexec/ld.elf_so/t_dlclose_thread.c
cvs rdiff -u -r0 -r1.1 src/tests/libexec/ld.elf_so/helper_recurdso/Makefile \
src/tests/libexec/ld.elf_so/helper_recurdso/h_helper_recurdso.c
cvs rdiff -u -r0 -r1.1 src/tests/libexec/ld.elf_so/helper_recurdso2/Makefile \
src/tests/libexec/ld.elf_so/helper_recurdso2/h_helper_recurdso2.c
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
From: "Taylor R Campbell" <riastradh@netbsd.org>
To: gnats-bugs@gnats.NetBSD.org
Cc:
Subject: PR/59751 CVS commit: src/tests/libexec/ld.elf_so
Date: Thu, 16 Jul 2026 15:30:25 +0000
Module Name: src
Committed By: riastradh
Date: Thu Jul 16 15:30:25 UTC 2026
Modified Files:
src/tests/libexec/ld.elf_so: t_dlclose_thread.c
Log Message:
t_dlclose_thread: Make timeouts manifest more predictably.
And use SIGABRT to get stack traces.
PR lib/59751: dlclose is not MT-safe depending on the libraries
unloaded
To generate a diff of this commit:
cvs rdiff -u -r1.4 -r1.5 src/tests/libexec/ld.elf_so/t_dlclose_thread.c
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
From: "Taylor R Campbell" <riastradh@netbsd.org>
To: gnats-bugs@gnats.NetBSD.org
Cc:
Subject: PR/59751 CVS commit: src
Date: Sat, 18 Jul 2026 04:26:42 +0000
Module Name: src
Committed By: riastradh
Date: Sat Jul 18 04:26:42 UTC 2026
Modified Files:
src/libexec/ld.elf_so: reloc.c rtld.c rtld.h
src/tests/libexec/ld.elf_so: t_dlclose_thread.c
Log Message:
ld.elf_so: Fix more races with recursive threaded dlopen/dlclose.
1. When loading an object, keep track of a finer-grained state for
relocation so that a concurrent _rtld_relocate_objects doesn't
relocate an object too early (e.g., while waiting in
_rtld_load_needed_objects for an object that is concurrently being
dlclosed to finish before we can map it afresh) leading to
spurious symbol resolution failures:
- OBJRELOC_NONE (0): object has been created but the rtld state
isn't enough to attempt relocation yet (either not all DT_NEEDED
entries have been resolved, or the dldags lists have not yet
been populated)
- OBJRELOC_READY (1): object has been created and rtld state is
ready to relocate (all DT_NEEDED entries have been resolved and
the dldags lists have been populated), but object has not yet
been relocated.
- OBJRELOC_DONE (2): object has been relocated successfully.
- OBJRELOC_FAILED (3): relocation failed, e.g. because of symbol
resolution failure.
Allowed transitions:
- NONE->READY
- READY->DONE
- READY->FAILED
2. When calling dlopen/dlclose from an object's constructor or
destructor, exclude that object and any object that needs it from
the topological ordering of objects for constructor/destructor calls
inside dlopen/dlclose -- we can't re-enter the same object's
constructor or destructor, and before the object's constructor or
destructor has returned (which can't happen until the recursive
dlopen or dlclose has returned), it would be wrong to invoke the
constructor or destructor of any object that needs it.
3. When dlclosing an object, mark any of its dagmembers as busy
dlclosing so a concurrent GC loop at the end of
_rtld_unload_object doesn't free them while we sleep in init/fini
functions before we can remove the DAG from the members' dldags.
Later, at the end of this call to _rtld_unload_object, we will
take care of them.
This requires changing the boolean dlclosing state to an integer
reference count. `int' is big enough because each thread can hold
at most one reference to each object, so the number of threads is
an upper bound on the reference count.
Unfortunately, there is still a bug lurking: every now and then,
t_dlclose_thread:dlclose_thread_recursive2 trips one of the abort
branches in libh_helper_recurdso2.so, e.g. because the destructor for
libh_helper_recurdso.so ran first. But the frequency of these
failures has gone down dramatically, from around 19/20 to about 1/20
trials. Simply serializing dlopen/dlclose calls in an application is
likely to be an effective workaround. And these changes fix issues
in single-threaded recursive dlopen/dlclose even if the multithreaded
case has a remaining low-probability race.
PR lib/59751: dlclose is not MT-safe depending on the libraries
unloaded
To generate a diff of this commit:
cvs rdiff -u -r1.121 -r1.122 src/libexec/ld.elf_so/reloc.c
cvs rdiff -u -r1.226 -r1.227 src/libexec/ld.elf_so/rtld.c
cvs rdiff -u -r1.156 -r1.157 src/libexec/ld.elf_so/rtld.h
cvs rdiff -u -r1.5 -r1.6 src/tests/libexec/ld.elf_so/t_dlclose_thread.c
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
From: "Taylor R Campbell" <riastradh@netbsd.org>
To: gnats-bugs@gnats.NetBSD.org
Cc:
Subject: PR/59751 CVS commit: src/libexec/ld.elf_so
Date: Sat, 18 Jul 2026 05:06:39 +0000
Module Name: src
Committed By: riastradh
Date: Sat Jul 18 05:06:39 UTC 2026
Modified Files:
src/libexec/ld.elf_so: xmalloc.c
Log Message:
ld.elf_so: Fix ASSERT macro in xmalloc.c.
1. Use #p, not "p", in the expansion of #define ASSERT(p), for C as of
this millennium.
2. Consistently make ASSERT(...) a single expression.
Prompted by enabling MALLOC_DEBUG to track down issues in:
PR lib/59751: dlclose is not MT-safe depending on the libraries
unloaded
To generate a diff of this commit:
cvs rdiff -u -r1.25 -r1.26 src/libexec/ld.elf_so/xmalloc.c
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
From: "Martin Husemann" <martin@netbsd.org>
To: gnats-bugs@gnats.NetBSD.org
Cc:
Subject: PR/59751 CVS commit: [netbsd-11] src
Date: Sun, 19 Jul 2026 16:17:14 +0000
Module Name: src
Committed By: martin
Date: Sun Jul 19 16:17:14 UTC 2026
Modified Files:
src/distrib/sets/lists/debug [netbsd-11]: shl.mi
src/distrib/sets/lists/tests [netbsd-11]: shl.mi
src/libexec/ld.elf_so [netbsd-11]: reloc.c rtld.c rtld.h xmalloc.c
src/tests/libexec/ld.elf_so [netbsd-11]: Makefile t_dlclose_thread.c
Added Files:
src/tests/libexec/ld.elf_so/helper_recurdso [netbsd-11]: Makefile
h_helper_recurdso.c
src/tests/libexec/ld.elf_so/helper_recurdso2 [netbsd-11]: Makefile
h_helper_recurdso2.c
Log Message:
Pull up following revision(s) (requested by riastradh in ticket #386):
tests/libexec/ld.elf_so/helper_recurdso2/Makefile: revision 1.1
distrib/sets/lists/tests/shl.mi: revision 1.18
tests/libexec/ld.elf_so/t_dlclose_thread.c: revision 1.4
tests/libexec/ld.elf_so/t_dlclose_thread.c: revision 1.5
tests/libexec/ld.elf_so/t_dlclose_thread.c: revision 1.6
libexec/ld.elf_so/rtld.c: revision 1.227
libexec/ld.elf_so/xmalloc.c: revision 1.26
tests/libexec/ld.elf_so/helper_recurdso/Makefile: revision 1.1
tests/libexec/ld.elf_so/Makefile: revision 1.36
libexec/ld.elf_so/rtld.h: revision 1.157
tests/libexec/ld.elf_so/helper_recurdso/h_helper_recurdso.c: revision 1.1
tests/libexec/ld.elf_so/helper_recurdso2/h_helper_recurdso2.c: revision 1.1
distrib/sets/lists/debug/shl.mi: revision 1.410
libexec/ld.elf_so/reloc.c: revision 1.122
t_dlclose_thread: Test recursive dlopen/dlclose too.
Simple test first:
- h_helper_recurdso dlopens h_helper_dso2
- h_helper_dso2 needs h_helper_dso1
When dlopening h_helper_recurdso, the initialization order must be:
ENTER h_helper_recurdso
-> h_helper_dso1
-> h_helper_dso2
LEAVE h_helper_recurdso
For the more complex test, we have the relations:
- h_helper_recurdso2 needs h_helper_recurdso
- h_helper_recurdso dlopens h_helper_dso2
- h_helper_dso2 needs h_helper_dso1
(And the finalization order must be in reverse.)
When dlopening h_helper_recurdso2, the initialization order,
therefore, must be:
ENTER h_helper_recurdso
-> h_helper_dso1
-> h_helper_dso2
LEAVE h_helper_recurdso
h_helper_recurdso2
PR lib/59751: dlclose is not MT-safe depending on the libraries
unloaded
(Really, this is about a followup bug in the fix for that issue,
which broke recursive dlopen/dlclose.)
t_dlclose_thread: Make timeouts manifest more predictably.
And use SIGABRT to get stack traces.
PR lib/59751: dlclose is not MT-safe depending on the libraries
unloaded
ld.elf_so: Fix more races with recursive threaded dlopen/dlclose.
1. When loading an object, keep track of a finer-grained state for
relocation so that a concurrent _rtld_relocate_objects doesn't
relocate an object too early (e.g., while waiting in
_rtld_load_needed_objects for an object that is concurrently being
dlclosed to finish before we can map it afresh) leading to
spurious symbol resolution failures:
- OBJRELOC_NONE (0): object has been created but the rtld state
isn't enough to attempt relocation yet (either not all DT_NEEDED
entries have been resolved, or the dldags lists have not yet
been populated)
- OBJRELOC_READY (1): object has been created and rtld state is
ready to relocate (all DT_NEEDED entries have been resolved and
the dldags lists have been populated), but object has not yet
been relocated.
- OBJRELOC_DONE (2): object has been relocated successfully.
- OBJRELOC_FAILED (3): relocation failed, e.g. because of symbol
resolution failure.
Allowed transitions:
- NONE->READY
- READY->DONE
- READY->FAILED
2. When calling dlopen/dlclose from an object's constructor or
destructor, exclude that object and any object that needs it from
the topological ordering of objects for constructor/destructor calls
inside dlopen/dlclose -- we can't re-enter the same object's
constructor or destructor, and before the object's constructor or
destructor has returned (which can't happen until the recursive
dlopen or dlclose has returned), it would be wrong to invoke the
constructor or destructor of any object that needs it.
3. When dlclosing an object, mark any of its dagmembers as busy
dlclosing so a concurrent GC loop at the end of
_rtld_unload_object doesn't free them while we sleep in init/fini
functions before we can remove the DAG from the members' dldags.
Later, at the end of this call to _rtld_unload_object, we will
take care of them.
This requires changing the boolean dlclosing state to an integer
reference count. `int' is big enough because each thread can hold
at most one reference to each object, so the number of threads is
an upper bound on the reference count.
Unfortunately, there is still a bug lurking: every now and then,
t_dlclose_thread:dlclose_thread_recursive2 trips one of the abort
branches in libh_helper_recurdso2.so, e.g. because the destructor for
libh_helper_recurdso.so ran first. But the frequency of these
failures has gone down dramatically, from around 19/20 to about 1/20
trials. Simply serializing dlopen/dlclose calls in an application is
likely to be an effective workaround. And these changes fix issues
in single-threaded recursive dlopen/dlclose even if the multithreaded
case has a remaining low-probability race.
PR lib/59751: dlclose is not MT-safe depending on the libraries
unloaded
ld.elf_so: Fix ASSERT macro in xmalloc.c.
1. Use #p, not "p", in the expansion of #define ASSERT(p), for C as of
this millennium.
2. Consistently make ASSERT(...) a single expression.
Prompted by enabling MALLOC_DEBUG to track down issues in:
PR lib/59751: dlclose is not MT-safe depending on the libraries
unloaded
To generate a diff of this commit:
cvs rdiff -u -r1.367.2.11 -r1.367.2.12 src/distrib/sets/lists/debug/shl.mi
cvs rdiff -u -r1.16.4.1 -r1.16.4.2 src/distrib/sets/lists/tests/shl.mi
cvs rdiff -u -r1.120.2.1 -r1.120.2.2 src/libexec/ld.elf_so/reloc.c
cvs rdiff -u -r1.221.2.3 -r1.221.2.4 src/libexec/ld.elf_so/rtld.c
cvs rdiff -u -r1.150.2.3 -r1.150.2.4 src/libexec/ld.elf_so/rtld.h
cvs rdiff -u -r1.12.44.1 -r1.12.44.2 src/libexec/ld.elf_so/xmalloc.c
cvs rdiff -u -r1.28.2.3 -r1.28.2.4 src/tests/libexec/ld.elf_so/Makefile
cvs rdiff -u -r1.1.2.3 -r1.1.2.4 \
src/tests/libexec/ld.elf_so/t_dlclose_thread.c
cvs rdiff -u -r0 -r1.1.2.2 \
src/tests/libexec/ld.elf_so/helper_recurdso/Makefile \
src/tests/libexec/ld.elf_so/helper_recurdso/h_helper_recurdso.c
cvs rdiff -u -r0 -r1.1.2.2 \
src/tests/libexec/ld.elf_so/helper_recurdso2/Makefile \
src/tests/libexec/ld.elf_so/helper_recurdso2/h_helper_recurdso2.c
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
State-Changed-From-To: analyzed->open
State-Changed-By: riastradh@NetBSD.org
State-Changed-When: Sun, 19 Jul 2026 17:19:21 +0000
State-Changed-Why:
Lotta bugs fixed, one very low-probability race remains that I haven't
yet figured out.
From: "Taylor R Campbell" <riastradh@netbsd.org>
To: gnats-bugs@gnats.NetBSD.org
Cc:
Subject: PR/59751 CVS commit: src/libexec/ld.elf_so
Date: Sun, 19 Jul 2026 19:55:24 +0000
Module Name: src
Committed By: riastradh
Date: Sun Jul 19 19:55:24 UTC 2026
Modified Files:
src/libexec/ld.elf_so: xmalloc.c
Log Message:
ld.elf_so: Fix reversed sense of previous change to ASSERT macro.
Had tested the part of the change replacing botch("p") by botch(#p);
then didn't test the change from `if (!(p)) botch(#p)' to
`(__predict_false(p) ? botch(#p) : (void)0)'. Oops.
Now I have tested this with MALLOC_DEBUG enabled in ld.elf_so.
PR lib/59751: dlclose is not MT-safe depending on the libraries
unloaded
To generate a diff of this commit:
cvs rdiff -u -r1.26 -r1.27 src/libexec/ld.elf_so/xmalloc.c
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
From: "Taylor R Campbell" <riastradh@netbsd.org>
To: gnats-bugs@gnats.NetBSD.org
Cc:
Subject: PR/59751 CVS commit: src/libexec/ld.elf_so
Date: Sun, 19 Jul 2026 19:56:06 +0000
Module Name: src
Committed By: riastradh
Date: Sun Jul 19 19:56:06 UTC 2026
Modified Files:
src/libexec/ld.elf_so: rtld.c
Log Message:
ld.elf_so: Set _rtld_objself.refcount = 1.
This is the object for ld.elf_so itself. It can be opened with
dlopen("/usr/libexec/ld.elf_so"), and paths downstream of that assert
that the returned object has refcount > 0 to detect use-after-free
mistakes in rtld. Since ld.elf_so must never be unloaded, let's just
make sure the reference count is always positive.
(It's conceivable that one could dlopen an object with a DT_RPATH
entry having "/usr/libexec" and a DT_NEEDED entry having "ld.elf_so",
causing recursive loading of ld.elf_so -- and if one then dlcloses
the same object, it might lead to trying to free _rtld_objself. So
perhaps _rtld_load_object should just increment the reference count
of _rtld_objself itself. But this is a simpler change that already
fixes some existing tests -- such as any rump tests -- when used with
an ld.elf_built with -DDEBUG.)
Followup for:
PR lib/59751: dlclose is not MT-safe depending on the libraries
unloaded
To generate a diff of this commit:
cvs rdiff -u -r1.227 -r1.228 src/libexec/ld.elf_so/rtld.c
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
From: "Taylor R Campbell" <riastradh@netbsd.org>
To: gnats-bugs@gnats.NetBSD.org
Cc:
Subject: PR/59751 CVS commit: src/libexec/ld.elf_so
Date: Sun, 19 Jul 2026 20:06:42 +0000
Module Name: src
Committed By: riastradh
Date: Sun Jul 19 20:06:42 UTC 2026
Modified Files:
src/libexec/ld.elf_so: Makefile
Log Message:
ld.elf_so: Flip on DEBUG in current.
This enables assertions (and the LD_DEBUG environment variable).
We can disable it in release branches to reduce performance impact,
but let's not have the assertions bitrot in current.
Prompted by diagnosing fallout from:
PR lib/59751: dlclose is not MT-safe depending on the libraries
unloaded
To generate a diff of this commit:
cvs rdiff -u -r1.155 -r1.156 src/libexec/ld.elf_so/Makefile
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
From: "Taylor R Campbell" <riastradh@netbsd.org>
To: gnats-bugs@gnats.NetBSD.org
Cc:
Subject: PR/59751 CVS commit: src/libexec/ld.elf_so
Date: Mon, 20 Jul 2026 01:49:48 +0000
Module Name: src
Committed By: riastradh
Date: Mon Jul 20 01:49:48 UTC 2026
Modified Files:
src/libexec/ld.elf_so: search.c
Log Message:
ld.elf_so: Fix assertion: obj may be NULL _or_ OBJ_ERR (-1) here
NULL means the object wasn't found and we should keep searching;
OBJ_ERR means the object was found but loading it failed and we
should stop. Only if the object is _neither_ NULL _nor_ OBJ_ERR is
it expected to be an object with positive refcount.
Followup for
PR lib/59751: dlclose is not MT-safe depending on the libraries
unloaded
To generate a diff of this commit:
cvs rdiff -u -r1.28 -r1.29 src/libexec/ld.elf_so/search.c
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
From: "Martin Husemann" <martin@netbsd.org>
To: gnats-bugs@gnats.NetBSD.org
Cc:
Subject: PR/59751 CVS commit: [netbsd-11] src/libexec/ld.elf_so
Date: Mon, 20 Jul 2026 09:21:43 +0000
Module Name: src
Committed By: martin
Date: Mon Jul 20 09:21:43 UTC 2026
Modified Files:
src/libexec/ld.elf_so [netbsd-11]: rtld.c xmalloc.c
Log Message:
Pull up following revision(s) (requested by riastradh in ticket #391):
libexec/ld.elf_so/rtld.c: revision 1.228
libexec/ld.elf_so/xmalloc.c: revision 1.27
ld.elf_so: Fix reversed sense of previous change to ASSERT macro.
Had tested the part of the change replacing botch("p") by botch(#p);
then didn't test the change from `if (!(p)) botch(#p)' to
`(__predict_false(p) ? botch(#p) : (void)0)'. Oops.
Now I have tested this with MALLOC_DEBUG enabled in ld.elf_so.
PR lib/59751: dlclose is not MT-safe depending on the libraries
unloaded
ld.elf_so: Set _rtld_objself.refcount = 1.
This is the object for ld.elf_so itself. It can be opened with
dlopen("/usr/libexec/ld.elf_so"), and paths downstream of that assert
that the returned object has refcount > 0 to detect use-after-free
mistakes in rtld. Since ld.elf_so must never be unloaded, let's just
make sure the reference count is always positive.
(It's conceivable that one could dlopen an object with a DT_RPATH
entry having "/usr/libexec" and a DT_NEEDED entry having "ld.elf_so",
causing recursive loading of ld.elf_so -- and if one then dlcloses
the same object, it might lead to trying to free _rtld_objself. So
perhaps _rtld_load_object should just increment the reference count
of _rtld_objself itself. But this is a simpler change that already
fixes some existing tests -- such as any rump tests -- when used with
an ld.elf_built with -DDEBUG.)
Followup for:
PR lib/59751: dlclose is not MT-safe depending on the libraries
unloaded
To generate a diff of this commit:
cvs rdiff -u -r1.221.2.4 -r1.221.2.5 src/libexec/ld.elf_so/rtld.c
cvs rdiff -u -r1.12.44.2 -r1.12.44.3 src/libexec/ld.elf_so/xmalloc.c
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
From: "Taylor R Campbell" <riastradh@netbsd.org>
To: gnats-bugs@gnats.NetBSD.org
Cc:
Subject: PR/59751 CVS commit: src/libexec/ld.elf_so
Date: Tue, 21 Jul 2026 04:05:06 +0000
Module Name: src
Committed By: riastradh
Date: Tue Jul 21 04:05:06 UTC 2026
Modified Files:
src/libexec/ld.elf_so: search.c
Log Message:
ld.elf_so: Fix one more mistake in handling _rtld_load_object.
This can return NULL (meaning object not found or something went wrong
with the object) or OBJ_ERR (meaning the object has DF_1_NOOPEN set or
the caller passed RTLD_NOLOAD to dlopen() and the object was not
already loaded) or a valid object.
I reviewed all paths out of _rtld_load_object to make sure they
gracefully handle all three cases (NULL, OBJ_ERR, valid object), and
this assertion was the only path that didn't.
Fixes buggy assertion added for:
PR lib/59751: dlclose is not MT-safe depending on the libraries
unloaded
May fix:
PR bin/60472: ld.elf_so(1) changes cause a segmentation fault in
dlopen(3)
To generate a diff of this commit:
cvs rdiff -u -r1.29 -r1.30 src/libexec/ld.elf_so/search.c
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
From: "Martin Husemann" <martin@netbsd.org>
To: gnats-bugs@gnats.NetBSD.org
Cc:
Subject: PR/59751 CVS commit: [netbsd-11] src/libexec/ld.elf_so
Date: Wed, 22 Jul 2026 05:22:33 +0000
Module Name: src
Committed By: martin
Date: Wed Jul 22 05:22:32 UTC 2026
Modified Files:
src/libexec/ld.elf_so [netbsd-11]: search.c tls.c xmalloc.c
Log Message:
Pull up following revision(s) (requested by riastradh in ticket #393):
libexec/ld.elf_so/search.c: revision 1.30
libexec/ld.elf_so/xmalloc.c: revision 1.28
libexec/ld.elf_so/tls.c: revision 1.30
libexec/ld.elf_so/tls.c: revision 1.31
libexec/ld.elf_so/search.c: revision 1.29
ld.elf_so: Fix assertion: obj may be NULL _or_ OBJ_ERR (-1) here
NULL means the object wasn't found and we should keep searching;
OBJ_ERR means the object was found but loading it failed and we
should stop. Only if the object is _neither_ NULL _nor_ OBJ_ERR is
it expected to be an object with positive refcount.
Followup for
PR lib/59751: dlclose is not MT-safe depending on the libraries
unloaded
ld.elf_so: Fix static TLS alignment on variant II platforms.
Only affects obscure architectures like x86, though.
Sprinkle assertions to make sure this breaks in other ways on other
architectures too, like variant I, or variant II with _lwp_gettcb().
Fair's fair, right?
XXX We should consider verifying that every Elf_Phdr::p_align is
reasonable (i.e., is a power of two, or is zero but only if p_memsz
is also zero), and that p_filesz <= p_memsz, in headers.c for the
main object and in map_object.c for other objects.
PR bin/60469: bin/60469: assertion "ALIGNED_P(q, obj->tlsalign)"
failed: file "/usr/src/libexec/ld.elf_so/tls.c", line 333
ld.elf_so: Mark new variables __debugused, not __diagused.
They are used in ld.elf_so builds with DEBUG, not with DIAGNOSTIC!
PR bin/60469: bin/60469: assertion "ALIGNED_P(q, obj->tlsalign)"
failed: file "/usr/src/libexec/ld.elf_so/tls.c", line 333
ld.elf_so: Fix one more mistake in handling _rtld_load_object.
This can return NULL (meaning object not found or something went wrong
with the object) or OBJ_ERR (meaning the object has DF_1_NOOPEN set or
the caller passed RTLD_NOLOAD to dlopen() and the object was not
already loaded) or a valid object.
I reviewed all paths out of _rtld_load_object to make sure they
gracefully handle all three cases (NULL, OBJ_ERR, valid object), and
this assertion was the only path that didn't.
Fixes buggy assertion added for:
PR lib/59751: dlclose is not MT-safe depending on the libraries
unloaded
May fix:
PR bin/60472: ld.elf_so(1) changes cause a segmentation fault in
dlopen(3)
To generate a diff of this commit:
cvs rdiff -u -r1.27.10.1 -r1.27.10.2 src/libexec/ld.elf_so/search.c
cvs rdiff -u -r1.23.2.2 -r1.23.2.3 src/libexec/ld.elf_so/tls.c
cvs rdiff -u -r1.12.44.3 -r1.12.44.4 src/libexec/ld.elf_so/xmalloc.c
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
>Unformatted:
(Contact us)
$NetBSD: query-full-pr,v 1.51 2026/08/10 02:28:17 riastradh Exp $
$NetBSD: gnats_config.sh,v 1.10 2026/05/13 22:00:09 riastradh Exp $
Copyright © 1994-2026
The NetBSD Foundation, Inc. ALL RIGHTS RESERVED.