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

NULL-deref kernel panic at attach: VC header declaring zero streaming interfaces leaves sc->data NULL and uvc_drv_init_video() dereferences it

Summary

The sanity branch only warns and clamps vsnum>1 - vsnum==0 (bIfaceNums=0, bLength<12, or missing header) is accepted, sc->data never allocated, parse_ctrl returns success, and init_video derefs data->num_altsetting. Guest-verified with real functions: Fatal trap 12 fault VA 0xa, guest dead; patched code EINVALs at parse + NULL-guard at attach, guest survives. Evil-USB attach-time local DoS. vsnum>=1 EINVAL + two-layer guards fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5306 Β· 15 files
FileTypeDescriptionSize
README.md β€” 2.0 KB ↓ raw
VERDICT.md β€” 2.9 KB ↓ raw
uvcharness.c β€” 8.8 KB view raw
Makefile β€” 119 B ↓ download
build.sh β€” 97 B view raw
run.sh β€” 151 B view raw
build.log β€” 924 B view raw
run.log β€” 1.1 KB view raw
fix.log β€” 2.4 KB view raw
panic.txt β€” 1.7 KB view raw
env.txt β€” 372 B view raw
fix.diff β€” 2.9 KB view raw
manifest.json β€” 1022 B view raw
verdict.json β€” 2.9 KB view raw
code_hash.txt β€” 65 B view raw

DF-5306 β€” NULL-deref kernel panic at attach when the VideoControl header lists zero VideoStreaming interfaces

Where

sys/bus/u4b/uvc/uvc_drv.c:2106-2146 (uvc_drv_parse_standard_ctrl(), case UDESCSUB_VC_HEADER) β€” with bIfaceNums == 0 (or bLength < 12, or no VC header descriptor at all) vsnum becomes/stays 0, the sc->data = uvc_drv_init_data(...) loop never runs, and uvc_drv_parse_ctrl() still returns 0 (success). uvc_drv_attach() (uvc_drv.c:2723-2731) then calls uvc_drv_init_video(sc, sc->ctrl, sc->data /* == NULL */), which dereferences data->num_altsetting at uvc_drv.c:1513:

    if (data->num_altsetting > 1)     /* data == NULL -> read at 0xA */

Attack model

Crafted USB webcam descriptors (evil-USB device; QEMU-emulatable): a UVC control interface whose header declares no streaming interfaces. Kernel panics during device attach β€” local DoS.

Build / run (as verified)

./build.sh
./run.sh          # kldload, then: sysctl hw.uvcharness=2

The harness runs the real uvc_drv_parse_ctrl() on a blob with bIfaceNums=0, prints the resulting sc.data == NULL, then calls the real uvc_drv_init_video(sc, ctrl, NULL).

Expected (baseline)

HARNESS DF-5306: parse_ctrl ret=0 sc.data=0 (expected NULL)
HARNESS DF-5306: calling real uvc_drv_init_video(sc, ctrl, NULL) ...
Fatal trap 12: page fault while in kernel mode
fault virtual address = 0xa
Stopped at sysctl_harness+0x30b: cmpb $0x1,0xa(%r9)
-> guest dead (db> prompt)

0xa is exactly the offset of num_altsetting in struct uvc_drv_data (uvc_drv.h:645-651).

After fix.diff

HARNESS DF-5306: parse_ctrl ret=22 sc.data=0      (EINVAL at parse)
HARNESS DF-5306: returned 0 (UNEXPECTED - no panic)
-> guest stays up

Files

panic.txt   verbatim serial console of the crashed run
run.log     baseline console output
fix.log     post-fix output (no panic, guest alive)
fix.diff    git-apply-able unified diff (git apply -p0)
VERDICT.md
↓ download raw

DF-5306 VERDICT

Status: reproduced (kernel panic / local DoS from crafted USB descriptors at attach), impact: panic, confidence: certain.

