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

METEORSETGEO + METEORSACTPIXFMT pixel-format mismatch yields OOB DMA write and OOB uiomove heap info-leak

Summary

METEORSETGEO at bktr_core.c:1657 allocates bigbuf = rows*cols*frames*2 (YUV_422 Bpp=2). METEORSACTPIXFMT at :2328 switches bktr->pixfmt to RGB 4Bpp WITHOUT resizing bigbuf. video_read at :1084: count=rows*cols*pixfmt_table[new].Bpp (=2x buffer) then uiomove(bigbuf,count,uio) -> OOB heap read. start_capture bzero at :3519 uses new Bpp -> OOB DMA write. RISC DMA builder uses pitch=cols*Bpp(new) -> OOB DMA write past bigbuf. Max over-read/write ~4MB past buffer end. Unprivileged (mode 0444). Fix: verify rows*cols*new_Bpp<=alloc_pages*PAGE_SIZE in METEORSACTPIXFMT, clamp count in video_read.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1216 Β· 12 files
FileTypeDescriptionSize
harness.c trigger-source self-contained reproduction of the cited OOB arithmetic 4.7 KB view raw
build.sh build-script cc -O2 -Wall -o harness harness.c 65 B view raw
run.sh run-script ./harness 89 B view raw
build.log build-log final successful build (in-guest cc) 13 B view raw
run.log run-log decisive harness run printing OOB=4177936 286 B view raw
env.txt environment uname + compiler + PCI inventory 543 B view raw
fix.diff suggested-fix add METEORSACTPIXFMT Bpp-vs-bigbuf bounds check 962 B view raw
fix_build.log fix-build-log patched nativekernel build, rc=0 5.6 MB ↓ download
VERDICT.md verdict full narrative 4.8 KB ↓ raw
poc_bpp_mismatch.c trigger-source-orig orchestrator's draft device-PoC (no /dev/bktr0 on guest) 1.6 KB view 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
VERDICT.md verdict full narrative
↓ download raw

DF-1216 β€” METEORSETGEO + METEORSACTPIXFMT pixel-format mismatch

Verdict

REPRODUCED (source-level harness). The bug is real and the OOB read/write arithmetic is confirmed; impact ceiling is OOB kernel-heap read (read()) + OOB kernel-heap write (start_capture bzero/RISC DMA). The kernel code path cannot be triggered on this audit guest because QEMU does not emulate the Brooktree Bt848 PCI video-capture device β€” /dev/bktr0 does not exist and the PCI vendor/device IDs (109e:0350/0351) are absent from pciconf -lv. The fix.diff applies cleanly to the in-guest source tree and nativekernel succeeds (rc=0); no run-time exercise of the path is possible on this guest because the device is missing.

Mechanism (cited path:line, confirmed against sys/dev/video/bktr/bktr_core.c)

  1. METEORSETGEO at bktr_core.c:1657 computes the bigbuf allocation as temp = geo->rows * geo->columns * geo->frames * 2. The literal * 2 hard-codes YUV_422 (Bpp=2). The buffer is mapped into alloc_pages pages.
  2. METEORSACTPIXFMT at bktr_core.c:2328-2337 accepts any index 0..11 from the user, assigns bktr->pixfmt = *(int *)arg, and returns. It does not verify that the new format's Bpp is compatible with the already- allocated bigbuf, and does not reallocate.
  3. video_read at bktr_core.c:1084-1085 computes count = rows * cols * pixfmt_table[bktr->pixfmt].public.Bpp. With pixfmt=5 (RGB 4Bpp from pixfmt_table[5] at bktr_core.c:230) and the YUV-sized buffer, count is 2Γ— the buffer size.
  4. uiomove at bktr_core.c:1106 then reads count bytes out of bigbuf, reading up to count - alloc_pages*PAGE_SIZE bytes past the end (OOB heap read β†’ kernel-memory info leak).
  5. start_capture at bktr_core.c:3519-3524 does bzero(bigbuf, rows*cols*frames*pixfmt_table[bktr->pixfmt].Bpp) and the RISC DMA builder uses pitch = cols*Bpp(new) β†’ OOB heap write (up to ~4 MiB past buffer end).

The attacker needs only an open bktr fd (the device is cr--r--r-- world-readable per the finding, although the open may further be gated by caps_priv_check_self); the two ioctls + read() sequence is fully unprivileged on real hardware.

Harness proof (harness.c)

Extracts the exact pixfmt_table[] from bktr_core.c:220-238 and the exact arithmetic from lines 1657, 2333, 1084, 3522, and prints the OOB length:

After METEORSETGEO(YUV_422): bigbuf=4186112 bytes alloc_pages=1022
video_read OOB=4177936 bytes  start_capture OOB=4177936 bytes
CONFIRMED: read() reads 4177936 bytes past bigbuf end (heap leak);
           start_capture bzero/DMA writes 4177936 bytes past bigbuf end (heap corruption)

Reproduced deterministically over 3 runs.

