[PATCH v2] posix: Avoid allocation while holding atfork_lock [BZ 34321]

H.J. Lu hjl.tools@gmail.com
Fri Sep 25 22:49:44 GMT 2026


On Sun, Sep 20, 2026 at 12:29 PM H.J. Lu <hjl.tools@gmail.com> wrote:
>
> On Thu, Sep 17, 2026 at 11:44 PM Adhemerval Zanella Netto
> <adhemerval.zanella@linaro.org> wrote:
> >
> >
> >
> > On 17/09/26 03:09, H.J. Lu wrote:
> > > On Thu, Sep 17, 2026 at 8:01 AM H.J. Lu <hjl.tools@gmail.com> wrote:
> > >>
> > >> On Wed, Sep 16, 2026 at 8:07 PM Adhemerval Zanella Netto
> > >> <adhemerval.zanella@linaro.org> wrote:
> > >>>
> > >>>
> > >>>
> > >>> On 15/09/26 20:26, H.J. Lu wrote:
> > >>>> On Tue, Sep 15, 2026 at 7:22 PM Adhemerval Zanella Netto
> > >>>> <adhemerval.zanella@linaro.org> wrote:
> > >>>>>
> > >>>>>
> > >>>>>
> > >>>>> On 11/09/26 21:20, H.J. Lu wrote:
> > >>>>>> On Fri, Aug 14, 2026 at 11:43 PM Adhemerval Zanella
> > >>>>>> <adhemerval.zanella@linaro.org> wrote:
> > >>>>>>>
> > >>>>>>> __register_atfork inserted the new handler into a dynamic array while
> > >>>>>>> holding atfork_lock, and growing that array calls the public/iterposable
> > >>>>>>> malloc.  An allocator replacement whose own lock is also taken from a
> > >>>>>>> registered pthread_atfork prepare handler can therefore deadlock:
> > >>>>>>>
> > >>>>>>>   Thread A: fork                          Thread B: __register_atfork
> > >>>>>>>   -------------------------------------   -----------------------------------
> > >>>>>>>   :139 unlock(atfork_lock)
> > >>>>>>>   :141 prepare() -> lock(L)   [holds L]
> > >>>>>>>                                           :39  lock(atfork_lock)  [holds atfork]
> > >>>>>>>                                           :44  emplace -> malloc -> lock(L)
> > >>>>>>>                                                BLOCKS on L (held by A)
> > >>>>>>>   :144 lock(atfork_lock)
> > >>>>>>>        BLOCKS on atfork (held by B)
> > >>>>>>>   => DEADLOCK (A: holds L, waits atfork; B: holds atfork, waits L)
> > >>>>>>>
> > >>>>>>> Replace the dynamic array with a doubly linked list (include/list.h).
> > >>>>>>> A new node is allocated outside atfork_lock and only linked in while the
> > >>>>>>> lock is held.  __unregister_atfork moves nodes to a free pool instead of
> > >>>>>>> freeing them, and __register_atfork reuses a pooled node when one is
> > >>>>>>> available, so neither path allocates or frees under atfork_lock in the
> > >>>>>>> steady state; malloc runs only to grow the pool, always outside the lock.
> > >>>>>>> This preserves the current allocation guarantee of the dynarray.
> > >>>>>>
> > >>>>>> Does this fix any reported jemalloc bugs like
> > >>>>>>
> > >>>>>> https://github.com/jemalloc/jemalloc/issues/1805
> > >>>>>
> > >>>>> I don't think so because this is an unrelated issue. This seems to be a
> > >>>>> jemalloc-internal fork-unsafety, where the rtree rtree_node_init is a bare
> > >>>>> atomic flag not covered by jemalloc_prefork() (and this is documented on
> > >>>>> src/jemalloc.c:3289).
> > >>>>>
> > >>>>> Also, the reporter's platform (Amazon Linux 1, glibc 2.17) can't exhibit
> > >>>>> BZ 34321 at all. The __register_atfork only started calling malloc under
> > >>>>> atfork_lock with the dynarray refactor 27761a1042d (glibc 2.28), before
> > >>>>> that, nptl used an mmap-based handler pool precisely to avoid malloc there.
> > >>>>
> > >>>> Does it fix any jemalloc related bugs?
> > >>> I am not sure, since this does really come from jemalloc usage afaiu [1].
> > >>>
> > >>> [1] https://sourceware.org/pipermail/libc-alpha/2026-May/177297.html
> > >>
> > >> Here is a patch to add a testcase based on
> > >>
> > >> https://sourceware.org/pipermail/libc-alpha/2026-May/177613.html
> > >>
> > >> Your patch didn't fix it.
> > >
> > > Here is the updated test using elf/dl-minimal-malloc.c.
> > This test is invalid, running is never cleared. Also every registration adds a prepare
> > handler that sleeps 1 s while 4 threads register, which would take an indefinitely
> > amount of time.
> >
> > Under gdb with the fix applied, atfork_lock is 0, the fork thread is asleep inside the
> > test's own prepare handler, and the registering threads are in the malloc that now runs
> > outside the lock (register-atfork.c:106), with registrations still advancing. That is
> > the test sleeping in itself, not a deadlock.
> >
> > It also does not reproduce the bug on the unfixed code, since the module only interposes
> > malloc/free, and the old dynarray path calls the interposable malloc under atfork_lock
> > exactly once, All later growth is done with realloc, which the module does not interpose
> > and which never takes its lock. The original report's allocator covered the reallocation
> > path too.
>
> Can we adjust the test so that it is valid and reproduce the deadlock
> with a separate malloc?
>
> > The tst-atfork5 addresses both problems: bounded registrations synchronized by a barrier
> > with the module's prepare holding the allocator lock, NULL bulk handlers, a finite
> > fork count. It also deadlocks/times out on the pre-fix code and passes with the fix.
>

I opened:

https://sourceware.org/bugzilla/show_bug.cgi?id=34674

There is a deadlock in the fork handler without LD_PRELOAD.

-- 
H.J.


More information about the Libc-alpha mailing list