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

probe/enumerate BSP-validation asymmetry: disabled second BSP entry passes probe then trips KKASSERT(!found_bsp) β€” deterministic boot-time kernel panic

Summary

probe skips DISABLED entries before its duplicate-BSP check but pass2 does the bookkeeping with NO enabled check and KKASSERTs on the second BP-flagged entry regardless of EN: [EN|BP][BP] passes probe and panics enumerate (INVARIANTS is in the stock GENERIC). Guest verbatim-logic reproducer: probe PASS then pass2 PANIC. Same firmware threat class as DF-1034/DF-5326. Mirror-probe-semantics fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5339 Β· 11 files
FileTypeDescriptionSize
bsp_asymmetry.c β€” 4.5 KB view raw
build.sh β€” 108 B view raw
run.sh β€” 96 B view raw
README.md β€” 730 B ↓ raw
VERDICT.md β€” 4.5 KB ↓ raw
build.log β€” 10 B view raw
run.log β€” 526 B view raw
env.txt β€” 190 B view raw
fix.diff β€” 482 B view raw
manifest.json β€” 863 B view raw
verdict.json β€” 3.7 KB view raw

DF-5339 β€” probe/enumerate BSP-validation asymmetry -> boot-time KKASSERT panic

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 bsp_asymmetry bsp_asymmetry.c

Run

./bsp_asymmetry

Expected

See run.log (decisive lines quoted in verdict.json:evidence).

VERDICT.md
↓ download raw

DF-5339 β€” probe/enumerate BSP-validation asymmetry β†’ boot-time KKASSERT panic

Verdict: defect certain at code level; state machine proven on the audited guest; kernel-boot manifestation untested (needs crafted firmware MP table; standard QEMU/SeaBIOS guest cannot inject one)

1. Root cause

Two callbacks validate the same MP-table processor entries with different rules:

mptable_lapic_probe_callback() β€” sys/platform/pc64/x86_64/mptable.c:818-845:

if ((ent->cpu_flags & PROCENTRY_FLAG_EN) == 0)
    return 0;           /* mptable.c:827-829: disabled entries skipped ... */
arg->cpu_count++;
...
if (ent->cpu_flags & PROCENTRY_FLAG_BP) {   /* ...BEFORE the BSP bookkeeping */
    if (arg->found_bsp)
        return EINVAL;      /* mptable.c:838-841: duplicate-BSP check,
                       but only ENABLED entries ever reach it */
    arg->found_bsp = 1;
}

mptable_lapic_pass2_callback() β€” sys/platform/pc64/x86_64/mptable.c:674-686:

if (ent->cpu_flags & PROCENTRY_FLAG_BP) {   /* NO EN check */
    KKASSERT(!arg->found_bsp);      /* mptable.c:684 β€” fires on the SECOND
                       BP-flagged entry, enabled or not */
    arg->found_bsp = 1;
}

mptable_lapic_probe() runs first and gates whether the mptable enumerator is selected (mptable.c:847-887, lapic_config() at lapic.c:1163-1179). mptable_lapic_enumerate() then re-walks the table with pass1/pass2.

2. Trigger and manifestation

MP configuration table:

entry cpu_flags apic_id
#0 EN | BP 0
#1 BP (EN clear) 1
  • probe: entry #1 is disabled β†’ skipped before the BP check β†’ cpu_count=1, found_bsp=1, no duplicate seen β†’ probe passes, mptable enumerator selected.
  • enumerate pass2: entry #0 sets found_bsp=1; entry #1 carries the raw BP bit β†’ KKASSERT(!arg->found_bsp) fails β†’ kernel panic at boot.

KKASSERT is compiled in whenever INVARIANTS is configured (sys/sys/systm.h:94-101); the stock kernel enables it (sys/config/X86_64_GENERIC:56, and the running guest kernel is built from that config). On non-INVARIANTS kernels the assert is compiled out and the entry is otherwise ignored (processor_entry skips disabled entries, mptable.c:561-562) β€” no effect.

