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

cam_extend_set() has no lower-bound index validation: controlled kernel-pointer OOB write before the ps[] array (incl. cross-object corruption), NULL-page fatal trap on fresh arrays, and signed-overflow panic in the growth size math

Summary

Only index >= nelem is checked: any negative index reaches the ps[index] read AND write - a fully controlled kernel-pointer write at ps-8N for arbitrary N, proven to corrupt an adjacent live heap chunk via slab grooming (marker written cross-object); a fresh array faults at 0xffffffffffffffd8; and index+EXTEND_CHUNK signed-wraps so index=0x7ffffffb panics in kmem_slab_alloc with a ~2^64 kmalloc. The mirror cam_extend_get DOES validate index<0 - the setter has no guard at all, not even DIAGNOSTIC (the DF-5550/5551 family class). All current callers pass kernel-allocated u_int32 unit_number (traced to ground), so latent/root-gated today - but the API takes a raw int and any future caller forwarding a user/protocol-derived index gets the OOB write. Full-range validation + spinlock fix validated on a rebuilt kernel (oob_writes=0, both crash knobs SURVIVED).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5662 Β· 12 files
FileTypeDescriptionSize
oob5662.c β€” 6.9 KB view raw
build.sh β€” 208 B view raw
run.sh β€” 319 B view raw
build.log β€” 677 B view raw
run.log β€” 729 B view raw
panic-ovf.txt β€” 673 B view raw
panic-nullcrash.txt β€” 735 B view raw
fix_run.log β€” 950 B view raw
fix.diff β€” 4.0 KB view raw
cam_extend.fixed.c β€” 3.7 KB view raw
fix_build.log β€” 1.2 KB view raw
env.txt β€” 812 B view raw

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

fix.diff applied to guest /usr/src (+ #include for INT_MAX), full nativekernel rebuild + installkernel + reboot. Exact PoC re-run: demo oob_writes=4->0, cross_object=1->0; nullcrash Fatal-trap-12 -> clean 'returned without trapping' (SURVIVED); ovf kernel_map-panic -> clean return (SURVIVED). Positive growth path still functional; guest stable.

fix_run.log, fix_build.log, cam_extend.fixed.c, fix.diff
↓ fix.diffper-fix-DF-5662

Confirmed kernel references

Detail

Exploit chain

KLD harness (root) -> cam_extend_set(ea, -N, ptr) with groomed slab (per-CPU LIFO zone reuse, kern_slaballoc.c:1576-1585) -> chosen kernel pointer written at ps-8N into the object preceding the ps[] allocation. Unprivileged user->root chain does NOT exist today: no in-tree caller forwards attacker-controlled indices (unit numbers allocated sequentially in cam_periph.c). Documented as latent full-control heap corruption primitive of the shared CAM array allocator.

Evidence (decisive lines)

run.log (4 OOB writes + CROSS-OBJECT WRITE cross_object=1), panic-nullcrash.txt (Fatal trap 12, Stopped at cam_extend_set+0x29), panic-ovf.txt (panic kmem_slab_alloc, cam_extend_set in stack), fix_run.log (oob_writes=0 cross_object=0, SURVIVED x2 on fixed kernel #1 Oct 9 14:34:16)

PoC changes

Authored from scratch (no seed): KLD with sysctl-gated phases (demo/ovf/nullcrash); slab grooming via zeroed 64B M_DEVBUF chunks freed LIFO so ps[] lands adjacent to a controlled neighbor; mirror struct used read-only to observe the real allocator's ps/nelem. v1 of nothing else; one local fix (double-free in groom loop caught pre-transfer).

Verified recommended fix

Reject index < 0 and index > INT_MAX - EXTEND_CHUNK in cam_extend_set(), NULL-check the growth kmalloc, and add a spinlock to struct extend_array serializing set/get/release (see fix.diff).

Verdict

cam_extend_set() validates only index >= nelem (cam_extend.c:56); any negative index reaches the line-70 read and line-75 write of ea->ps[index]. Proven on the live kernel with the production function: (1) controlled kernel-pointer OOB write at ps-8N incl. cross-object corruption of a groomed adjacent heap chunk (marker 0xcafe5662cafe5662); (2) fresh-array negative index -> Fatal trap 12 at cam_extend_set+0x29 (fault VA 0xffffffffffffffd8); (3) index 0x7ffffffb -> index+EXTEND_CHUNK signed overflow -> kmalloc ~2^64 -> panic kmem_slab_alloc kernel_map out of space. No unprivileged trigger exists today (all callers pass kernel-allocated u_int32 unit_number), so filed Medium/latent; primitive is fully proven. Fix (index validation + NULL check + spinlock) validated by nativekernel rebuild: baseline behaviors gone (oob_writes=0, cross_object=0, both crash knobs survived).