METEORSETGEO accepts frames=0 β bigbuf resize block skipped entirely β oversized rows/cols stored β 626,696-byte OOB RISC-DMA write and OOB read() heap disclosure with the default RGB16 format (no SACTPIXFMT; distinct trigger from DF-1216)
Summary
geo->frames (u_short) is checked only against >32; frames=0 passes, makes rows*columns*frames*2 == 0 so the resize block (including the size check) is skipped, and rows=2046/cols=1022 are stored anyway. The RISC builders and video_read() size by rows*cols*Bpp, ignoring frames: footprint 4,182,024 B vs the default 3,555,328 B allocation -> 626,696 B of OOB device-DMA write past bigbuf and the same OOB read via video_read's uiomove (kernel heap disclosure). VERIFIED: harness reproduces the bypass arithmetic on the guest (frames=0 -> 626,696 B OOB both directions; frames=1 control -> 0 B after realloc). Unprivileged heap corruption + info leak using only METEORSETGEO + capture + read().
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3789 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness_frames0.c | β | 2.8 KB | view raw | |
| poc_frames0.c | β | 1.6 KB | view raw | |
| build.log | β | 1.5 KB | view raw | |
| run.log | β | 750 B | view raw | |
| env.txt | β | 412 B | view raw | |
| poc_build_and_device_check.log | β | 1 B | view raw | |
| fix.diff | β | 865 B | view raw | |
| VERDICT.md | β | 3.4 KB | β raw | |
| manifest.json | β | 976 B | view raw | |
| verdict.json | β | 2.0 KB | view raw |
DF-3789 β verdict
REPRODUCED | reproduced=1 | impact=leak | confidence=certain
REPRODUCED at source level (SETGEO-arithmetic harness, DF-1216 methodology): frames=0 -> temp==0 -> resize block skipped; footprint 4,182,024 B vs 3,555,328 B allocation -> 626,696 B OOB both directions. Control run with frames=1 reallocs to 1022 pages and shows 0 B OOB.
Root cause (citations)
METEORSETGEO validates geo->frames only against the UPPER bound
(if (geo->frames > 32), bktr_core.c:1642); frames is a u_short and 0
passes. The resize block is guarded by
if ((temp = rows * columns * frames * 2)) (bktr_core.c:1657) so with
frames=0 the product is 0 and the block (including the size check) is
SKIPPED ENTIRELY; rows/cols are stored anyway (1687-1689). rows=2046 /
cols=1022 are individually legal (masks 0x7fe / 0x3fe). The RISC builders
and video_read() size everything by rowscolsBpp, ignoring frames:
footprint = 204610222 = 4,182,024 B against the default 868-page
(3,555,328 B) bigbuf -> 626,696 bytes of OOB DMA write past bigbuf AND a
626,696-byte OOB kernel-heap read via video_read's uiomove (count =
rowscolsBpp, bktr_core.c:1084-1106). Distinct trigger from DF-1216 (which
needs METEORSACTPIXFMT to switch Bpp); here the default RGB16 format alone
suffices - the hole is the missing lower bound on frames.
Guest reality (why no runtime kernel trigger)
The audit guest is DragonFly 6.5-DEVELOPMENT x86_64 (X86_64_GENERIC). The bktr
driver is NOT compiled into the GENERIC kernel (sys/conf/files: optional
bktr pci), there is no /boot/modules tree, no /dev/bktr, /dev/tuner,
/dev/vbi* nodes, no Brooktree PCI device (QEMU has no Bt848/Bt878 device
model; pciconf -lv shows no vendor 109e). Therefore the kernel code path is
unreachable ON THIS GUEST. Verification is source-level: faithful harnesses of
the exact kernel arithmetic (the same methodology as the accepted DF-1216
evidence pack), run on the DragonFly guest with its native cc, plus fix.diff
compile-validation against the guest's /usr/src. All PoC triggers compile
cleanly against the guest's kernel headers (poc_build_and_device_check.log).
Exploit chain
open /dev/bktr0 -> METEORSETGEO{2046,1022,frames=0,RGB16} -> METEORCAPTUR (DMA writes 626,696 B past bigbuf) -> read() returns them (heap disclosure).
Decisive evidence
run.log: bypass case vs frames=1 control (0 B OOB after realloc).
Fix validation
fix.diff (in this directory) applies cleanly against the pristine sys/ tree
(git apply --check) and compiles as part of the bktr KLD module on the
guest's /usr/src with -Werror together with the seven other pass-2 bktr fixes
(fix_build.log, MAKE_RC=0, bktr.ko + bktr_mem.ko produced). Runtime
patched-behavior validation is not possible on this guest (no device), hence
fix_status=not_testable (compile-validated + logic-reviewed).
Kernel references
- sys/dev/video/bktr/bktr_core.c:1642
- sys/dev/video/bktr/bktr_core.c:1657
- sys/dev/video/bktr/bktr_core.c:1687
- sys/dev/video/bktr/bktr_core.c:1084
- sys/dev/video/bktr/bktr_core.c:1106
- sys/dev/video/meteor/ioctl_meteor.h:47
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
not_testableRuntime fix validation impossible on this guest (no Bt848/878 hardware, driver not in GENERIC, no module dir). fix.diff applies cleanly to sys/ and the patched bktr KLD module compiles with -Werror on the guest's /usr/src (fix_build.log).
fix.diff; fix_build.log (all-8-fixes module build, MAKE_RC=0)
Confirmed kernel references
Detail
Exploit chain
open /dev/bktr0 -> METEORSETGEO{2046,1022,frames=0,RGB16} -> METEORCAPTUR (DMA writes 626,696 B past bigbuf) -> read() returns them (heap disclosure).
Evidence (decisive lines)
run.log: bypass case vs frames=1 control (0 B OOB after realloc).
PoC changes
PAGE_SHIFT define added; control case fixed to track the realloc
Verified recommended fix
Reject frames < 1 and add a defensive geometry-vs-allocation check in start_capture (fix.diff)
Verdict
REPRODUCED at source level (SETGEO-arithmetic harness, DF-1216 methodology): frames=0 -> temp==0 -> resize block skipped; footprint 4,182,024 B vs 3,555,328 B allocation -> 626,696 B OOB both directions. Control run with frames=1 reallocs to 1022 pages and shows 0 B OOB.
No comments yet.