DF-5343 / htfix_bp_flag_inherit.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 | /* * 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; } |