On Sat, 20 Dec 2025, Henryk Paluch wrote:

I have found exact cause while atexit(3) from dlopen(3) crashes later under NetBSD (tested all major releases since 8.3) while it works under Linux, OpenBSD and FreeBSD.

It is because NetBSD's atexit(3) on https://cvsweb.netbsd.org/bsdweb.cgi/src/lib/libc/stdlib/atexit.c?rev=1.35;content-type=text%2Fx-cvsweb-markup calls __cxa_atexit_internal with dso=NULL (last argument):

int
atexit(void (*func)(void))
{

        return (__cxa_atexit_internal((void (*)(void *))func, NULL, NULL));
}

When later __cxa_finalize is run on dlclose(3) it will not call its callback because condition (dso == ah->ah_dso) will be always false ( ah->ah_dso == NULL which is != dso) - that was set in atexit(3) call. Therefore DSO callback will be called on global exit (when DSO is no longer mapped to memory) causing SIGSEGV.


Correct, but, ...

I looked into OpenBSD 7.8's /usr/src/lib/csu/crtbeginS.c that does something like:

int
atexit(void (*fn)(void))
{
       return (__cxa_atexit((void (*)(void *))fn, NULL, &__dso_handle));
}
asm(".hidden atexit");

which properly passes implicit __dso_handle making this case to work.


on NetBSD, this'll cause atexit(3)-registered funcs. in PIEs to be called by
__cxa_atexit(), no? (Plus, that's supposed to be `__dso_handle' passed there,
right? `__dso_handle' is already a pointer? see eg. for some other arches. here:
https://github.com/NetBSD/src/blob/trunk/lib/csu/common/crtbegin.c#L51)

See:

https://github.com/NetBSD/src/blob/trunk/lib/csu/arch/x86_64/crtbegin.S#L60

where this bit of magic is arranged (for amd64).

There might be a way to get this working the way you suggest, but that ended
up by me not only patching stdlib/atexit.c, but, lib/csu/common/crt0-common.c
also as well to explicitly set `__dso_handle = NULL' before calling main():

https://github.com/NetBSD/src/blob/trunk/lib/csu/common/crt0-common.c#L375

So I gave this up when I tried the same thing a coupla days ago.

Instead, I have a patch which does things the FreeBSD way (in __cxa_finalize()
check `dso' against the library LOAD addresses to make sure we're calling
the correct atexit handler). But, this is still a bit hackish at the mo' and
not at all fully tested.

-RVP

Reply via email to