// https://syzkaller.appspot.com/bug?id=3c56e196ab4ea69c575d9ad2cb2898b10a5f25ac #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include static void print_debug_locks_status(const char *stage) { FILE *f = fopen("/proc/lockdep_stats", "r"); if (!f) { printf("[*] [%s] /proc/lockdep_stats not accessible\n", stage); return; } char line[256]; int dl = -1; while (fgets(line, sizeof(line), f)) { if (strstr(line, "debug_locks:")) { if (sscanf(line, " debug_locks: %d", &dl) == 1) break; } } fclose(f); printf("[*] [%s] lockdep debug_locks status: %d\n", stage, dl); } static void establish_edge_3(void) { printf("[*] Attempting to establish Edge #3 via an independent configfs subsystem...\n"); /* Candidate 1: nullb */ if (mkdir("/sys/kernel/config/nullb/nullb0", 0755) == 0) { printf("[+] mkdir /sys/kernel/config/nullb/nullb0 successful.\n"); int res = rmdir("/sys/kernel/config/nullb/nullb0"); if (res < 0) { printf("[-] Failed to rmdir /sys/kernel/config/nullb/nullb0: %s\n", strerror(errno)); exit(1); } printf("[+] rmdir /sys/kernel/config/nullb/nullb0 successful (Edge #3 established via nullb).\n"); return; } printf("[-] /sys/kernel/config/nullb not available or failed: %s. Trying netconsole...\n", strerror(errno)); /* Candidate 2: netconsole */ if (mkdir("/sys/kernel/config/netconsole/target0", 0755) == 0) { printf("[+] mkdir /sys/kernel/config/netconsole/target0 successful.\n"); int res = rmdir("/sys/kernel/config/netconsole/target0"); if (res < 0) { printf("[-] Failed to rmdir /sys/kernel/config/netconsole/target0: %s\n", strerror(errno)); exit(1); } printf("[+] rmdir /sys/kernel/config/netconsole/target0 successful (Edge #3 established via netconsole).\n"); return; } printf("[-] /sys/kernel/config/netconsole not available or failed: %s. Scanning /sys/kernel/config...\n", strerror(errno)); /* Candidate 3: dynamic discovery across all non-gadget/non-target configfs subsystems */ DIR *d = opendir("/sys/kernel/config"); if (!d) { printf("[-] Failed to opendir /sys/kernel/config: %s\n", strerror(errno)); exit(1); } struct dirent *de; int established = 0; while ((de = readdir(d)) != NULL) { if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..") || !strcmp(de->d_name, "usb_gadget") || !strcmp(de->d_name, "target")) continue; char path[512]; snprintf(path, sizeof(path), "/sys/kernel/config/%s/repro_leaf", de->d_name); printf("[*] Trying mkdir %s\n", path); if (mkdir(path, 0755) == 0) { printf("[+] mkdir %s successful.\n", path); int res = rmdir(path); if (res < 0) { printf("[-] Failed to rmdir %s: %s\n", path, strerror(errno)); closedir(d); exit(1); } printf("[+] rmdir %s successful (Edge #3 established via %s).\n", path, de->d_name); established = 1; break; } /* Check subdirectories in case the subsystem contains default groups */ char parent_path[512]; snprintf(parent_path, sizeof(parent_path), "/sys/kernel/config/%s", de->d_name); DIR *subd = opendir(parent_path); if (subd) { struct dirent *sde; while ((sde = readdir(subd)) != NULL) { if (!strcmp(sde->d_name, ".") || !strcmp(sde->d_name, "..")) continue; snprintf(path, sizeof(path), "/sys/kernel/config/%s/%s/repro_leaf", de->d_name, sde->d_name); printf("[*] Trying mkdir %s\n", path); if (mkdir(path, 0755) == 0) { printf("[+] mkdir %s successful.\n", path); int res = rmdir(path); if (res < 0) { printf("[-] Failed to rmdir %s: %s\n", path, strerror(errno)); closedir(subd); closedir(d); exit(1); } printf("[+] rmdir %s successful (Edge #3 established via %s/%s).\n", path, de->d_name, sde->d_name); established = 1; break; } } closedir(subd); if (established) break; } } closedir(d); if (!established) { printf("[-] Failed to establish Edge #3: no suitable independent configfs subsystem found.\n"); exit(1); } } int main(void) { setvbuf(stdout, NULL, _IONBF, 0); printf("[*] Starting reproducer for circular locking dependency in uvcg_extension_bm_controls_store\n"); /* Step 1: Ensure /sys/kernel/config directory exists and configfs is mounted */ struct stat st; if (stat("/sys/kernel/config", &st) < 0) { int res = mkdir("/sys/kernel/config", 0755); if (res < 0 && errno != EEXIST) { printf("[-] Failed to mkdir /sys/kernel/config: %s\n", strerror(errno)); exit(1); } printf("[+] mkdir /sys/kernel/config successful.\n"); } else { printf("[+] /sys/kernel/config exists.\n"); } int res = mount("none", "/sys/kernel/config", "configfs", 0, NULL); if (res < 0) { if (errno == EBUSY) { printf("[+] configfs is already mounted at /sys/kernel/config.\n"); } else { printf("[-] Failed to mount configfs: %s\n", strerror(errno)); exit(1); } } else { printf("[+] mount configfs successful.\n"); } print_debug_locks_status("Initial"); /* Step 2: Establish Lock Edge #3 cleanly (&sb->s_type->i_mutex_key -> &p->frag_sem) * Using an independent subsystem (e.g. nullb or netconsole) prevents recording premature * edges to gadget_subsys.su_mutex or target_core_subsys.su_mutex, keeping debug_locks active. */ establish_edge_3(); print_debug_locks_status("After Edge #3"); /* Step 3: Establish Lock Edge #1 (gadget_subsys.su_mutex -> &opts->dep_lock) * Creating a gadget directory and TCM function instance. * function_make holds gadget_subsys.su_mutex while tcm_set_name acquires opts->dep_lock. */ const char *gadget_dir = "/sys/kernel/config/usb_gadget/g1"; res = mkdir(gadget_dir, 0755); if (res < 0 && errno != EEXIST) { printf("[-] Failed to mkdir %s: %s\n", gadget_dir, strerror(errno)); exit(1); } printf("[+] mkdir %s successful.\n", gadget_dir); const char *tcm_func = "/sys/kernel/config/usb_gadget/g1/functions/tcm.usb0"; res = mkdir(tcm_func, 0755); if (res < 0 && errno != EEXIST) { printf("[-] Failed to mkdir %s: %s\n", tcm_func, strerror(errno)); exit(1); } printf("[+] mkdir %s successful (Edge #1 established).\n", tcm_func); print_debug_locks_status("After Edge #1"); /* Step 4: Establish Lock Edge #2 (&opts->dep_lock -> &sb->s_type->i_mutex_key) * Creating target fabric and TPG under target configfs. * usbg_make_tpg holds opts->dep_lock and calls configfs_depend_item_unlocked, * which acquires configfs root inode i_mutex. */ const char *target_fabric = "/sys/kernel/config/target/usb_gadget"; res = mkdir(target_fabric, 0755); if (res < 0 && errno != EEXIST) { printf("[-] Failed to mkdir %s: %s\n", target_fabric, strerror(errno)); exit(1); } printf("[+] mkdir %s successful.\n", target_fabric); const char *target_wwn = "/sys/kernel/config/target/usb_gadget/naa.1234567890123456"; res = mkdir(target_wwn, 0755); if (res < 0 && errno != EEXIST) { printf("[-] Failed to mkdir %s: %s\n", target_wwn, strerror(errno)); exit(1); } printf("[+] mkdir %s successful.\n", target_wwn); const char *target_tpg = "/sys/kernel/config/target/usb_gadget/naa.1234567890123456/tpgt_1"; res = mkdir(target_tpg, 0755); if (res < 0 && errno != EEXIST) { printf("[-] Failed to mkdir %s: %s\n", target_tpg, strerror(errno)); exit(1); } printf("[+] mkdir %s successful (Edge #2 established).\n", target_tpg); print_debug_locks_status("After Edge #2"); /* Step 5: Establish Lock Edge #0 and Trigger Deadlock (&p->frag_sem -> gadget_subsys.su_mutex) * Create UVC function and extension unit. */ const char *uvc_func = "/sys/kernel/config/usb_gadget/g1/functions/uvc.usb0"; res = mkdir(uvc_func, 0755); if (res < 0 && errno != EEXIST) { printf("[-] Failed to mkdir %s: %s\n", uvc_func, strerror(errno)); exit(1); } printf("[+] mkdir %s successful.\n", uvc_func); const char *uvc_ext = "/sys/kernel/config/usb_gadget/g1/functions/uvc.usb0/control/extensions/ext.0"; res = mkdir(uvc_ext, 0755); if (res < 0 && errno != EEXIST) { printf("[-] Failed to mkdir %s: %s\n", uvc_ext, strerror(errno)); exit(1); } printf("[+] mkdir %s successful.\n", uvc_ext); /* Write to bmControls attribute to invoke uvcg_extension_bm_controls_store */ const char *bm_controls = "/sys/kernel/config/usb_gadget/g1/functions/uvc.usb0/control/extensions/ext.0/bmControls"; int fd = open(bm_controls, O_WRONLY); if (fd < 0) { printf("[-] Failed to open %s: %s\n", bm_controls, strerror(errno)); exit(1); } printf("[+] open %s successful.\n", bm_controls); const char *data = "1\n"; ssize_t written = write(fd, data, strlen(data)); if (written < 0) { printf("[-] Failed to write to %s: %s\n", bm_controls, strerror(errno)); close(fd); exit(1); } printf("[+] write to %s successful (Edge #0 executed, circular dependency triggered).\n", bm_controls); res = close(fd); if (res < 0) { printf("[-] Failed to close %s fd: %s\n", bm_controls, strerror(errno)); exit(1); } printf("[+] close %s fd successful.\n", bm_controls); print_debug_locks_status("After write"); sleep(1); printf("[+] Reproducer finished.\n"); return 0; }