โฌข DragonFlyBSD Kernel Audit
DF-5343 / htfix_bp_flag_inherit.c
โ† back to finding โ†“ download raw
/*
 * DF-5343 โ€” HT-fixup fake processor entries inherit the BSP flag:
 * cpu_id_to_apic_id[0] is clobbered and CPU slots are skipped
 * (mptable.c:700-709 + processor_entry mptable.c:556-577).
 *
 * pass2 synthesizes fake logical CPUs:
 *
 *     proc.cpu_flags = (force_enable) ? PROCENTRY_FLAG_EN : ent->cpu_flags;
 *     proc.apic_id   = ent->apic_id;
 *     for (i = 1; i < arg->logical_cpus; i++) {
 *             proc.apic_id++;
 *             processor_entry(&proc, arg->cpu);
 *             arg->cpu++;                      <-- unconditional
 *     }
 *
 * For the (mandatory) BSP entry, ent->cpu_flags == EN|BP, so every fake
 * entry carries BP too.  processor_entry() then takes the BSP branch
 * (mptable.c:565-568):
 *
 *     if (entry->cpu_flags & PROCENTRY_FLAG_BP) {
 *             lapic_set_cpuid(0, entry->apic_id);   <-- cpu 0 remapped!
 *             return 0;
 *     }
 *
 * so cpu_id_to_apic_id[0] ends up as bsp_id + logical_cpus - 1 (last
 * fake wins), the fake never registers in its slot, and the caller's
 * unconditional arg->cpu++ burns the slot anyway.  The untouched slot
 * reads as apic id 0 from BSS, and start_all_aps() SIPIs
 * CPUID_TO_APICID(x) for x = 1..naps โ€” targeting the BSP's own id (or a
 * phantom) -> smp_startup_mask never completes -> boot hang
 * (mp_machdep.c:482-505, 595).  All IPIs to cpu 0 are also mis-addressed.
 *
 * Engages on ANY mptable (non-ACPI) HT system where the fixup activates โ€”
 * including the canonical single-processor-entry table (cpu_count==1
 * path, mptable.c:433-435) and under-reporting multi-CPU tables โ€”
 * whenever hw.lapic_force_enable is unset (default).
 *
 * processor_entry(), the fake-entry loop and lapic_set_cpuid are
 * verbatim; arrays are the kernel's BSS arrays (lapic.c:122-123).
 */
#include <stdio.h>
#include <string.h>

typedef unsigned char u_char;

#define PROCENTRY_FLAG_EN	0x01
#define PROCENTRY_FLAG_BP	0x02
#define MAXCPU			256

/* lapic.c:122-123 (BSS) */
int cpu_id_to_apic_id[256];
int apic_id_to_cpu_id[256];

static void
lapic_set_cpuid(int cpu_id, int apic_id)	/* lapic.c:1213-1217 */
{
	cpu_id_to_apic_id[cpu_id] = apic_id;
	apic_id_to_cpu_id[apic_id] = cpu_id;
}

struct PROCENTRY {
	u_char  type, apic_id, apic_version, cpu_flags;
	unsigned int cpu_signature, feature_flags, reserved1, reserved2;
};

/* ---- verbatim: processor_entry (mptable.c:556-577) ---- */
static int
processor_entry(const struct PROCENTRY *entry, int cpu)
{
	if (!(entry->cpu_flags & PROCENTRY_FLAG_EN))
		return 0;
	if (entry->cpu_flags & PROCENTRY_FLAG_BP) {
		lapic_set_cpuid(0, entry->apic_id);
		return 0;		/* its already been counted */
	}
	else if (cpu < MAXCPU) {
		lapic_set_cpuid(cpu, entry->apic_id);
		return 1;
	}
	return 0;
}

/* ---- verbatim: fake-entry synthesis of mptable_lapic_pass2_callback
 *      (mptable.c:691-710); force_enable is the global tunable ---- */