Exploit-chain note

This is a write-capable primitive on real hardware. Because the audit guest lacks the Bt848 device, no kernel-side chain can be developed here. The OOB write is large (~4 MiB), so a successful trigger on real HW would corrupt adjacent kernel heap and yield a reliable DoS at minimum; exploitation (strings-into-heap would require additional research but the offset is attacker-tunable via rows/cols/pixfmt). Documented here as a primitive characterization only; impact ceiling for this run is heap-corruption DoS / info-leak.

PoC changes

  • Replaced the original poc_bpp_mismatch.c (which attempts to open /dev/bktr0 that does not exist on this guest) with harness.c, a self-contained userland reproduction of the cited arithmetic.
  • Added fix.diff, build.sh, run.sh, env.txt, VERDICT.md, manifest.json, full build.log/run.log/fix_build.log.

Fix

fix.diff adds the missing bounds check inside METEORSACTPIXFMT: bktr->rows * bktr->cols * bktr->frames * pixfmt_table[idx].Bpp must not exceed alloc_pages * PAGE_SIZE when bigbuf != 0. This is the targeted root-cause fix; it supersedes the finding markdown's "clamp count in video_read" note by closing the bug at the actual configuration-change point so neither video_read nor start_capture can ever see a too-large Bpp.

Fix-validation

The diff applies cleanly (patch -p1 --forward, hunk #1 succeeded at line 2330) and make -j6 nativekernel KERNCONF=X86_64_GENERIC completes with rc=0 (fix_build.log). The kernel-side before/after run cannot be demonstrated on this guest because the device is absent, so fix_status: "not_testable" β€” the diff is verified to compile and the changed logic is traced to close the cited code 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

not_testable
baseline reproduced→ patch + rebuild →patched clean

not_testable because the bt848 video-capture device is absent from the audit guest so the in-kernel PoC cannot be run; validated that fix.diff applies cleanly (patch -p1 --forward, hunk #1 at line 2330) and that the single-fix nativekernel compiles with rc=0 (fix_build.log). Traced the changed logic to close the cited overflow at the configuration-change point.

baseline (harness): video_read OOB=4177936 bytes / start_capture OOB=4177936 bytes
patched kernel build: === NK_DONE rc=0 === (kernel compiles with new bounds check)
↓ fix.diffDragonFly 6.5-DEVELOPMENT #0 master+df1216-fix (single-fix kernel built and linked, rc=0)

Confirmed kernel references

Detail

Exploit chain

none (HW-gated): no bt848 device on the audit guest, so the kernel-side chain cannot be exercised. Primitive characterized via harness: a write-capable ~4 MiB OOB heap read (info leak) + OOB heap write (start_capture bzero/RISC DMA). Realistic ceiling on real HW: heap corruption DoS / kernel info leak; full escalation would require the bt848 device. Documented in harness.c and VERDICT.md.

Evidence (decisive lines)

After METEORSETGEO(YUV_422): bigbuf=4186112 bytes alloc_pages=1022
video_read OOB=4177936 bytes  start_capture OOB=4177936 bytes
CONFIRMED: read() reads 4177936 bytes past bigbuf end (heap leak); start_capture bzero/DMA writes 4177936 bytes past bigbuf end (heap corruption)

PoC changes

Replaced orchestrator's draft poc_bpp_mismatch.c (which tries to open /dev/bktr0 that does not exist on this guest) with harness.c β€” a self-contained userland reproduction of the exact cited arithmetic. Added fix.diff (METEORSACTPIXFMT Bpp-vs-bigbuf check), build.sh/run.sh, env.txt, full build/run/fix_build logs, VERDICT.md, manifest.json.

Verified recommended fix

fix.diff adds the missing bounds check inside METEORSACTPIXFMT (bktr_core.c:2333): compute need = rowscolsframespixfmt_table[idx].Bpp and reject with EINVAL when bigbuf!=0 && need > alloc_pagesPAGE_SIZE. This closes the bug at the configuration-change point so neither video_read nor start_capture can ever see a too-large Bpp. Supersedes finding proposal (which suggested clamping count in video_read).

Verdict

REPRODUCED at the source-logic level. bktr_core.c:1657 METEORSETGEO hardcodes bigbuf = rowscolsframes2 (YUV Bpp=2); bktr_core.c:2333 METEORSACTPIXFMT sets bktr->pixfmt=5 (RGB 4Bpp) with no realloc/bounds check; bktr_core.c:1084 video_read then computes count = rowscols*pixfmt_table[pixfmt].Bpp = 2x buffer; bktr_core.c:1106 uiomove reads 4177936 bytes past bigbuf end. start_capture bzero (bktr_core.c:3521) writes 4177936 bytes past bigbuf. Harness replicates the exact buggy arithmetic and prints OOB=4177936 bytes deterministically over 3 runs. The guest lacks a bt848 PCI video-capture device (no /dev/bktr0 in QEMU) so the in-kernel path cannot be triggered here; this is a harness proof.