# DF-2344 evidence pack

`umcs7840_attach()` discards the return value of the GPIO register read
at `sys/bus/u4b/serial/umcs.c:324`; on USB control-transfer failure the
local `uint8_t data` (declared at :301, never initialized) is read at
:325 (`data & MCS7840_DEV_GPIO_4PORTS`) to choose the 2-vs-4-port
configuration. The next register read (MODE, :339) checks its return
correctly — only the GPIO read omits the check.

## Verdict (short)

**REPRODUCED on the stock-codegen harness kernel** (shared rig with
DF-2343; see `../DF-2343/`). With the GPIO control transfer failing,
`sc_numports` followed a groom byte placed on the kernel stack below
`attach()`'s frame — 4/4 deterministic:

| groom byte (uninit slot content) | bit0 | sc_numports |
|---|---|---|
| 0xA5 | 1 | **4** |
| 0x00 | 0 | **2** |
| 0x01 | 1 | **4** |
| 0x42 | 0 | **2** |

`data` has its address taken (passed as `&data` at :324), so it lives in
`attach()`'s stack frame and reads whatever the call chain left there —
the harness grooms exactly that region (`groom_stack()` immediately
before `umcs7840_attach()`; no intervening call). With the read
succeeding, the decision follows the *device* value instead (0x01→4,
0x00→2), proving the sim is not the decider. Attach **succeeds** with
the garbage-derived port count either way.

**Impact: none beyond wrong configuration** (Info, as filed). The
uninitialized byte only selects 2 vs 4; the per-port port numbers are
hardcoded constants within `UMCS7840_MAX_PORTS` (umcs.c:328-336), so no
out-of-bounds indexing follows. Worst realistic case with a
failing/buggy MCS7840 device at attach: wrong number of ttyU* units
created (a 2-port device exposing 4 units, or vice versa), or an odd
attach failure downstream. No info leak (the byte is not printed), no
memory-safety consequence, no user trigger (attach-time only, requires
the hardware to misbehave). **PRIVESC: none** — explicitly closed.

Fix (fix_2344.diff): check the return like the MODE read does and fail
attach (`goto detach` → ENXIO). Validated on the same rig: GPIO-failure
attach now returns ENXIO (rc=6) deterministically for every poison byte,
`sc_numports` stays 0, and a good device still attaches with 4 ports
(no regression).

## Rig

Shared with DF-2343 (verbatim-interposition harness, kernel-compiled):
`umcs_harness.c` `#include`s the sha256-gated unmodified umcs.c
(`33f9ea99…bef67`); `usbd_do_request_proc` is simulated with faithful
no-write-on-error semantics (on STALL the caller's buffer is NOT
written — exactly like the real control-transfer path — which is what
leaves `data` uninitialized); everything else (ucom_attach, make_dev,
locks) is stock. Scenario `./umcshctl 4 <byte> 3` = fail the GPIO read +
groom the stack with `<byte>` + call the real `umcs7840_attach()`.

## How to reproduce

1. Guest up; push `../DF-2343/` to guest:/root/umcs; `sh prep.sh`;
   `sh build.sh`; reboot into UMCSH.
2. `sh run1_baseline.sh` (or directly):
   `./umcshctl 4 0x01 0; ./umcshctl 5; ./umcshctl 4 0x00 0; ./umcshctl 5`
   (sanity: device value decides) then
   `./umcshctl 4 0xA5 3; ./umcshctl 5; ./umcshctl 4 0x00 3; …`
   (read fails: groom byte decides — see run.log).
3. Fix A/B: `patch < fix_2344.diff` in guest
   /usr/src/sys/bus/u4b/serial/, rebuild, reboot, `sh run3_fixed.sh`
   → ENXIO for all poison bytes, good device still 4 ports.

## Files

(sources shared with ../DF-2343/, copied here for self-containment)
umcs_harness.c, umcsh.h, umcshctl.c, tioc0.c, prep.sh, build.sh
build.log        FULL stock-codegen kernel build transcript (shared)
run.log          the DF-2344 battery: baseline poison runs + fixed A/B
fix_2344.diff    the validated return-check fix
env.txt          guest environment
VERDICT.md       full narrative
manifest.json, verdict.json
