# DF-5496 VERDICT — umct cfg-chain synchronous-EP0 stall

**Status: REPRODUCED** (KLD-harness-with-production-code class; no
MCT/Belkin/Sitecom USB serial device model exists on the QEMU guest).
Impact: **dos** (local, evil-USB precondition; uninterruptible occupancy
of the per-adapter ucom taskqueue; blocks close(2) SIGKILL-immune and
the USB hub event thread on unplug). Confidence: **certain** (every
simulated request is the real in-kernel cost of an unanswered EP0
data/status stage, on the real usb_process taskqueue, with production
request counts and queueing order).

## Root cause (path:line)

- Every umct cfg step is a synchronous EP0 vendor write via
  `ucom_cfg_do_request` (umct.c:332-333 → usb_serial.h:220-221 →
  `usbd_do_request_proc`) with a **1000 ms timeout** (umct.c:333). The
  `usb_error_t` is **discarded** at the only site there is
  (umct.c:334-337, "(ignored)"), so no chain ever aborts on failure.
- An evil adapter that never answers the data/status stage makes each
  request eat its full timeout (usb_request.c:561 `max_ticks`, :643
  "WXFER" sleep, :700-704 `USB_ERR_TIMEOUT`); an adapter answering at
  just-under-timeout makes every request *succeed* — identical
  occupancy, no error to propagate.
- Chains run on the per-adapter ucom taskqueue
  (`sc_super_ucom.sc_tq`, usb_serial.c:322). close(2) is synchronous
  against it (`ucom_close` → `ucom_shutdown` (HUPCL, one more request)
  → `ucom_queue_command(ucom_cfg_close)` → `usb_proc_mwait`,
  usb_serial.c:671-672 + 953-957; uninterruptible `cv_wait`,
  usb_process.c:364-393 — a plain cv_wait, no signal delivery, that
  drops sc_lock while asleep, so a concurrently-queueing tcsetattr
  loop extends the wait until it stops). Unplug stalls the USB hub
  event thread the same way (`ucom_detach` → `usb_proc_drain`,
  usb_serial.c:394).

## Production chain accounting (pass-2 precision)

The core queues, on first open (usb_serial.c:842 param, :870 start,
:865 cfg_open, :870 start, :874-878 line-state, :880 status):
**the first param task runs before the cfg_open task and bails at
usb_serial.c:1626-1628 (`!UCOM_FLAG_LL_READY`)** — the first-open param
is a no-op for every ucom driver. umct registers no `ucom_cfg_open`
(umct.c:175-187), so the core's hz/10 open pause (usb_serial.c:777)
does not run either, and `umct_cfg_get_status` reads cached bytes
(umct.c:386-387; kept fresh by umct_intr_callback_sub, umct.c:360-361
— no EP0). What issues EP0 on first open is the single coalesced
line-state task (usb_serial.c:1357-1420): DTR=1 + RTS=1 + BREAK=0 →
**3 × 1000 ms** (umct.c:413 set_dtr SET_MCR, :426 set_rts SET_MCR,
:400 set_break(0) SET_LCR). Close adds the HUPCL DTR drop
(usb_serial.c:694-696 → umct.c:413): **+1 × 1000 ms**.

Every later tcsetattr (param task double-buffered, usb_serial.c:1691-1693,
LL_READY now set):

- **any rate** — there is no B0 branch in umct_cfg_param
  (umct.c:465-504); umct_pre_param accepts everything (umct.c:459-463)
  and the same two registers are programmed for B0 as for any speed:
  SET_BAUD (umct.c:472) + SET_LCR (umct.c:503) = **2 × 1000 ms + hz/10
  core pause (usb_serial.c:1636) ≈ 2.1 s per tcsetattr**;
- TIOCMSET (DTR+RTS) → 2 × 1000 ms; TIOCSBRK/TIOCCBRK → 1 × 1000 ms.

Amplification: the msignal double buffer COALESCES a rapid tcsetattr
burst to at most 2 queued instances (usb_process.c:259-331, t==3
branch) — a burst of 5 measured 2 full param executions (phase A3) —
but a **paced or sustained** tcsetattr loop keeps the taskqueue busy
~2.1 s per instance for as long as the abuser keeps queueing, and
because `usb_proc_mwait`'s cv_wait drops sc_lock while asleep, the
closer cannot lock them out: close(2) blocks SIGKILL-immune until the
abuser stops plus up to 2 × 2.1 s residual + 1.0 s HUPCL. Measured
with the closer entering ~150 ms into the chain: first open + close
**3885 ms**; +1 tcsetattr **5952 ms**; +5-burst tcsetattr **8049 ms**.
Unplug mid-cycle pins the USB hub event thread identically
(usb_serial.c:394) per cycle.

