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

Dump walk trusts kernel page-table frames at panic time: unguarded dump_add_page(PTE frame) plus missing bitmap bounds check gives an uncontrolled OOB kernel write / double-panic instead of a crash dump

Summary

dump_add_page(pdp[i]/pd[j] & PG_FRAME) are not is_dumpable-guarded (unlike the leaf/2MB paths) and dump_add_page/drop have no bounds check against vm_page_dump_size: one corrupted PDP/PD entry (exactly what a successful kernel-memory-corruption exploit leaves) turns the panic-time walk into an attacker-chosen-offset silent bit-OR into kernel memory (frame 2^46 -> nested panic at +2GB) or a supervisor-write fault (frame 2^47 -> Fatal trap 12 at exactly &vm_page_dump[frame>>18], no dump produced). int idx also truncates for pa >= 2^49. Fix (is_dumpable guards + bitmap bounds) validated: identical plant -> 'Dump complete'. Precondition is prior ring-0-level corruption so no privilege boundary crossed.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5536 Β· 13 files
FileTypeDescriptionSize
README.md β€” 1.1 KB ↓ raw
VERDICT.md β€” 3.6 KB ↓ raw
mddiag.c β€” 7.7 KB view raw
build.sh β€” 92 B view raw
run.sh β€” 125 B view raw
run.log β€” 1.2 KB view raw
run.patched.log β€” 875 B view raw
panic.txt β€” 147.9 KB view raw
panic.2-silent-corruption.txt β€” 131.0 KB view raw
env.txt β€” 625 B view raw
fix.diff β€” 1.6 KB view raw
manifest.json β€” 941 B view raw
verdict.json β€” 4.0 KB view raw

DF-5536 β€” panic-time dump walk trusts kernel page-table frames: uncontrolled OOB write / double-panic in dump_add_page

Target

sys/platform/pc64/x86_64/minidump_machdep.c (pass 2)

Build / Run (root on the guest; dumpdev required)

See build.sh / run.sh. Modes of mddiag.ko via sysctl debug.mddiag=<report|panic|maptest|booby>.

booby mode plants pdp[64] = |V in an unused kernel PDP slot (nothing live is disturbed), then panics; the crash-dump walk then processes the planted entry.

Expected

  • baseline, frame 2^46: silent bit-OR into live kernel memory (bitmap+2GB) -> nested panic (vm_object_hold_shared assert), no dump.
  • baseline, frame 2^47: "Fatal trap 12: page fault while in kernel mode", fault VA == predicted &vm_page_dump[frame>>18], "supervisor write data" -> nested panic, guest wedged, no dump. (panic.txt)
  • patched (fix.diff): walk skips the entry, "Dump complete", savecore extracts the core. (run.patched.log)

Files

mddiag.c, fix.diff, panic.txt (2^47 fatal trap), panic.2-silent-corruption.txt (2^46), run.log (baseline), run.patched.log (fix validation), env.txt, build.sh, run.sh

VERDICT.md
↓ download raw

Verdict β€” DF-5536 (REPRODUCED)

Finding

The dump walk trusts kernel page-table frames at panic time:

  • minidump_machdep.c:260 β€” dump_add_page(pdp[i] & PG_FRAME) (the PD-page frame) and :281 β€” dump_add_page(pd[j] & PG_FRAME) (the PT-page frame) are not guarded by is_dumpable() (unlike the leaf-PFN paths at :271-272 and :291-292).
  • dump_add_page() (:452-460) / dump_drop_page() (:463-471) perform atomic_set_long(&vm_page_dump[idx], 1ul << bit) with idx = pa >> 18 and no bounds check against vm_page_dump_size (bitmap covers only physical RAM, vm/vm_page.c:398-403).
  • :263/:285 then do PHYS_TO_DMAP(frame) reads of the same unvalidated frames.

With a single corrupted PDP/PD entry (which is precisely the state a successful kernel-memory-corruption exploit leaves behind, or the residue of the very bug that caused the panic), idx is attacker-chosen: the write offset is frame >> 15 bytes past vm_page_dump, for any 52-bit frame β€” an uncontrolled OOB kernel write (single bit-OR, 8-byte aligned) or a kernel page fault, both during the crash-dump path.

Reproduction (baseline, fresh guest, X86_64_GENERIC 6.5-DEV)

