Wake internal condvar parks on exit_group teardown#161
Merged
Conversation
jserv
reviewed
Jul 7, 2026
94a3c17 to
59f18a9
Compare
jserv
requested changes
Jul 9, 2026
jserv
left a comment
Contributor
There was a problem hiding this comment.
Rebase latest main branch and resolve conflicts.
exit_group teardown wakes every worker parked in a guest syscall wait -- thread_interrupt_all kicks threads inside hv_vcpu_run, the wakeup pipe covers epoll/poll, and futex_interrupt_request covers futex waits. Three host-side condvar parks sit outside that set: siblings waiting out the fork barrier (fork_cond in thread_fork_barrier_check), a tracer blocked for a ptrace-stop (ptrace_cond in thread_ptrace_wait), and a tracee held in ptrace-stop (resume_cond in thread_ptrace_stop). None were signalled by teardown and none re-checked proc_exit_group_requested, so a worker parked there slept past thread_join_workers' poll cap, was detached, and was still live when guest memory was unmapped. Worse than the UAF window: a tracer parked on ptrace_cond for a CLONE_THREAD tracee that never stops deadlocked outright, because only VM-clone exits broadcast ptrace_cond -- if the parked thread was main, teardown was never reached and the process hung. Add thread_wake_exit_waiters(), called from every teardown/exit entry point after the exit-group flag is set: sc_exit_group, main()'s post-run-loop join, guest_destroy, and the last-VM-clone-exit trigger in vm_clone_thread_run. It broadcasts fork_cond plus every live per-slot ptrace_cond/resume_cond, and the three wait loops now re-check proc_exit_group_requested on wake. sys_wait4's pid == -1 loop gains the same check before re-arming its 100ms pid_cond quantum, so a tracer redirected out of thread_ptrace_wait does not just re-park one layer down; the -EINTR is never guest-visible because the run loop breaks on the flag first. thread_join_workers also explicitly skips the main thread's slot: its entry only deactivates inside guest_destroy, which runs on the main thread after its own join, so a worker waiting on it would burn the full poll cap for nothing. test-exit-group-teardown constructs each park and races exit_group(42) against it. The "wait" scenario deadlocks without this fix (driver timeout) and exits 42 with it; all three scenarios survive 150 consecutive stress runs. PTRACE_SEIZE on a same-group thread is EPERM on real Linux, so the ptrace scenarios degrade to a plain exit_group(42) under differential testing. Fixes: sysprog21#158
59f18a9 to
a9f3f31
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #158.
exit_group teardown wakes every worker parked in a guest syscall wait, but missed three host-side condvar parks: siblings on the fork barrier (
fork_cond), a tracer waiting for a ptrace-stop (ptrace_cond), and a tracee held in ptrace-stop (resume_cond). None were signalled by teardown, so a parked worker could outlivethread_join_workers' cap and still be live whenguest_destroyunmapped guest memory — or, for a tracer waiting on a tracee that never stops, deadlock the whole process (only VM-clone exits broadcastptrace_cond).Fix:
thread_wake_exit_waiters(), called from both teardown entry points (sc_exit_group,guest_destroy) after the exit-group flag is set: broadcastsfork_condand every liveptrace_cond/resume_cond.sys_wait4's blocking loop, re-checkproc_exit_group_requested()on wake instead of looping forever.thread_join_workersnow skips the main thread's slot — it never deactivates untilguest_destroyruns, so waiting on it from a worker-initiated exit_group just burned the join cap on both sides and caused apthread_detach/UAF race. This was an existing ~10% SIGSEGV on main during exit_group-vs-fork races, found while stress-testing this fix.Coverage: new
tests/test-exit-group-teardown(wait/stop/fork scenarios,expected_rc=42). Thewaitscenario deadlocks on unpatched main;forkcrashes ~10% of runs.make check109/109; 150 stress runs of all three scenarios, 0 failures.