β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-5447

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5447 Β· 15 files
FileTypeDescriptionSize
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, so uvscom_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 harness
  • Makefile, build.sh, run.sh
  • build.log, run.log, run.fixed.log β€” full untrimmed output
  • fix.diff β€” git-apply-able fix (restore intended switch)
  • VERDICT.md, manifest.json, verdict.json
VERDICT.md
↓ download raw

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 done with the error β‡’ every tcsetattr(2)/TIOCSETA on the tty fails with EINVAL and ucom_cfg_param is 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)

  1. #include <sys/termios.h> does not exist for -nostdinc kernel builds β†’ <sys/tty.h> (which pulls <sys/_termios.h>).
  2. 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).
  3. uvscom_devclass / uvscom_driver become 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

fixed
baseline reproduced→ patch + rebuild →patched reproduced

fix.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.
↓ fix.diffper-fix-DF-5447

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) is unavailable under kernel -nostdinc -> ; (2) harness prelude must not include bus/u4b headers before uvscom.c, else usb_debug.h's include guard defeats uvscom.c's USB_DEBUG_VAR-first include and DPRINTF/DPRINTFN expand to implicit nested externs (-Werror); (3) uvscom_devclass/uvscom_driver must be referenced because DRIVER_MODULE was neutered.

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).