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)
PoC verification
Evidence pack
findings/poc/DF-5536 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 β 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 byis_dumpable()(unlike the leaf-PFN paths at:271-272and:291-292).dump_add_page()(:452-460) /dump_drop_page()(:463-471) performatomic_set_long(&vm_page_dump[idx], 1ul << bit)withidx = pa >> 18and no bounds check againstvm_page_dump_size(bitmap covers only physical RAM, vm/vm_page.c:398-403).:263/:285then doPHYS_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:
- frame = 2^46 (
panic.txt): write targetvm_page_dump[2^28] = 0xfffff800c0000000(bitmap+2 GB, mapped kernel memory) β silent bit-OR into a live kernel object β nestedpanic: assertion "obj != NULL" failed in vm_object_hold_sharedβ guest wedged, no dump. - frame = 2^47 (
panic.2β direct attribution): predicted targetvm_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 faultThe fault VA matches the rig's printed&vm_page_dump[frame>>18]byte-for-byte and the fault is a supervisor write β i.e. theatomic_set_longindump_add_pagecalled 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
fixedApplied 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)
Confirmed kernel references
- sys/platform/pc64/x86_64/minidump_machdep.c:260
- sys/platform/pc64/x86_64/minidump_machdep.c:263
- sys/platform/pc64/x86_64/minidump_machdep.c:281
- sys/platform/pc64/x86_64/minidump_machdep.c:285
- sys/platform/pc64/x86_64/minidump_machdep.c:452
- sys/platform/pc64/x86_64/minidump_machdep.c:463
- sys/vm/vm_page.c:399
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.
No comments yet.