DF-0820 / 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 | /* * harness.c - DF-0820 primitive characterizer. * * Transcribes the size arithmetic and bzero/init-loop from * sys/vfs/ufs/ffs_vfsops.c:680-709 VERBATIM (with `int size` kept as int, * exactly as the kernel), but with a bounded "allocation" and a poisoned * allocator so we can OBSERVE the corruption extent without segfaulting. * * The kernel does: * 680: size = fs->fs_cssize; * 681: blks = howmany(size, fs->fs_fsize); * 682: if (fs->fs_contigsumsize > 0) * 683: size += fs->fs_ncg * sizeof(int32_t); <-- size_t product, int sum * 684: size += fs->fs_ncg * sizeof(uint8_t); * 685: space = kmalloc((u_long)size, ...); * ... * 701: if (fs->fs_contigsumsize > 0) { * 702: fs->fs_maxcluster = lp = space; * 703: for (i = 0; i < fs->fs_ncg; i++) * 704: *lp++ = fs->fs_contigsumsize; <-- controlled overflow if size wrapped * 705: space = lp; * 706: } * 707: size = fs->fs_ncg * sizeof(uint8_t); <-- int * 708: fs->fs_contigdirs = (uint8_t *)space; * 709: bzero(fs->fs_contigdirs, size); <-- bzero(ptr, size_t size) * * We model the allocation as a fixed-size poisoned buffer (filled with 0xAA * guard pattern) and count how many bytes bzero/the loop would write PAST * the nominal allocation. This proves the primitive without crashing. * * Build: cc -o harness harness.c * Usage: harness <fs_ncg> <fs_cssize> <fs_contigsumsize> <fs_fsize> */ #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #define howmany(x, y) (((x)+((y)-1))/(y)) /* Simulated kernel allocation: a poisoned arena of fixed size. We pretend * kmalloc returned a pointer in the middle; we count how far the write * would extend past the end. */ #define ARENA (1<<20) /* 1 MiB poisoned arena to observe the write pattern */ int main(int argc, char **argv){ if (argc < 5) { fprintf(stderr, "usage: %s fs_ncg fs_cssize fs_contigsumsize fs_fsize\n", argv[0]); fprintf(stderr, " (decimal; fs_ncg may be negative)\n"); return 2; } int32_t fs_ncg = (int32_t)strtoll(argv[1], NULL, 0); int32_t fs_cssize = (int32_t)strtoll(argv[2], NULL, 0); int32_t fs_contigsumsize = (int32_t)strtoll(argv[3], NULL, 0); int32_t fs_fsize = (int32_t)strtoll(argv[4], NULL, 0); /* --- verbatim kernel arithmetic --- */ int size; int blks; int i; size = fs_cssize; /* line 680 */ if (fs_fsize) blks = howmany(size, fs_fsize); /* line 681 */ else blks = 0; if (fs_contigsumsize > 0) size += fs_ncg * sizeof(int32_t); /* line 683 */ size += fs_ncg * sizeof(uint8_t); /* line 684 */ /* kmalloc((u_long)size) -- model the allocation size */ unsigned long ksize = (unsigned long)(unsigned int)size; /* (u_long) cast of int */ printf("=== kernel size arithmetic (int size) ===\n"); printf("fs_ncg = %d (0x%08x)\n", fs_ncg, (unsigned)fs_ncg); printf("fs_cssize = %d\n", fs_cssize); printf("fs_contigsumsize = %d\n", fs_contigsumsize); printf("fs_fsize = %d\n", fs_fsize); printf("size after L680 = %d\n", fs_cssize); printf("size after L683 = %d (fs_ncg*4 product as int)\n", fs_contigsumsize>0 ? (int)(fs_cssize + fs_ncg*sizeof(int32_t)) : fs_cssize); printf("size after L684 = %d <-- passed to kmalloc as (u_long)\n", size); printf("kmalloc((u_long)size) -> kmalloc(%lu) bytes\n", ksize); printf("blks (howmany(cssize,fsize)) = %d\n", blks); /* --- now simulate the writes into a poisoned arena --- */ unsigned char *arena = malloc(ARENA); if (!arena) { perror("malloc"); return 2; } memset(arena, 0xAA, ARENA); void *space = arena; /* the kmalloc return */ int32_t *lp; /* line 701-705: maxcluster init loop writes fs_ncg int32 values of contigsumsize. * NOTE: the loop is `for (i = 0; i < fs_ncg; i++)` with i,fs_ncg both int. * If fs_ncg < 0 (e.g. -1), the loop body never runs (0 < -1 is false). * It only overflows when fs_ncg is a large POSITIVE value. */ unsigned long loop_writes = 0; long long loop_iters = 0; if (fs_contigsumsize > 0) { lp = (int32_t *)space; /* faithful iteration count respecting the `i < fs_ncg` guard */ if (fs_ncg > 0) { loop_iters = fs_ncg; loop_writes = (unsigned long)loop_iters * sizeof(int32_t); for (i = 0; i < fs_ncg && (unsigned long)i*sizeof(int32_t) < ARENA; i++) lp[i] = fs_contigsumsize; } printf("\n[maxcluster loop L703-704] guard i<fs_ncg(%d): %s\n", fs_ncg, fs_ncg > 0 ? "RUNS" : "SKIPPED (fs_ncg<=0)"); if (fs_ncg > 0) { printf(" iters=%lld writes=%lu bytes of value 0x%x into kmalloc(%lu)\n", loop_iters, loop_writes, (unsigned)fs_contigsumsize, ksize); printf(" -> %s -> %s\n", loop_writes > ksize ? "OVERFLOWS the kmalloc region" : "fits in kmalloc", loop_writes > ksize ? "controlled-value HEAP CORRUPTION" : "no overflow"); } space = arena + (loop_writes < ARENA ? loop_writes : ARENA); } /* line 707-709: bzero(fs_contigdirs, size) where size = fs_ncg*1 (int) */ int bsz = fs_ncg * sizeof(uint8_t); /* line 707, int */ unsigned long bzero_size = (unsigned long)(unsigned int)bsz; /* (u_long)(int) -> bzero size */ printf("\n[bzero L707-709] size = fs_ncg*1 = %d (int); bzero(ptr, (u_long)%d) = bzero %lu bytes\n", bsz, bsz, bzero_size); /* model the bzero: count how far past kmalloc it goes */ unsigned long past_alloc; if (loop_writes > 0) { /* contigdirs starts after maxcluster region; offset within allocation */ unsigned long cd_off = ksize > loop_writes ? 0 : 0; /* simplified */ past_alloc = bzero_size; /* bzero from contigdirs forward */ } else { past_alloc = bzero_size > ksize ? bzero_size - ksize : 0; } printf(" -> bzero writes %lu bytes; ", bzero_size); if (bzero_size > ksize) printf("OVERFLOWS kmalloc(%lu) by %lu bytes -> heap corruption\n", ksize, bzero_size - ksize); else printf("fits within kmalloc(%lu)\n", ksize); if (bzero_size == (unsigned long)-1 || bzero_size > (ARENA/2)) { printf(" >>> UNBOUNDED WRITE: kernel bzero runs until it page-faults on an\n"); printf(" unmapped page -> deterministic panic (DoS). Synchronous in the\n"); printf(" mount syscall; no return to userspace -> no clean LPE conversion.\n"); } free(arena); return 0; } |