// https://syzkaller.appspot.com/bug?id=de9b8ad01b1056fa3a394d45b95f8c0f1a8fb497 #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef IFLA_VXLAN_ID #define IFLA_VXLAN_ID 1 #endif #ifndef IFLA_VXLAN_GROUP #define IFLA_VXLAN_GROUP 2 #endif #ifndef IFLA_VXLAN_LINK #define IFLA_VXLAN_LINK 3 #endif #ifndef IFLA_VXLAN_PORT #define IFLA_VXLAN_PORT 15 #endif struct if_list { int count; char names[64][IFNAMSIZ]; unsigned int indices[64]; }; static void snapshot_interfaces(struct if_list *list) { memset(list, 0, sizeof(*list)); struct if_nameindex *ni = if_nameindex(); if (!ni) return; for (struct if_nameindex *p = ni; p->if_index != 0 && list->count < 64; p++) { list->indices[list->count] = p->if_index; strncpy(list->names[list->count], p->if_name, IFNAMSIZ - 1); list->count++; } if_freenameindex(ni); } static int find_new_lapb(const struct if_list *before, char *out_name, unsigned int *out_idx) { for (int retry = 0; retry < 20; retry++) { struct if_nameindex *ni = if_nameindex(); if (!ni) { usleep(10000); continue; } for (struct if_nameindex *p = ni; p->if_index != 0; p++) { if (strncmp(p->if_name, "lapb", 4) == 0) { int found = 0; for (int i = 0; i < before->count; i++) { if (before->indices[i] == p->if_index) { found = 1; break; } } if (!found) { strncpy(out_name, p->if_name, IFNAMSIZ - 1); out_name[IFNAMSIZ - 1] = '\0'; *out_idx = p->if_index; if_freenameindex(ni); return 0; } } } if_freenameindex(ni); usleep(10000); } return -1; } static void addattr_l(struct nlmsghdr *n, int maxlen, int type, const void *data, int alen) { int len = RTA_LENGTH(alen); if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen) return; struct rtattr *rta = (struct rtattr *)(((char *)n) + NLMSG_ALIGN(n->nlmsg_len)); rta->rta_type = type; rta->rta_len = len; if (alen > 0 && data) memcpy(RTA_DATA(rta), data, alen); n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len); } static struct rtattr *addattr_nest(struct nlmsghdr *n, int maxlen, int type) { struct rtattr *nest = (struct rtattr *)(((char *)n) + NLMSG_ALIGN(n->nlmsg_len)); addattr_l(n, maxlen, type, NULL, 0); return nest; } static void addattr_nest_end(struct nlmsghdr *n, struct rtattr *nest) { nest->rta_len = (char *)n + NLMSG_ALIGN(n->nlmsg_len) - (char *)nest; } static int nl_send_and_check(int nl_fd, struct nlmsghdr *n) { struct sockaddr_nl sa; memset(&sa, 0, sizeof(sa)); sa.nl_family = AF_NETLINK; struct iovec iov = { n, n->nlmsg_len }; struct msghdr msg = { .msg_name = &sa, .msg_namelen = sizeof(sa), .msg_iov = &iov, .msg_iovlen = 1 }; if (sendmsg(nl_fd, &msg, 0) < 0) { return -errno; } char buf[4096]; iov.iov_base = buf; iov.iov_len = sizeof(buf); ssize_t res = recvmsg(nl_fd, &msg, 0); if (res < 0) return -errno; struct nlmsghdr *h = (struct nlmsghdr *)buf; if (h->nlmsg_type == NLMSG_ERROR) { struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(h); return err->error; } return 0; } static int set_if_flags(int inet_fd, const char *name, short flags_add, short flags_del) { struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, name, IFNAMSIZ - 1); if (ioctl(inet_fd, SIOCGIFFLAGS, &ifr) < 0) { printf("[-] SIOCGIFFLAGS on %s failed: %s\n", name, strerror(errno)); return -1; } ifr.ifr_flags |= flags_add; ifr.ifr_flags &= ~flags_del; if (ioctl(inet_fd, SIOCSIFFLAGS, &ifr) < 0) { printf("[-] SIOCSIFFLAGS on %s failed: %s\n", name, strerror(errno)); return -1; } return 0; } static int set_ip_ioctl(int inet_fd, const char *name, const char *ip_str, const char *mask_str) { struct ifreq ifr; struct sockaddr_in sin; memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, name, IFNAMSIZ - 1); memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; if (inet_pton(AF_INET, ip_str, &sin.sin_addr) != 1) { printf("[-] inet_pton failed for %s\n", ip_str); return -1; } memcpy(&ifr.ifr_addr, &sin, sizeof(sin)); if (ioctl(inet_fd, SIOCSIFADDR, &ifr) < 0) { printf("[-] SIOCSIFADDR failed on %s: %s\n", name, strerror(errno)); return -1; } memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, name, IFNAMSIZ - 1); memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; if (inet_pton(AF_INET, mask_str, &sin.sin_addr) != 1) { printf("[-] inet_pton failed for %s\n", mask_str); return -1; } memcpy(&ifr.ifr_netmask, &sin, sizeof(sin)); if (ioctl(inet_fd, SIOCSIFNETMASK, &ifr) < 0) { printf("[-] SIOCSIFNETMASK failed on %s: %s\n", name, strerror(errno)); return -1; } return 0; } static int add_addr(int nl_fd, uint32_t ifindex, const char *ip_str, int prefixlen) { struct { struct nlmsghdr n; struct ifaddrmsg ifa; char buf[256]; } req; memset(&req, 0, sizeof(req)); req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)); req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_ACK; req.n.nlmsg_type = RTM_NEWADDR; req.ifa.ifa_family = AF_INET; req.ifa.ifa_prefixlen = prefixlen; req.ifa.ifa_flags = 0; req.ifa.ifa_scope = RT_SCOPE_UNIVERSE; req.ifa.ifa_index = ifindex; struct in_addr addr; if (inet_pton(AF_INET, ip_str, &addr) != 1) return -EINVAL; addattr_l(&req.n, sizeof(req), IFA_LOCAL, &addr, sizeof(addr)); addattr_l(&req.n, sizeof(req), IFA_ADDRESS, &addr, sizeof(addr)); return nl_send_and_check(nl_fd, &req.n); } static int add_route(int nl_fd, uint32_t oif, const char *dst_ip) { struct { struct nlmsghdr n; struct rtmsg r; char buf[256]; } req; memset(&req, 0, sizeof(req)); req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_ACK; req.n.nlmsg_type = RTM_NEWROUTE; req.r.rtm_family = AF_INET; req.r.rtm_dst_len = 32; req.r.rtm_table = RT_TABLE_MAIN; req.r.rtm_protocol = RTPROT_BOOT; req.r.rtm_scope = RT_SCOPE_LINK; req.r.rtm_type = RTN_UNICAST; struct in_addr addr; if (inet_pton(AF_INET, dst_ip, &addr) != 1) return -EINVAL; addattr_l(&req.n, sizeof(req), RTA_DST, &addr, sizeof(addr)); addattr_l(&req.n, sizeof(req), RTA_OIF, &oif, sizeof(oif)); int res = nl_send_and_check(nl_fd, &req.n); if (res < 0 && res != -EEXIST) { printf("[-] RTM_NEWROUTE returned error: %d (%s)\n", res, strerror(-res)); return res; } return 0; } static int delete_link(int nl_fd, const char *name) { struct { struct nlmsghdr n; struct ifinfomsg ifi; char buf[256]; } req; memset(&req, 0, sizeof(req)); req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; req.n.nlmsg_type = RTM_DELLINK; req.ifi.ifi_family = AF_UNSPEC; addattr_l(&req.n, sizeof(req), IFLA_IFNAME, name, strlen(name) + 1); int res = nl_send_and_check(nl_fd, &req.n); if (res < 0 && res != -ENODEV) { printf("[-] RTM_DELLINK for %s returned error: %d (%s)\n", name, res, strerror(-res)); } return res; } static int create_vxlan(int nl_fd, const char *name, uint32_t link_ifindex, const char *group_ip, uint16_t port, uint32_t vni_id) { struct { struct nlmsghdr n; struct ifinfomsg ifi; char buf[512]; } req; memset(&req, 0, sizeof(req)); req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK; req.n.nlmsg_type = RTM_NEWLINK; req.ifi.ifi_family = AF_UNSPEC; addattr_l(&req.n, sizeof(req), IFLA_IFNAME, name, strlen(name) + 1); struct rtattr *linkinfo = addattr_nest(&req.n, sizeof(req), IFLA_LINKINFO); addattr_l(&req.n, sizeof(req), IFLA_INFO_KIND, "vxlan", strlen("vxlan") + 1); struct rtattr *data = addattr_nest(&req.n, sizeof(req), IFLA_INFO_DATA); addattr_l(&req.n, sizeof(req), IFLA_VXLAN_ID, &vni_id, sizeof(vni_id)); struct in_addr grp; if (inet_pton(AF_INET, group_ip, &grp) != 1) return -EINVAL; addattr_l(&req.n, sizeof(req), IFLA_VXLAN_GROUP, &grp, sizeof(grp)); addattr_l(&req.n, sizeof(req), IFLA_VXLAN_LINK, &link_ifindex, sizeof(link_ifindex)); uint16_t be_port = htons(port); addattr_l(&req.n, sizeof(req), IFLA_VXLAN_PORT, &be_port, sizeof(be_port)); addattr_nest_end(&req.n, data); addattr_nest_end(&req.n, linkinfo); int res = nl_send_and_check(nl_fd, &req.n); if (res < 0) { printf("[-] Failed to create %s: %d (%s)\n", name, res, strerror(-res)); return res; } printf("[+] Created %s successfully.\n", name); return 0; } int main(void) { setvbuf(stdout, NULL, _IONBF, 0); printf("[*] Starting reproducer for lapb_data_received circular locking dependency...\n"); /* Open control sockets */ int inet_fd = socket(AF_INET, SOCK_DGRAM, 0); if (inet_fd < 0) { printf("[-] Failed to open AF_INET socket: %s\n", strerror(errno)); exit(1); } printf("[+] AF_INET socket opened successfully.\n"); int nl_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); if (nl_fd < 0) { printf("[-] Failed to open NETLINK_ROUTE socket: %s\n", strerror(errno)); exit(1); } printf("[+] NETLINK_ROUTE socket opened successfully.\n"); int packet_fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); if (packet_fd < 0) { printf("[-] Failed to open AF_PACKET socket: %s\n", strerror(errno)); exit(1); } printf("[+] AF_PACKET socket opened successfully.\n"); /* Step 1: Cleanup old vxlan0 link if present */ delete_link(nl_fd, "vxlan0"); /* Step 2: Underlay Setup (tap0 -> lapb0) */ int tun_fd = open("/dev/net/tun", O_RDWR); if (tun_fd < 0) { printf("[-] Failed to open /dev/net/tun: %s\n", strerror(errno)); exit(1); } printf("[+] /dev/net/tun opened successfully.\n"); struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); ifr.ifr_flags = IFF_TAP | IFF_NO_PI; strncpy(ifr.ifr_name, "tap0", IFNAMSIZ - 1); int res = ioctl(tun_fd, TUNSETIFF, &ifr); if (res < 0) { printf("[-] TUNSETIFF for tap0 failed: %s\n", strerror(errno)); exit(1); } printf("[+] Created tap0 interface successfully.\n"); struct if_list snap1; snapshot_interfaces(&snap1); res = set_if_flags(inet_fd, "tap0", IFF_UP | IFF_RUNNING, 0); if (res < 0) { printf("[-] Failed to bring tap0 UP: %s\n", strerror(errno)); exit(1); } printf("[+] Brought tap0 UP successfully.\n"); char lapb0_name[IFNAMSIZ]; unsigned int lapb0_idx = 0; res = find_new_lapb(&snap1, lapb0_name, &lapb0_idx); if (res < 0) { printf("[-] Failed to detect new lapb interface for tap0: %s\n", strerror(errno)); exit(1); } printf("[+] Detected lapb interface for tap0: %s (ifindex %u)\n", lapb0_name, lapb0_idx); /* Step 3: Establish Dependency 1 (lapb_list_lock -> &lapb->lock) */ res = set_if_flags(inet_fd, lapb0_name, IFF_UP | IFF_RUNNING, 0); if (res < 0) { printf("[-] Failed to bring %s UP: %s\n", lapb0_name, strerror(errno)); exit(1); } printf("[+] Brought %s UP successfully.\n", lapb0_name); res = set_if_flags(inet_fd, lapb0_name, 0, IFF_UP); if (res < 0) { printf("[-] Failed to bring %s DOWN: %s\n", lapb0_name, strerror(errno)); exit(1); } printf("[+] Brought %s DOWN successfully (established lapb_list_lock -> &lapb->lock).\n", lapb0_name); res = set_if_flags(inet_fd, lapb0_name, IFF_UP | IFF_RUNNING, 0); if (res < 0) { printf("[-] Failed to bring %s back UP: %s\n", lapb0_name, strerror(errno)); exit(1); } printf("[+] Brought %s back UP successfully.\n", lapb0_name); /* Step 4: Configure Addressing and FIB Route on lapb0 */ res = set_ip_ioctl(inet_fd, lapb0_name, "192.168.1.1", "255.255.255.0"); if (res < 0) { printf("[-] Failed to configure IP address on %s via ioctl: %s\n", lapb0_name, strerror(errno)); exit(1); } res = add_addr(nl_fd, lapb0_idx, "192.168.1.1", 24); if (res < 0 && res != -EEXIST) { printf("[-] add_addr failed on %s: %d (%s)\n", lapb0_name, res, strerror(-res)); exit(1); } printf("[+] Configured 192.168.1.1/24 on %s successfully.\n", lapb0_name); res = add_route(nl_fd, lapb0_idx, "192.168.1.2"); if (res < 0 && res != -EEXIST) { printf("[-] Failed to add route to 192.168.1.2 on %s: %s\n", lapb0_name, strerror(-res)); exit(1); } printf("[+] Configured route to 192.168.1.2/32 dev %s successfully.\n", lapb0_name); /* Step 5: Establish Dependency 3 (_xmit_X25 -> &lapbeth->up_lock cleanly) * NOTE: skb->data[0] = 0x03 (NOT 0x00, 0x01, or 0x02) hits 'default: goto drop;' * in lapbeth_xmit(), avoiding lapb_data_request() and preventing &lapb->lock acquisition. */ struct sockaddr_ll sll; memset(&sll, 0, sizeof(sll)); sll.sll_family = AF_PACKET; sll.sll_protocol = htons(ETH_P_ALL); sll.sll_ifindex = lapb0_idx; unsigned char x25_pkt[] = { 0x03, 0x00, 0x00, 0x00 }; ssize_t sres = sendto(packet_fd, x25_pkt, sizeof(x25_pkt), 0, (struct sockaddr *)&sll, sizeof(sll)); if (sres < 0) { printf("[-] Direct transmit on %s failed: %s\n", lapb0_name, strerror(errno)); exit(1); } printf("[+] Direct transmit on %s succeeded (%zd bytes, established _xmit_X25 -> &lapbeth->up_lock).\n", lapb0_name, sres); /* Step 6: Create and Bring UP vxlan0 */ struct if_list snap2; snapshot_interfaces(&snap2); res = create_vxlan(nl_fd, "vxlan0", lapb0_idx, "192.168.1.2", 4789, 100); if (res < 0) { printf("[-] Failed to create vxlan0: %d\n", res); exit(1); } res = set_if_flags(inet_fd, "vxlan0", IFF_UP | IFF_RUNNING, 0); if (res < 0) { printf("[-] Failed to bring vxlan0 UP: %s\n", strerror(errno)); exit(1); } printf("[+] Brought vxlan0 UP successfully.\n"); char lapb1_name[IFNAMSIZ]; unsigned int lapb1_idx = 0; res = find_new_lapb(&snap2, lapb1_name, &lapb1_idx); if (res < 0) { printf("[-] Failed to detect new lapb interface for vxlan0: %s\n", strerror(errno)); exit(1); } printf("[+] Detected lapb interface for vxlan0: %s (ifindex %u)\n", lapb1_name, lapb1_idx); /* Step 7: Establish Dependency 2 (&lapb->lock -> _xmit_X25) * Bringing lapb1 UP triggers lapb_device_event(NETDEV_UP), which holds * &lapb->lock and transmits SABM out vxlan0 -> lapb0 (_xmit_X25). */ res = set_if_flags(inet_fd, lapb1_name, IFF_UP | IFF_RUNNING, 0); if (res < 0) { printf("[-] Failed to bring %s UP: %s\n", lapb1_name, strerror(errno)); exit(1); } printf("[+] Brought %s UP successfully (established &lapb->lock -> _xmit_X25).\n", lapb1_name); /* Step 8: Trigger Deadlock in lapb_data_received * Writing ETH_P_DEC Ethernet frames into tap0 causes lapbeth_rcv(tap0) * to execute with spin_lock_bh(&lapbeth0->up_lock). * lapbeth_rcv() calls lapb_data_received() -> lapb_devtostruct(), * which attempts to acquire read_lock_bh(&lapb_list_lock). * This completes the cycle: * lapb_list_lock -> &lapb->lock -> _xmit_X25 -> &lapbeth->up_lock -> lapb_list_lock */ unsigned char dec_pkt[16] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* dst MAC: broadcast */ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, /* src MAC */ 0x60, 0x00, /* ETH_P_DEC (0x6000) */ 0x00, 0x00 /* LAPB length = 0 */ }; unsigned char dec_pkt2[19] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* dst MAC: broadcast */ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, /* src MAC */ 0x60, 0x00, /* ETH_P_DEC (0x6000) */ 0x03, 0x00, /* LAPB length = 3 */ 0x03, 0x2f, 0x00 /* SABM payload */ }; printf("[*] Injecting ETH_P_DEC frames into tap0 to trigger circular deadlock in lapb_data_received...\n"); for (int i = 0; i < 10; i++) { ssize_t wres = write(tun_fd, dec_pkt, sizeof(dec_pkt)); if (wres < 0) { printf("[-] write dec_pkt to tap0 failed: %s\n", strerror(errno)); exit(1); } wres = write(tun_fd, dec_pkt2, sizeof(dec_pkt2)); if (wres < 0) { printf("[-] write dec_pkt2 to tap0 failed: %s\n", strerror(errno)); exit(1); } usleep(2000); } printf("[+] Injected ETH_P_DEC frames successfully.\n"); printf("[*] Waiting for lockdep warnings...\n"); sleep(2); close(tun_fd); close(packet_fd); close(nl_fd); close(inet_fd); printf("[+] Reproducer finished.\n"); return 0; }