Threat model: MP table bytes live in firmware-provided EBDA/base/BIOS memory; the scanned regions and the table contents are attacker-controlled only by malicious/compromised firmware or a compromised boot loader β€” the same class and severity bar as accepted DF-1034 (malformed INT entry boot panic) and DF-5326 (degenerate SMAP).

3. Reproduction (verbatim logic, on the audited guest)

bsp_asymmetry.c copies both callbacks' control flow verbatim (u_char flag semantics preserved; KKASSERT modeled as a resumable kernel-panic path). Built with the guest cc 8.3 [DragonFly], run.log:

== trigger: [EN|BP] [BP] (2nd disabled BSP) ==
  probe : cpu_count=1 found_bsp=1 -> PASS (mptable enumerator selected)
  pass2 : KERNEL PANIC -> assertion "!arg->found_bsp" failed in mptable_lapic_pass2_callback at mptable.c:684
== control: [EN|BP] [EN|BP] (both enabled) ==
  probe : cpu_count=2 found_bsp=1 -> EINVAL (mptable enumerator rejected, ACPI path used)
== control: [EN|BP] [EN] (healthy table) ==
  probe : cpu_count=2 found_bsp=1 -> PASS
  pass2 : completed, found_bsp=1 (no panic)

The end-to-end kernel-boot panic was NOT armed: injecting an MP table requires control of firmware-provided memory that stock QEMU/SeaBIOS does not expose (no fw_cfg path builds _MP_/PCMP structures), and there is no unprivileged in-guest path β€” the table is parsed exactly once at SI_BOOT2_PRESMP.

fix.diff β€” skip disabled entries before the BSP bookkeeping in pass2, mirroring probe semantics (processor_entry already ignores them; the KKASSERT(arg2.found_bsp) at mptable.c:802 stays satisfied because probe guarantees an enabled BSP exists, mptable.c:876-882).

5. Fix validation

fix.diff passes git apply --check against the pristine tree. A patched-kernel boot test is not constructible on this guest for the same reason the baseline is not (no table injection), 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-5339/fix.diff (git apply --check passes); reasoning in VERDICT.md
↓ fix.diffper-fix-DF-5339

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log β€” trigger table: probe PASS then 'pass2: KERNEL PANIC -> assertion "!arg->found_bsp" failed in mptable_lapic_pass2_callback at mptable.c:684'; control [EN|BP][EN|BP]: probe EINVAL; control [EN|BP][EN]: pass2 completes. VERDICT.md section 2 walks the two callbacks side by side.

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

pass2: skip disabled PROC entries before BSP bookkeeping, mirroring probe (mptable.c:683)

Verdict

Defect is certain at code level and its state machine is proven on the audited guest: mptable_lapic_probe_callback() (mptable.c:818-845) skips DISABLED processor entries BEFORE its duplicate-BSP check, while mptable_lapic_pass2_callback() (mptable.c:674-686) performs the BSP bookkeeping with NO enabled check and KKASSERTs on the second BP-flagged entry regardless of EN. An MP configuration table of [PROC entry #1 flags=EN|BP, PROC entry #2 flags=BP (disabled)] therefore PASSES probe (cpu_count=1, found_bsp=1) and then trips KKASSERT(!arg->found_bsp) at mptable.c:684 during enumerate -> deterministic boot-time kernel panic on INVARIANTS kernels (the stock X86_64_GENERIC enables INVARIANTS, sys/config/X86_64_GENERIC:56). The guest-compiled verbatim-logic reproducer prints exactly this divergence plus both controls (two enabled BSPs correctly rejected by probe; healthy table completes pass2). The end-to-end kernel-boot manifestation is UNTESTED: arming it requires a crafted MP table in firmware-provided EBDA/base/BIOS memory, which stock QEMU/SeaBIOS does not permit injecting and which no unprivileged in-guest actor can influence (same threat class and severity as accepted DF-1034 and DF-5326).