Skip to content

Wake internal condvar parks on exit_group teardown#161

Merged
jserv merged 1 commit into
sysprog21:mainfrom
Max042004:fix-teardown-condvar-wake
Jul 9, 2026
Merged

Wake internal condvar parks on exit_group teardown#161
jserv merged 1 commit into
sysprog21:mainfrom
Max042004:fix-teardown-condvar-wake

Conversation

@Max042004

@Max042004 Max042004 commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

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 outlive thread_join_workers' cap and still be live when guest_destroy unmapped guest memory — or, for a tracer waiting on a tracee that never stops, deadlock the whole process (only VM-clone exits broadcast ptrace_cond).

Fix:

  • New thread_wake_exit_waiters(), called from both teardown entry points (sc_exit_group, guest_destroy) after the exit-group flag is set: broadcasts fork_cond and every live ptrace_cond/resume_cond.
  • The three wait loops, plus sys_wait4's blocking loop, re-check proc_exit_group_requested() on wake instead of looping forever.
  • thread_join_workers now skips the main thread's slot — it never deactivates until guest_destroy runs, so waiting on it from a worker-initiated exit_group just burned the join cap on both sides and caused a pthread_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). The wait scenario deadlocks on unpatched main; fork crashes ~10% of runs. make check 109/109; 150 stress runs of all three scenarios, 0 failures.

cubic-dev-ai[bot]

This comment was marked as resolved.

Comment thread src/syscall/syscall.c
Comment thread src/syscall/proc.c
Comment thread src/runtime/thread.c Outdated
@Max042004 Max042004 force-pushed the fix-teardown-condvar-wake branch from 94a3c17 to 59f18a9 Compare July 9, 2026 06:09

@jserv jserv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
@Max042004 Max042004 force-pushed the fix-teardown-condvar-wake branch from 59f18a9 to a9f3f31 Compare July 9, 2026 07:00
@jserv jserv merged commit 69dc6b1 into sysprog21:main Jul 9, 2026
9 checks passed
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.

exit_group teardown cannot reach workers parked on internal condvars (fork_cond, ptrace_cond)

2 participants