mp_bootaddress() integer underflow on degenerate base-memory size corrupts the kernel physical memory map
Summary
Unchecked u_int arithmetic on the firmware/loader-supplied first-region size: basemem <= 12KB wraps mptramp_pagetables to ~0xFFFFC000 which is laundered into physmap[1] (kernel believes the whole low 4GB incl. MMIO holes is RAM); basemem 13-16KB lands the AP page tables on the IVT/BDA at PA 0, mutually corrupting the warm-reset vector. Guest arithmetic reproducer (verbatim fn, real bootMP_size): wrapped/IVT-smash rows proven, 638 row matches this guest's real SeaBIOS basemem. Firmware/boot-chain threat class (same as accepted DF-1034); stock QEMU cannot express the trigger so boot manifestation honestly untested. Fail-fast-lower-bound fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5326 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.9 KB | β raw | |
| VERDICT.md | β | 5.9 KB | β raw | |
| mp_bootaddr_underflow.c | β | 2.8 KB | view raw | |
| build.sh | β | 252 B | view raw | |
| run.sh | β | 119 B | view raw | |
| build.log | β | 26 B | view raw | |
| run.log | β | 1.4 KB | view raw | |
| env.txt | β | 410 B | view raw | |
| fix.diff | β | 1.2 KB | view raw | |
| manifest.json | β | 1.4 KB | view raw | |
| verdict.json | β | 4.4 KB | view raw |
DF-5326 β mp_bootaddress() integer underflow (Low)
sys/platform/pc64/x86_64/mp_machdep.c:206-219 β mp_bootaddress(basemem)
performs unchecked 32-bit unsigned arithmetic on the firmware/loader-supplied
size of the first usable memory region. For basemem <= 12 (KB),
mptramp_pagetables = boot_address - 3*PAGE_SIZE wraps to ~0xFFFFC000-0xFFFFF000
and is stored into physmap[1] (machdep.c:2272), making the kernel believe the
entire low 4GB (incl. MMIO/ROM holes) is usable RAM. For basemem 13-16 it
returns 0 and the AP trampoline page tables are written over the IVT/BDA.
Threat: malicious/compromised firmware or boot chain (loader-collected BIOS E820/EFI map). Boot-time only; no unprivileged runtime surface.
Reproduce (arithmetic proof on the audit guest)
The guest is DragonFly 6.5-DEVELOPMENT X86_64_GENERIC (see env.txt). Push the reproducer to the guest, build with the guest cc, run:
B64=$(openssl base64 -A -in mp_bootaddr_underflow.c)
./dfbsd-qemu/vm.sh run_root "echo $B64 | openssl base64 -d -A > /tmp/mpb.c && \
cc -O2 -Wall -Wextra -o /tmp/mpb /tmp/mpb.c && echo BUILD_OK && /tmp/mpb"
(Or on any Unix host with a C compiler: cc -O2 -o mp_bootaddr_underflow
mp_bootaddr_underflow.c && ./mp_bootaddr_underflow.)
Expected
BUILD_OK
bootMP_size = 170 (0xaa) bytes [from mptramp_end-mptramp_start]
...
0 0xfffff000 0xffffc000 WRAPPED: physmap[1] ~= 4GB -> whole low 4GB marked RAM
13 0x00003000 0x00000000 ZERO: trampoline PTs smash IVT/BDA at PA 0
17 0x00004000 0x00001000 sane
638 0x0009f000 0x0009c000 sane
basemem β€ 12 β WRAPPED; 13β16 β ZERO (PA 0); β₯ 17 β sane. The 638 row is the value a real SeaBIOS produces on this guest (trampoline at 0x9F000).
The kernel-boot manifestation (physmap corruption) is NOT exercised: the trigger needs a crafted SMAP which stock QEMU/SeaBIOS cannot express (see VERDICT.md Β§3).
DF-5326 β mp_bootaddress() integer underflow corrupts the kernel physical memory map
Verdict: defect certain at code level; arithmetic reproduced on the audited guest; end-to-end kernel-boot manifestation untested (boot-time trigger requires control of the loader-provided BIOS SMAP, which the standard QEMU/SeaBIOS guest cannot express)
1. Root cause
sys/platform/pc64/x86_64/mp_machdep.c:206-219:
u_int
mp_bootaddress(u_int basemem)
{
bootMP_size = mptramp_end - mptramp_start;
boot_address = trunc_page(basemem * 1024); /* round down to 4k boundary */
if (((basemem * 1024) - boot_address) < bootMP_size)
boot_address -= PAGE_SIZE; /* not enough, lower by 4k */
/* 3 levels of page table pages */
mptramp_pagetables = boot_address - (PAGE_SIZE * 3);
return mptramp_pagetables;
}
basemem is the size in KB of the first usable memory region reported by the
firmware, laundered through the boot chain:
sys/platform/pc64/x86_64/machdep.c:2270βbase_memory = physmap[1] / 1024;wherephysmap[]is built atmachdep.c:2256-2260from the loader-suppliedMODINFOMD_SMAP(BIOS INT15 E820) or EFI memory map (add_smap_entries()/add_efi_map_entries()).sys/platform/pc64/x86_64/machdep.c:2272βphysmap[1] = mp_bootaddress(base_memory);
There is no lower-bound validation on basemem anywhere on this path. For a
degenerate first usable region the unsigned 32-bit arithmetic wraps:
basemem <= 12KB βmptramp_pagetables = boot_address - 0x3000wraps to0xFFFFD000-0xFFFFC000, and that wrapped value is written intophysmap[1]β the kernel now believes the first usable RAM region is[0, ~0xFFFFC000), i.e. the entire low 4GB including all PCI/MMIO/ROM holes is RAM.Maxmem/physmem accounting (machdep.c:2285+), vm_page probing and early allocations are then derived from a bogus map β kernel memory corruption / unbootable system.basemem13β16 KB βmptramp_pagetables == 0: the three AP-initial page table pages written bystart_all_aps()(mp_machdep.c:414-438,pt4/pt3/pt2) land on the real-mode IVT/BDA at PA 0, and the warm-reset vector written atmp_machdep.c:446(WARMBOOT_OFF == KERNBASE+0x467) is written inside thept4page β mutual corruption, AP startup undefined.- Secondary (theoretical, needs a β₯4TB first region):
basemem = physmap[1]/1024truncates tou_intandbasemem * 1024itself can wrap inu_int.
2. Reproduction (arithmetic level, on the audited guest)
mp_bootaddr_underflow.c copies the function verbatim (u_int semantics
preserved) and uses the real bootMP_size from the running audited kernel
(nm /boot/kernel/kernel: mptramp_start=0xffffffff8113b8c0,
mptramp_end=0xffffffff8113b96a β 170 bytes). Built and executed on the guest
with the stock cc 8.3 [DragonFly]. Output (run.log):
basemem(KB) boot_address mptramp_pagetables effect on physmap[1]
0 0xfffff000 0xffffc000 WRAPPED: physmap[1] ~= 4GB -> whole low 4GB marked RAM
1 0x00000000 0xffffd000 WRAPPED ...
...
12 0x00002000 0xfffff000 WRAPPED ...
13 0x00003000 0x00000000 ZERO: trampoline PTs smash IVT/BDA at PA 0
...
16 0x00003000 0x00000000 ZERO ...
17 0x00004000 0x00001000 sane
638 0x0009f000 0x0009c000 sane <- real SeaBIOS value on this guest
The sane row (638 KB) matches the classic trampoline placement (0x9F000 / PTs at 0x9C000), confirming the reproducer models the live code path.
3. Why the kernel-boot manifestation was not exercised
Triggering the real code path requires the loader to hand the kernel a SMAP whose first usable region ends below ~17 KB. On the audit guest:
- the SMAP is generated by SeaBIOS at loader boot time (fixed 0β639KB layout);
QEMU exposes no interface to shrink the first usable region below 640 KB
(
etc/e820fw_cfg only adds reserved entries); - the SMAP blob is collected by the loader from the BIOS at loader runtime β it is not read from any file an in-guest attacker can write;
- marking low memory reserved via fw_cfg moves the first usable region
start, not its end β
physmap[1]stays β639 KB, sobasememstays sane.
So the only realistic carriers of the trigger are malicious/compromised firmware or a tampered boot chain (boot area / loader binary), which matches the Low severity and the same threat class as the accepted DF-1034.
4. Impact ceiling
Boot-time kernel memory-map corruption from crafted firmware/boot chain:
unbootable machine at best; at worst a physmem map that hands out MMIO/ROM
pages as RAM (silent memory corruption). No runtime (post-boot), unprivileged,
or remote reachability β the function runs exactly once, early in init386().
5. Fix
fix.diff (authored against the read-only sys/ tree, never applied there):
fail fast on a degenerate basemem instead of silently wrapping β consistent
with the tree's existing boot-time panics on malformed firmware tables (e.g.
madt_lapic_pass1() panics on a failed MADT walk). Not kernel-build-validated
(finding is Low / boot-time / firmware-gated; fix validation reserved for
memory-corruption findings that reproduce).
6. Artifacts
mp_bootaddr_underflow.cβ verbatim-logic reproducer (guest-compiled)build.sh/run.shβ exact commandsrun.logβ full guest build+run output (BUILD_OK + table)env.txtβ guest uname, dmesg SMP path, kernel symbols, cc versionfix.diffβ proposed fix
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
not_testablefix.diff (boot-time panic on degenerate basemem, mirroring the tree's existing panics on malformed firmware tables) is authored against the read-only sys/ tree and passes git apply --check, but could not be kernel-build-validated: the bug's kernel manifestation cannot be armed on this guest (needs crafted SMAP), so there is no baseline to compare a patched kernel against.
findings/poc/DF-5326/fix.diff (git apply --check -p1 passes); reasoning in VERDICT.md section 5
Confirmed kernel references
Detail
Evidence (decisive lines)
findings/poc/DF-5326/run.log β guest-compiled reproducer output: basemem 0/1/2/4/8/9/12 -> 0xffffc000..0xfffff000 (WRAPPED), 13-16 -> 0x00000000 (PTs at PA 0), 17/32/638 -> sane; the 638 KB row equals the real SeaBIOS basemem on this guest. VERDICT.md section 1 traces basemem from physmap[1] (machdep.c:2270) through the wrap and back into physmap[1] (machdep.c:2272); fix.diff applies cleanly (git apply --check).
PoC changes
No seed PoC existed; authored from scratch. Reproducer copies mp_bootaddress() verbatim (u_int wrap semantics preserved) and parameterizes bootMP_size with the real value (mptramp_end-mptramp_start = 170) taken from the running guest kernel's symbols.
Verified recommended fix
panic() in mp_bootaddress() when basemem1024 < bootMP_size + 4PAGE_SIZE (fail fast on degenerate firmware memory map) β see fix.diff
Verdict
Defect is certain at code level and its arithmetic is proven on the audited guest: mp_bootaddress() (sys/platform/pc64/x86_64/mp_machdep.c:206-219) performs unchecked u_int arithmetic on the firmware/loader-supplied size of the first usable memory region; for basemem <= 12 KB, mptramp_pagetables = boot_address - 3*PAGE_SIZE wraps to 0xFFFFC000-0xFFFFF000 and is stored into physmap[1] (machdep.c:2272), making the kernel treat the whole low 4GB (incl. MMIO/ROM holes) as usable RAM; for basemem 13-16 KB it returns 0 so the AP trampoline page tables (mp_machdep.c:414-438) are written over the real-mode IVT/BDA at PA 0. The verbatim-logic reproducer, parameterized with the real bootMP_size (170 bytes) from the guest kernel symbols, was compiled with the guest cc and produced exactly the predicted wrap/zero/sane table (run.log). The end-to-end kernel-boot manifestation is UNTESTED: arming it requires a crafted SMAP (first usable region ending below ~17 KB), which stock QEMU/SeaBIOS cannot express (etc/e820 fw_cfg only adds reserved regions) and which no unprivileged in-guest actor can influence. Low severity, boot-time only, malicious-firmware/boot-chain threat class (same as accepted DF-1034).
No comments yet.