DF-2858 / shmfork_race2.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 | /* * DF-2858 v2 - corrected detector. * The parent's attacher thread attaches the SAME segment at a FIXED * address (0x50000000) in a shmat/shmdt loop. A fork can land: * (a) outside the window: child inherits either nothing (cap=shmseg, * no inherited mapping) or the live mapping (cap=shmseg-1 WITH * mapping at 0x50000000) - both normal; * (b) inside sys_shmat's reserved-but-unmapped window: child inherits * reserved=1/shmid=-1 and NO mapping. On a vulnerable kernel the * slot can never be cleared -> child capacity < shmseg with NO * inherited mapping = BURNED SLOT. With the shmfork fix the * inherited reserved flag is cleared -> cap == shmseg, no mapping. * Child detection: attach loop count + probe whether 0x50000000 is * mapped (mincore). */ #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/mman.h> #include <sys/wait.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #define FIXADDR ((void *)0x50000000UL) #define SHMSEG_TEST 16 static int shmid; static volatile int *stop; static void * attacher(void *arg __unused) { while (!*stop) { void *p = shmat(shmid, FIXADDR, 0); if (p == (void *)-1) { usleep(200); continue; } usleep(50); if (shmdt(p) < 0) fprintf(stderr, "[!] shmdt: %s\n", strerror(errno)); } return NULL; } static int addr_mapped(void *a) { char vec[1]; return mincore(a, 4096, vec) == 0 ? 1 : 0; } int main(int argc, char **argv) { int forks = argc > 1 ? atoi(argv[1]) : 3000; int shmseg = SHMSEG_TEST; stop = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0); *stop = 0; if ((shmid = shmget(IPC_PRIVATE, 256 * 1024, IPC_CREAT | 0666)) < 0) err(1, "shmget"); fprintf(stderr, "[*] shmseg test cap=%d\n", shmseg); pthread_t th; if (pthread_create(&th, NULL, attacher, NULL)) err(1, "pthread_create"); long burns = 0, normal = 0, clean = 0, total = 0; for (int i = 0; i < forks; i++) { pid_t p = fork(); if (p == 0) { int n = 0; void *last[64]; memset(last, 0, sizeof(last)); while (n < shmseg) { void *q = shmat(shmid, NULL, 0); if (q == (void *)-1) break; last[n++] = q; } int inherited = addr_mapped(FIXADDR); for (int k = 0; k < n; k++) shmdt(last[k]); if (!inherited && n < shmseg) { printf("BURN pid=%d cap=%d inherited=0\n", getpid(), n); fflush(stdout); _exit(42); } _exit(0); } int st; waitpid(p, &st, 0); total++; if (WIFEXITED(st) && WEXITSTATUS(st) == 42) burns++; else { /* classify */ ; } if ((i % 500) == 0) fprintf(stderr, "[*] %d forks, %ld burns\n", i, burns); } *stop = 1; fprintf(stderr, "[*] DONE forks=%ld burned-slot children=%ld\n", total, burns); if (burns > 0) { fprintf(stderr, "[*] REPRODUCED: fork landed in sys_shmat's " "reservation window and the child inherited a permanently " "unusable shmseg slot\n"); return 2; } fprintf(stderr, "[*] no burned slots observed this run\n"); return 0; } |