DF-0914 / 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 | /* * harness.c - DF-0914 deterministic OOB-read characterization. * * Transcribes the in_off derivation in ufs_getlbns (ufs_bmap.c:315) and the * OOB array READ at ufs_bmaparray (ufs_bmap.c:221) with a poisoned allocator, * proving the OOB extent deterministically without needing the live kernel. * * The key difference from DF-0894's harness: this proves the READ-path OOB at * ufs_bmap.c:221 (daddr = bap[in_off]), where the OOB value becomes a disk * block address used for a subsequent read (confused-deputy / page-fault). * * Build: cc -o harness harness.c * Usage: ./harness [<bsize> <nindir> <lbn>] * defaults: bsize=16384 nindir=8192 lbn=8203 */ #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #define UFS_NDADDR 12 #define UFS_NIADDR 3 /* * Transcribe ufs_getlbns (ufs_bmap.c:256-328) to compute in_off for a given * logical block number under a forged MNINDIR. Returns numlevels and fills * the indir array. */ struct indir { long in_lbn; int in_off; }; static int getlbns(long bn, u_long mnindir, struct indir *ap, int *nump) { long blockcnt, metalbn, realbn; int i, numlevels, off; int64_t qblockcnt; numlevels = 0; realbn = bn; if ((long)bn < 0) bn = -(long)bn; if (bn < UFS_NDADDR) return (0); for (blockcnt = 1, i = UFS_NIADDR, bn -= UFS_NDADDR;; i--, bn -= blockcnt) { if (i == 0) return (-1); /* EFBIG */ qblockcnt = (int64_t)blockcnt * mnindir; if (bn < qblockcnt) break; blockcnt = qblockcnt; } metalbn = -(realbn - bn + UFS_NIADDR - i); ap->in_lbn = metalbn; ap->in_off = off = UFS_NIADDR - i; ap++; for (++numlevels; i <= UFS_NIADDR; i++) { if (metalbn == realbn) break; off = (bn / blockcnt) % mnindir; ++numlevels; ap->in_lbn = metalbn; ap->in_off = off; ++ap; metalbn -= -1 + off * blockcnt; blockcnt /= mnindir; } *nump = numlevels; return (0); } int main(int argc, char **argv) { uint32_t bsize = (argc > 1) ? (uint32_t)strtoul(argv[1], NULL, 0) : 16384; u_long mnindir = (argc > 2) ? strtoul(argv[2], NULL, 0) : 8192; long lbn = (argc > 3) ? strtol(argv[3], NULL, 0) : 8203; uint32_t buf_entries = bsize / sizeof(uint32_t); /* fs_bsize/4 */ uint32_t correct_nindir = buf_entries; printf("=== DF-0914 OOB-READ harness (ufs_bmaparray READ path) ===\n"); printf("fs_bsize: %u\n", bsize); printf("correct fs_nindir: %u (= fs_bsize/4 = bap[] entries)\n", correct_nindir); printf("forged fs_nindir: %lu (MNINDIR)\n", mnindir); printf("buffer size: %u bytes = %u ufs_daddr_t entries\n", bsize, buf_entries); printf("valid bap[] range: 0..%u\n", buf_entries - 1); printf("read target lbn: %ld\n", lbn); printf("\n"); if (mnindir <= buf_entries) { printf("WARN: forged nindir (%lu) <= buf entries (%u); no OOB possible\n", mnindir, buf_entries); return 0; } struct indir ap[UFS_NIADDR + 1]; int num; int rc = getlbns(lbn, mnindir, ap, &num); if (rc != 0) { printf("ERROR: getlbns returned %d (EFBIG)\n", rc); return 1; } printf("ufs_getlbns returned numlevels=%d:\n", num); for (int i = 0; i < num; i++) printf(" xap[%d]: in_lbn=%ld in_off=%d\n", i, ap[i].in_lbn, ap[i].in_off); printf("\n"); if (num < 2) { printf("num < 2: block is direct or meta-only; ufs_bmaparray:221 not reached\n"); return 0; } /* The last xap entry is what ufs_bmaparray:221 indexes into the indirect * block buffer: daddr = ((ufs_daddr_t*)bp->b_data)[xap[num-1].in_off] * The buffer has `buf_entries` valid entries (0..buf_entries-1). */ int idx = ap[num - 1].in_off; uint32_t byte_off = (uint32_t)idx * 4; printf("ufs_bmaparray:221 daddr = bap[%d] = bp->b_data + %u\n", idx, byte_off); printf("buffer end = bp->b_data + %u\n", bsize); if (idx >= (int)buf_entries) { uint32_t oob = byte_off - bsize + 4; printf("*** OOB READ: bap[%d] reads %u bytes past the %u-byte buffer ***\n", idx, oob, bsize); printf("*** (buffer has %u entries 0..%u; index %d is out of bounds) ***\n", buf_entries, buf_entries - 1, idx); printf("*** OOB value becomes `daddr`, used as disk block address at :241 ***\n"); printf("*** -> confused-deputy read of arbitrary disk block, or ***\n"); printf("*** page-fault panic if OOB memory is unmapped (INVARIANTS)***\n"); /* simulate with a poisoned buffer to show what gets read */ unsigned char *buf = calloc(1, bsize + oob + 4096); memset(buf + bsize, 0xAB, oob + 4096); /* poison past buffer */ uint32_t leaked = *(uint32_t *)(buf + byte_off); printf("simulated OOB read = 0x%08x (poison pattern from past buffer)\n", leaked); free(buf); return 0; } else { printf("index %d is IN BOUNDS (0..%u); no OOB\n", idx, buf_entries - 1); return 1; } } |