DF-2915 / sqprobe.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 | /* * sqprobe.c - DF-2915 root-cause probe (safe on the stock kernel). * * Proves that objcache_create_simple()-backed caches return NON-ZEROED * objects, which is the false assumption behind the KKASSERT at * sys/kern/subr_sleepqueue.c:209: * * wc = objcache_get(sleepq_wc_cache, M_WAITOK); * KKASSERT(wc->wc_wchan == NULL && wc->wc_refs == 0); * * sleepq_wc_cache is created by objcache_create_simple(M_SLEEPQ, 48) * (subr_sleepqueue.c:145-146). objcache_create_simple() wires up * objcache_malloc_alloc as the allocator with NO ctor * (sys/kern/kern_objcache.c:386-389), and objcache_malloc_alloc() * kmalloc()s without M_ZERO (kern_objcache.c:571-577). * * Method: spray 48-byte M_SLEEPQ objects filled with 0xAA, free them, * then build a cache exactly like sleepq_wc_cache does and pull objects. * Fresh/recycled chunks come back 0xAA-filled -> the KKASSERT condition * is false -> panic on INVARIANTS kernels; garbage wc enters * sc_wchead on production kernels. */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/malloc.h> #include <sys/objcache.h> #include <sys/module.h> extern struct malloc_type M_SLEEPQ[1]; /* subr_sleepqueue.c:107 */ #define WC_SIZE 48 /* sizeof(struct sleepqueue_wchan) */ #define SPRAY 8192 #define PROBE 256 /* mirror of subr_sleepqueue.c:97-104 */ struct demo_wc { void *tqe_next; /* TAILQ_ENTRY wc_entry */ void **tqe_prev; const void *wc_wchan; /* +16, checked by KKASSERT :209 */ void *wc_sc; /* +24 */ u_int wc_refs; /* +28, checked by KKASSERT :209 */ int wc_type; /* +32 */ u_int wc_blocked[2]; /* +36 */ }; static __inline int wc_bad(const struct demo_wc *w) { return (w->wc_wchan != NULL || w->wc_refs != 0 || w->wc_blocked[0] != 0 || w->wc_blocked[1] != 0); } static int sqprobe_run(void) { void **m; void *objs[PROBE]; struct objcache *oc; int i, bad = 0; m = kmalloc(SPRAY * sizeof(void *), M_TEMP, M_WAITOK | M_ZERO); for (i = 0; i < SPRAY; ++i) { m[i] = kmalloc(WC_SIZE, M_SLEEPQ, M_WAITOK); memset(m[i], 0xAA, WC_SIZE); } for (i = 0; i < SPRAY; ++i) kfree(m[i], M_SLEEPQ); /* exactly how subr_sleepqueue.c:145-146 creates sleepq_wc_cache */ oc = objcache_create_simple(M_SLEEPQ, WC_SIZE); for (i = 0; i < PROBE; ++i) { objs[i] = objcache_get(oc, M_WAITOK); if (wc_bad(objs[i])) ++bad; } kprintf("SQDEMO probe: %d/%d objcache_get() objects violate the " "KKASSERT precondition at subr_sleepqueue.c:209 " "(uninitialized wc_wchan/wc_refs/wc_blocked)\n", bad, PROBE); for (i = 0; i < PROBE; ++i) objcache_put(oc, objs[i]); kfree(m, M_TEMP); return (bad > 0); } static int sqprobe_handler(module_t mod, int type, void *data) { switch (type) { case MOD_LOAD: if (sqprobe_run()) kprintf("SQDEMO probe: ROOT CAUSE OF DF-2915 " "REPRODUCED on stock kernel\n"); else kprintf("SQDEMO probe: objects came back zeroed " "(unlucky slab) - rerun\n"); break; default: break; } return 0; } static moduledata_t sqprobe_mod = { "sqprobe", sqprobe_handler, NULL }; DECLARE_MODULE(sqprobe, sqprobe_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); |