# 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`:

```c
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`:

```c
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:

1. `lapic_set_cpuid(0, fake_apic_id)` runs for each fake →
   **`cpu_id_to_apic_id[0]` is clobbered to `bsp_id + logical_cpus - 1`**
   (last fake wins; arrays at `lapic.c:122-123`), and every IPI addressed to
   CPU 0 goes to a wrong/phantom APIC id from then on;
2. the fake returns 0 (not registered), but the caller's unconditional
   `arg->cpu++` **burns the CPU slot anyway** — the slot keeps its BSS value
   `cpu_id_to_apic_id[slot] == 0`;
3. `start_all_aps()` (`mp_machdep.c:482-505`) SIPIs `CPUID_TO_APICID(x)` for
   `x = 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 on
   `smp_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.
