β¬’ 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 142 B view raw
run.sh run-script ./harness 41 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.4 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 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.