[Bug 298878] linux(4): epoll_pwait(2)/epoll_pwait2(2) with a sigmask leave a stale signal mask, deadlocking Bun apps (Claude Code, opencode)

From: <bugzilla-noreply_at_freebsd.org>
Date: Sun, 27 Sep 2026 19:27:33 UTC
https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=298878

--- Comment #6 from commit-hook@FreeBSD.org ---
A commit in branch main references this bug:

URL:
https://cgit.FreeBSD.org/src/commit/?id=16a284b1cdfd45ba99c2723e7f88497e76b235ad

commit 16a284b1cdfd45ba99c2723e7f88497e76b235ad
Author:     Grzegorz Junka <list1@gjunka.com>
AuthorDate: 2026-09-27 19:22:20 +0000
Commit:     Konstantin Belousov <kib@FreeBSD.org>
CommitDate: 2026-09-27 19:22:20 +0000

    linux(4): Fix signal mask restoration in epoll_pwait(2)/epoll_pwait2(2)

    Since 80c7315d17ce ("Restore signal mask in epoll_pwait.") the caller's
    signal mask is saved in a local variable, but TDP_OLDMASK is still set
    and the TDA_SIGSUSPEND AST is still scheduled.  On return to user mode
    that AST, or postsig() if a signal is delivered, then installs
    td_oldsigmask, which this code never writes and which holds whatever
    mask the thread had at its last sigsuspend(2), pselect(2) or ppoll(2).
    As a result every epoll_pwait(2)/epoll_pwait2(2) call with a non-NULL
    sigmask can leave the thread with a stale signal mask.  In addition, the
    explicit restore at the end overwrote the return value, so EINTR (and
    any other error) was reported to user space as 0.

    Save the old mask in td_oldsigmask and let the AST restore it, as
    kern_pselect() and kern_poll_kfds() do: schedule TDA_SIGSUSPEND if the
    wait was interrupted, so the signal is delivered with the temporary mask
    in place, and TDA_PSELECT otherwise.  This matches Linux, which restores
    the saved mask unless the syscall returns -EINTR.

    This deadlocks Bun-based programs such as Claude Code (>= 2.1.269) and
    opencode.  JavaScriptCore suspends threads for conservative GC stack
    scanning by sending SIGPWR and waiting for the target's handler, which
    calls sigsuspend(2) with SIGPWR blocked.  Afterwards td_oldsigmask
    contains SIGPWR, the event loop's next epoll_pwait2(2) (Bun always
    passes an empty sigmask) blocks SIGPWR on the main thread, and the next
    GC suspend request waits forever.

    PR:             298878
    Fixes:          80c7315d17ce ("Restore signal mask in epoll_pwait.")
    MFC after:      1 week

 sys/compat/linux/linux_event.c | 25 ++++++++++++++-----------
 1 file changed, 14 insertions(+), 11 deletions(-)

-- 
You are receiving this mail because:
You are the assignee for the bug.