// https://syzkaller.appspot.com/bug?id=53a6a1bdcd1daddc717d8193f9b7514f8e4c41c7 // Copyright 2026 syzkaller project authors. All rights reserved. // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. // IMPORTANT: Do not copy the macros or definitions below directly into your reproducer. // Instead, add the following line to your reproducer: // #include "race_toolkit.h" // --- Race Condition Toolkit --- // Macros and snippets for CPU pinning, memory barriers, and userfaultfd. #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Unbuffered I/O: Ensure logs are written immediately. #define SETUP_UNBUFFERED_IO() setvbuf(stdout, NULL, _IONBF, 0) // CPU Pinning: Pin the current thread to a specific CPU core. #define PIN_TO_CPU(cpu) \ do { \ cpu_set_t mask; \ CPU_ZERO(&mask); \ CPU_SET(cpu, &mask); \ if (sched_setaffinity(0, sizeof(mask), &mask) == -1) { \ perror("sched_setaffinity"); \ } \ } while (0) // Memory Barrier: Ensure memory ordering. #define MB() __atomic_thread_fence(__ATOMIC_SEQ_CST) // Spin-wait Barrier: Wait until a memory location has a specific value. // Best for tight race windows (low latency, no context switches). #define WAIT_ON(addr, val) \ do { \ while (__atomic_load_n(addr, __ATOMIC_ACQUIRE) != (val)) \ ; \ } while (0) // Signal: Set a memory location to a specific value to release a WAIT_ON. #define SIGNAL(addr, val) __atomic_store_n(addr, val, __ATOMIC_RELEASE) // --- Timing Primitives --- // Robust timing loops in VM environments (using CLOCK_MONOTONIC to avoid time(NULL) jumps). static inline double timer_elapsed_sec(struct timespec* start) { struct timespec now; if (clock_gettime(CLOCK_MONOTONIC, &now) == -1) { perror("clock_gettime(CLOCK_MONOTONIC) elapsed"); exit(1); } return (double)(now.tv_sec - start->tv_sec) + (double)(now.tv_nsec - start->tv_nsec) / 1e9; } // Initialize a monotonic timer variable. #define TIMER_START(t) \ struct timespec t; \ if (clock_gettime(CLOCK_MONOTONIC, &t) == -1) { \ perror("clock_gettime(CLOCK_MONOTONIC) start"); \ exit(1); \ } // Check if the elapsed time since 't' is less than 'sec' seconds. #define TIMER_NOT_EXPIRED(t, sec) (timer_elapsed_sec(&(t)) < (double)(sec)) // Futex-based Event: Shared with syzkaller executor. // Best for general synchronization or longer waits to save CPU. typedef struct { int state; } event_t; static void event_init(event_t* ev) { ev->state = 0; } static void event_reset(event_t* ev) { ev->state = 0; } static void event_set(event_t* ev) { if (__atomic_load_n(&ev->state, __ATOMIC_ACQUIRE)) { fprintf(stderr, "event already set\n"); exit(1); } __atomic_store_n(&ev->state, 1, __ATOMIC_RELEASE); syscall(SYS_futex, &ev->state, FUTEX_WAKE | FUTEX_PRIVATE_FLAG, 1000000); } static void event_wait(event_t* ev) { while (!__atomic_load_n(&ev->state, __ATOMIC_ACQUIRE)) syscall(SYS_futex, &ev->state, FUTEX_WAIT | FUTEX_PRIVATE_FLAG, 0, 0); } // userfaultfd setup: Register a memory range for page fault handling. static int setup_uffd(void* addr, size_t len) { int uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK); if (uffd == -1) return -1; struct uffdio_api api = {.api = UFFD_API, .features = 0}; if (ioctl(uffd, UFFDIO_API, &api) == -1) { close(uffd); return -1; } struct uffdio_register reg = { .range = {.start = (uintptr_t)addr, .len = len}, .mode = UFFDIO_REGISTER_MODE_MISSING}; if (ioctl(uffd, UFFDIO_REGISTER, ®) == -1) { close(uffd); return -1; } return uffd; } // --- Guidance on Usage --- // 1. Use WAIT_ON/SIGNAL for tight race conditions to avoid scheduling overhead. // 2. Use event_t (futexes) for general coordination or when waiting for longer periods. // 3. Always use PIN_TO_CPU to increase race probability on multi-core systems. // 4. Use setup_uffd to register a memory range for page fault handling. This allows you to // pause a thread accessing that memory until you handle the fault, creating a reliable // and controllable race window. // 5. Call SETUP_UNBUFFERED_IO() at the start of main() to ensure that logs are printed // immediately. This is essential for understanding the exact interleaving of events // when debugging race conditions. // 6. For timing-based loops (e.g., running a race for 10 seconds), do NOT use time(NULL) // or loops relying on real-time clocks, as VM clocks are highly unreliable and can fail or drift. // Instead, use the robust monotonic timing primitives TIMER_START and TIMER_NOT_EXPIRED: // TIMER_START(start); // while (TIMER_NOT_EXPIRED(start, 10.0)) { // // Your race logic here // } #include #include #include #include #include #include #include #include #include #include #include #include #define AF_IEEE802154 36 #define IEEE802154_ADDR_LEN 8 #define IEEE802154_ADDR_NONE 0x0 #define IEEE802154_ADDR_SHORT 0x2 #define IEEE802154_ADDR_LONG 0x3 struct ieee802154_addr_sa { int addr_type; uint16_t pan_id; union { uint8_t hwaddr[IEEE802154_ADDR_LEN]; uint16_t short_addr; }; }; struct sockaddr_ieee802154 { sa_family_t family; struct ieee802154_addr_sa addr; }; #define NL802154_GENL_NAME "nl802154" #define NL802154_CMD_TRIGGER_SCAN 35 #define NL802154_CMD_ABORT_SCAN 36 #define NL802154_ATTR_IFINDEX 3 #define NL802154_ATTR_PAGE 7 #define NL802154_ATTR_SCAN_TYPE 31 #define NL802154_ATTR_SCAN_CHANNELS 33 #define NL802154_ATTR_SCAN_DURATION 36 #define NL802154_SCAN_ACTIVE 2 struct nl_req { struct nlmsghdr n; struct genlmsghdr g; char buf[256]; }; static int nl_sock; static int nl802154_id; static int wpan_ifindex; static int get_family_id(int sock, const char *name) { struct { struct nlmsghdr n; struct genlmsghdr g; uint16_t nla_len; uint16_t nla_type; char name[16]; } req = { .n.nlmsg_len = sizeof(req), .n.nlmsg_type = GENL_ID_CTRL, .n.nlmsg_flags = NLM_F_REQUEST, .g.cmd = CTRL_CMD_GETFAMILY, .nla_len = sizeof(req.name) + 4, .nla_type = CTRL_ATTR_FAMILY_NAME, }; strncpy(req.name, name, sizeof(req.name)); if (send(sock, &req, sizeof(req), 0) < 0) { printf("[-] Failed to send GETFAMILY request: %s\n", strerror(errno)); exit(1); } char buf[4096]; int len = recv(sock, buf, sizeof(buf), 0); if (len < 0) { printf("[-] Failed to recv GETFAMILY response: %s\n", strerror(errno)); exit(1); } struct nlmsghdr *nlh = (struct nlmsghdr *)buf; if (nlh->nlmsg_type == NLMSG_ERROR) return -1; struct genlmsghdr *gh = (struct genlmsghdr *)NLMSG_DATA(nlh); struct nlattr *attr = (struct nlattr *)((char *)gh + GENL_HDRLEN); int attr_len = nlh->nlmsg_len - NLMSG_HDRLEN - GENL_HDRLEN; int family_id = -1; while (attr_len >= sizeof(struct nlattr)) { if (attr->nla_type == CTRL_ATTR_FAMILY_ID) { family_id = *(uint16_t *)((char *)attr + 4); break; } int rlen = NLA_ALIGN(attr->nla_len); attr = (struct nlattr *)((char *)attr + rlen); attr_len -= rlen; } /* Drain any leftover messages */ while (recv(sock, buf, sizeof(buf), MSG_DONTWAIT) > 0) {} return family_id; } static void nla_put_u32(struct nl_req *req, int type, uint32_t val) { struct nlattr *attr = (struct nlattr *)((char *)req + req->n.nlmsg_len); attr->nla_type = type; attr->nla_len = 8; *(uint32_t *)((char *)attr + 4) = val; req->n.nlmsg_len += 8; } static void nla_put_u8(struct nl_req *req, int type, uint8_t val) { struct nlattr *attr = (struct nlattr *)((char *)req + req->n.nlmsg_len); attr->nla_type = type; attr->nla_len = 5; *(uint8_t *)((char *)attr + 4) = val; req->n.nlmsg_len += NLA_ALIGN(5); } static void trigger_scan(void) { struct nl_req req = { .n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN), .n.nlmsg_type = nl802154_id, .n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK, .g.cmd = NL802154_CMD_TRIGGER_SCAN, }; nla_put_u32(&req, NL802154_ATTR_IFINDEX, wpan_ifindex); nla_put_u8(&req, NL802154_ATTR_SCAN_TYPE, NL802154_SCAN_ACTIVE); nla_put_u8(&req, NL802154_ATTR_PAGE, 0); nla_put_u32(&req, NL802154_ATTR_SCAN_CHANNELS, 1 << 13); nla_put_u8(&req, NL802154_ATTR_SCAN_DURATION, 4); send(nl_sock, &req, req.n.nlmsg_len, 0); char buf[4096]; recv(nl_sock, buf, sizeof(buf), 0); while (recv(nl_sock, buf, sizeof(buf), MSG_DONTWAIT) > 0) {} } static void abort_scan(void) { struct nl_req req = { .n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN), .n.nlmsg_type = nl802154_id, .n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK, .g.cmd = NL802154_CMD_ABORT_SCAN, }; nla_put_u32(&req, NL802154_ATTR_IFINDEX, wpan_ifindex); send(nl_sock, &req, req.n.nlmsg_len, 0); char buf[4096]; recv(nl_sock, buf, sizeof(buf), 0); while (recv(nl_sock, buf, sizeof(buf), MSG_DONTWAIT) > 0) {} } static int dgram_sock; static struct sockaddr_ieee802154 dst_addr; static void *tx_thread(void *arg) { char buf[64] = {0}; while (1) { sendto(dgram_sock, buf, sizeof(buf), 0, (struct sockaddr *)&dst_addr, sizeof(dst_addr)); } return NULL; } static void *scan_thread(void *arg) { while (1) { trigger_scan(); usleep(100); abort_scan(); } return NULL; } int main() { SETUP_UNBUFFERED_IO(); nl_sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC); if (nl_sock < 0) { printf("[-] Failed to create AF_NETLINK socket: %s\n", strerror(errno)); exit(1); } printf("[+] AF_NETLINK socket created.\n"); struct sockaddr_nl snl = { .nl_family = AF_NETLINK }; if (bind(nl_sock, (struct sockaddr *)&snl, sizeof(snl)) < 0) { printf("[-] Failed to bind AF_NETLINK socket: %s\n", strerror(errno)); exit(1); } printf("[+] AF_NETLINK socket bound.\n"); /* Trigger mac802154_hwsim module loading via generic netlink */ int hwsim_id = get_family_id(nl_sock, "MAC802154_HWSIM"); if (hwsim_id < 0) { printf("[-] Failed to get MAC802154_HWSIM family id\n"); exit(1); } printf("[+] MAC802154_HWSIM family id: %d\n", hwsim_id); nl802154_id = get_family_id(nl_sock, NL802154_GENL_NAME); if (nl802154_id < 0) { printf("[-] Failed to get nl802154 family id\n"); exit(1); } printf("[+] nl802154 family id: %d\n", nl802154_id); int fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { printf("[-] Failed to create AF_INET socket: %s\n", strerror(errno)); exit(1); } printf("[+] AF_INET socket created.\n"); struct ifreq ifr = {}; wpan_ifindex = -1; /* Find and bring up a wpan interface */ for (int i = 0; i < 10; i++) { sprintf(ifr.ifr_name, "wpan%d", i); if (ioctl(fd, SIOCGIFFLAGS, &ifr) == 0) { ifr.ifr_flags |= IFF_UP | IFF_RUNNING; if (ioctl(fd, SIOCSIFFLAGS, &ifr) == 0) { if (ioctl(fd, SIOCGIFINDEX, &ifr) == 0) { wpan_ifindex = ifr.ifr_ifindex; printf("[+] Found wpan interface %s with ifindex %d\n", ifr.ifr_name, wpan_ifindex); break; } } } } close(fd); if (wpan_ifindex < 0) { printf("[-] Failed to find wpan interface\n"); exit(1); } dgram_sock = socket(AF_IEEE802154, SOCK_DGRAM, 0); if (dgram_sock < 0) { printf("[-] Failed to create AF_IEEE802154 socket: %s\n", strerror(errno)); exit(1); } printf("[+] AF_IEEE802154 socket created.\n"); dst_addr.family = AF_IEEE802154; dst_addr.addr.addr_type = IEEE802154_ADDR_SHORT; dst_addr.addr.pan_id = 0xbeef; dst_addr.addr.short_addr = 0x2; pthread_t t1, t2; if (pthread_create(&t1, NULL, tx_thread, NULL) != 0) { printf("[-] Failed to create tx_thread\n"); exit(1); } if (pthread_create(&t2, NULL, scan_thread, NULL) != 0) { printf("[-] Failed to create scan_thread\n"); exit(1); } printf("[+] Threads created. Racing...\n"); /* Let the threads race for a few seconds */ sleep(5); printf("[+] Done.\n"); return 0; }