# DF-5436 VERDICT

**Status: reproduced (mechanism-level, production code in KLD harness).**
**Impact: dos (broken exclusive open, dead reader session, reset/status
poll during open session).  No memory corruption demonstrated in the
production path.**

## Why reproduced

The exact production logic at fault — `unlpt_open()`'s lock-free
`sc_fflags` gate (ulpt.c:427), its delayed commit (ulpt.c:456), the
per-fifo `sc_fifo_open[]` assignment (ulpt.c:441/454), `ulpt_close()`'s
unlocked bit clear (ulpt.c:465), `ulpt_open()`'s reset guard
(ulpt.c:413) and `ulpt_watchdog()`'s `sc_fflags == 0` condition
(ulpt.c:730) — was compiled into a KLD with only the usb calls stubbed
at equivalent cost.  The framework contract was modelled from the
production source: per-cdev serialization (devfs per-vnode lock +
`usb_fifo_open`'s curr_cpd, usb_dev.c:699-710) and **nothing wider**;
`D_MPSAFE` dev_ops exactly like production `usb_ops` (usb_dev.c:133) so
the kernel BGL does not serialize the two opens (without D_MPSAFE the
race cannot fire at all — verified empirically, see poc_changes).

With two CPU-pinned processes free-running open/close on the two cdevs
of one softc (the /dev/ulpt0 vs /dev/unlpt0 pair, O_RDONLY — the
unprivileged direction on the 0644 production nodes):

- `opens_while_open` grew 53566 -> 103639 -> 158208 over three 10 s
  runs (**~50,000 exclusive-open violations per 10 s**); every one of
  those opens returned SUCCESS for a direction that was already open.
- `window_crossed` = 3 in a 6 s calibration run: direct canary proof of
  a peer commit landing inside our own gate window (the load measured
  27 us average gate->commit).
- `resets_while_open` grew to 311727: `ulpt_open`'s SOFT_RESET decision
  (`sc_fflags == 0`, ulpt.c:413) taken while a session was actually
  open — the reset guard is defeated at open-rate.
- With `hw.hx5436.fixed_gate=1` (the fix.diff mechanism: `sc_lock` held
  across gate..commit in open and close, plus `sc_fifo_open[] = NULL`
  on close), three further 10 s runs added **zero** new
  `opens_while_open` and **zero** new `resets_while_open`; the EBUSY
  rate rose from ~6k to ~80k per run (the gate correctly rejecting
  instead of losing).

End-to-end with a physical/QEMU USB printer is not possible on this
guest (QEMU 7.2 has no usb-printer device — verified via
`qemu-system-x86_64 -device help`), hence the KLD-harness evidence
class; the raced code is the production source text.

## Downstream effects (production reasoning, cited)

- Dead reader session: after a double-open, `sc_fifo_open[USB_FIFO_RX]`
  names only the last opener's fifo (ulpt.c:441), so the first session's
  `usb_read()` (usb_dev.c:1457-1481) sleeps forever — local DoS of
  whichever process lost the race (e.g. a status-reading daemon).
- `ulpt_reset()` into an open session: two SOFT_RESET class requests
  (ulpt.c:177-187) issued while another fd is streaming — the exact
  condition the `sc_fflags == 0` guard exists to prevent.
- Watchdog status poll during an open session (ulpt.c:730-731) — the
  documented printer-firmware-bug condition (ulpt.c:726-729).
- The stale `sc_fifo_open[]` debris (production never NULLs it) redirects
  the bulk callbacks' data destination between sessions; within this
  USB stack's gating (sticky USB_ERR_CANCELLED at usb_transfer.c:2570,
  started-flag check at usb_transfer.c:2397-2403, sc_lock discipline in
  usb_fifo_close at usb_dev.c:783-857) it does not yield a UAF — the
  same staleness exists in FreeBSD HEAD (checked, identical
  ulpt_close) and is latent hardening, not a demonstrable corruption.

## The early-harness panic (artifact, not a production claim)

Harness v1's close copy freed the buffer via `sc_fifo_open[dir]` instead
of the closing fifo's own slot — an infidelity vs production
(`usb_fifo_free_buffer(fifo)` frees the *argument*, ulpt.c:468).  Under
the now-firing race that produced a double free and
`panic: assertion "*kup < 0" failed in _kfree` (see
panic-early-harness.txt).  This is **a harness bug**, fixed before the
recorded runs; it nonetheless demonstrates (a) the race fires hard
enough to corrupt shared driver state within seconds, and (b) the class
of cascade a driver suffers once the gate loses — in any driver that,
like the harness, frees through the shared pointer.  Production ulpt's
wrong-free surface is instead the *fifo redirection* above.

## Fix

`fix.diff` (validated as `hw.hx5436.fixed_gate=1` in the recorded runs):
hold `sc_lock` across the gate..commit section of `unlpt_open()` and the
bookkeeping of `ulpt_close()`, and clear `sc_fifo_open[dir]` on close.
With the mechanism enabled, zero anomalies across three 10 s
contention runs (vs ~50k/10 s baseline).

## Not verified / limits

- No physical evil-printer end-to-end run (no device model on guest).
- `polls_while_open` stayed 0 (1 Hz callout vs microsecond desync
  windows); the underlying desync is proven by the other counters.
- No memory corruption on the production path (see above for why the
  stale-pointer trail dead-ends in this stack).

## 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.
