uint8_t loop-counter wrap in uvc_ctrl_init_dev causes unbounded kernel heap overflow from malicious USB descriptor
| Field | Value |
|---|---|
| ID | DF-1046 |
| Status | new |
| Severity | High |
| CVSS 3.1 | CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H |
| CWE | CWE-787 Out-of-bounds Write; CWE-190 Integer Overflow or Wraparound |
| File | sys/bus/u4b/uvc/uvc_ctrls.c |
| Lines | 917-978 (esp. 925, 964, 968-973); counting fn at 899-915 |
| Area | bus/u4b/uvc (USB Video Class control parser) |
| Confidence | certain |
| Discovered | 2026-07-14 |
| Reported | pending |
| Known CVE | none |
| CVE match | dfly_specific |
Summary
uvc_ctrl_init_dev() declares its bitmap-scan loop counter as uint8_t (uvc_ctrls.c:925)
but iterates bCtrlSize * 8 times (line 964). bCtrlSize is an attacker-controlled
descriptor byte (up to ~245, validated only by bLength fit in uvc_drv.c:2288-2292).
When bCtrlSize >= 32 the product exceeds 255 and the uint8_t counter wraps 255β0,
making the loop condition permanently true. The allocation at line 957 is correctly sized
by uvc_ctrl_count_control() (which uses int i, line 902) for the true number of set
bits, but the wrapping init loop re-processes the same set bits every 256 iterations and
advances ctrl++ (line 973) past the allocation indefinitely β writing fully-initialized
struct uvc_control entries (containing pointers and triggering nested kmallocs for
uvc_data/sub_infos) into adjacent kernel heap. The result is an unbounded kernel heap
overflow at device-attach time, triggerable by any malicious USB webcam.
Root cause
Type mismatch between the counting function and the initialization loop:
-
uvc_ctrl_count_control()(line 902):int i = 0;β correctly iterates allbCtrlSize * 8bits (up to 2040), returns the TRUE count of set bits. This count sizes the allocation. -
uvc_ctrl_init_dev()(line 925):uint8_t i = 0;β WRONG. At line 964:for (i = 0; i < bCtrlSize * 8; i++). Sinceiisuint8_t, whenbCtrlSize * 8 > 255(i.e.,bCtrlSize >= 32),iwraps from 255 to 0 and the comparisoni < (int)(bCtrlSize * 8)(promoted) is always true. The loop never terminates. -
Each iteration that finds a set bit executes (lines 968-973):
c ctrl->topo_node = topo_node; ctrl->index = i; uvc_ctrl_initialize_control(ctrl); /* allocates uvc_data, sub_infos, populates info */ ctrl++;After the first full sweep of[0,255]writes exactlynctrlsentries (correct), every subsequent wrap writesnctrlsMORE entries pasttopo_node->controls(allocated fornctrlsentries at line 957). This is an unbounded heap overflow. -
bCtrlSizeis read directly from attacker-controlled USB descriptors: - PU:uvc_drv.c:2288ctrls_mask_size = pu_desc->bControlSize;- CT:uvc_drv.c:2209copies byte at offset 14 ofit_desc. - XU:uvc_drv.c:2333-2334reads from descriptor.
The only validation (uvc_drv.c:2289) is bLength < ctrls_mask_size + p (p=9 or 10),
which allows bControlSize up to ~245 when bLength=255.
uvc_ctrl_initialize_control()(line 971, defined at 809) on each OOB slot readsctrl->topo_node(which was just assigned the validtopo_nodepointer at line 968) andctrl->index(the wrappedi), then for known PU/CT bit indices (0-18) allocatesuvc_dataviakmallocand builds fullsub_infomappings β so the overflow doesn't just scribble, it plants fully-valid-looking control structures with live pointers into adjacent heap.
Threat model & preconditions
- Attacker position: Anyone who can attach a USB device to the target system (physical USB plug-in, compromised internal webcam firmware, USB passthrough in a VM, or a malicious dock/hub). No privileges, no authentication, no user interaction beyond plug-in.
- Privileges gained or impact: Unbounded kernel heap corruption with reliable kernel
panic (DoS) and potential local privilege escalation β the overwritten adjacent heap
objects may contain function pointers or credential structures;
struct uvc_controlplanted into adjacent slots contains livesub_infopointers whoseget/setfields andv4l2_idfields could be weaponized with heap grooming. The XU path additionally means the attacker controlsbControlSizewithout needing a spec-valid camera terminal. - Required config or capabilities: Default kernel with
uvcconfigured (the default for any DragonFlyBSD install that supports USB video). USB port access. - Reachability:
uvc_drv_attachβuvc_ctrl_init_devat device enumeration time. The bug fires on plug-in; no userspace action is needed.
Proof of concept
PoC source: findings/poc/DF-1046/malicious_uvc_descriptor.bin and
findings/poc/DF-1046/README.md
Required descriptor payload (VideoControl interface, Processing Unit):
| Field | Value | Meaning |
|---|---|---|
| bDescriptorType | 0x24 | CS_INTERFACE |
| bDescriptorSubtype | 0x05 | UDESCSUB_VC_PROCESSING_UNIT |
| bLength | 0xFF | 255 (satisfies bLength >= ctrls_mask_size + 9) |
| bControlSize | 0x20 | 32 β THE TRIGGER: β₯32 causes uint8_t wrap |
| bmControls[0] | 0x01 | bit 0 set = Brightness control present |
| bmControls[1..31] | 0x00 | rest zero |
| pad to bLength | β | fill remaining bytes to reach bLength=255 |
With bControlSize=32 and 1 bit set: uvc_ctrl_count_control returns nctrls=1.
kmalloc allocates 1 Γ sizeof(struct uvc_control) (~150 bytes). The init loop iterates
i=0..255 (finds bit 0, writes 1 entry in-bounds), then i wraps to 0, finds bit 0
again, writes a SECOND entry out-of-bounds, ctrl++ advances. Repeat forever: each
256-iteration sweep writes 1 more OOB entry. Within seconds the heap is massively corrupted
and the kernel panics.
Build & run
No userspace binary needed β the bug is in the kernel driver triggered at attach. Delivery options:
- Option A (QEMU): Create a USB device configfs gadget on a Linux host with the above
descriptor, pass through to the DF guest via
-device usb-host. - Option B (hardware): Flash a GreatFET One or similar USB emulator with a UVC gadget
descriptor having
bControlSize=32. - Option C (syzkaller): Use syzkaller's USB fuzzing (vusb) interface.
Expected output
uvc0: <malicious USB camera> at usbus0 uvc0: Processing Unit: bControlSize=32 bmControls=0x01... Fatal trap 12: page fault while in kernel mode fault virtual address = 0x<address past topo_node->controls allocation> uvc_ctrl_init_dev(...) at uvc_ctrls.c:973 (ctrl++ past end) uvc_drv_attach(...) at uvc_drv.c:... device_probe_and_attach(...) at subr_bus.c:...
A DEBUG/INVARIANTS kernel will additionally emit slab corruption warnings from adjacent
freed/allocation metadata.
Impact
Any USB device that presents a UVC descriptor with bControlSize >= 32 triggers an
unbounded kernel heap overflow at attach. The overflow is in M_UVC slab but the
unbounded nature quickly crosses into adjacent slabs. With heap grooming the planted
struct uvc_control entries (containing live pointers in topo_node, uvc_data,
sub_infos fields) can be turned into a code-execution primitive. Without grooming, the
result is a deterministic kernel panic at device plug-in.
This is a "kernel memory corruption" finding per the AGENT.md severity rubric, hence High even though the CVSS vector reflects the physical-access constraint (AV:P).
Recommended fix
Change the loop counter from uint8_t to unsigned int so it can represent the full range
[0, bCtrlSize * 8) without wrap. Also add a defensive bound on ctrl to prevent any
future mismatch between the count and the loop from causing an overflow.
--- a/sys/bus/u4b/uvc/uvc_ctrls.c
+++ b/sys/bus/u4b/uvc/uvc_ctrls.c
@@ -922,2 +922,2 @@ uvc_ctrl_init_dev(struct uvc_softc *sc, struct uvc_drv_ctrl *ctrls)
struct uvc_topo_node *topo_node, *tmp;
struct uvc_control *ctrl = NULL;
- uint8_t bCtrlSize = 0;
+ unsigned int bCtrlSize = 0;
uint32_t nctrls = 0;
uint8_t *bmCtrls = NULL;
- uint8_t i = 0;
+ unsigned int i = 0;
+ uint32_t ctrl_idx = 0;
struct uvc_xu_node_info *node_info_xu = NULL;
struct uvc_pu_node_info *node_info_pu = NULL;
struct uvc_ct_node_info *node_info_ct = NULL;
@@ -963,4 +963,8 @@ uvc_ctrl_init_dev(struct uvc_softc *sc, struct uvc_drv_ctrl *ctrls)
ctrl = topo_node->controls;
for (i = 0; i < bCtrlSize * 8; i++) {
if (uvc_test_bit(bmCtrls, i) == 0)
continue;
+
+ if (ctrl_idx >= nctrls) {
+ kprintf("%s: bitmap/count mismatch (bit %u, "
+ "ctrl_idx %u >= nctrls %u)\n",
+ __func__, i, ctrl_idx, nctrls);
+ break;
+ }
+
ctrl->topo_node = topo_node;
ctrl->index = i;
uvc_ctrl_initialize_control(ctrl);
ctrl++;
+ ctrl_idx++;
}
}
The type change alone (uint8_t β unsigned int for both bCtrlSize and i) closes the
wrap. The added ctrl_idx guard is defense-in-depth: uvc_ctrl_count_control and the init
loop now use the same-width counters, and the explicit bound prevents writing past the
allocation even if the bitmap is somehow mutated between count and init. The same widening
should be applied to the XU bControlSize path for consistency.
References
- USB Device Class Definition for Video Devices Rev 1.5, Β§2.4.2 (Processing Unit) and
Β§2.4.1 (Camera Terminal) β
bControlSizefield sys/bus/u4b/uvc/uvc_ctrls.c:899-915βuvc_ctrl_count_control(correct, usesint i)sys/bus/u4b/uvc/uvc_ctrls.c:917-978βuvc_ctrl_init_dev(wrong, usesuint8_t i)sys/bus/u4b/uvc/uvc_drv.c:2209, 2288-2289, 2333-2334β attacker-controlledbControlSize- CWE-190 Integer Overflow or Wraparound
- CWE-787 Out-of-bounds Write
Timeline
- 2026-07-14 Discovered during automated audit.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1046 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | trigger-source | userspace harness replicating uvc_ctrl_init_dev loop semantics; -DBUGGY=uint8_t i, default=unsigned int i | 4.6 KB | view raw |
| build.sh | build-script | cc -DBUGGY/-default into harness_buggy/harness_fixed | 615 B | view raw |
| run.sh | run-script | runs both variants at bCtrlSize=32 | 529 B | view raw |
| malicious_uvc_descriptor.bin | descriptor-blob | 255-byte UVC Processing-Unit descriptor with bControlSize=32 (for live USB delivery, not exercisable on this guest) | 255 B | β download |
| harness_run.log | run-log | full harness output on guest, buggy vs fixed, multiple bCtrlSize values | 1.5 KB | view raw |
| fix_run.log | fix-validation | harness re-run on patched #1 kernel: BUGGY still 4096 OOB, FIXED 0 OOB | 995 B | view raw |
| env.txt | environment | uname, cc version, sysctls, kldstat, pciconf (NO USB HW) | 445 B | view raw |
| source_after_patch.txt | source-diff | grep of patched source showing the new kprintf guard at line 970 | 126 B | view raw |
| fix.diff | suggested-fix | git-apply-able: widen bCtrlSize+i to unsigned int, add ctrl_idx>=nctrls guard | 932 B | view raw |
| VERDICT.md | verdict | full narrative: mechanism, primitive, harness proof, fix, fix-validation | 8.6 KB | β raw |
| README.md | readme | original PoC readme (USB-gadget delivery path) | 5.2 KB | β raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-1046 PoC β UVC bControlSize uint8_t wrap β unbounded heap overflow
Trigger
A malicious USB Video Class device descriptor with bControlSize >= 32 in
its Processing Unit (PU), Camera Terminal (CT), or Extension Unit (XU)
descriptor. The kernel's uvc_ctrl_init_dev declares its bitmap-scan loop
counter as uint8_t, so the loop wraps 255β0 and never terminates when
bCtrlSize * 8 > 255. Each wrap writes another full struct uvc_control
entry past the kmalloc'd array, corrupting adjacent kernel heap until the
kernel page-faults.
Malicious Processing Unit descriptor (minimal)
| Field | Offset | Value | Meaning |
|---|---|---|---|
| bLength | 0 | 0xFF | 255 (satisfies bLength >= ctrls_mask_size + 9) |
| bDescriptorType | 1 | 0x24 | CS_INTERFACE |
| bDescriptorSubtype | 2 | 0x05 | UDESCSUB_VC_PROCESSING_UNIT |
| bUnitID | 3 | 0x02 | arbitrary, must be unique within VC interface |
| bSourceID | 4 | 0x01 | references Camera Terminal (or any other entity) |
| wMaxMultiplier | 5..6 | 0x00 | not used |
| bControlSize | 7 | 0x20 | 32 β THE TRIGGER (>= 32 causes uint8_t wrap) |
| bmControls[0..31] | 8..39 | 0x01, 0x00 Γ 31 | bit 0 set (Brightness), rest zero |
| bmVideoStandards | 40 | 0x00 | not used |
| pad | 41.. | 0x00 | fill to reach bLength=255 |
The descriptor must be embedded in a valid UVC VideoControl interface with
at least one Camera Terminal and one Input Terminal so topology construction
succeeds. The Linux configfs USB gadget subsystem is the most convenient
way to deliver it.
Build a USB gadget on a Linux host (configfs)
#!/bin/sh
# Run as root on a Linux host with dwc2/dummy_hcd or a real USB controller
# in gadget mode.
MOD=gadget-zero-df1046
GADGET=/sys/kernel/config/usb_gadget/$MOD
mkdir -p $GADGET
echo 0x1d6b > $GADGET/idVendor # Linux Foundation
echo 0x0104 > $GADGET/idProduct # Multifunction Composite Gadget
echo 0x0100 > $GADGET/bcdDevice
echo 0x0200 > $GADGET/bcdUSB
mkdir -p $GADPT/strings/0x409
echo "DF1046" > $GADGET/strings/0x409/serialnumber
echo "DFPoC" > $GADGET/strings/0x409/manufacturer
echo "MaliciousUVC" > $GADGET/strings/0x409/product
mkdir -p $GADGET/configs/c.1/strings/0x409
echo "Config 1" > $GADGET/configs/c.1/strings/0x409/configuration
# Build the UVC function β most fields are the kernel UVC gadget defaults,
# but the Processing Unit bmControls mask must be expanded to 32 bytes with
# at least one bit set. Linux's uvc-gadget configfs exposes this via
# functions/uvc.0/control/header/h/bmControls when extended.
# (For a minimal PoC we patch the gadget's descriptor table at runtime; a
# real maintainer reproduction can also use a custom GreatFET/Rubber Ducky
# device that emits the raw descriptor bytes above.)
mkdir -p $GADGET/functions/uvc.0
# ... configure Processing Unit with bControlSize=32 ...
ln -s $GADGET/functions/uvc.0 $GADGET/configs/c.1/
# Bind to a UDC
UDC=$(ls /sys/class/udc | head -1)
echo "$UDC" > $GADGET/UDC
For a self-contained PoC, use a GreatFET One or ** facedancer** with the
byte stream above. The 256-byte descriptor payload is in
malicious_uvc_descriptor.bin in this directory.
Run under QEMU
# Boot a DFly guest, pass the host gadget through.
qemu-system-x86_64 -enable-kvm -m 1G \
-device usb-host,vendorid=0x1d6b,productid=0x0104 \
-drive file=dragonfly.img,format=raw
Or with the GreatFET plugged into the host, just usb-host its real VID/PID.
Expected output
uvc0: <Malicious UVC camera> at usbus0 uvc0: Processing Unit: bControlSize=32 bmControls=0x01... Fatal trap 12: page fault while in kernel mode fault virtual address = 0x<address past topo_node->controls allocation> cpuid = 0; apic id = 00000000 Trace: uvc_ctrl_init_dev() at uvc_ctrls.c:973 (ctrl++ past end) uvc_drv_attach() at uvc_drv.c:... device_probe_and_attach() at subr_bus.c:... ...
Static verification fallback
If hardware/QEMU is impractical, both of these are static-verification wins:
- Confirm
uvc_ctrl_count_controlatuvc_ctrls.c:902usesint i. - Confirm
uvc_ctrl_init_devatuvc_ctrls.c:925usesuint8_t i. - Confirm the loop at
uvc_ctrls.c:964isfor (i = 0; i < bCtrlSize * 8; i++).
The type mismatch is the bug; bCtrlSize * 8 can reach 245*8=1960, far
beyond uint8_t's range.
Kernel references
sys/bus/u4b/uvc/uvc_ctrls.c:899-915βuvc_ctrl_count_control(correct, usesint i)sys/bus/u4b/uvc/uvc_ctrls.c:917-978βuvc_ctrl_init_dev(wrong, usesuint8_t i)sys/bus/u4b/uvc/uvc_ctrls.c:925βuint8_t i = 0;(THE BUG)sys/bus/u4b/uvc/uvc_ctrls.c:964βfor (i = 0; i < bCtrlSize * 8; i++)(wrap)sys/bus/u4b/uvc/uvc_ctrls.c:968-973β the OOB write (ctrl->...; ctrl++)sys/bus/u4b/uvc/uvc_drv.c:2288-2289β attacker-controlled bControlSize
DF-1046 β VERDICT
Verdict
REPRODUCED (primitive confirmed at source + harness level). Impact: corruption (latent β unbounded kernel heap overflow at UVC device-attach). Live trigger requires USB hardware that this guest lacks; not exploitable to uid=0 here because the path is dormant on a guest with no USB controller and no uvc.ko loaded.
The bug (confirmed in source)
sys/bus/u4b/uvc/uvc_ctrls.c:917-978 β uvc_ctrl_init_dev():
- Line 925:
uint8_t i = 0;β the loop counter is 8 bits wide. - Line 964:
for (i = 0; i < bCtrlSize * 8; i++) {β the bound is the int-promoted productbCtrlSize * 8, which can reach 245 Γ 8 = 1960. - Line 973:
ctrl++;β each iteration that finds a set bit advances the write cursor.
When bCtrlSize >= 32 (attacker-controlled descriptor byte, validated only weakly at uvc_drv.c:2289), the bound bCtrlSize * 8 exceeds 255. Because only i is uint8_t, after i = 255 the i++ wraps to 0; the comparison 0 < 256 (or any value > 255) stays true, and the loop never terminates. Every 256 iterations re-finds the same set bits and writes another full struct uvc_control entry (containing the valid topo_node pointer, plus whatever uvc_ctrl_initialize_control() kmallocs into uvc_data/sub_infos) past the nctrls-sized topo_node->controls allocation at line 957.
This is an unbounded kernel heap overflow triggered purely by descriptors delivered at USB device enumeration.
Compare with uvc_ctrl_count_control() at lines 899β915: it declares int i (line 902) and correctly counts every set bit. The count returned from it sizes the allocation. The init loop's uint8_t i is a plain typo-style type mismatch with the counting function β but the consequence is catastrophic.
Reachability / threat model
- The function is called only from
uvc_drv_attach(uvc_drv.c:2728) at USB device plug-in. bControlSizeis read directly from attacker-controlled descriptors:- Processing Unit:
uvc_drv.c:2288ctrls_mask_size = pu_desc->bLength >= 8 ? pu_desc->bControlSize : 0; - Camera Terminal:
uvc_drv.c:2209(byte at offset 14 ofit_desc). - Extension Unit:
uvc_drv.c:2333-2334. - The only validation (
uvc_drv.c:2289,:2212,:2336) isbLength < ctrls_mask_size + p, which withbLength = 255allowsbControlSizeup to ~245 β far past the 32-byte wrap threshold. - An attacker who can attach a USB device (physical plug-in, malicious webcam firmware, USB passthrough, malicious dock/hub) triggers the overflow with no privileges, no authentication, and no user interaction beyond plug-in. CVSS AV:P reflects the physical-access constraint.
Why this run stops at "corruption" rather than uid=0 (a VALID hard blocker)
The audit guest has no USB PCI controller at all (pciconf -lv shows only hostb/isa/atapci/virtio/vga/acpi) and uvc.ko is not loaded in the default boot (kldstat shows only ehci/xhci). Even loading the module does not exercise uvc_ctrl_init_dev β that fires only on UVC-device attach, and we have no USB device to attach, no QEMU USB-bus passthrough configured, and no in-kernel USB-fuzz interface. There is therefore no way for an unprivileged user (or root) to drive the vulnerable code path live on this guest.
This is the Phase-6 valid hard blocker: "the vulnerable code path is dead/unreachable at runtime on this guest AND no harness can exercise it." The primitive is proven at the harness level (see below); making it fire live would require either (a) restarting QEMU with -device usb-ehci -device usb-host,... and a malicious UVC gadget on the host, or (b) adding an in-kernel descriptor-injection harness β neither of which an unprivileged user can do, and both of which are outside this guest's configuration.
Per the realism test, however, the bug itself is fully real on default hardware that has USB (which is essentially every real DragonFlyBSD deployment): device usb is in X86_64_GENERIC, uvc.ko ships in /boot/kernel/, and any UVC webcam with bControlSize >= 32 in its PU/CT/XU descriptor triggers the overflow at plug-in.
Primitive characterization (harness)
findings/poc/DF-1046/harness.c is a userspace C harness that replicates the exact loop semantics of uvc_ctrl_init_dev lines 963-974: it allocates an nctrls-slot sink (mirroring kmalloc(nctrls * sizeof(struct uvc_control), M_UVC, ...) at line 957), then runs the loop with i typed either as uint8_t (-DBUGGY, the current kernel) or unsigned int (the fix). bmControls is bCtrlSize bytes with only bit 0 set, matching the finding's PoC descriptor.
Results on DragonFly 6.5-DEVELOPMENT #1, cc 8.3:
BUGGY build (uint8_t i β mirrors sys/bus/u4b/uvc/uvc_ctrls.c:925) bCtrlSize=31 bound=248 : iters=1 in-bounds=1 oob=0 (below wrap threshold β control) bCtrlSize=32 bound=256 : iters=β in-bounds=1 oob=4096 (THE TRIGGER) bCtrlSize=64 bound=512 : iters=β in-bounds=1 oob=4096 FIXED build (unsigned int i β the patched kernel) bCtrlSize=32 bound=256 : iters=1 in-bounds=1 oob=0 bCtrlSize=64 bound=512 : iters=1 in-bounds=1 oob=0 bCtrlSize=245 bound=1960: iters=1 in-bounds=1 oob=0
bCtrlSize=31 is the sanity control: with the bound below 256 the loop terminates normally even with uint8_t i. bCtrlSize=32 and above is the bug: 4096 OOB writes of full struct uvc_control-sized slots past the allocation, capped only by the harness's OOB_CAP (in the kernel there is no cap β it page-faults on adjacent slab/unmapped memory β panic, or worse, silently corrupts adjacent heap on a no-INVARIANTS kernel).
Fix
findings/poc/DF-1046/fix.diff β widen both bCtrlSize and the loop counter i from uint8_t to unsigned int in uvc_ctrl_init_dev, and add a ctrl_idx >= nctrls defense-in-depth guard inside the loop so any future bitmap/count mismatch breaks instead of overflowing. This matches the finding markdown's ## Recommended fix proposal.
Fix validation (Phase 8)
- Baseline (
#0, unpatched): harness BUGGY build shows 4096 OOB writes forbCtrlSize=32. The kernel module source hasuint8_t iat line 925. - Apply fix.diff:
cd /usr/src && patch -p1 < /root/fix.diffβ both hunks apply cleanly (line 919, line 966). - Build single-fix kernel:
make -j6 nativekernel KERNCONF=X86_64_GENERICβ rc=0, no errors, kernel + uvc.ko rebuilt. - Install + reboot:
kernel.stripped β /boot/kernel/kernel, reboot βkern.version = DragonFly 6.5-DEVELOPMENT #1: Tue Jul 14 10:50:44 UTC 2026. - After (
#1, patched): re-run the harness on the patched kernel β the FIXED build (which mirrors the patched source) shows 0 OOB writes at everybCtrlSize. The patcheduvc.koloads and unloads cleanly (kldload uvcrc=0,kldunload uvcrc=0). Disassembly ofuvc_ctrl_init_devin the rebuiltuvc.koconfirms the loop counter is now%r15d(32-bit) and the newcmp %ebx,-0x118(%rbp); jbeis thectrl_idx >= nctrlsguard. - fix_status:
fixed(live runtime test isnot_testableon this guest because no USB HW, but the source-level fix is verified by clean compile + loadable module + harness equivalence + disasm).
PoC changes
findings/poc/DF-1046/harness.cβ NEW userspace harness that replicates the exact buggy loop and proves the wrap β OOB-write primitive (4096 OOB writes forbCtrlSize=32withuint8_t i, 0 withunsigned int i).findings/poc/DF-1046/build.sh,run.shβ NEW repro scripts.findings/poc/DF-1046/fix.diffβ NEW standalone git-apply-able fix (widensiandbCtrlSize, addsctrl_idxguard).findings/poc/DF-1046/{harness_run.log,fix_run.log,env.txt,source_after_patch.txt}β NEW evidence.findings/poc/DF-1046/malicious_uvc_descriptor.binβ unchanged (the descriptor the finding ships; relevant only for live USB delivery which we can't do on this guest).
Kernel references (confirmed during verification)
sys/bus/u4b/uvc/uvc_ctrls.c:925βuint8_t i = 0;(THE BUG)sys/bus/u4b/uvc/uvc_ctrls.c:964βfor (i = 0; i < bCtrlSize * 8; i++)(wrap point)sys/bus/u4b/uvc/uvc_ctrls.c:957βkmalloc(nctrls * sizeof(*ctrl), M_UVC, ...)(the allocation that gets overflowed)sys/bus/u4b/uvc/uvc_ctrls.c:968-973β the OOB write (ctrl->... = ...; ctrl++;)sys/bus/u4b/uvc/uvc_ctrls.c:899-915βuvc_ctrl_count_control(usesint i, correct)sys/bus/u4b/uvc/uvc_drv.c:2288-2289β PU path: attacker-controlledbControlSize+ weak validationsys/bus/u4b/uvc/uvc_drv.c:2209, 2212β CT pathsys/bus/u4b/uvc/uvc_drv.c:2333-2336β XU pathsys/bus/u4b/uvc/uvc_drv.c:2728βuvc_ctrl_init_dev(sc, sc->ctrl)call site inuvc_drv_attach
Fix verification
fixedVALIDATED: harness BUGGY=4096 OOB writes; FIXED=0. Patched uvc.ko compiles+loads+disasm confirms 32-bit counter. Live not_testable (no USB HW).
BEFORE: 4096 OOB writes. AFTER: 0 OOB writes (3 bCtrlSize values tested). uvc.ko kldload rc=0.
Confirmed kernel references
Detail
Exploit chain
BLOCKED by valid hard blocker: no USB controller on guest, uvc.ko not loaded. Path reachable only at USB device-attach. Primitive proven at harness level: unbounded heap overflow with live pointers. Escalation needs USB HW passthrough.
Evidence (decisive lines)
BUGGY: 4096 OOB writes, 2147487745 iterations. FIXED: 0 OOB, 1 iteration. No USB HW on guest (pciconf).
PoC changes
Authored: harness.c (uint8_t vs unsigned int loop comparison), build.sh, run.sh, fix.diff (widen i+bCtrlSize to unsigned int + ctrl_idx>=nctrls guard), VERDICT.md, manifest.json.
Verified recommended fix
Change uint8_t i and bCtrlSize to unsigned int at uvc_ctrls.c:925; add ctrl_idx>=nctrls break guard. Matches finding proposal. Full diff in findings/poc/DF-1046/fix.diff.
Verdict
REPRODUCED (harness). uvc_ctrl_init_dev uint8_t i loop counter wraps at bCtrlSize*8>=256 (uvc_ctrls.c:925/964). Harness BUGGY: 4096 OOB writes (struct uvc_control with live pointers) past nctrls-sized alloc. FIXED (unsigned int): 0 OOB. Live requires USB HW absent on guest.
No comments yet.