DF-0139 / sleepq_hash_oob.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 | /* * DF-0139 - SLEEPQ_HASH misplaced-mask OOB index demonstrator. * * sys/kern/subr_sleepqueue.c:80-85: * * #define SLEEPQ_HSIZE 1024 * #define SLEEPQ_HMASK (SLEEPQ_HSIZE - 1) // 0x3FF (10 bits) * #define SLEEPQ_HASH(wchan) ((((uintptr_t)(wchan) >> 10) ^ \ * ((uintptr_t)(wchan) & SLEEPQ_HMASK))) * #define SLEEPQ_LOOKUP(wchan) &sleepq_chains[SLEEPQ_HASH(wchan)] * * The `& SLEEPQ_HMASK` masks ONLY the second XOR operand, not the whole * result. For any kernel pointer whose high bits survive `>> 10`, the hash * value far exceeds SLEEPQ_HSIZE (1024), so sleepq_chains[huge] is a wild * out-of-bounds pointer into the kernel bss/data segment. * * This harness replicates the macro exactly (uintptr_t == unsigned long on the * 64-bit kernel) and demonstrates that EVERY realistic kernel wchan produces an * index far outside [0, 1024). sleepq_lock()/sleepq_wclookup() (lines 120, * 182) dereference this immediately on first call -> immediate panic or silent * memory corruption. * * The code is compiled into the shipped GENERIC kernel * (nm /boot/kernel/kernel | grep sleepq_lock) * but has ZERO in-tree callers today -- it exists solely for future FreeBSD/ * Linux-KPI compatibility. The defect fires the instant any caller is wired * up. This is a latent critical-grade defect, confirmed at the code level and * by this standalone arithmetic reproduction (no live caller exists to trigger * a runtime panic on a default guest). */ #include <stdio.h> #include <stdint.h> #include <stddef.h> #define SLEEPQ_HSIZE 1024 #define SLEEPQ_HMASK (SLEEPQ_HSIZE - 1) /* Exact copy of the kernel macro (sys/kern/subr_sleepqueue.c:82-83). */ #define SLEEPQ_HASH(wchan) ((((uintptr_t)(wchan) >> 10) ^ \ ((uintptr_t)(wchan) & SLEEPQ_HMASK))) /* What the macro almost certainly was meant to be. */ #define SLEEPQ_HASH_FIXED(wchan) ((((uintptr_t)(wchan) >> 10) ^ \ ((uintptr_t)(wchan) & SLEEPQ_HMASK)) & SLEEPQ_HMASK) struct sleepq_chain { char pad[32]; }; static struct sleepq_chain sleepq_chains[SLEEPQ_HSIZE]; int main(void) { /* Realistic DragonFly kernel wait-channel pointers (kmem direct-map range * 0xffff_8000_0000_0000 +, and common kmalloc/kobj addresses). */ uintptr_t samples[] = { 0xffff800000000000UL, /* typical kmem object base */ 0xffffffff80b34000UL, /* kernel-text/data symbol */ 0xfffffe0012345678UL, /* malloc'd struct */ 0xffff800123456000UL, /* page-aligned kmem */ 0xffff8000deadbeefUL, (uintptr_t)&sleepq_chains[0], /* the array itself (a real kernel global) */ }; int i, oob = 0; printf("sleepq_chains[] has %d entries (indices 0..%d allowed)\n", SLEEPQ_HSIZE, SLEEPQ_HSIZE - 1); printf("%-22s %-20s %-14s %s\n", "wchan", "SLEEPQ_HASH", "IN-BOUNDS?", "fixed"); for (i = 0; i < (int)(sizeof(samples)/sizeof(samples[0])); i++) { uintptr_t h = SLEEPQ_HASH(samples[i]); uintptr_t hf = SLEEPQ_HASH_FIXED(samples[i]); int inb = (h < SLEEPQ_HSIZE); if (!inb) oob++; printf("0x%016lx 0x%016lx %-14s 0x%lx\n", (unsigned long)samples[i], (unsigned long)h, inb ? "YES" : "*** OOB ***", (unsigned long)hf); } printf("\n%d/%d realistic kernel wchan pointers produce an OUT-OF-BOUNDS " "index into sleepq_chains[1024].\n", oob, (int)(sizeof(samples)/sizeof(samples[0]))); /* Prove the dereference is wild: emulate SLEEPQ_LOOKUP pointer offset. */ { uintptr_t base = (uintptr_t)&sleepq_chains[0]; size_t eltsz = sizeof(struct sleepq_chain); uintptr_t wchan = 0xffff8000deadbeefUL; uintptr_t h = SLEEPQ_HASH(wchan); uintptr_t wild = base + h * eltsz; printf("\nEmulating SLEEPQ_LOOKUP(0x%lx) on array @ %p:\n", (unsigned long)wchan, (void *)base); printf(" index = 0x%lx (array has %d slots)\n", (unsigned long)h, SLEEPQ_HSIZE); printf(" &sleepq_chains[index] = %p -> %llu BYTES past array start\n", (void *)wild, (unsigned long long)(wild - base)); printf(" spin_lock()/TAILQ on this address corrupts unrelated kernel " "memory.\n"); } if (oob > 0) { printf("\nRESULT: BUG CONFIRMED -- SLEEPQ_HASH returns out-of-range " "indices for normal kernel pointers.\n"); return 0; } printf("\nRESULT: no OOB (unexpected).\n"); return 1; } |