# DF-5504 VERDICT — ucycom cfg-stall (reproduced, mechanism-level)

## Finding

`ucycom_cfg_write()` (ucycom.c:412-444) issues ONE synchronous EP0
`SET_REPORT(feature)` per cfg callback via
`ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, sc->sc_temp_cfg, 0, 1000)`
(ucycom.c:438 — 1000 ms timeout) and DISCARDS the `usb_error_t`
("device request failed, err=%s (ignored)", ucycom.c:440-443). It is called from:

* `ucycom_cfg_open()` (ucycom.c:299-306) — every first `open(2)` of the tty
  (ucom core queues it at usb_serial.c:865-867 after a `cfg_param` task queued by
  the in-open `ucom_param()` call at usb_serial.c:842; that first param task
  no-ops before `UCOM_FLAG_LL_READY` is set — usb_serial.c:1626 — so the first
  open costs one request, not two);
* `ucycom_cfg_param()` (ucycom.c:474-507) — every `tcsetattr(2)` of an open port
  (usb_serial.c:1691-1693).

The ucom core adds `usb_pause_mtx(sc->sc_lock, hz/10)` after each callback
(usb_serial.c:777 and :1636), so each cfg task occupies the per-adapter ucom
taskqueue for ~1100 ms against an evil device. `close(2)` is synchronous against
that queue — `ucom_close()` → `ucom_queue_command(ucom_cfg_close)` →
`usb_proc_mwait()` (usb_serial.c:671-672 + 955-957), an uninterruptible wait
(usb_process.c:385-393) — and unplug pins the USB hub event thread the same way
through `ucom_detach()` → `usb_proc_drain()` (usb_serial.c:394). ucycom has NO
`ucom_cfg_close` callback, so close itself issues zero EP0 requests; the damage
is purely the wait behind queued cfg tasks. ucycom is the mildest member of the
cfg-stall family (1 request per task vs. 3–14 for uchcom/uslcom/umoscom/ubsa/
umct/uftdi/uvscom), but the amplification is the same shape: a local user
sustaining a `tcsetattr` loop on the open `/dev/cuaU*` keeps ~1.1 s/task of
uninterruptible occupancy going for as long as the loop runs, and SIGKILL cannot
preempt a closer already inside `usb_proc_mwait`.

## Reproduction (KLD-harness-with-production-code)

No Cypress CY7C64013 device model exists on the guest. The harness compiles the
VERBATIM production code (sed-extracted at build time from the guest's md5-gated
`/usr/src`: `ucycom.c` c7b432ce…, `usb_serial.c` dc5c04db…):

* ucycom.c:62-101+181-182 (softc/constants), :112,117-119 (protos), :299-306,
  :412-444, :446-472, :474-507 — the driver's whole cfg chain;
* usb_serial.c:635-680 (`ucom_queue_command`), :756-780, :906-922, :1619-1637
  (core cfg task wrappers);

and runs it on the REAL `usb_proc_create/msignal/mwait` taskqueue machinery
(the same machinery ucom's cfg callbacks run on), with a real zeroed softc and
`lockinit(…, LK_CANRECURSE)` exactly as ucycom_attach does (ucycom.c:214). The
single interposed symbol is `usbd_do_request_proc` (usb_request.c:760): an
evil-device model that sleeps `timo` ms ("WXFER") and returns
`USB_ERR_TIMEOUT` (silent) or succeeds at `timo-50` (slow-answer), logging each
request's verbatim framing. Task queueing replicates ucom_open's production
shape (usb_serial.c:829-872) and the user's tcsetattr shape (:1690-1693), both
through the verbatim `ucom_queue_command`.

## Measured (run.log, run.2.log — fresh guest, hz=100)

* Phase A (first open + 1 tcsetattr + close; silent device): closer blocked
  **2094 / 2111 ms**, 2 EP0 requests burned (cfg_open + cfg_param tasks; the
  first-open param task no-ops as in production).
* Phase 2 (2 tcsetattrs): 2106 / 2097 ms — the rapid second tcsetattr coalesces
  into the pending `sc_param_task` double-buffer slot (production
  last-wins semantics); a *sustained* loop re-queues as tasks drain, extending
  the occupancy for as long as the attacker keeps looping (~1.1 s per processed
  task; sibling DF-5496 measured the same coalescing with a 5-burst).
* Phase S (device answers at 950 ms — every request SUCCEEDS): **1987 / 1989 ms**.
  This is the ucycom-specific point: with only one request per task, the
  family's "abort the chain on first error" mitigation is a no-op even when the
  device does fail, and covers nothing when it succeeds. The only in-file lever
  is the timeout itself.
* Phase C (fix modeled on a second sed-fixed extraction of the same verbatim
  code, `0, 1000` → `0, UCYCOM_CFG_TIMEOUT(250)`): **638–661 ms**.

## Fix validation

* fix.diff (UCYCOM_CFG_TIMEOUT 250): `git apply --check` clean on the audited
  repo; the patched production ucycom.c (md5 7044be95… built on host, spliced
  byte-identical into the guest tree) compiled `-Werror`-clean, 0 warnings, as
  the real in-tree-style `ucycom.ko` (fix_compile.log, RC=0).
* Behavioral proof at mechanism level: phase C 2094 → 638-661 ms.
* Scope caveat: compile + mechanism level per the family evidence class; no
  physical crafted Earthmate exists on this guest.

## Pass-2 negative results (this file, recorded for weight)

* RX HID framing bounded: 64013 `len = buf[1]` (device, ≤255) and 63743 3-bit
  count are both clamped by `actlen` (ucycom.c:558-559); `actlen ≤ frame len =
  sc_ilen ≤ 1026 = bufsize` (attach clamp ucycom.c:247-254; usb_transfer.c:681).
* TX 2-frame control write bounded by construction: frame0 must be 8 bytes
  (usb_transfer.c:1560-1565) and the data stage ≤ `bufsize - 8` = 1026
  (usb_transfer.c:681,718-726); `sc_olen - offset ≥ 0` since `sc_olen ≥ 2`
  enforced (ucycom.c:248); `usbd_copy_in` walks only allocated pages
  (usb_busdma.c:131-149).
* `sc_temp_cfg[5..31]` sent to the device when `sc_flen > 5` is zero-filled, not
  uninitialized: the newbus softc is `kmalloc(…, M_ZERO)` (subr_bus.c:1953).
  (And the recipient is the attacker's own device.)
* Attach error path (`goto detach` → `ucycom_detach` → `ucom_detach` without
  `ucom_attach` having run) is safe: `ucom_detach` returns immediately unless
  `UCOM_FLAG_ATTACHED` is set (usb_serial.c:371-372).
* `usbd_req_get_hid_desc` M_ZERO's its buffer and passes the device-declared
  length as both alloc size and request length (usb_hid.c:819-835): short
  transfers zero-pad, no uninit read reaches `hid_report_size`.
* `ucycom_pre_param` whitelists speeds 600–57600 (ucycom.c:446-472); B0/0 →
  EINVAL, so `ucycom_cfg_write` never sees a degenerate divisor (the chip takes
  the raw baud word; there is no division in the driver at all).
* The 64013 TX length byte `data[1] = actlen` truncates >255-byte writes
  (protocol misframing only; requires a malicious HID descriptor declaring
  olen > 257 and only misframes the attacker's own device — no host impact).
* Unplug-lifecycle/UAF concerns are DF-5320 (usb_serial.c core), not re-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.
