# DF-2785 VERDICT

**Status: reproduced (unprivileged kernel panic → local DoS, certain). Fix validated (fixed).**

## Baseline (stock kernel #0, INVARIANTS): panic in ~3 seconds

First attempt design churned without filling the undo table (0 errors
in 21M iterations — churn threads in lockstep kept only ~2 entries
alive; kept as run.log commentary on iteration counts).  Corrected
churn (persistent 25 filler entries + dip/re-fill cycles, staggered
threads) panicked the guest within ~3s of launch as an unprivileged
user:

```
panic: semop - can't undo undos
cpuid = 3
Trace beginning at frame 0xfffff8011833b888
sys_semop() at sys_semop+0x65d
syscall2() at syscall2+0x11e
Debugger("panic")
```
(serial console; full capture in panic.txt; guest wedged in DDB → down.)

Root cause (sys/kern/sysv_sem.c:998-1006): the SEM_UNDO rollback loop
`panic()`s if a reverse-order rollback adjust fails.  The "reverse
order guarantees we won't run out of space" argument is valid only
single-threaded: `semundo_adjust()` (:219-269) keys entries by
`(semid, semnum)` in the process-shared `p->p_sem_undo` and takes only
`p_token`, per call — nothing serializes one thread's undo vector
against sibling threads of the same process.  Victim vector
`{w,+1,U}{w,-1,U}{x,+1,U}{x,-1,U}`: adjust#1 deletes the entry adjust#0
created (net-zero), so if adjust#2 fails on a full table
(`un_cnt == semume == 25`), the rollback of sops[1] looks up an entry
that is absent **by construction** while the table is full → EINVAL →
panic.  Churn threads only need to re-fill the table inside the
sub-microsecond gap between the victim's adjust#1 and adjust#2.

## Fix validation (patched kernel #1: panic → kprintf + break)

Race re-run for 300+ seconds on the patched kernel:

```
victim : 306184192 iters, 21247 errors        (failure path heavily exercised)
semop: SEM_UNDO rollback lost a race, pid 860 (dmesg)
… 170 occurrences of "lost a race" …          (the exact formerly-panicking path)
guest: up                                      (baseline: dead in ~3s)
```

Every one of the 170 race events would have been the panic on the
stock kernel.  Bad behavior gone: no panic; the condition is logged.

## Impact ceiling

Medium — unprivileged, reliable (seconds) local kernel panic = local
DoS.  `panic()` is an intentional kernel halt, so no memory corruption
beyond the DoS itself; a fuller fix would serialize undo vectors
per-process (mutex across the adjust/rollback phase) rather than only
removing the crash.

## Artifacts

panic.txt (serial console), run.log (baseline narrative + fixed-run
progress), run.fixed.log (patched-kernel run + dmesg count), env.txt,
fix.diff (validated in-guest: build RC=0, installkernel RC=0).