static int
pass2_fake(const struct PROCENTRY *ent, int arg_cpu, int logical_cpus,
    int force_enable)
{
	struct PROCENTRY proc;
	int i;

	bzero(&proc, sizeof(proc));
	proc.type = 0;
	proc.cpu_flags = (force_enable) ?
	    PROCENTRY_FLAG_EN : ent->cpu_flags;		/* mptable.c:702 โ€” BP leaks in */
	proc.apic_id = ent->apic_id;

	for (i = 1; i < logical_cpus; i++) {
		proc.apic_id++;
		processor_entry(&proc, arg_cpu);
		arg_cpu++;				/* mptable.c:708 โ€” unconditional */
	}
	return arg_cpu;
}

static void
dump(const char *tag, int naps)
{
	int c;

	printf("    cpu->apic: [0]=%-3d [1]=%-3d [2]=%-3d [3]=%-3d   naps=%d\n",
	    cpu_id_to_apic_id[0], cpu_id_to_apic_id[1],
	    cpu_id_to_apic_id[2], cpu_id_to_apic_id[3], naps);
	printf("    start_all_aps SIPI targets:");
	for (c = 1; c <= naps && c < 4; c++)
		printf(" cpu%d->apic%d%s", c, cpu_id_to_apic_id[c],
		    cpu_id_to_apic_id[c] == 0 ? "(== BSP id, phantom: never starts)" : "");
	printf("\n    %s\n", tag);
}

static void
reset(void)
{
	memset(cpu_id_to_apic_id, 0, sizeof(cpu_id_to_apic_id));
	memset(apic_id_to_cpu_id, 0, sizeof(apic_id_to_cpu_id));
}

#define ENT(_id, _fl) { 0, (_id), 0x14, (_fl), 0, 0, 0, 0 }

int
main(void)
{
	const struct PROCENTRY bsp = ENT(0, PROCENTRY_FLAG_EN | PROCENTRY_FLAG_BP);
	const struct PROCENTRY ap0 = ENT(2, PROCENTRY_FLAG_EN);
	int cpu;

	/* Scenario 1: canonical under-reporting HT firmware โ€” table lists
	 * ONLY the BSP (id 0); cpu_count==1 -> logical_cpus = lcpus_max = 2. */
	reset();
	printf("scenario 1: table = { BSP id0 }, HTT, lcpus_max=2 -> logical_cpus=2\n");
	printf("  (pass2: processor_entry(BSP) -> lapic_set_cpuid(0,0), cpu stays 1)\n");
	lapic_set_cpuid(0, 0);
	cpu = pass2_fake(&bsp, 1, 2, 0);		/* force_enable = 0 (default) */
	printf("  after fake loop: arg->cpu=%d (slot 1 burned, nothing registered)\n", cpu);
	dump("  RESULT: cpu0 remapped to apic 1 (BSP id clobbered); slot 1 -> BSS 0;\n"
	     "          start_all_aps SIPIs apic 0 for cpu1 -> mask never completes -> BOOT HANG", 1);

	/* Scenario 2: distance path, ids {0,2}, lcpus_max=2 -> logical_cpus=2,
	 * naps = 2*2-1 = 3.  Same BP leak on the BSP entry. */
	reset();
	printf("\nscenario 2: table = { BSP id0, AP id2 }, logical_cpus=2 -> naps=3\n");
	lapic_set_cpuid(0, 0);
	cpu = pass2_fake(&bsp, 1, 2, 0);
	if (processor_entry(&ap0, cpu))
		cpu++;
	cpu = pass2_fake(&ap0, cpu, 2, 0);
	dump("  RESULT: cpu0 -> apic 1 (clobbered), cpu1 -> BSS 0 (unregistered slot)", 3);

	/* Scenario 3: force_enable=1 (hw.lapic_force_enable) โ€” fakes get EN
	 * only: proves the ternary's intent and the fix. */
	reset();
	printf("\nscenario 3 (intent/fix): force_enable=1 -> fakes carry EN only\n");
	lapic_set_cpuid(0, 0);
	cpu = pass2_fake(&bsp, 1, 2, 1);
	if (processor_entry(&ap0, cpu))
		cpu++;
	cpu = pass2_fake(&ap0, cpu, 2, 1);
	dump("  RESULT: cpu0 -> apic 0 (correct), cpu1 -> apic 1, cpu2 -> apic 2, cpu3 -> apic 3", 3);

	return 0;
}