Skip to content

mpm_event: Fix just_die() calling non-async-signal-safe code. (#967) - #1020

Open
arturobernalg wants to merge 1 commit into
apache:2.4.xfrom
arturobernalg:backport/pr967-bz70267-event-just-die-async-signal-24x
Open

arturobernalg wants to merge 1 commit into
apache:2.4.xfrom
arturobernalg:backport/pr967-bz70267-event-just-die-async-signal-24x

Conversation

@arturobernalg

Copy link
Copy Markdown
Member

(cherry picked from commit 470b195)

Backport of #967.

BZ: https://bz.apache.org/bugzilla/show_bug.cgi?id=70267


[x] This PR does not report or address a security vulnerability (see SECURITY.md and https://www.apache.org/security/#reporting-a-vulnerability).
[x] Code follows the httpd style guide.
[x] New log messages (level debug or higher) use an empty APLOGNO() tag (not applicable -- no new log messages).
[x] If the change is user-visible, a changes-entries/*.txt file is included (carried over unchanged from the trunk commit).
[x] Test cases based on pyhttpd are included where appropriate (not applicable -- this is a signal-delivery-timing race during child-process startup, not something the pytest harness can trigger deterministically; same rationale as the original PR #967. Verified instead by rebuilding this exact 2.4.x worktree and confirming at the disassembly level that just_die() now calls _exit() directly instead of clean_child_exit()).

…#967)

just_die() is the SIGTERM handler installed in a child immediately
after fork(), before child_main() has done its allocator-heavy early
startup work. It called clean_child_exit(), which runs hook
callbacks, calls apr_pool_destroy() (more malloc/free), and exit()
(also not async-signal-safe, e.g. it may run atexit handlers that
allocate). A signal handler runs on the thread it interrupts, so if
that thread was itself inside malloc() when the signal arrived, this
could re-enter the allocator on the same thread while it was already
in progress, which is not safe and can deadlock or corrupt it.

Reproduced the underlying mechanism directly: a SIGTERM handler that
mirrors just_die()'s exact shape (take the same lock the interrupted
thread may hold, do real malloc/free work, then exit()) self-deadlocks
20/20 times when the handler and the interrupted thread contend for
the same lock, and 20/20 clean when the handler just calls _exit()
instead, matching the fix below. Against the real glibc allocator
without widening the window, 300/300 trials came back clean, since
that critical section is only nanoseconds wide and far narrower than
the scheduling latency of an externally delivered signal -- which is
also why this surfaces rarely in the wild (reported from a CI
environment where added instrumentation apparently widened the real
window enough to hit it) rather than routinely.

This window closes once child_main() installs its real, async-signal-
safe SIGTERM handling (a no-op handler that only unblocks a syscall,
with the actual shutdown processed synchronously outside signal
context, same pattern as the listener thread's own wakeup signal).
just_die() only needs to cover the brief gap before that, when
nothing meaningful has been set up yet to justify running hook
callbacks or pool cleanup. Just exit immediately instead.

PR 70267

(cherry picked from commit 470b195)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant