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)
PoC verification
Evidence pack
findings/poc/DF-5662 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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
fixedfix.diff applied to guest /usr/src (+ #include
fix_run.log, fix_build.log, cam_extend.fixed.c, fix.diff
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).
No comments yet.