git: libthread_xu: Link with '-z nodelete'; unloading it is unsafe
Aaron LI
aly at crater.dragonflybsd.org
Thu Jul 2 23:06:06 PDT 2026
commit c3192b1e26e9928ea9d21f66fcf39fb573b2b68a
Author: LI Leding <lileding at gmail.com>
Date: Thu Jul 2 16:34:23 2026 +0800
libthread_xu: Link with '-z nodelete'; unloading it is unsafe
Loading libthread_xu has irreversible process-wide effects:
* rtld lazily rebinds libc weak pthread stubs (e.g. _spinlock) to
the strong definitions in this library. There is no mechanism to
undo GOT bindings on dlclose(), so after an unload libc keeps
calling into the unmapped region.
* __isthreaded stays set -- the library cannot prove that no other
threads exist, so it cannot safely clear it -- which keeps the
libc malloc on the locked path through the dangling _spinlock
pointer.
* _thr_signal_init() installs signal handlers pointing into the
library, and _thr_signal_deinit() is empty, so signal delivery
after an unload also jumps into unmapped memory.
Consequently a program that dlopen()s a dependency chain pulling in
libthread_xu, creates a thread, and later dlclose()s the handle
crashes on the next free(): libc jumps through its GOT to _spinlock+0
inside the unmapped library.
Real-world impact: vulkaninfo (which does not link against pthread
itself) dlopens the Vulkan loader, whose Mesa ICD depends on
libpthread. It exits with SIGSEGV on every run during instance
destruction.
Reproducer (SIGSEGV before this change, "survived" after):
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
static void *body(void *a) { return a; }
int main(void) {
void *h = dlopen("/usr/lib/libpthread.so.0",
RTLD_NOW | RTLD_GLOBAL);
int (*create)(void *, const void *,
void *(*)(void *), void *) =
dlsym(h, "pthread_create");
int (*join)(void *, void **) = dlsym(h, "pthread_join");
void *t = NULL, *r;
create(&t, NULL, body, NULL);
join(t, &r);
free(malloc(4096));
dlclose(h);
free(malloc(4096)); /* SIGSEGV here */
printf("survived\n");
return 0;
}
Fix it the way FreeBSD fixed libthr in 2009 [1]: mark the DSO
DF_1_NODELETE so dlclose() never unmaps it. rtld-elf already honors the
flag.
[1] FreeBSD commit 35c608253dd2
Turn on nodelete linker flag because libthr can not be unloaded
safely, it does hook on to libc.
Co-Authored-By: Claude Fable 5 <noreply at anthropic.com>
Github-PR: #38
Summary of changes:
lib/libthread_xu/Makefile | 4 ++++
1 file changed, 4 insertions(+)
http://gitweb.dragonflybsd.org/dragonfly.git/commitdiff/c3192b1e26e9928ea9d21f66fcf39fb573b2b68a
--
DragonFly BSD source repository
More information about the Commits
mailing list