HT-fixup synthetic processor entries inherit the BSP flag: lapic_set_cpuid(0, fake_id) clobbers cpu_id_to_apic_id[0] and unconditionally burned CPU slots leave phantom SIPI targets β SMP boot hang / permanent cpu-0 IPI mis-addressing
Summary
Fake-entry synthesis copies ent->cpu_flags INCLUDING BP into synthetic entries (the fixup engages exactly on under-reporting tables, so the BSP entry derives fakes) - BSP-derived fakes take processor_entry's BSP branch: lapic_set_cpuid(0, fake_id) clobbers the real cpu0 mapping, the fake never registers, burned slots leave phantom ids -> start_all_aps SIPIs phantoms (boot hang) and later IPIs to cpu0 are mis-addressed. Guest verbatim-logic reproducer both scenarios. strip-BP-bit + advance-on-registration fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5343 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| htfix_bp_flag_inherit.c | β | 5.7 KB | view raw | |
| build.sh | β | 124 B | view raw | |
| run.sh | β | 104 B | view raw | |
| README.md | β | 780 B | β raw | |
| VERDICT.md | β | 4.8 KB | β raw | |
| build.log | β | 10 B | view raw | |
| run.log | β | 1.0 KB | view raw | |
| env.txt | β | 190 B | view raw | |
| fix.diff | β | 691 B | view raw | |
| manifest.json | β | 1.1 KB | view raw | |
| verdict.json | β | 4.1 KB | view raw |
DF-5343 β HT-fixup fake processor entries inherit the BSP flag -> cpu0 APIC mapping clobbered, boot hang
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 htfix_bp_flag_inherit htfix_bp_flag_inherit.c
Run
./htfix_bp_flag_inherit
Expected
See run.log (decisive lines quoted in verdict.json:evidence).
DF-5343 β HT-fixup fake processor entries inherit the BSP flag β cpu_id_to_apic_id[0] clobbered β boot hang
Verdict: defect certain at code level; mapping corruption proven on the audited guest with verbatim pass2/processor_entry logic; kernel-boot manifestation untested (needs an under-reporting MP table on HT hardware booted via mptable)
1. Root cause
mptable_lapic_pass2_callback() β sys/platform/pc64/x86_64/mptable.c:691-710:
bzero(&proc, sizeof(proc));
proc.type = 0;
proc.cpu_flags = (force_enable) ? PROCENTRY_FLAG_EN : ent->cpu_flags;
/* mptable.c:702 β copies BP along with EN */
proc.apic_id = ent->apic_id;
for (i = 1; i < arg->logical_cpus; i++) {
proc.apic_id++;
processor_entry(&proc, arg->cpu);
arg->cpu++; /* mptable.c:708 β unconditional */
}
processor_entry() β mptable.c:556-577:
if (!(entry->cpu_flags & PROCENTRY_FLAG_EN))
return 0;
if (entry->cpu_flags & PROCENTRY_FLAG_BP) {
lapic_set_cpuid(0, entry->apic_id); /* mptable.c:566 β CPU 0 remapped! */
return 0; /* never registers in its slot */
} else if (cpu < MAXCPU) {
lapic_set_cpuid(cpu, entry->apic_id);
return 1;
}
Every MP table must contain a BSP entry (mptable_lapic_probe requires it,
mptable.c:876-882), and that entry is necessarily EN|BP. The HT fixup
engages precisely on under-reporting tables β the cpu_count == 1
single-BSP case (mptable.c:433-435) is the canonical one β so the fake
entries synthesized from the BSP entry carry the BP bit, take the BSP branch
of processor_entry(), and:
lapic_set_cpuid(0, fake_apic_id)runs for each fake βcpu_id_to_apic_id[0]is clobbered tobsp_id + logical_cpus - 1(last fake wins; arrays atlapic.c:122-123), and every IPI addressed to CPU 0 goes to a wrong/phantom APIC id from then on;- the fake returns 0 (not registered), but the caller's unconditional
arg->cpu++burns the CPU slot anyway β the slot keeps its BSS valuecpu_id_to_apic_id[slot] == 0; start_all_aps()(mp_machdep.c:482-505) SIPIsCPUID_TO_APICID(x)forx = 1..napsβ which now includes slots mapped to apic id 0 (the BSP itself) or phantom ids β the AP never checks in β boot spins forever onsmp_startup_mask(mp_machdep.c:595).
The force_enable arm of the ternary (PROCENTRY_FLAG_EN only, no BP) shows
the intended semantics for fakes β the non-force arm simply forgot to strip
the flag.
2. Reproduction (verbatim logic, on the audited guest)
htfix_bp_flag_inherit.c copies processor_entry(), the fake-entry loop and
lapic_set_cpuid() verbatim over the kernel's BSS arrays. run.log:
scenario 1: table = { BSP id0 }, HTT, lcpus_max=2 -> logical_cpus=2
after fake loop: arg->cpu=2 (slot 1 burned, nothing registered)
cpu->apic: [0]=1 [1]=0 [2]=0 [3]=0 naps=1
start_all_aps SIPI targets: cpu1->apic0(== BSP id, phantom: never starts)
RESULT: cpu0 remapped to apic 1 (BSP id clobbered); slot 1 -> BSS 0;
start_all_aps SIPIs apic 0 for cpu1 -> mask never completes -> BOOT HANG
scenario 2: table = { BSP id0, AP id2 }, logical_cpus=2 -> naps=3
cpu->apic: [0]=1 [1]=0 [2]=2 [3]=3 naps=3
RESULT: cpu0 -> apic 1 (clobbered), cpu1 -> BSS 0 (unregistered slot)
scenario 3 (intent/fix): force_enable=1 -> fakes carry EN only
cpu->apic: [0]=0 [1]=1 [2]=2 [3]=3 naps=3
RESULT: cpu0 -> apic 0 (correct), cpu1 -> apic 1, cpu2 -> apic 2, cpu3 -> apic 3
Scenario 3 doubles as the fix validation: identical inputs, fakes without the
BP bit and registration-gated cpu++ produce the correct 0/1/2/3 mapping.
3. Threat model / why kernel-boot manifestation is untested
Engages on any mptable-enumerated (non-ACPI) HT system whose firmware table
under-reports logical CPUs β buggy-but-benign legacy firmware or crafted
malicious tables (same class as DF-1034). The standard guest boots via ACPI
MADT, so the path does not run there, and there is no runtime or unprivileged
reach (SI_BOOT2_PRESMP/SI_BOOT2_LAPIC one-shot).
4. Recommended fix
fix.diff β synthetic logical CPUs are never the BSP: strip
PROCENTRY_FLAG_BP from the copied flags, and advance arg->cpu only when
processor_entry() actually registered the fake.
5. Fix validation
git apply --check passes; scenario 3 of the reproducer validates the fix's
semantics at logic level. Patched-kernel A/B boot test not constructible on
this guest (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-5343/fix.diff (git apply --check passes); reasoning in VERDICT.md
Confirmed kernel references
- sys/platform/pc64/x86_64/mptable.c:700
- sys/platform/pc64/x86_64/mptable.c:702
- sys/platform/pc64/x86_64/mptable.c:705
- sys/platform/pc64/x86_64/mptable.c:707
- sys/platform/pc64/x86_64/mptable.c:708
- sys/platform/pc64/x86_64/mptable.c:565
- sys/platform/pc64/x86_64/mptable.c:566
- sys/platform/pc64/x86_64/mp_machdep.c:482
- sys/platform/pc64/x86_64/mp_machdep.c:595
- sys/platform/pc64/apic/lapic.c:1213
Detail
Evidence (decisive lines)
run.log β scenario 1: 'cpu->apic: [0]=1 [1]=0 ... cpu1->apic0(== BSP id, phantom: never starts) -> BOOT HANG'; scenario 2: '[0]=1 [1]=0 [2]=2 [3]=3'; scenario 3 (force_enable): '[0]=0 [1]=1 [2]=2 [3]=3 (correct)'.
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
clear PROCENTRY_FLAG_BP on synthetic entries and gate arg->cpu++ on registration (mptable.c:702-709)
Verdict
Defect is certain at code level; mapping corruption proven on the audited guest with verbatim logic. mptable_lapic_pass2_callback() synthesizes fake logical CPUs with proc.cpu_flags = ent->cpu_flags (mptable.c:702) and advances arg->cpu unconditionally (mptable.c:705-709). The BSP entry is mandatory in every table, is necessarily EN|BP, and the fixup engages precisely on under-reporting tables (cpu_count==1 single-BSP case, mptable.c:433-435, or spaced ids), so its fake entries carry the BP bit, take processor_entry()'s BSP branch (mptable.c:565-568) and call lapic_set_cpuid(0, fake_apic_id) β cpu_id_to_apic_id[0] is clobbered to bsp_id+logical_cpus-1, the fake never registers in its slot, and the burned slot later reads apic id 0 from BSS. Reproducer scenario 1 (table = {BSP id0}, lcpus_max=2): cpu0->apic1, slot1 unregistered, naps=1 -> start_all_aps SIPIs CPUID_TO_APICID(1)=0 (the BSP itself) -> smp_startup_mask never completes -> BOOT HANG (mp_machdep.c:595); all IPIs to cpu0 are mis-addressed thereafter. Scenario 2 (ids {0,2}, naps=3) shows the same clobber plus a skipped slot. Scenario 3 with force_enable=1 (fakes get EN only) produces the correct 0/1/2/3 mapping β proving both the intent of the ternary and the fix. Engages on ANY mptable (non-ACPI) HT system whose fixup activates with default tunables; end-to-end kernel manifestation UNTESTED (needs crafted/legacy firmware table on HT hardware; the standard guest boots via ACPI MADT).
No comments yet.