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)
PoC verification
Evidence pack
findings/poc/DF-5306 Β· 15 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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)
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 withbIfaceNums == 0(orbLength < 12, or a control interface with no VC header descriptor at all) yieldsvsnum = 0.sys/bus/u4b/uvc/uvc_drv.c:2112-2116β the sanity check only warns (DPRINTF) and clampsvsnum > 1down to 1;vsnum == 0is accepted, the streaming-interface loop (2122-2145) never runs,sc->datastays NULL, anduvc_drv_parse_standard_ctrl()returns 0.sys/bus/u4b/uvc/uvc_drv.c:2723-2731βuvc_drv_attach()ignores the possibility and callsuvc_drv_init_video(sc, sc->ctrl, sc->data)withsc->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
fixedfix.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
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.
No comments yet.