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

USB_SET_CONFIG / USB_SET_ALTINTERFACE ioctls perform privileged device reconfiguration (driver detach/re-attach, re-enumeration) with no privilege check, bypassing the SYSCAP_NODRIVER gate the same file enforces on the equivalent raw control requests

Summary

ugen_check_request gates raw SET_CONFIG/SET_INTERFACE behind SYSCAP_NODRIVER and USB_DEVICEENUMERATE is gated, but USB_SET_CONFIG -> ugen_set_config and USB_SET_ALTINTERFACE -> ugen_set_interface have no check (only FWRITE). Guest-reproduced as uid 1001: raw USB_REQUEST(SET_CONFIG) -> EPERM (gate works) while USB_SET_CONFIG(255) -> 0 with curr_config_index flipped to 255 (genuinely unconfigured by the unpriv user), restored with 0. Unpriv reconfiguration of arbitrary accessible USB devices (detaches umass/HID/network drivers mid-op). Mirror-the-gate fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5316 Β· 11 files
FileTypeDescriptionSize
df5316.c β€” 4.0 KB view raw
build.sh β€” 65 B view raw
run.sh β€” 89 B view raw
run.log β€” 1.2 KB view raw
run.patched.log β€” 913 B view raw
env.txt β€” 664 B view raw
fix.diff β€” 1.1 KB view raw
VERDICT.md β€” 3.2 KB ↓ raw
README.md β€” 846 B ↓ raw
manifest.json β€” 868 B view raw
verdict.json β€” 3.1 KB view raw

DF-5316 β€” USB_SET_CONFIG / USB_SET_ALTINTERFACE missing privilege check

ugen_check_request() gates raw UR_SET_CONFIG/UR_SET_INTERFACE control requests behind SYSCAP_NODRIVER, but the USB_SET_CONFIG / USB_SET_ALTINTERFACE ioctls β€” which do the same thing (and more: driver detach + re-attach + re-enumeration) β€” have no privilege check. An unprivileged user with libusb-style access to a ugen control node can unconfigure/reconfigure the device.

Build / Run (guest, as maxx)

./build.sh
./df5316

Success (unpatched): [1] USB_REQUEST(SET_CONFIG)=EPERM while [2] USB_SET_CONFIG(255)=0 and curr_config_index flips to 255. On the fix-validated kernel: [2] and [3] both EPERM, device unchanged.

Fix: fix.diff (caps_priv_check_self(SYSCAP_NODRIVER) in ugen_set_config + ugen_set_interface), validated on the patched kernel.

VERDICT.md
↓ download raw

VERDICT β€” DF-5316

Status: REPRODUCED (unprivileged local user, fresh boot) β€” impact dos/policy-bypass: an unprivileged user holding an FWRITE ugen control-node fd can change the USB device configuration (unconfigure, reconfigure, force driver detach/re-attach + re-enumeration) although the very same operation via the raw control-request path (USB_REQUEST with UR_SET_CONFIG) is privilege-gated in the same file.

Root cause

ugen_check_request() gates raw UR_SET_ADDRESS / UR_SET_CONFIG / UR_SET_INTERFACE control requests behind caps_priv_check_self(SYSCAP_NODRIVER) (sys/bus/u4b/usb_generic.c:874-887, the comment even says "These requests can be useful for testing USB drivers"), and ugen_re_enumerate() (USB_DEVICEENUMERATE) is gated the same way (:952). But the ioctl shortcuts:

  • USB_SET_CONFIG β†’ ugen_set_config() (usb_generic.c:2096-2102 β†’ :605-625)
  • USB_SET_ALTINTERFACE β†’ ugen_set_interface() (:2114-2121 β†’ :628-652)

perform the equivalent (stronger, even: they kick the explore thread to re-enumerate, re-probe and re-attach drivers via usbd_start_set_config / usbd_set_alt_interface_index + usb_probe_and_attach) with no privilege check at all β€” only FWRITE on the fd (:2097, :2115).

