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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5340 Β· 11 files
FileTypeDescriptionSize
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).

VERDICT.md
↓ download raw

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-supplied MODINFOMD_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).

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_testable
baseline no→ patch + rebuild →patched clean

fix.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
↓ fix.diffper-fix-DF-5340

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.