mptable_search() base-memory scan: (base_memory-1)*1024 u_int underflow β KKASSERT(target!=0) boot panic (basemem==1) or mapdev scan of the top-of-4GB MMIO hole (basemem==0)
Summary
Same degenerate-SMAP input class DF-5326 proved: basemem==1 wraps to target 0 -> KKASSERT panic; basemem==0 wraps to 0xFFFFFC00 and pmap_mapdev (accepts ANY pa) scans the top-of-4GB MMIO hole with a span crossing the 4GB wrap. Guest verbatim-arithmetic reproducer both cases. Skip-degenerate-branch fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5340 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| basemem_scan_underflow.c | β | 3.4 KB | view raw | |
| build.sh | β | 126 B | view raw | |
| run.sh | β | 105 B | view raw | |
| README.md | β | 770 B | β raw | |
| VERDICT.md | β | 4.3 KB | β raw | |
| build.log | β | 10 B | view raw | |
| run.log | β | 990 B | view raw | |
| env.txt | β | 190 B | view raw | |
| fix.diff | β | 750 B | view raw | |
| manifest.json | β | 880 B | view raw | |
| verdict.json | β | 3.7 KB | view raw |
DF-5340 β (base_memory-1)*1024 underflow in mptable_search base-memory scan (DF-5326 class)
Boot-time (firmware/loader-gated) defect in sys/platform/pc64/x86_64/mptable.c.
The kernel-boot manifestation cannot be armed on the standard QEMU/SeaBIOS guest
(no MP-table injection path, no unprivileged in-guest trigger), so per the
DF-5326 precedent this pack carries a verbatim-logic reproducer that was
built and executed ON the audited guest and proves the exact code-level
divergence (see run.log). Kernel impact classification: untested.
Build (guest or host)
cc -O2 -Wall -Wextra -o basemem_scan_underflow basemem_scan_underflow.c
Run
./basemem_scan_underflow
Expected
See run.log (decisive lines quoted in verdict.json:evidence).
DF-5340 β (base_memory - 1) * 1024 underflow in the mptable base-memory scan (DF-5326 class)
Verdict: defect certain at code level; arithmetic proven on the audited guest; kernel-boot manifestation untested (needs crafted loader SMAP + empty EBDA; strictly dominated in blast radius by DF-5326, but a distinct unguarded instance in this file)
1. Root cause
sys/platform/pc64/x86_64/mptable.c:270-301, mptable_search():
if (ebda_addr != 0) {
target = (u_int32_t)ebda_addr;
if ((x = mptable_search_sig(target, 1024 / 4)) > 0)
return x;
} else {
/* last 1K of base memory, effective 'top of base' passed in */
target = (u_int32_t)((base_memory - 1) * 1024); /* mptable.c:284 */
if ((x = mptable_search_sig(target, 1024 / 4)) > 0)
return x;
}
and mptable_search_sig():
KKASSERT(target != 0); /* mptable.c:368 */
map_size = count * sizeof(u_int32_t);
addr = pmap_mapdev((vm_paddr_t)target, map_size); /* mptable.c:371 */
Inputs (both firmware/loader-controlled, no lower bound anywhere):
base_memory(u_int, KB) =physmap[1] / 1024βmachdep.c:2270, built from the loader-suppliedMODINFOMD_SMAP/EFI map. This is exactly the input DF-5326 proved can be degenerate (findings/poc/DF-5326/).ebda_addr=*(u_short *)(KERNBASE + 0x40e) << 4βmachdep.c:2275-2276; the else-branch is reached when the BDA EBDA pointer is zero.
Unsigned consequences in u_int:
| base_memory | (base_memory-1)*1024 (mod 2^32) |
effect |
|---|---|---|
| 0 | 0xFFFFFC00 | pmap_mapdev(0xFFFFFC00, 1024) β top-of-4GB MMIO hole; span 0xFFFFFC00+0x400 wraps past 4GB; the intended base-memory scan never happens; reads garbage (0xFF on QEMU, machine-check-class on some real HW) |
| 1 | 0x00000000 | KKASSERT(target != 0) fails β boot-time kernel panic (INVARIANTS kernels; X86_64_GENERIC:56) |
| β₯ 2 | intended last-1K-of-base region | correct |
pmap_mapdev_attr() (pmap.c:6192-6218) validates nothing β it installs PTEs
for ANY physical address.
2. Reproduction (verbatim arithmetic, on the audited guest)
basemem_scan_underflow.c copies the else-branch and the KKASSERT verbatim.
run.log (guest cc 8.3):
base_memory=0 -> target=0xfffffc00 scan [fffffc00..00000000]: WRAPPED past 4GB, top-of-4GB MMIO hole, not base memory base_memory=1 -> target=0x00000000 KERNEL PANIC -> assertion "target != 0" failed in mptable_search_sig at mptable.c:368 base_memory=2 -> target=0x00000400 scan [00000400..00000800]: sane region ... base_memory=639 -> target=0x0009f800 scan [0009f800..0009fc00]: sane region
The 639 row is the real SeaBIOS value on this guest class β healthy firmware
never trips this; a degenerate first usable region (physmap[1] < 2048) does.
Note DF-5326's mp_bootaddress(base_memory) fires on the same input and
corrupts physmap[1] itself, so on a real system both defects manifest
together; this file's instance is reported separately because it is a distinct
function with a distinct sink (mapdev/KKASSERT vs physmap corruption) and is
fixed independently.
3. Why kernel-boot manifestation is untested
Arming requires a loader-supplied SMAP whose first usable region ends below
2048 bytes AND a zero BDA EBDA pointer. Stock QEMU/SeaBIOS builds neither
(etc/e820 fw_cfg only adds reserved regions); no unprivileged in-guest actor
reaches mptable_search() β it runs exactly once at SI_BOOT2_PRESMP
(mptable.c:265).
4. Recommended fix
fix.diff β only take the base-memory branch when base_memory > 1, leaving
the BIOS-area scans as the fallback (a system with < 2 KB of base memory has
no MP floating pointer worth finding).
5. Fix validation
git apply --check passes. Patched-kernel boot comparison is not
constructible on this guest (same injection constraint), hence
fix_status: not_testable.
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 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 injected firmware tables), so there is no baseline to compare a patched kernel against.
findings/poc/DF-5340/fix.diff (git apply --check passes); reasoning in VERDICT.md
Confirmed kernel references
Detail
Evidence (decisive lines)
run.log β 'base_memory=1 -> target=0x00000000 KERNEL PANIC (assertion target != 0, mptable.c:368)'; 'base_memory=0 -> target=0xfffffc00 ... WRAPPED past 4GB'; basemem 2/16/511/638/639 -> sane scan regions. fix.diff guards the branch with base_memory > 1.
PoC changes
No seed PoC existed; authored from scratch. Reproducer copies the cited mptable.c logic verbatim (u_int/u_char semantics preserved); KKASSERT is modeled as a kernel-panic path (setjmp-resumable in the harness), and pmap_mapdev/MMIO access is elided where userland cannot express it (clearly marked in the source header).
Verified recommended fix
skip the last-1K base-memory scan when base_memory <= 1 (mptable.c:282-287)
Verdict
Defect is certain at code level and its arithmetic is proven on the audited guest: when the EBDA pointer is absent (ebda_addr==0, read from BDA 0x40e at machdep.c:2275-2276), mptable_search() computes the 'last 1K of base memory' scan target as (u_int32_t)((base_memory-1)*1024) (mptable.c:284) where base_memory = physmap[1]/1024 (machdep.c:2270) comes straight from the loader-supplied SMAP/EFI memory map with no lower bound β the same input DF-5326 proved degenerate. base_memory==1 wraps the u_int subtraction to target==0 and mptable_search_sig() KKASSERTs target!=0 (mptable.c:368) -> boot-time panic on INVARIANTS kernels (X86_64_GENERIC). base_memory==0 wraps to target=0xFFFFFC00: pmap_mapdev() (pmap.c:6192-6218, accepts ANY pa) then maps and scans the top-of-4GB MMIO hole with a span crossing the 4GB wrap instead of base memory. The guest-compiled verbatim-arithmetic reproducer prints target=0 for basemem 1 (KKASSERT panic path modeled) and 0xfffffc00 (wrapped) for basemem 0, with 2..639 producing the intended region. End-to-end kernel-boot manifestation UNTESTED: needs a crafted loader SMAP (first usable region <= 2047 bytes) unavailable on the standard QEMU/SeaBIOS guest; strictly dominated in blast radius by DF-5326's physmap[1] corruption which fires on the same input, but a distinct unguarded instance in this file.
No comments yet.