uvscom_cfg_read_status() returns uninitialized kernel stack on a successful SHORT vendor control-IN (core actlen=NULL zeroization gap) β return value currently has no consumer, latent DF-1062/2342-class hazard
Summary
uint8_t data[2] stays uninit when a control-IN succeeds short: the core zeroizes the short-read tail only when actlen!=NULL but ucom_cfg_do_request hardcodes NULL - return (data[0]|(data[1]<<8)) reads uninit stack. No consumer today (sole caller discards it; status fed from the intr pipe) but the NetBSD original uses exactly this read for modem status. One-line init fix (compile-validated).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5448 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.1 KB | β raw | |
| VERDICT.md | β | 3.5 KB | β raw | |
| fix.diff | β | 322 B | view raw | |
| fix_compile.log | β | 7.2 KB | view raw | |
| env.txt | β | 454 B | view raw | |
| manifest.json | β | 820 B | view raw | |
| verdict.json | β | 3.1 KB | view raw |
DF-5448 β uvscom_cfg_read_status() returns uninitialized kernel stack on a
successful short vendor control-IN response
The bug
sys/bus/u4b/serial/uvscom.c:714-734:
static uint16_t
uvscom_cfg_read_status(struct uvscom_softc *sc)
{
struct usb_device_request req;
usb_error_t err;
uint8_t data[2]; /* <-- UNINITIALIZED stack */
req.bmRequestType = UT_READ_VENDOR_DEVICE; /* 0xC0: device-to-host */
req.bRequest = UVSCOM_READ_STATUS;
USETW(req.wValue, 0);
USETW(req.wIndex, 0);
USETW(req.wLength, 2);
err = ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
&req, data, 0, 1000);
if (err) {
DPRINTFN(0, "device request failed, err=%s (ignored)\n", ...);
}
return (data[0] | (data[1] << 8)); /* <-- may be uninit */
}
Core semantics (sys/bus/u4b/usb_request.c:783-788,
usbd_do_request_proc):
/* on failure we zero the data */
/* on short packet we zero the unused data */
if ((len != 0) && (req->bmRequestType & UE_DIR_IN)) {
if (err)
memset(data, 0, len); /* error: fully zeroed */
else if (actlen && *actlen != len)
memset(((uint8_t *)data) + *actlen, 0, len - *actlen);
}
The short-read tail zeroization only happens when actlen != NULL β but
ucom_cfg_do_request (sys/bus/u4b/serial/usb_serial.h:220-221) hardcodes
actlen = NULL:
#define ucom_cfg_do_request(udev,com,req,ptr,flags,timo) \
usbd_do_request_proc(udev,&(com)->sc_super->sc_tq,req,ptr,flags,NULL,timo)
So:
- request fails (STALL/timeout): buffer zeroed β return 0. Safe.
- request succeeds with a short data stage β a control-IN transfer that
ends in a 0-byte short packet leaves both bytes uninitialized; a 1-byte
response leaves data[1] uninitialized (usb_request.c:662-665 copies
only acttemp bytes, 680-681) β return (data[0] | (data[1] << 8))
reads uninitialized kernel stack. Exactly the DF-1062/DF-2342 (uchcom)
class, reached through the DF-5438 (ulpt)-style gap.
Impact
Currently none reaches userspace: the sole caller
uvscom_cfg_open() (uvscom.c:640) discards the return value, and
sc_lsr / sc_msr / sc_unit_status are fed only from the interrupt pipe
(uvscom.c:425-446). The uninitialized value is computed and thrown away β
a latent hazard, not a live leak: it becomes a real kernel-stack disclosure
the moment any code consumes the return (the NetBSD original this driver
was ported from uses exactly such a status read to update the modem-status
state). Filed as hardening: initialize the buffer (defense in depth, same
treatment the core already gives the error path).
Evidence
Static, code-certain (no SUNTAC device model exists for execution; the Info-severity class was not executed β see verdict.json status=untested). Path: uvscom.c:719,733 β usb_serial.h:220-221 β usb_request.c:662-665, 680-681, 783-788.
Fix
fix.diff β one line: initialize data. (Optionally the core macro could
pass a real actlen; that is a ucom-wide change tracked with the family.)
Files
README.md,VERDICT.md,fix.diff,manifest.json,verdict.jsonfix_compile.logβ patched uvscom.ko compiles clean (combined guest build)
DF-5448 VERDICT β uninitialized stack returned by uvscom_cfg_read_status()
on a successful short control-IN (NO CONSUMER β latent, Info)
Status: NOT EXECUTED (static, code-certain; Info-severity hardening class β no SUNTAC device model exists to exercise the short-read data stage). The defect is proven by direct source tracing below; the fix compiles clean on the guest (fix_compile.log, combined patched build RC=0).
The path, line by line
uvscom_cfg_read_status()β uvscom.c:714-734:
uint8_t data[2]; /* :719 UNINITIALIZED */
req.bmRequestType = UT_READ_VENDOR_DEVICE; /* :721 0xC0 = IN */
USETW(req.wLength, 2);
err = ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
&req, data, 0, 1000); /* :727-728 */
if (err) { DPRINTFN(0, "... (ignored)\n", ...); }
return (data[0] | (data[1] << 8)); /* :733 possibly uninit */
-
ucom_cfg_do_requestβ usb_serial.h:220-221 β expands tousbd_do_request_proc(udev, &sc_super->sc_tq, req, ptr, flags,NULL, timo)β theactlenout-parameter is hardcoded NULL. -
usbd_do_request_procβ usb_request.c:780-788:
if ((len != 0) && (req->bmRequestType & UE_DIR_IN)) {
if (err)
memset(data, 0, len); /* error: zeroed */
else if (actlen && *actlen != len) /* actlen == NULL! */
memset(((uint8_t *)data) + *actlen, 0, len - *actlen);
}
- request fails (STALL/timeout): buffer fully zeroed β returns 0. SAFE.
- request succeeds with a short data stage: the data-phase loop
copies only
acttempbytes (usb_request.c:662-665temp = acttemp, copy at 680-681) and returns err==0; the tail-zeroization branch is guarded byactlen &&which is FALSE here (NULL) βdata[1](1-byte reply) or both bytes (0-byte reply, a 0-length short packet terminates a control-IN data stage) remain uninitialized kernel stack, which line 733 reads and returns.
This is exactly the uchcom class (DF-1062/DF-2342, uninitialized kernel stack via short/malformed vendor control-IN) reached through the same core gap as DF-5438 (ulpt status callback).
Why Info and not Low
The sole caller discards the return: uvscom_cfg_open() uvscom.c:640
uvscom_cfg_read_status(sc); β value unused; sc_lsr/sc_msr/
sc_unit_status are fed exclusively from the interrupt pipe
(uvscom.c:425-446). The uninitialized value is computed and thrown away:
no kernel-memory disclosure, no control-flow impact today. It becomes a
live DF-1062-class leak the moment any code consumes the return value (the
NetBSD original uses this very status read to update modem-line state, so
the porting-direction risk is real). Filed as hardening; one-line fix.
Fix
fix.diff: uint8_t data[2] = {0, 0}; β compiled clean as part of the
combined patched uvscom.ko build on the guest (fix_compile.log, RC=0).
Behavioral validation not possible without a device model (fix_status=
not_testable for the runtime dimension; compile dimension validated).
Environment
env.txt: DragonFly 6.5-DEVELOPMENT, X86_64_GENERIC, cc 8.3, guest left pristine.
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.
Fix verification
not_testableCompile-dimension validated (patched production uvscom.ko builds -Werror-clean RC=0 on the guest, fix_compile.log); runtime dimension not testable without a SUNTAC device model -- the uninitialized-read path requires a crafted short control-IN data stage.
fix.diff (sha256 fc1e9be926f7708aec146584515283631b46210c6738ea17ae2147b2dac09d23); fix_compile.log (RC=0, combined patched build).
Confirmed kernel references
Detail
Evidence (decisive lines)
Source trace in VERDICT.md: uvscom.c:719 (uninit data[2]), uvscom.c:733 (uninit read in return), usb_serial.h:220-221 (actlen=NULL), usb_request.c:783-788 (tail zeroization gated on actlen), usb_request.c:662-665+680-681 (short-copy semantics); uvscom.c:640 (return discarded -- why impact is none).
PoC changes
No PoC authored: executing the short-read data stage requires a USB device model for the SUNTAC VID:PID that does not exist on the QEMU guest; the defect is provable (and was proven) by direct source tracing of the exact core semantics.
Verified recommended fix
Initialize the read buffer in uvscom_cfg_read_status() (one line, full diff in fix.diff); optionally fix the core macro to pass actlen.
Verdict
Static, code-certain finding NOT executed (Info-severity hardening class; no SUNTAC VID:PID device model exists to exercise a short control-IN data stage -- same treatment as the DF-5438 ulpt sibling). uvscom_cfg_read_status() (uvscom.c:714-734) returns data[0]|(data[1]<<8) from a stack buffer that the core leaves uninitialized when a control-IN SUCCEEDS with a short data stage: usbd_do_request_proc() zeroizes the short-read tail only when actlen != NULL (usb_request.c:786-787) and ucom_cfg_do_request hardcodes actlen=NULL (usb_serial.h:220-221); the data-phase loop copies only acttemp bytes (usb_request.c:662-665, 680-681). A 1-byte reply leaves data[1] uninitialized, a 0-length data stage leaves both bytes uninitialized. Impact TODAY is none: the sole caller uvscom_cfg_open() (uvscom.c:640) discards the return value and sc_lsr/sc_msr/sc_unit_status are fed only from the interrupt pipe (uvscom.c:425-446) -- latent DF-1062/DF-2342-class hazard that turns into a kernel-stack disclosure the moment any code consumes the return value.
No comments yet.