DF-2798 / trigger_oobfree.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 | /* * trigger_oobfree.c - alist out-of-domain free discipline triggers * (DF-2798 candidate). -DNDEBUG emulates a production kernel * (KKASSERT no-op). Geometry: blocks=100 -> root meta (radix 512, * skip 16), leaves at slots 1..4 cover blocks 0..127 but only 0..99 * are in domain; slots 5..16 exist (rootblks=17) yet are never * initialized -- we pre-poison them to prove they are never touched. * * T1: alist_free(bl, 128, 32): 128+32 > bl_blocks. * - INVARIANTS kernel: KKASSERT at alist_free:286 panics. * - production kernel: KKASSERT compiled out; the free lands in * alst_meta_free's FULL-COVER branch (:745) which has NO * bl_blocks check (the "beyond limit" panic :784 only guards * the PARTIAL branch) -> parent bits for child 4 silently set * to ALL-FREE, bl_free += 32 (132 > capacity!). * Then alist_alloc(bl, 0, 32) -> alst_meta_alloc DIRECT path * (:552, also no bl_blocks check) matches the poisoned bits and * RETURNS BLOCK 128 -- out of domain. * For the in-kernel consumer this would be: * PHYS_TO_VM_PAGE(128 << PAGE_SHIFT) -> beyond vm_page_array. * T2: alist_free(bl, 120, 4): partial child beyond the limit -> hits * the unconditional panic at :784 ("attempt to free block beyond * limit") -- shows the asymmetry (partial=loud, full=silent). * T3: poison persistence dump: slots 5..16 never written (the * corruption is purely in the parent's meta bits). */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <unistd.h> #include <sys/wait.h> #define main alist_debug_main #include "kern/subr_alist.c" #undef main #define POISON32 0xAAAAAAAAU static struct alist bl; static almeta_t records[64]; static void t1_silent_oob(void) { alist_blk_t r; int i, dirty = 0; memset(records, POISON32, sizeof(records)); alist_init(&bl, 100, records, 64); printf(" geometry: blocks=%u radix=%u skip=%u rootblks=%u\n", bl.bl_blocks, bl.bl_radix, bl.bl_skip, bl.bl_rootblks); alist_free(&bl, 0, 100); printf(" after free-all: bl_free=%u root bitmap=%08x\n", bl.bl_free, bl.bl_root->bm_bitmap); /* caller slip: free 32 blocks at 128 (out of domain) */ alist_free(&bl, 128, 32); printf(" after alist_free(bl,128,32): bl_free=%u (capacity=%u!) " "root bitmap=%08x <-- NO PANIC, bits 8..9 set (child4=11)\n", bl.bl_free, bl.bl_blocks, bl.bl_root->bm_bitmap); /* exhaust the 3 in-domain all-free children (blocks 0..95) */ for (i = 0; i < 3; ++i) r = alist_alloc(&bl, 0, 32); printf(" 3x alist_alloc(bl,0,32) -> %u (in-domain exhausted)\n", r); /* now the allocation is forced onto the poisoned child 4 */ r = alist_alloc(&bl, 0, 32); printf(" alist_alloc(bl,0,32) returned %u <-- bl_blocks=%u: " "OUT-OF-DOMAIN block number handed out\n", r, bl.bl_blocks); printf(" bl_free=%u (restored), root bitmap=%08x\n", bl.bl_free, bl.bl_root->bm_bitmap); for (i = 5; i < (int)bl.bl_rootblks; ++i) if (records[i].bm_bitmap != POISON32 || records[i].bm_bighint != POISON32) dirty++; printf(" uninitialized slots 5..%d written: %d (corruption is " "logical, in parent meta bits)\n", (int)bl.bl_rootblks - 1, dirty); } static void t2_partial_oob(void) { memset(records, POISON32, sizeof(records)); alist_init(&bl, 100, records, 64); alist_free(&bl, 0, 100); printf(" calling alist_free(bl, 120, 4) (partial child beyond " "limit)...\n"); fflush(stdout); alist_free(&bl, 120, 4); /* panics via panic() at :784 */ printf(" UNEXPECTEDLY returned\n"); } int main(void) { pid_t p; int st; setvbuf(stdout, NULL, _IONBF, 0); printf("== T1 (silent full-cover OOB free -> out-of-domain alloc) " "==\n"); t1_silent_oob(); printf("== T2 (partial OOB free -> loud panic :784) ==\n"); p = fork(); if (p == 0) t2_partial_oob(); waitpid(p, &st, 0); printf(" child exit=%d (1 == panic'ed as expected)\n", WEXITSTATUS(st)); return 0; } |