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