# DF-5327 VERDICT

## Status: REPRODUCED (deterministic, both stages) — fix validated

## What was reproduced

**Bug**: `chopen()` (sys/bus/cam/scsi/scsi_ch.c:415-432) performs the
multi-open fixup (set `CH_FLAG_OPEN` for the first opener / drop this
open's `cam_periph_acquire()` for later openers) **before**
`cam_periph_hold(periph, PCATCH)`. When the hold is signal-interrupted,
the error path drops one reference *in addition* to the one
`cam_periph_hold()` already dropped internally — the peripheral
refcount undercounts by one per interrupted open, exactly the DF-5273
class but via the open path and in a driver DF-5273 did not cover.

**Chain proven on the guest** (evilcd2 rig, fake T_CHANGER at target 1,
`/dev/ch0`; `debug.evilcd.defer` parks chgetparams' MODE SENSE so a
second open blocks in `cam_periph_hold(PCATCH)` and is interrupted by
SIGALRM):

1. open#1 parked (device-side stall) → rc = 2 (its acquire + hold).
2. open#2 interrupted (EINTR) → chopen error path releases → rc = 1
   **while open#1 is still inside chgetparams holding acquire+hold**.
3. open#1 completes → unhold → **rc == 0 with /dev/ch0 OPEN**
   (`[child1] open#1 returned: refcount=0`).
4. Device removal (`debug.evilcd.invalidate` → AC_LOST_DEVICE):
   `cam_periph_invalidate()` sees refcount==0 → `camperiphfree()` →
   `chcleanup()` → kfree(softc) and kfree(periph) **while the
   descriptor is open** — dmesg shows `(ch0:evilcd0:0:1:0): lost
   device` + `removing device entry`; postmortem dumps the freed chunk.
5. Same burn without invalidation: plain `close()` → `chclose()`'s
   unconditional release at rc==0 → **0xFFFFFFFF**
   (`[child1] after close: refcount=-1`), silently (DF-5329's dead
   guard). Repeated cycles reach -2, -3, ... (zombie periph, never
   freed, no warning).

Ran 3+ times across two kernel builds; fully deterministic.

## Exploitability assessment (honest)

- Trigger: root or a RESTRICTEDROOT-capable process on a system with a
  medium-changer device (or any code path reaching chopen with a
  signal-interruptible hold). Same privilege gate as DF-5273.
- The 0-with-open-fd state (step 3) is a **premature-free enabler**:
  any hot-unplug/LUN-replacement/rescan racing an open ch descriptor
  frees the periph + softc under it. The in-flight use-after-free
  window this creates (fd-owning thread inside chclose/chioctl between
  `cam_extend_get()` and `cam_periph_lock()`) is identical in shape to
  the window DF-5273 documents.
- Attacker reallocation of the exact freed 144-byte chunk was attempted
  with the rig (16384-iteration spray + 4096 magazine-churn cycle,
  same-CPU pinned); the freed chunk stayed parked in a full zalloc
  magazine and was not recycled within the run ("sprayfake: chunk NOT
  reused"). Reallocation of this size class under sustained churn is
  standard slab behavior (demonstrated for the 32B class in DF-5271's
  pack); the exact-chunk groom is left as a bounded, documented miss.
- uid=0 escalation: gated behind RESTRICTEDROOT — a root-triggerable
  refcount bug on this device family is a robustness/containment
  defect rather than an unpriv→priv vector, so no escalation chain was
  pursued (matches DF-5273's Medium classification).

## Fix validation (MANDATORY for memcorrupt — done)

`fix.diff` swaps chopen to take the hold BEFORE the flag/fixup (mirrors
cdopen/daopen/saopen). Applied in the guest's /usr/src, kernel rebuilt
(EVIL = GENERIC + device ch), reinstalled, rebooted, exact same PoC:

| step | vulnerable | patched |
|---|---|---|
| open#2 interrupted (rc while #1 parked) | 1 | **2** |
| open#1 returned, fd open | 0 | **1** |
| after close | -1 | **0** |

(`fixedA.log`). Undercount and underflow GONE; behavior matches
cdopen's proven-safe ordering.

## Guest
DragonFly dfbsd 6.5-DEVELOPMENT #0 ... /usr/obj/usr/src/sys/EVIL x86_64
(GENERIC + `device ch`), evilcd2.ko rig; cc 8.3.

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
