uvisor VISOR GET_CONNECTION_INFORMATION: actlen never validated after a SHORT_XFER_OK control-IN into an 18-byte stack struct; the USB_DEBUG connection loop consumes uninitialized kernel stack when an evil Visor short-transfers (Info: no production consumer)
Summary
The VISOR branch never checks actlen (contrast the PALM4 branch's actlen<12 guard) and bypasses the wrapper zeroization via the raw call: a zero-length/2-byte data stage leaves num_ports/connections[] uninit - only the USB_DEBUG loop consumes it (production hardwires nports=1). Same pattern became a real leak in DF-5455 where a consumer existed. actlen>=sizeof guard fix compile-validated.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5489 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.5 KB | β raw | |
| VERDICT.md | β | 3.4 KB | β raw | |
| fix.diff | β | 564 B | view raw | |
| fix_compile.log | β | 7.2 KB | view raw | |
| env.txt | β | 443 B | view raw | |
| verdict.json | β | 4.2 KB | view raw |
DF-5489 β uvisor VISOR GET_CONNECTION_INFORMATION: actlen never validated;
the USB_DEBUG connection loop consumes uninitialized kernel stack when an
evil Visor short-transfers (Info β no production consumer)
The code (production)
The VISOR branch of uvisor_init() (uvisor.c:380-428) reads an
18-byte struct uvisor_connection_info coninfo from the device with
USB_SHORT_XFER_OK:
err = usbd_do_request_flags(udev, NULL,
&req, &coninfo, USB_SHORT_XFER_OK,
&actlen, USB_DEFAULT_TIMEOUT); /* uvisor.c:387-389 */
if (err) {
goto done;
}
/* NO actlen CHECK β contrast the PALM4 branch's
"if (actlen < 12)" at uvisor.c:447-451 */
usbd_do_request_flags() is the raw entry point, not
usbd_do_request_proc() β the zeroize-on-error / zero-pad-on-short logic
(usb_request.c:782-787) does NOT apply, and the controller DMAs only the
bytes actually transferred. A device that answers with a zero-length or
2-byte data stage (both legal under USB_SHORT_XFER_OK; err == 0)
leaves connections[] / part of num_ports as uninitialized stack.
The only consumer is the #ifdef USB_DEBUG block (uvisor.c:395-428):
np = UGETW(coninfo.num_ports) (clamped to UVISOR_MAX_CONN=8 at
uvisor.c:401-403 β no OOB), then per-port
coninfo.connections[i].port_function_id (selects a string) and
coninfo.connections[i].port (printed as %d). With a USB_DEBUG kernel
and hw.usb.uvisor.debug >= 1, up to ~18 stale stack bytes are
disclosed to the console/msgbuf as numbers/string-selections.
Production kernels never read coninfo β dead store, no userspace
path β hence Info, same treatment as the DF-5448 (uvscom) and DF-5438
(ulpt) siblings. The bug class is real (this is exactly how DF-5455
umoscom leaked into TIOCMGET, where the buffer had a consumer), and the
PALM4 sibling branch shows the intended hygiene (actlen < 12 check,
uvisor.c:447-451).
Threat model
Evil/wedged Handspring VISOR-VID:PID adapter (082d:0100 etc., uvisor.c:254); requires a USB_DEBUG kernel build for any observable effect.
Evidence class
Static, code-certain β NOT executed (no Visor device model; USB_DEBUG is not enabled on the guest's X86_64_GENERIC kernel, so the consumer does not even compile there). Same treatment as DF-5448/DF-5438.
Fix
fix.diff mirrors the PALM4 check: require
actlen >= UVISOR_CONNECTION_INFO_SIZE (18) before the (debug) consumer,
failing with USB_ERR_INVAL on a short transfer. The patched production
uvisor.ko compiles -Werror clean on the guest (fix_compile.log).
DF-5489 VERDICT β static, code-certain, NOT executed (Info class)
Finding: the VISOR branch of uvisor_init() (uvisor.c:380-428) never
validates actlen after usbd_do_request_flags(..., &coninfo,
USB_SHORT_XFER_OK, &actlen, USB_DEFAULT_TIMEOUT) (uvisor.c:387-389).
That is the raw request entry point β the zeroize-on-error /
zero-pad-on-short protection in usbd_do_request_proc()
(usb_request.c:782-787) does not apply β so a short or zero-length (both
legal under SHORT_XFER_OK) vendor response leaves the 18-byte stack
buffer partially uninitialized. The #ifdef USB_DEBUG consumer
(uvisor.c:400-426) then reads UGETW(coninfo.num_ports) and
connections[i].port / .port_function_id and prints them (DPRINTF),
disclosing up to ~18 stale kernel-stack bytes to console/msgbuf on a
USB_DEBUG kernel with hw.usb.uvisor.debug >= 1.
Why not executed
- The guest's X86_64_GENERIC kernel is not built with USB_DEBUG, so the consumer does not exist in the running kernel.
- No QEMU Visor VID:PID device model exists (
-device usb-serialis a Netchip 0525:7701 that binds uftdi, not uvisor). - Even with both, the observable is a weak debug-log disclosure of number-formatted stack bytes β no memory-safety consequence (the loop count is clamped, uvisor.c:401-403; there is no OOB).
Production impact: none (dead store)
Outside #ifdef USB_DEBUG, coninfo is never read after the request;
num_ports never feeds ucom_attach() (uvisor hardwires nports=1,
uvisor.c:341). So this is a hardening/hygiene finding in the exact shape
of the family's DF-5448 (uvscom) / DF-5438 (ulpt) / DF-5455 (umoscom,
where the buffer DID have a consumer β Low) entries. It is filed so the
next refactor that starts consuming connection info (e.g. a multi-port
uvisor like FreeBSD's) does not inherit an unvalidated device buffer.
Line-accurate confirmation (guest tree == audited tree,
uvisor.c sha256 b742032fβ¦d703844438)
- Request: uvisor.c:382-389 (wLength 18 = sizeof(coninfo) β buffer size itself is correct; only completeness is unchecked).
- Missing check: immediately after uvisor.c:391-393's error check; the
PALM4 branch demonstrates the intended pattern at uvisor.c:447-451
(
if (actlen < 12)β which correctly covers the offsets its code later reads: 1, 8, 9). - Uninit consumers (USB_DEBUG only): uvisor.c:400 (
UGETW(coninfo.num_ports)), uvisor.c:407 (port_function_id), uvisor.c:424-425 (portprinted). - No zeroization: call goes to
usbd_do_request_flagsdirectly; the_procwrapper that would zero-pad (usb_request.c:780-787) is bypassed.
Fix validation
fix.diff adds the missing actlen < UVISOR_CONNECTION_INFO_SIZE β
USB_ERR_INVAL guard (mirroring uvisor.c:447-451). The patched
production uvisor.c compiled -Werror clean as a kmod on the guest
(fix_compile.log, RC=0). Not runtime-tested (see above) β fix_status
build-validated only.
Guest hygiene
Guest untouched by this finding's verification (build-only scratch dirs removed; production uvisor.c md5 unchanged 28d0d390d06dd00f9dbfc03106b453a7).
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 uvisor.ko builds -Werror-clean RC=0 on the guest, fix_compile.log); runtime dimension not testable without a Visor device model and a USB_DEBUG kernel -- the guard is the same shape as the proven PALM4 check at uvisor.c:447-451.
fix.diff (sha256 30c63c71...); fix_compile.log (RC=0).
Confirmed kernel references
Detail
Evidence (decisive lines)
Line-accurate static confirmation in VERDICT.md: request uvisor.c:382-389 (wLength 18 == sizeof(coninfo) -- buffer size correct, only completeness unchecked); missing check after the error test at uvisor.c:391-393; intended pattern demonstrated by the PALM4 sibling at uvisor.c:447-451; USB_DEBUG-only consumers at uvisor.c:400, 407, 424-425; no zeroization (raw usbd_do_request_flags bypasses usb_request.c:782-787); guest tree == audited tree (uvisor.c sha256 b742032f...). fix_compile.log: patched production uvisor.ko builds -Werror-clean RC=0.
PoC changes
No seed PoC existed; no runtime PoC authored (static Info-class finding, DF-5448/DF-5438 precedent). fix.diff authored and compile-validated.
Verified recommended fix
Require actlen >= UVISOR_CONNECTION_INFO_SIZE (18) before consuming coninfo, mirroring the PALM4 actlen check (full diff in fix.diff).
Verdict
Static, code-certain finding NOT executed (Info-severity hardening class; same treatment as the DF-5448 uvscom and DF-5438 ulpt siblings): the VISOR branch of uvisor_init() reads an 18-byte struct uvisor_connection_info from the device with USB_SHORT_XFER_OK (uvisor.c:387-389) and never validates actlen -- in contrast to the PALM4 branch's 'if (actlen < 12)' check at uvisor.c:447-451 which correctly covers the offsets that branch consumes (1, 8, 9). The call goes to the raw usbd_do_request_flags(), not usbd_do_request_proc(), so the zeroize-on-error / zero-pad-on-short protection (usb_request.c:782-787) does not apply: an evil Visor-VID:PID device answering with a zero-length or 2-byte data stage (both legal under SHORT_XFER_OK, err==0) leaves connections[]/num_ports as uninitialized kernel stack. The ONLY consumer is the #ifdef USB_DEBUG block (uvisor.c:395-428: UGETW(num_ports) at :400, port_function_id at :407, port printed at :424-425) which discloses up to ~18 stale stack bytes to console/msgbuf on a USB_DEBUG kernel with hw.usb.uvisor.debug >= 1; the loop count is clamped (uvisor.c:401-403), so there is no OOB. Production kernels never read coninfo (dead store; nports is hardwired to 1 at uvisor.c:341), so there is no userspace path and no memory-safety impact -- filed so a future multi-port refactor does not inherit an unvalidated device buffer (this is exactly how DF-5455 umoscom became a real leak: same unguarded pattern, but with a TIOCMGET consumer). Not runtime-testable: no QEMU Visor VID:PID device model exists and the guest's X86_64_GENERIC kernel is not built with USB_DEBUG, so the consumer does not exist in the running kernel.
No comments yet.