DF-2762 / setgroups_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 | /* DF-2762: sys_setgroups (kern_prot.c:682-696) calls cratom_proc() FIRST -- * publishing the new credential to the process -- and only then copyins the * new group list into the live credential. Consequences demonstrated here * from ONE process: * (a) a concurrent thread can observe a MIXED old/new group set; * (b) a failing copyin (EFAULT) returns an error but leaves the published * credential partially mutated (documented contract: unchanged). */ #include <stdio.h> #include <string.h> #include <errno.h> #include <pthread.h> #include <unistd.h> #include <sys/param.h> #include <sys/mman.h> static volatile int stop, pause_mut; static long mixed, reads, postfault_bad; static gid_t setA[8], setB[8]; static void *mutator(void *x) { int i = 0; (void)x; while (!stop) { while (pause_mut) usleep(100); if (setgroups(8, (i++ & 1) ? setA : setB) != 0) fprintf(stderr, "setgroups: %s\n", strerror(errno)); if ((i % 200) == 0) { pause_mut = 1; /* freeze mutation during the probe */ /* faulting copyin AFTER the cred was published: * 7 valid gids followed by a PROT_NONE page so the * kernel copyin() copies 28 bytes and THEN faults */ gid_t before[NGROUPS_MAX]; int nbefore = getgroups(NGROUPS_MAX, before); { static gid_t *straddle; int k; if (!straddle) { char *p = mmap(NULL, 2*4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0); mprotect(p + 4096, 4096, PROT_NONE); straddle = (gid_t *)(p + 4096 - 7*sizeof(gid_t)); for (k = 0; k < 7; k++) straddle[k] = 9000 + k; } if (setgroups(8, straddle) != -1 || errno != EFAULT) fprintf(stderr, "expected EFAULT got %d\n", errno); } gid_t after[NGROUPS_MAX]; int nafter = getgroups(NGROUPS_MAX, after); pause_mut = 0; if (nbefore >= 0 && nafter >= 0) { size_t nb = (size_t)nbefore * sizeof(gid_t); if (nafter != nbefore || memcmp(before, after, nb) != 0) { postfault_bad++; if (postfault_bad <= 3) { int k; fprintf(stderr, "[+] EFAULT RETURNED BUT CREDENTIAL MUTATED: "); fprintf(stderr, "before{"); for (k = 0; k < nbefore; k++) fprintf(stderr, "%d,", before[k]); fprintf(stderr, "} after{"); for (k = 0; k < nafter; k++) fprintf(stderr, "%d,", after[k]); fprintf(stderr, "}\n"); } } } } } return NULL; } static void *observer(void *x) { gid_t g[NGROUPS_MAX]; (void)x; while (!stop) { int n = getgroups(NGROUPS_MAX, g); if (n == 8) { int a = 0, b = 0, k; for (k = 0; k < n; k++) { if (g[k] < 5000) a = 1; /* setA values */ if (g[k] >= 5000) b = 1; /* setB values */ } reads++; if (a && b) { mixed++; if (mixed <= 3) { int j; fprintf(stderr, "[+] MIXED GROUP SET OBSERVED: {"); for (j = 0; j < n; j++) fprintf(stderr, "%d,", g[j]); fprintf(stderr, "}\n"); } } } } return NULL; } int main(void) { pthread_t m, o; int i; for (i = 0; i < 8; i++) { setA[i] = 1000 + i; setB[i] = 5000 + i; } printf("[*] racing setgroups(setA) vs setgroups(setB) + EFAULT probe + observer\n"); pthread_create(&m, NULL, mutator, NULL); pthread_create(&o, NULL, observer, NULL); sleep(5); stop = 1; pthread_join(m, NULL); pthread_join(o, NULL); printf("[*] %ld reads, %ld mixed observations, %ld post-EFAULT mutations\n", reads, mixed, postfault_bad); printf("[%c] %s\n", (mixed||postfault_bad)?'+':'-', (mixed||postfault_bad) ? "COW-THEN-MUTATE WINDOW REPRODUCED" : "no violation observed this run"); return (mixed || postfault_bad) ? 0 : 1; } |