## Harness and why it is faithful

`um5496stall_harness.c` replicates, on the real `usb_process`
machinery: the exact first-open queue order including the LL_READY
gate; the 3/2/1-request production chains (line-state / tcsetattr /
HUPCL-close); the core's hz/10 param pause; the absent core open-pause
(no callback); the synchronous closer (HUPCL line task queued via
msignal, then `usb_proc_msignal`+`usb_proc_mwait`, entered ~150 ms into
the chain like a real close(2) after open); per-request cost =
`lksleep("WXFER", USB_MS_TO_TICKS(timeout))` on a **unique per-request
wait channel** (production sleeps on the unique xfer pointer,
usb_request.c:643 — the DF-5476 harness v1 lesson carried here from
day one); and the production double-buffer coalescing semantics
(phase A3 burst of 5 → 2 param executions, instrumented).

## Fix validation

- `fix.diff` (8 hunks): `umct_cfg_do_request()` returns `usb_error_t`;
  `umct_cfg_set_break/set_dtr/set_rts` abort on failure;
  `umct_cfg_param` aborts the chain on the first failed SET_BAUD;
  cfg-path timeout bounded `UMCT_CFG_TIMEOUT` = 250 ms (umct.c:333
  was 1000).
- Mechanism-level validation per family standard: harness phase B
  (abort-on-first-error) cuts the measured closer block from 5952 ms
  to **3033 ms** and phase C (abort + 250 ms bound) to **834 ms**
  (run.log); the 8-hunk fix.diff applies 8/8 clean (`git apply
  --check` on the audit tree; `patch -p1` on the guest) and compiles
  the patched **production** umct.ko with zero compiler diagnostics on
  the guest (`fix_compile.log`, `-Werror`; the only "error" string in
  the log is the literal `-Werror` flag). A full patched-kernel boot
  is not required to observe a per-request timeout bound + early-abort
  behavior the harness exercises directly.

## Honesty notes

- First fix.diff draft used `!= USB_ERR_OK`, which does not exist in
  this tree's usb.h (success is implicitly 0) — the patched module
  failed to compile; corrected to `!= 0` (family precedent, DF-5476)
  and rebuilt clean. Attempt count includes this.
- No MCT hardware exists on the guest; the device's side of each
  request (never answering the data/status stage) is simulated by the
  documented in-kernel cost of exactly that condition (the WXFER
  timeout sleep), not by a USB device model.

## Negative results this pass (why umct is otherwise clean)

- **No uninit-return class**: umct issues ZERO control-IN reads
  anywhere — `UMCT_GET_MSR`/`UMCT_GET_LCR` (umct.c:72-75) are dead
  constants; status is served from intr-cached `sc_lsr/sc_msr`
  (umct.c:381-388). The DF-1062/2342/5448/5455 class cannot exist.
- **Correct intr length gate**: `umct_intr_callback_sub` checks
  `actlen < 2` before copying the 2-byte status payload (umct.c:353-358)
  — the ulpt DF-5438 class is absent. Read path passes `actlen`
  (device-controlled but bounded by the frame length) to
  `ucom_put_data` (umct.c:605-610); write path bounded by the
  core-computed `sc_obufsize` (umct.c:281, SITECOM clamp :283-287).
- **No sync EP0 from the USB callback thread** (umcs DF-5382 class
  absent): all three xfer callbacks only copy data and resubmit.
- **Attach/detach error paths safe**: both `umct_attach` failure
  branches (umct.c:258-262 transfer-setup, :290-292 ucom_attach) goto
  `umct_detach`; `ucom_detach` no-ops unless `UCOM_FLAG_ATTACHED`
  (usb_serial.c:371-372) and `ucom_attach` self-cleans partial failure
  (usb_serial.c:344-348); `usbd_transfer_unsetup` on NULL xfers is a
  no-op. No uether DF-5334 goto-detach panic class.
- **Endpoint-swap heuristic memory-safe** (umct.c:269-279): swaps
  whole xfer pointers whose DMA buffers stay sized to their own
  endpoints; dispatch guarded by `sc_swap_cb` (umct.c:547-566);
  written once at attach before `ucom_attach` (no race window).
- **umct_calc_baud/pre_param**: unsupported rates (B50..B200, B230400+,
  arbitrary speeds) silently program divisor 0 — a functional
  wrong-baud issue (Linux mct_u232 maps divisor 0 to 2400 baud),
  upstream-inherited, no security impact (not filed).

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