The problem here is the weak declaration:

$ eu-readelf --symbols=.dynsym /lib64/libcap-ng.so.0.0.0 | grep pthread_atfork
   28: 0000000000000000      0 NOTYPE  WEAK   DEFAULT    UNDEF pthread_atfork

In the Fedora 29 build, the constructor looks like this:

Dump of assembler code for function init_lib:
   0x00000000000025d0 <+0>:     endbr64 
   0x00000000000025d4 <+4>:     cmpq   $0x0,0x4a0c(%rip)        # 0x6fe8
   0x00000000000025dc <+12>:    je     0x25ee <init_lib+30>
   0x00000000000025de <+14>:    lea    0xcb(%rip),%rdx        # 0x26b0 <deinit>
   0x00000000000025e5 <+21>:    xor    %esi,%esi
   0x00000000000025e7 <+23>:    xor    %edi,%edi
   0x00000000000025e9 <+25>:    jmpq   0x24f0 <pthread_atfork@plt>
   0x00000000000025ee <+30>:    retq

src/cap-ng.c has this:

/*
 * The pthread_atfork function is being made weak so that we can use it
 * if the program is linked with pthreads and not requiring it for
 * everything that uses libcap-ng.
 */
extern int __attribute__((weak)) pthread_atfork(void (*prepare)(void),
        void (*parent)(void), void (*child)(void));
…
static void init_lib(void) __attribute__ ((constructor));
static void init_lib(void)
{
        if (pthread_atfork)
                pthread_atfork(NULL, NULL, deinit);
}

This is wrong.  pthread_atfork needs to be *strong* reference, otherwise
the implementation in libc_nonshared.a is not used.  This implementation
provides the correct __dso_handle argument, allowing unregistration at
dlclose.

For glibc 2.28 and later, the fix should be simple: Just delete the weak
declaration.  For older glibc versions, you need to call
__register_atfork directly, with an explicit __dso_handle argument.  (I
believe systemd has an example of this which looks correct.)  This is a
stable glibc ABI, despite all those glibc internals.

We cannot fix this in libpthread because of the tail call in init_lib.
It destroys the caller's stack frame, so the identity of the calling DSO
is not available to pthread_atfork.  (Without the tail call, we could
use __builtin_return_address (0) and the internal variant of dladdr to
figure out the caller.)

Thanks,
Florian

Reply via email to