DF-2692 / verdict.json
{ "finding_id": "DF-2692", "status": "untested", "reproduced": 0, "impact": "none", "confidence": "likely", "verdict": "Structural use-after-free race, proven by inspection but not staged on the guest: lwpsignal()'s final delivery path holds only lp->lwp_token after the find_lwp_for_signal token swap (kern_sig.c:1348-1351), yet sigsetfrompid() at kern_sig.c:1425 reads and writes p->p_sigacts->ps_frominfo[sig] (kern_sig.c:233-241) without p_token, while kern_execve() replaces and can kfree() p->p_sigacts under p_token alone (kern_exec.c:413-421, free path requires a vfork/RFSIGSHARE co-holder's reaping to complete inside the kmalloc+bcopy window). Winning window is nanoseconds; primitive is an 8-byte semi-controlled write (sender pid+uid) at a selectable offset (sig 1..127) inside a freed M_SUBPROC chunk. Same-process variants (trapsignal/postsig/kern_sigaction) are excluded by killalllwps() running before the exec swap; only the foreign kill()/lwp_kill() path is exposed.", "exploit_chain": "", "evidence": [ "VERDICT.md - full path:line trace of the missing common lock and the staging requirements" ], "kernel_refs": [ "sys/kern/kern_sig.c:1336-1354", "sys/kern/kern_sig.c:1425", "sys/kern/kern_sig.c:228-242", "sys/kern/kern_exec.c:413-421", "sys/kern/kern_exit.c:1301-1306", "sys/kern/kern_fork.c:521-528" ], "poc_changes": "no runnable PoC - race window too narrow to stage reliably in the available budget; kept as inspection finding", "attempts": 0, "guest_uname": "", "runtime_sec": 0, "guest_dirty": 0, "build_cmd": "", "run_cmd": "", "code_hash": "", "notes": "If triage wants a live repro: hammer kill(victim, SIGUSR1) from N CPUs while a vfork cluster churns exec/reap; watch for corruption/panic. Recommended fix is cheap (hold p_token across the final-path sigsetfrompid call) and would also harden the routed-delivery path generally.", "recommended_fix": "Hold p->p_token across sigsetfrompid() on the final lwpsignal delivery path (line 1425), or acquire it shared inside sigsetfrompid()" } |