Page MenuHomeFreeBSD

filemount_panic.c

Authored By
nick_spun.io
Jul 27 2026, 4:30 AM
Size
3 KB
Referenced Files
None
Subscribers
None

filemount_panic.c

/*
* Isolated reproducer for the panic in PR 282596, fixed by D58178, as a bare
* syscall sequence: nmount(2) of a single-file nullfs mount, execve(2) of the
* mount point, rename(2) of the containing directory, then kern.proc.pathname
* on the resulting process.
*
* cc -o filemount_panic filemount_panic.c
* ./filemount_panic # as root, on an INVARIANTS kernel
*
* panic: condition vp->v_type == VDIR || VN_IS_DOOMED(vp) not met
* at ../../../kern/vfs_cache.c:NNNN (vn_fullpath_dir)
* vn_fullpath_dir()
* vn_fullpath_hardlink()
* proc_get_binpath()
* sysctl_kern_proc_pathname()
*
* The mount root of a single-file nullfs mount is a VREG vnode with VV_ROOT
* set, so namei() has no directory to hand back as ni_dvp and returns the
* covered vnode, which for a file mount is itself a regular file. execve(2)
* stores that as p_textdvp; the sysctl below reaches vn_fullpath_hardlink(),
* which restarts the walk from p_textdvp assuming it is a directory, and
* vn_fullpath_dir() then trips VNPASS(vp->v_type == VDIR || VN_IS_DOOMED(vp)).
*
* vn_fullpath_hardlink() only reaches vn_fullpath_dir() once the lock-free
* namecache reverse walk it tries first has failed, so the cover file must not
* be in the namecache. The rename(2) evicts it: cache_vop_rename() purges the
* renamed directory, dropping the entries that name its children.
*/
#include <sys/param.h>
#include <sys/mount.h>
#include <sys/sysctl.h>
#include <sys/uio.h>
#include <err.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static void
opt(struct iovec *iov, int *n, const char *name, const char *val)
{
iov[*n].iov_base = __DECONST(char *, name);
iov[(*n)++].iov_len = strlen(name) + 1;
iov[*n].iov_base = __DECONST(char *, val);
iov[(*n)++].iov_len = strlen(val) + 1;
}
int
main(void)
{
char dir[] = "/tmp/filemount.XXXXXX";
char cover[MAXPATHLEN], newdir[MAXPATHLEN], path[MAXPATHLEN];
struct iovec iov[6];
size_t len;
pid_t pid;
int mib[4], n, fd;
if (mkdtemp(dir) == NULL)
err(1, "mkdtemp");
snprintf(cover, sizeof(cover), "%s/cover", dir);
snprintf(newdir, sizeof(newdir), "%s.renamed", dir);
/* The file to mount over: a regular file with one hardlink. */
if ((fd = open(cover, O_WRONLY | O_CREAT, 0755)) < 0)
err(1, "open %s", cover);
close(fd);
n = 0;
opt(iov, &n, "fstype", "nullfs");
opt(iov, &n, "fspath", cover);
opt(iov, &n, "target", "/bin/sleep");
if (nmount(iov, n, 0) != 0)
err(1, "nmount /bin/sleep over %s", cover);
printf("mounted /bin/sleep over %s\n", cover);
switch ((pid = fork())) {
case -1:
err(1, "fork");
case 0:
/* execve() stores the covered vnode as p_textdvp. */
execl(cover, "cover", "300", NULL);
err(1, "execl %s", cover);
}
sleep(1);
/* Evict the cover file's namecache entry. */
if (rename(dir, newdir) != 0)
err(1, "rename %s", dir);
printf("querying kern.proc.pathname for pid %d\n", (int)pid);
fflush(stdout);
mib[0] = CTL_KERN;
mib[1] = KERN_PROC;
mib[2] = KERN_PROC_PATHNAME;
mib[3] = pid;
len = sizeof(path);
if (sysctl(mib, 4, path, &len, NULL, 0) != 0)
warn("sysctl kern.proc.pathname");
else
printf("pathname = %s\n", path);
return (0);
}

File Metadata

Mime Type
text/x-c
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
35632529
Default Alt Text
filemount_panic.c (3 KB)

Event Timeline

nick_spun.io changed the visibility from "nick_spun.io (Nick Price)" to "Public (No Login Required)".Mon, Jul 27, 2:18 PM