uvscom_pre_param() inverted switch: every baud rate β including all 11 supported ones β returns EINVAL; the port can never be programmed and uvscom_cfg_param() is unreachable
Summary
All eleven supported-speed cases fall into the shared return(EINVAL) with default - the trailing return(0) is dead code (a transcription inversion of the NetBSD original): ucom_param rejects every termios change before queuing cfg_param, so every tcsetattr fails EINVAL and SET_SPEED/SET_PARAM never reach the device - the port is permanently locked to default termios, driver functionally dead for its entire user base. Production-code KLD: 0/11 accepted baseline, 11/11 with the fix. Restore-the-switch fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5447 Β· 15 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.9 KB | β raw | |
| VERDICT.md | β | 3.9 KB | β raw | |
| uvscom_preparam_harness.c | β | 4.5 KB | view raw | |
| Makefile | β | 143 B | β download | |
| build.sh | β | 160 B | view raw | |
| run.sh | β | 156 B | view raw | |
| build.log | β | 7.1 KB | view raw | |
| run.log | β | 1.1 KB | view raw | |
| run.fixed.log | β | 197 B | view raw | |
| fix_build.log | β | 7.1 KB | view raw | |
| fix_run.log | β | 197 B | view raw | |
| fix.diff | β | 675 B | view raw | |
| env.txt | β | 454 B | view raw | |
| manifest.json | β | 1.0 KB | view raw | |
| verdict.json | β | 4.1 KB | view raw |
DF-5447 β uvscom_pre_param() inverted switch: every baud rate (including all
11 supported ones) is rejected with EINVAL
The bug (production code, sys/bus/u4b/serial/uvscom.c:514-533)
static int
uvscom_pre_param(struct ucom_softc *ucom, struct termios *t)
{
switch (t->c_ospeed) {
case B150:
case B300:
...
case B115200:
default:
return (EINVAL); <-- EVERY path, supported speeds included
}
return (0); <-- unreachable (dead code)
}
All eleven case labels (the exact speeds the hardware supports and that
uvscom_cfg_param() uvscom.c:543-579 knows how to program) fall straight into
return (EINVAL). The trailing return (0) is unreachable. This is a
transcription inversion of the NetBSD original (NetBSD uvscom.c
uvscom_pre_param returns 0 for the listed speeds and EINVAL only for
default:).
Impact
ucom_param() (sys/bus/u4b/serial/usb_serial.c:1678-1684) uses the
ucom_pre_param callback to validate termios and bails out with the error
before queueing ucom_cfg_param (usb_serial.c:1691-1693). Consequence on a
live uvscom adapter:
- every
tcsetattr(2)/ TIOCSETA on /dev/ttyU0.* (or the cua device) fails with EINVAL β the port can never be programmed (baud, parity, stop bits); ucom_open()'s own initial programming call (usb_serial.c:842,ucom_param(tp, &t), error discarded) also fails silently, souvscom_cfg_param()never executes at all β the device is never given UVSCOM_SET_SPEED / UVSCOM_SET_PARAM (uvscom.c:581,614);- the tty is stuck at the default termios forever. Fails closed (EINVAL), no memory-safety impact β a functional kill of the serial port.
An attacker who supplies a crafted "SUNTAC" USB adapter (or a user of a real one) gets a /dev/ttyU0 that silently never works: every stty fails. Driver is dead weight for its entire user base.
Evidence class
No SUNTAC VID:PID device model exists for the QEMU guest β
KLD-harness-with-production-code: the harness #includes the production
sys/bus/u4b/serial/uvscom.c verbatim (module-registration macros neutered so
it does not double-register on uhub) and calls the real, static
uvscom_pre_param() for every supported speed. Baseline: all return EINVAL.
After fix.diff: supported speeds return 0, B230400 returns EINVAL.
Reproduce
./build.sh # in-tree kmod build: cd /usr/src/sys/bus/u4b/serial/hx5447 && make ./run.sh # kldload the harness; dmesg shows per-speed results
Expected baseline dmesg: UVS5447[A]: pre_param(B9600) = 35 (EINVAL) for ALL
eleven supported speeds, UVS5447[A]: SUPPORTED speeds accepted: 0/11 (BUG).
Expected after fix.diff (phase B rerun): accepted: 11/11, B230400 = EINVAL.
Files
uvscom_preparam_harness.cβ production-code KLD harnessMakefile,build.sh,run.shbuild.log,run.log,run.fixed.logβ full untrimmed outputfix.diffβ git-apply-able fix (restore intended switch)VERDICT.md,manifest.json,verdict.json
DF-5447 VERDICT β uvscom_pre_param() inverted switch
Status: REPRODUCED (production code) β impact: functional DoS of the serial port (fails closed); fix validated behaviorally on the guest.
What was run
KLD-harness-with-production-code (no SUNTAC VID:PID device model exists for
QEMU): uvscom_preparam_harness.c #includes the unmodified production
sys/bus/u4b/serial/uvscom.c (newbus DRIVER_MODULE/MODULE_DEPEND/
MODULE_VERSION registration macros expanded to nothing so no second "uvscom"
driver registers on uhub) and calls the real, file-static
uvscom_pre_param() (uvscom.c:514-533) for every baud rate the driver itself
lists as supported.
Baseline (run.log)
UVS5447[A]: calling production uvscom_pre_param() (uvscom.c:514-533) for all 11 supported speeds
UVS5447[A]: pre_param(B150) = 22 (EINVAL -- REJECTED)
... (B300 B600 B1200 B2400 B4800 B9600 B19200 B38400 B57600 B115200) ...
UVS5447[A]: pre_param(B230400) = 22 (ok, expected EINVAL)
UVS5447[A]: SUPPORTED speeds accepted: 0/11 (BUG: every tcsetattr fails EINVAL,
uvscom_cfg_param() is unreachable, uvscom.c:532 return(0) dead)
All eleven supported speeds return EINVAL(22). The return (0) at
uvscom.c:532 is unreachable dead code β every path through the switch,
including the eleven listed cases, falls into return (EINVAL) at line
530. This is a transcription inversion of the NetBSD original (supported
speeds β return (0), only default: β return (EINVAL)).
Why it matters (production call path, cited)
ucom_param()uses this callback to validate every termios change: usb_serial.c:1678-1684 β non-zero return βgoto donewith the error β everytcsetattr(2)/TIOCSETA on the tty fails with EINVAL anducom_cfg_paramis never queued (usb_serial.c:1691-1693).ucom_open()'s initial programming call also fails (error discarded, usb_serial.c:842) βuvscom_cfg_param()(uvscom.c:535-615) never executes on any open β the device never receives UVSCOM_SET_SPEED / UVSCOM_SET_PARAM (uvscom.c:581,614).- Net effect: the port is permanently locked to default termios; the driver is functionally dead for its whole user base. Fails closed (EINVAL), no memory-safety impact.
Fix validation (run.fixed.log / fix_run.log)
fix.diff applied to the guest's /usr/src copy of uvscom.c, same harness
rebuilt (build log in fix_build.log, -Werror clean), re-run:
UVS5447[A]: SUPPORTED speeds accepted: 0/11 (BUG: ...) <- baseline dmesg leftover UVS5447[A]: SUPPORTED speeds accepted: 11/11 (fixed)
With the fix, all eleven supported speeds are accepted and unsupported speeds (B230400) still return EINVAL. Baseline bad behavior GONE.
PoC bring-up notes (what had to be fixed vs. a naive include)
#include <sys/termios.h>does not exist for -nostdinc kernel builds β<sys/tty.h>(which pulls<sys/_termios.h>).- The harness prelude must NOT include any
bus/u4b/*headers before including uvscom.c: usb_debug.h's include guard would suppress uvscom.c's own#define USB_DEBUG_VAR uvscom_debug-first include, leaving DPRINTF/DPRINTFN undefined (implicit/nested-extern errors under -Werror). uvscom_devclass/uvscom_driverbecome unused when DRIVER_MODULE is neutered β referenced from a harness function to stay -Werror clean.
Environment
See env.txt: DragonFly 6.5-DEVELOPMENT #0 Thu Jul 2 06:02:54 UTC 2026, X86_64_GENERIC, cc 8.3, guest left pristine (uvscom.c md5 a58a99ef02d63a2d2e7f7b661f5224b3 restored, harness dirs removed, no leftover KLDs).
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
fixedfix.diff applied to the guest's /usr/src/sys/bus/u4b/serial/uvscom.c; the same production-code harness rebuilt -Werror-clean and re-run: 'SUPPORTED speeds accepted: 11/11 (fixed)' and pre_param(B230400) still EINVAL. Baseline bad behavior (0/11, all-EINVAL) GONE.
fix.diff (sha256 850a7c2a...); fix_build.log (RC=0); run.fixed.log ('11/11 (fixed)'); VERDICT.md 'Fix validation' section.
Confirmed kernel references
Detail
Exploit chain
Attacker supplies (or a victim owns) any SUNTAC VID:PID adapter handled by uvscom (uvscom.c:231-242) -> /dev/ttyU0 created -> open(2) succeeds at defaults but every stty/tcsetattr returns EINVAL and UVSCOM_SET_SPEED/SET_PARAM (uvscom.c:581,614) never reach the device -> serial port unusable for its purpose (local functional DoS; no privilege or memory-safety impact).
Evidence (decisive lines)
run.log: 'UVS5447[A]: pre_param(B150..B115200) = 22 (EINVAL -- REJECTED)' x11 and 'SUPPORTED speeds accepted: 0/11 (BUG: every tcsetattr fails EINVAL, uvscom_cfg_param() is unreachable, uvscom.c:532 return(0) dead)'; run.fixed.log: 'accepted: 11/11 (fixed)'; build.log/fix_build.log: -Werror-clean in-tree kmod builds; VERDICT.md has the full narrative.
PoC changes
No seed PoC existed; authored from scratch (production-code-include class). Bring-up fixes: (1)
Verified recommended fix
Restore the intended switch in uvscom_pre_param(): the eleven listed cases return 0, only default returns EINVAL (full diff in fix.diff).
Verdict
Reproduced with PRODUCTION CODE: a KLD harness that #includes the unmodified sys/bus/u4b/serial/uvscom.c (newbus registration macros neutered) calls the real static uvscom_pre_param() (uvscom.c:514-533) for all eleven supported baud rates and every one returns EINVAL(22) -- 'SUPPORTED speeds accepted: 0/11' -- the switch is an inverted transcription (all cases including B150..B115200 fall into return(EINVAL); the return(0) at uvscom.c:532 is dead). Consequence per the production call path: ucom_param() (usb_serial.c:1678-1684) rejects every termios change before queueing ucom_cfg_param, so every tcsetattr fails EINVAL and the device is never programmed (uvscom_cfg_param at uvscom.c:535-615 is unreachable, including the open-time call at usb_serial.c:842 whose error is discarded) -- the port is permanently locked to default termios; driver functionally dead, fails closed, no memory-safety impact. After applying fix.diff to the guest's /usr/src copy and rebuilding the same harness: 'accepted: 11/11 (fixed)' with B230400 still EINVAL (run.fixed.log).
No comments yet.