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)
PoC verification
Evidence pack
findings/poc/DF-5339 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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).
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.
4. Recommended fix
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_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-5339/fix.diff (git apply --check passes); reasoning in VERDICT.md
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).
No comments yet.