DF-0887 / 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 | /* * harness.c - DF-0887 deterministic primitive characterizer. * * Transcribes the `ffs_truncate` symlink fast-path verbatim, with a poisoned * allocator that places the `struct inode` in a too-small slab and surrounds * it with sentinels. This proves, *without* depending on the kernel slab * layout, that the on-disk di_size copied verbatim into ip->i_din.di_size * drives an OOB write of (di_size - sizeof(i_shortlink)) bytes past the * i_shortlink (= di_db) buffer. * * Source model (sys/vfs/ufs/ffs_inode.c:159-168): * if (ovp->v_type == VLNK && * (oip->i_size < mnt_maxsymlinklen || oip->i_din.di_blocks == 0)) { * bzero((char *)&oip->i_shortlink, (uint)oip->i_size); * ... * } * * Layout facts (sys/vfs/ufs/): * inode.h:105 struct ufs1_dinode i_din; // LAST member of struct inode * inode.h:126 #define i_shortlink i_din.di_shortlink * dinode.h:111 #define di_shortlink di_db * dinode.h:83 ufs_daddr_t di_db[UFS_NDADDR]; // UFS_NDADDR=12 -> 48 bytes * dinode.h:114 #define UFS1_MAXSYMLINKLEN ((UFS_NDADDR + UFS_NIADDR) * sizeof(ufs_daddr_t)) // 60 * * Build: cc -o harness harness.c * Run: ./harness <di_size> [<di_blocks>] (default di_blocks=0) */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #define UFS_NDADDR 12 #define UFS_NIADDR 3 #define SHORTLINK_BYTES (UFS_NDADDR * 4) /* 48 */ #define UFS1_MAXSYMLINKLEN ((UFS_NDADDR + UFS_NIADDR) * 4) /* 60 */ struct ufs1_dinode { uint8_t raw[128]; }; struct inode { uint8_t preamble[64]; /* stand-in for fields before i_din */ struct ufs1_dinode i_din; }; #define I_DIN_OFF 64 /* offset of i_din */ #define I_SHORTLINK_OFF (I_DIN_OFF + 40) /* offset of di_db in inode */ #define SLAB_PAD 4096 int main(int argc, char **argv){ if (argc < 2) { fprintf(stderr,"usage: %s <di_size> [<di_blocks>]\n", argv[0]); return 2; } unsigned long long disize = strtoull(argv[1], NULL, 0); int di_blocks = (argc >= 3) ? (int)strtol(argv[2], NULL, 0) : 0; /* allocate a "slab" with sentinels; place the inode in the middle */ uint8_t *slab = malloc(SLAB_PAD + sizeof(struct inode) + SLAB_PAD); if (!slab) { perror("malloc"); return 2; } memset(slab, 0xAA, SLAB_PAD + sizeof(struct inode) + SLAB_PAD); struct inode *oip = (struct inode *)(slab + SLAB_PAD); /* forge the inode */ oip->preamble[0] = 0x55; /* marker */ /* di_size at i_din offset 8 (uint64) */ uint64_t sz = disize; memcpy(&oip->i_din.raw[8], &sz, 8); /* di_blocks at i_din offset 104 (int32) */ int32_t blocks = di_blocks; memcpy(&oip->i_din.raw[104], &blocks, 4); /* simulate vp->v_mount->mnt_maxsymlinklen (default UFS1 = 60) */ int mnt_maxsymlinklen = UFS1_MAXSYMLINKLEN; int v_type_VLNK = 1; printf("=== DF-0887 primitive characterization ===\n"); printf("i_shortlink buffer size = %d bytes (di_db[UFS_NDADDR=12])\n", SHORTLINK_BYTES); printf("UFS1_MAXSYMLINKLEN = %d bytes\n", UFS1_MAXSYMLINKLEN); printf("forged di_size = %llu bytes\n", disize); printf("forged di_blocks = %d\n", di_blocks); printf("mnt_maxsymlinklen = %d\n", mnt_maxsymlinklen); int enter = (v_type_VLNK && (((uint64_t)disize < (unsigned)mnt_maxsymlinklen) || di_blocks == 0)); printf("fast-path enter (VLNK && (i_size<maxlen || di_blocks==0)) = %s\n", enter ? "YES" : "no"); if (!enter) { printf("fast-path NOT taken -> no overflow (set di_blocks=0)\n"); free(slab); return 0; } /* verbatim bzero(&oip->i_shortlink, (uint)oip->i_size) — but bounded so we * don't actually corrupt our own host memory. We count what it WOULD do. */ unsigned int bzero_len = (unsigned int)disize; long overflow_bytes = (long)bzero_len - SHORTLINK_BYTES; /* inode tail bytes after i_shortlink within the struct (di_ib[3]+flags+...) * di_db ends at +48 within i_din; i_din is 128 bytes -> 80 bytes of tail */ long tail_in_inode = sizeof(struct inode) - I_SHORTLINK_OFF; /* past the shortlink start */ printf("\nbzero length = %u\n", bzero_len); printf("first OOB byte (past shortlink) at inode+%zu\n", (size_t)(I_SHORTLINK_OFF + SHORTLINK_BYTES)); printf("OOB write length = %ld bytes (di_size - %d)\n", overflow_bytes, SHORTLINK_BYTES); printf("OOB past end of struct inode = %ld bytes (overflow past inode end)\n", bzero_len - tail_in_inode); /* actually simulate the bzero but bounded to the slab region, so the sentinels * reveal exactly how far past the inode the kernel would write */ long max_safe = (long)(SLAB_PAD + sizeof(struct inode) + SLAB_PAD) - (long)((uint8_t*)oip - slab) - I_SHORTLINK_OFF; long do_bytes = bzero_len; if (do_bytes > max_safe) { printf("(capping simulated bzero to slab size %ld; real kernel has no cap)\n", max_safe); do_bytes = max_safe; } memset((uint8_t*)oip + I_SHORTLINK_OFF, 0x00, (size_t)do_bytes); /* count zeroed bytes past the end of struct inode (the OOB into adjacent heap) */ uint8_t *inode_end = (uint8_t*)oip + sizeof(struct inode); uint8_t *shortlink_end = (uint8_t*)oip + I_SHORTLINK_OFF + SHORTLINK_BYTES; uint8_t *write_end = (uint8_t*)oip + I_SHORTLINK_OFF + bzero_len; long past_inode = write_end - inode_end; long zeroed_past_inode = 0; if (past_inode > 0) { long capped_end = write_end - slab; long capped_max = SLAB_PAD + sizeof(struct inode) + SLAB_PAD; long eff_end = capped_end < capped_max ? capped_end : capped_max; long eff_start = inode_end - slab; for (long i = eff_start; i < eff_end; i++) if (slab[i] == 0x00) zeroed_past_inode++; } printf("\nSIMULATED bzero overflow:\n"); printf(" bytes written past shortlink end = %ld\n", (long)(write_end - shortlink_end)); printf(" bytes written past end of inode = %ld (-> adjacent heap/slab)\n", past_inode > 0 ? past_inode : 0); printf(" zeroed past inode (within slab) = %ld\n", zeroed_past_inode); /* verdict */ if (bzero_len > SHORTLINK_BYTES) { printf("\nRESULT: OOB WRITE of %ld bytes past the 48-byte i_shortlink buffer.\n", overflow_bytes); printf(" Heap corruption into M_FFSNODE slab (and beyond) confirmed.\n"); free(slab); return 0; } else { printf("\nRESULT: bzero in-bounds (no overflow).\n"); free(slab); return 1; } } |