KLD mddiag.ko plants pdp[64] = <frame>|V in an unused KVA slot (nothing live is disturbed), then panics. Two variants:

  1. frame = 2^46 (panic.txt): write target vm_page_dump[2^28] = 0xfffff800c0000000 (bitmap+2 GB, mapped kernel memory) β†’ silent bit-OR into a live kernel object β†’ nested panic: assertion "obj != NULL" failed in vm_object_hold_shared β†’ guest wedged, no dump.
  2. frame = 2^47 (panic.2 β€” direct attribution): predicted target vm_page_dump[2^29] = 0xfffff80140000000 (bitmap+4 GB, unused KVA slot): Fatal trap 12: page fault while in kernel mode fault virtual address = 0xfffff80140000000 <- exactly the predicted vm_page_dump[idx] fault code = supervisor write data, page not present <- the atomic_set_long panic: page fault The fault VA matches the rig's printed &vm_page_dump[frame>>18] byte-for-byte and the fault is a supervisor write β€” i.e. the atomic_set_long in dump_add_page called unguarded from walk-1 line 260. No "Dump complete"; guest wedged (no reset).

Impact

Not a privilege boundary: the precondition is kernel page-table corruption (attacker is already ring-0-equivalent, or the machine is already crashing from a memory-safety bug). Consequence is loss of the crash dump exactly when it matters (evidence destroyed / availability of the dump path): the dump writer performs an uncontrolled kernel write or double-panics instead of writing the core. Severity: Low (panic-path robustness / hardening); the write primitive itself is real and demonstrated.

Fix

fix.diff (three layers): 1. is_dumpable(pdp[i] & PG_FRAME) guard before :260 (skip the whole slot); 2. is_dumpable(pd[j] & PG_FRAME) guard before :281 (skip the 2MB block β€” also removes the PHYS_TO_DMAP read of the bogus frame at :285); 3. bounds check pa < (vm_page_dump_size/8)*64 in dump_add_page/dump_drop_page (defense in depth; also fixes the theoretical int idx truncation for pa >= 2^49).

Validated on a patched kernel: same rig β†’ walk skips the planted entry β†’ "Dump complete", normal reboot, savecore extracts the core.

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

fixed
baseline reproduced→ patch + rebuild →patched reproduced

Applied fix.diff in the guest /usr/src, rebuilt and rebooted into the patched kernel; the identical booby run now skips the planted entry ('is_dumpable(frame)==0') and completes the dump normally instead of Fatal-trap-12ing - bad behavior gone.

run.patched.log: 'Dumping 668 MB ... Dump complete' on the patched kernel with pdp[64]=0x800000000000|V planted (baseline: Fatal trap 12 + nested panic + no dump)
↓ fix.diffper-fix-DF-5536

Confirmed kernel references

Detail

Exploit chain

requires prior kernel memory corruption (attacker already ring-0): plant PDP/PD entry with chosen frame -> panic -> dump walk calls dump_add_page(frame) -> atomic_set_long at vm_page_dump + frame/32KB (chosen offset, bit-OR, 8-byte aligned) -> silent kernel-memory corruption or page fault; demonstrated both outcomes

Evidence (decisive lines)

panic.txt: Fatal trap 12, fault VA 0xfffff80140000000 matching the rig-predicted &vm_page_dump[0x20000000], 'supervisor write data, page not present', 'panic: page fault', no dump. panic.2-silent-corruption.txt: frame 2^46 -> nested 'panic: assertion obj != NULL failed in vm_object_hold_shared'. run.patched.log: patched kernel, identical plant -> 'Dump complete'.

PoC changes

PoC authored fresh: KLD rig plants pdp[64]=frame|V in an unused kernel PDP slot (chosen far beyond kernel_vm_end after a first attempt in the first-free slot was disturbed by panic-time allocations) and panics via sysctl trigger.

Verified recommended fix

is_dumpable() guards on the PD/PT frames at :260/:281 (before any use, incl. the DMAP reads) plus a bitmap bounds check in dump_add_page/dump_drop_page (also fixes int idx truncation for pa >= 2^49).

Verdict

The dump walk uses kernel page-table frames without validation: dump_add_page(pdp[i]&PG_FRAME) at :260 and dump_add_page(pd[j]&PG_FRAME) at :281 are not is_dumpable-guarded (unlike the leaf paths at :271/:291), dump_add_page/dump_drop_page (:452/:463) have no bitmap bounds check, and :263/:285 DMAP-read the same raw frames. Demonstrated on the live guest by planting a single fake PDP entry: frame 2^47 yields 'Fatal trap 12: page fault while in kernel mode, fault virtual address = 0xfffff80140000000 (= vm_page_dump[frame>>18]), supervisor write data' -> nested panic, wedged guest, no dump; frame 2^46 silently bit-ORs a live kernel object 2GB past the bitmap and crashes later with vm_object_hold_shared(NULL). Precondition is kernel page-table corruption (post-exploitation or the original crashing bug), so no privilege boundary is crossed - severity Low - but the dump writer turns corrupted PTEs into an uncontrolled kernel write / loss of the crash dump exactly when forensics matter.