Root cause (path:line)

  • sys/bus/u4b/uvc/uvc_drv.c:2109 β€” vsnum = hdesc->bLength >= 12 ? hdesc->bIfaceNums : 0; A VC header with bIfaceNums == 0 (or bLength < 12, or a control interface with no VC header descriptor at all) yields vsnum = 0.
  • sys/bus/u4b/uvc/uvc_drv.c:2112-2116 β€” the sanity check only warns (DPRINTF) and clamps vsnum > 1 down to 1; vsnum == 0 is accepted, the streaming-interface loop (2122-2145) never runs, sc->data stays NULL, and uvc_drv_parse_standard_ctrl() returns 0.
  • sys/bus/u4b/uvc/uvc_drv.c:2723-2731 β€” uvc_drv_attach() ignores the possibility and calls uvc_drv_init_video(sc, sc->ctrl, sc->data) with sc->data == NULL.
  • sys/bus/u4b/uvc/uvc_drv.c:1513 β€” if (data->num_altsetting > 1): NULL dereference -> kernel panic. (uvc_drv_init_cur_fmt_frm() validates its arguments at 1451; uvc_drv_init_video() does not.)

Reproduction

Harness (uvcharness.ko, same KLD as DF-5305: real driver sources compiled in) builds a blob identical to DF-5305's except bIfaceNums = 0, runs the real uvc_drv_parse_ctrl() (ret=0, sc.data=0 β€” the exact attach-time state), then calls the real uvc_drv_init_video(sc, ctrl, NULL):

Fatal trap 12: page fault while in kernel mode
fault virtual address = 0xa            <- data->num_altsetting at NULL+10
Stopped at sysctl_harness+0x30b: cmpb $0x1,0xa(%r9)

Guest died (db>), vm.sh status = down β€” full transcript in panic.txt. This is the real code path a crafted device hits at attach: probe matches the VC interface, parse "succeeds", init_video panics.

Fix validation

fix.diff adds two layers: 1. uvc_drv_parse_standard_ctrl(): reject vsnum < 1 or bLength < 12 + vsnum with EINVAL (also fixes the DF-5307(c) 1-byte bIfaceList[0] over-read at bLength == 12); 2. uvc_drv_attach(): treat sc->data == NULL after parse as EINVAL; 3. defense in depth: uvc_drv_init_video() returns NULL for data == NULL before the dereference.

Applied to the guest's /usr/src, harness rebuilt, same trigger: parse_ctrl ret=22 (EINVAL) and init_video returned 0 (no panic); uptime confirms the guest stayed up (fix.log).

Impact

Denial of service (kernel panic) at USB device attach. Requires the attacker model of this audit (crafted USB device / QEMU-emulatable redirection); NULL-page deref is not a write primitive, so no escalation path.

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 applied to guest /usr/src; rebuilt harness; identical trigger produces EINVAL at parse and a clean NULL return from uvc_drv_init_video with the guest surviving (uptime verified). Baseline panic vs patched survival recorded in run.log vs fix.log.

fix.log, panic.txt, run.log
↓ fix.diffper-fix-DF-5306

Confirmed kernel references

Detail

Exploit chain

crafted USB webcam with VC interface but bIfaceNums=0 (or no VC header) -> uvc_drv_parse_ctrl returns 0 with sc->data NULL -> uvc_drv_attach calls uvc_drv_init_video(sc, ctrl, NULL) -> NULL deref at uvc_drv.c:1513 -> kernel panic at attach

Evidence (decisive lines)

run.log / panic.txt (Fatal trap 12, fault VA 0xa, Stopped at sysctl_harness+0x30b: cmpb $0x1,0xa(%r9)); fix.log (post-fix: ret=22, 'returned 0 (no panic)', uptime shows guest up)

PoC changes

No finding seed existed; same from-scratch harness as DF-5305, second mode (sysctl hw.uvcharness=2) exercises the NULL-data path.

Verified recommended fix

Reject VC headers with vsnum<1 or bLength<12+vsnum (EINVAL), and guard uvc_drv_attach()/uvc_drv_init_video() against sc->data==NULL (see fix.diff).

Verdict

Reproduced on the QEMU guest with the KLD harness that compiles the real uvc driver sources. A crafted config blob whose VC header declares bIfaceNums=0 makes the real uvc_drv_parse_ctrl() return success with sc->data==NULL; the real uvc_drv_init_video(sc, ctrl, NULL) then faults at uvc_drv.c:1513 (data->num_altsetting): Fatal trap 12, fault virtual address 0xa, backtrace 'cmpb $0x1,0xa(%r9)'. Guest died (db> prompt, vm.sh status down). DoS only (NULL-page read, no write primitive). Fix validated: with fix.diff applied to the guest /usr/src and the harness rebuilt, the same trigger yields parse_ctrl ret=22 (EINVAL) and init_video returns NULL with the guest alive.