DF-5339 / bsp_asymmetry.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 | /* * DF-5339 โ probe/enumerate BSP-validation asymmetry in mptable.c * * mptable_lapic_probe_callback() (mptable.c:818-845) skips DISABLED * processor entries before its duplicate-BSP check, so a table whose * second BP-flagged entry is disabled passes probe(). * mptable_lapic_pass2_callback() (mptable.c:674-686) does the BP * bookkeeping with NO enabled check and KKASSERTs on the second * BP-flagged entry regardless of EN -> boot-time kernel panic on * INVARIANTS kernels (X86_64_GENERIC has INVARIANTS, sys/config/X86_64_GENERIC:56). * * This harness copies both callbacks' control flow verbatim (u_char flag * semantics preserved) and demonstrates: probe() accepts the table, * enumerate pass2 then trips KKASSERT(!arg->found_bsp). */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <setjmp.h> typedef unsigned char u_char; #define PROCENTRY_FLAG_EN 0x01 #define PROCENTRY_FLAG_BP 0x02 #define APICID_MAX 255 static sigjmp_buf panic_jmp; static int in_panic_scope; static char panic_msg[256]; static void kpanic(const char *exp) { snprintf(panic_msg, sizeof(panic_msg), "assertion \"%s\" failed in mptable_lapic_pass2_callback " "at mptable.c:684", exp); if (in_panic_scope) { in_panic_scope = 0; siglongjmp(panic_jmp, 1); } fprintf(stderr, "KERNEL PANIC: %s\n", panic_msg); exit(86); } #define KKASSERT(x) do { if (!(x)) kpanic(#x); } while (0) struct PROCENTRY { u_char type; u_char apic_id; u_char apic_version; u_char cpu_flags; unsigned int cpu_signature, feature_flags, reserved1, reserved2; }; /* ---- verbatim control flow: mptable_lapic_probe_callback (mptable.c:818-845) ---- */ struct mptable_lapic_probe_cbarg { int cpu_count; int found_bsp; }; static int probe_callback(struct mptable_lapic_probe_cbarg *arg, const struct PROCENTRY *ent) { if ((ent->cpu_flags & PROCENTRY_FLAG_EN) == 0) return 0; /* mptable.c:827-829: disabled skipped BEFORE BP check */ arg->cpu_count++; if (ent->apic_id == APICID_MAX) return 1; /* EINVAL */ if (ent->cpu_flags & PROCENTRY_FLAG_BP) { if (arg->found_bsp) return 1; /* EINVAL: "more than one BSP" โ but only ENABLED ones ever reach here */ arg->found_bsp = 1; } return 0; } /* ---- verbatim control flow: BP bookkeeping of mptable_lapic_pass2_callback (mptable.c:674-686) ---- */ struct mptable_lapic_cbarg2 { int found_bsp; }; static int pass2_bp_check(struct mptable_lapic_cbarg2 *arg, const struct PROCENTRY *ent) { if (ent->cpu_flags & PROCENTRY_FLAG_BP) { /* NO EN check */ KKASSERT(!arg->found_bsp); /* mptable.c:684 */ arg->found_bsp = 1; } return 0; } static void run_table(const char *name, const struct PROCENTRY tbl[], int n) { struct mptable_lapic_probe_cbarg pa; struct mptable_lapic_cbarg2 p2; int i, probe_err; memset(&pa, 0, sizeof(pa)); memset(&p2, 0, sizeof(p2)); probe_err = 0; printf("== %s ==\n", name); for (i = 0; i < n; i++) { if (probe_callback(&pa, tbl + i)) { probe_err = 1; break; } } printf(" probe : cpu_count=%d found_bsp=%d -> %s\n", pa.cpu_count, pa.found_bsp, probe_err ? "EINVAL (mptable enumerator rejected, ACPI path used) " : "PASS (mptable enumerator selected)"); if (probe_err) return; /* enumerate never runs: no bug */ in_panic_scope = 1; if (sigsetjmp(panic_jmp, 1) == 0) { for (i = 0; i < n; i++) pass2_bp_check(&p2, tbl + i); in_panic_scope = 0; printf(" pass2 : completed, found_bsp=%d (no panic)\n", p2.found_bsp); } else { printf(" pass2 : KERNEL PANIC -> %s\n", panic_msg); } } #define ENT(_id, _fl) { 0, (_id), 0x14, (_fl), 0, 0, 0, 0 } int main(void) { /* Trigger: enabled BSP + second BP-flagged but DISABLED entry. * Probe sees only the enabled one -> found_bsp=1, accepts. * Pass2 asserts on the raw BP bit of entry #2 -> panic. */ const struct PROCENTRY trig[2] = { ENT(0, PROCENTRY_FLAG_EN | PROCENTRY_FLAG_BP), ENT(1, PROCENTRY_FLAG_BP), /* disabled: EN clear, BP set */ }; /* Control 1: two enabled BSP entries โ correctly caught by probe. */ const struct PROCENTRY ctrl1[2] = { ENT(0, PROCENTRY_FLAG_EN | PROCENTRY_FLAG_BP), ENT(1, PROCENTRY_FLAG_EN | PROCENTRY_FLAG_BP), }; /* Control 2: healthy table โ no panic anywhere. */ const struct PROCENTRY ctrl2[2] = { ENT(0, PROCENTRY_FLAG_EN | PROCENTRY_FLAG_BP), ENT(2, PROCENTRY_FLAG_EN), }; run_table("trigger: [EN|BP] [BP] (2nd disabled BSP)", trig, 2); run_table("control: [EN|BP] [EN|BP] (both enabled)", ctrl1, 2); run_table("control: [EN|BP] [EN] (healthy table)", ctrl2, 2); return 0; } |