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

Stale sassc->maxtargets after mprsas_realloc_targets desynchronizes every targets[] bounds check from the real array size (heap OOB read/write window |attach_facts - reset_facts| structs)

Summary

maxtargets is set exactly once at attach; realloc_targets resizes the array but never updates it. After a second facts change (IT/IR flip changes MaxVolumes, FW update changes MaxTargets), the array is F1-sized while all checks trust F0: the PhyNum fallback + full target-struct writes, the reinit zeroing walk, the find_target walks, and CAM's cpi->max_target export. Systemic sibling of DF-1475. One-line 'sassc->maxtargets = maxtargets' fix (compile-validated mpr.ko build).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4250 Β· 0 files
FileTypeDescriptionSize
empty

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

fixed: one-line maxtargets sync applied (with the DF-4251/4253 diffs) to pristine guest /usr/src, nativekernel rc=0, installed as kernel #1, rebooted; identical rig dispatch observes maxtargets==16 after realloc and no OOB access (rig_fixed_run.log), vs desync+112/112 clobbered slots on baseline.

rig_fixed_run.log: '[df42rig] post-realloc: sassc->maxtargets=16 array-really-holds=16 ... FIXED kernel' + RESULT bitmask 0x0; objdump-verified patched build; fix_build.log full untrimmed build + install completion.
↓ fix.diffper-fix-DF-4250

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log (untrimmed baseline decisive run + repeat): '[df42rig] post-realloc: sassc->maxtargets=128 array-really-holds=16 ==> DESYNC'; 'REAL mprsas_find_target_by_handle returned &targets[16..18] (index >= F1) ==> PRODUCTION OOB READ ... YES'; 'OOB WRITE census: 112/112 foreign slots clobbered'. rig_fixed_run.log: patched kernel #1 prints 'maxtargets=16 ... FIXED kernel?' and performs no OOB step (bitmask 0x0 vs baseline 0x7). fix_build.log (5.9MB, untrimmed): full patched-kernel build rc=0. Guard-chunk grooming gives byte-exact adjacency: production kmalloc landed on the freed guard slot both times (addresses in logs).

PoC changes

No seed PoC existed (pass-2 finding, previously 'untested'). Authored the df42rig KLD: real-header type-safe softc fabrication, extern linkage to the production globals, M_MPR guard-chunk grooming for byte-exact OOB evidence, sentinel-handle OOB-read proof, INDIAGRESET-flags OOB-write census, fixed-kernel early-exit branch for fix validation.

Verified recommended fix

sassc->maxtargets = maxtargets in mprsas_realloc_targets (one line, fix.diff)

Verdict

REPRODUCED with production code on the live guest: the shipped kernel's own mprsas_realloc_targets (extern-linked global T symbol, the exact call mpr_reinit makes at mpr.c:631-634) was driven on an attach-faithful fabricated softc (real driver headers, real M_MPR allocations, real cam_devq/cam_sim); it resized sassc->targets to 16 elements and left sassc->maxtargets at 128 β€” the desync itself is created by production code. The REAL mprsas_find_target_by_handle then walked i<128 over the 16-element array and returned &targets[16..18] β€” pointers into a foreign (rig guard) heap chunk: production OOB READ, proven by returned pointer. The REAL mprsas_handle_reinit zeroing walk (mpr_sas.c:1244-1250) then wrote handle/exp_dev_handle/outstanding/flags into all 112 foreign slots (19,712 bytes past the allocation): production OOB WRITE, 112/112 slots clobbered, byte-exact before/after (0xaaaa->0x0000, flags->INDIAGRESET). Repeated twice, bitmask 0x7 both times, guest healthy (corruption contained in rig-owned guard chunks). The only stand-in is the LSI HBA QEMU cannot emulate: on hardware the trigger is two facts-changing diag resets (IT/IR flip changes MaxVolumes, FW update changes MaxTargets) then any device add or the reinit walk itself. Fix validated on a rebuilt kernel (nativekernel, all hunks applied): same rig, same dispatch, same grooming (production kmalloc reused the freed guard chunk byte-exactly again) β€” maxtargets==16 after realloc, no OOB read, no OOB write.