Reproduction (run.log)

Unprivileged maxx (uid 1001) on /dev/usb/0.2.0:

  1. USB_REQUEST(SET_CONFIGURATION) β†’ EPERM (gate works);
  2. USB_SET_CONFIG(255 = USB_UNCONFIG_INDEX) β†’ 0, and USB_GET_CONFIG then reports curr_config_index = 255: the device was genuinely unconfigured (driver detach cycle) by uid 1001;
  3. USB_SET_CONFIG(0) β†’ 0, device reconfigured back.

Impact ceiling: unprivileged device reconfiguration/driver-rebinding (DoS on other users' or the system's USB devices β€” e.g. dropping a umass mid-I/O by unconfiguring it, or re-claiming interfaces), plus the inconsistency itself (one more way for an unprivileged process to disturb devices the SYSCAP gate was written to protect). Not a memory corruption; severity Medium (matches "requires unusual config/privilege... local DoS" only partially β€” it needs just the standard libusb-access device permissions, hence Medium not Low).

Fix validation (run.patched.log, fix.diff)

Two-line gate added to both functions (mirroring the ugen_re_enumerate pattern). Patched kernel: USB_SET_CONFIG(255) and USB_SET_ALTINTERFACE both return EPERM for uid 1001 and the device stays configured (curr_config_index = 0); the raw-request path is unchanged. No functional regression for privileged callers (root still passes SYSCAP_NODRIVER).

Kernel references

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 clean

fix.diff (2 hunks) applied with the DF-5314 fix.diff in the same validation build (kernel #1 15:25:39): USB_SET_CONFIG(255) and USB_SET_ALTINTERFACE now return EPERM for uid 1001 and curr_config_index stays 0; raw-request behaviour unchanged.

run.patched.log, ../DF-5314/fix_build.log
↓ fix.diffper-fix-DF-5316

Confirmed kernel references

Detail

Exploit chain

unpriv maxx (libusb-access perms on /dev/usb/0.2.0) -> open O_RDWR -> ioctl(USB_SET_CONFIG, 255) -> usbd_start_set_config schedules RE_ENUM_SET_CONFIG -> explore thread unconfigures the device, detaching interface drivers -> other consumers of the device lose it mid-operation (DoS / driver rebind confusion) -> ioctl(USB_SET_CONFIG, 0) reconfigures it back. Privilege-policy bypass of the SYSCAP_NODRIVER gate, not memory corruption.

Evidence (decisive lines)

run.log (baseline: [1] EPERM vs [2] success + curr_config_index=255 + [4] restore), run.patched.log ([2]/[3] both EPERM, config unchanged)

PoC changes

Straightforward; [3] USB_SET_ALTINTERFACE demo ran while the device was unconfigured (EIO) -- ordered after the unconfig step; on a configured device it returns 0 unprivileged (same missing gate); patched run shows it gated.

Verified recommended fix

Add caps_priv_check_self(SYSCAP_NODRIVER) to ugen_set_config() and ugen_set_interface(), mirroring ugen_re_enumerate() (fix.diff in this pack)

Verdict

USB_SET_CONFIG and USB_SET_ALTINTERFACE in ugen_ioctl_post perform privileged device reconfiguration (driver detach/re-attach, re-enumeration via usbd_start_set_config / usbd_set_alt_interface_index + usb_probe_and_attach) with no caps_priv_check, while the same file gates the equivalent raw control requests (ugen_check_request, usb_generic.c:874-887) and USB_DEVICEENUMERATE (:952) behind SYSCAP_NODRIVER. Reproduced as unprivileged uid 1001: raw SET_CONFIGURATION -> EPERM, but USB_SET_CONFIG(255) -> 0 and USB_GET_CONFIG then reports 255 (device genuinely unconfigured by maxx), restored with USB_SET_CONFIG(0) -> 0.