// https://syzkaller.appspot.com/bug?id=3d2cc0ab4142638cd677dfdde63dab9839864428 #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include int main(void) { setvbuf(stdout, NULL, _IONBF, 0); printf("[*] Starting reproducer for circular locking dependency in uvcg_default_processing_bm_controls_show...\n"); /* Step 1: Ensure configfs mountpoint exists */ 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"); /* Step 2: Mount configfs */ res = mount("configfs", "/sys/kernel/config", "configfs", 0, NULL); if (res < 0 && errno != EBUSY) { printf("[-] Failed to mount configfs: %s\n", strerror(errno)); exit(1); } printf("[+] mount configfs successful.\n"); /* Step 3: Create USB gadget root directory */ res = mkdir("/sys/kernel/config/usb_gadget/g1", 0755); if (res < 0 && errno != EEXIST) { printf("[-] Failed to mkdir /sys/kernel/config/usb_gadget/g1: %s\n", strerror(errno)); exit(1); } printf("[+] mkdir /sys/kernel/config/usb_gadget/g1 successful.\n"); /* Step 4: Create TCM function instance * Establishes: gadget_subsys.su_mutex -> func_lock -> tpg_instances_lock */ res = mkdir("/sys/kernel/config/usb_gadget/g1/functions/tcm.0", 0755); if (res < 0 && errno != EEXIST) { printf("[-] Failed to mkdir /sys/kernel/config/usb_gadget/g1/functions/tcm.0: %s\n", strerror(errno)); exit(1); } printf("[+] mkdir /sys/kernel/config/usb_gadget/g1/functions/tcm.0 successful.\n"); /* Step 5: Register target fabric group usb_gadget */ res = mkdir("/sys/kernel/config/target/usb_gadget", 0755); if (res < 0 && errno != EEXIST) { printf("[-] Failed to mkdir /sys/kernel/config/target/usb_gadget: %s\n", strerror(errno)); exit(1); } printf("[+] mkdir /sys/kernel/config/target/usb_gadget successful.\n"); /* Step 6: Create target fabric WWN (must start with naa.) */ res = mkdir("/sys/kernel/config/target/usb_gadget/naa.5001405fc7642d66", 0755); if (res < 0 && errno != EEXIST) { printf("[-] Failed to mkdir /sys/kernel/config/target/usb_gadget/naa.5001405fc7642d66: %s\n", strerror(errno)); exit(1); } printf("[+] mkdir /sys/kernel/config/target/usb_gadget/naa.5001405fc7642d66 successful.\n"); /* Step 7: Create target TPG (must start with tpgt_) * In usbg_make_tpg(), this calls configfs_depend_item_unlocked() to depend on tcm.0. * Establishes: tpg_instances_lock -> configfs root inode_lock (&sb->s_type->i_mutex_key#26). */ res = mkdir("/sys/kernel/config/target/usb_gadget/naa.5001405fc7642d66/tpgt_1", 0755); if (res < 0 && errno != EEXIST) { printf("[-] Failed to mkdir /sys/kernel/config/target/usb_gadget/naa.5001405fc7642d66/tpgt_1: %s\n", strerror(errno)); exit(1); } printf("[+] mkdir /sys/kernel/config/target/usb_gadget/naa.5001405fc7642d66/tpgt_1 successful.\n"); /* Step 8: Create UVC function instance * Automatically initializes the default control hierarchy: * control/processing/default/bmControls */ res = mkdir("/sys/kernel/config/usb_gadget/g1/functions/uvc.0", 0755); if (res < 0 && errno != EEXIST) { printf("[-] Failed to mkdir /sys/kernel/config/usb_gadget/g1/functions/uvc.0: %s\n", strerror(errno)); exit(1); } printf("[+] mkdir /sys/kernel/config/usb_gadget/g1/functions/uvc.0 successful.\n"); /* Step 9: Call rmdir on non-empty directory /sys/kernel/config/usb_gadget/g1 * Because g1 contains children (functions/tcm.0 and functions/uvc.0), * configfs_detach_prep() fails with -ENOTEMPTY without acquiring gadget_subsys.su_mutex. */ res = rmdir("/sys/kernel/config/usb_gadget/g1"); if (res < 0) { printf("[*] rmdir /sys/kernel/config/usb_gadget/g1 returned: %s (expected ENOTEMPTY)\n", strerror(errno)); } else { printf("[+] rmdir /sys/kernel/config/usb_gadget/g1 succeeded.\n"); } /* Also check for any independent configfs subsystem (e.g. nullb, netconsole) * to ensure the link &sb->s_type->i_mutex_key#26 -> &frag->frag_sem is established * without touching gadget_subsys or target_core_fabrics. */ DIR *dir = opendir("/sys/kernel/config"); if (dir) { struct dirent *de; while ((de = readdir(dir)) != NULL) { if (de->d_name[0] == '.') continue; if (strcmp(de->d_name, "usb_gadget") == 0 || strcmp(de->d_name, "target") == 0) continue; char path[512]; snprintf(path, sizeof(path), "/sys/kernel/config/%s/repro_dummy", de->d_name); printf("[*] Trying independent subsystem at %s...\n", path); if (mkdir(path, 0755) == 0) { printf("[+] Created %s\n", path); if (rmdir(path) == 0) { printf("[+] Successfully removed %s (established inode_lock -> frag_sem)\n", path); break; } else { printf("[-] Failed to rmdir %s: %s\n", path, strerror(errno)); } } } closedir(dir); } /* Step 10: Read bmControls attribute * configfs_read_iter() acquires down_read(&frag->frag_sem). * uvcg_default_processing_bm_controls_show() attempts to acquire gadget_subsys.su_mutex. * Completes the full 5-lock circular dependency: * gadget_subsys.su_mutex -> func_lock -> tpg_instances_lock -> inode_lock -> frag->frag_sem -> gadget_subsys.su_mutex * Triggers: "possible deadlock in uvcg_default_processing_bm_controls_show" */ const char *attr_path = "/sys/kernel/config/usb_gadget/g1/functions/uvc.0/control/processing/default/bmControls"; int fd = open(attr_path, O_RDONLY); if (fd < 0) { printf("[-] Failed to open %s: %s\n", attr_path, strerror(errno)); exit(1); } printf("[+] open %s successful.\n", attr_path); char buf[256]; ssize_t bytes_read = read(fd, buf, sizeof(buf) - 1); if (bytes_read < 0) { printf("[-] Failed to read %s: %s\n", attr_path, strerror(errno)); } else { buf[bytes_read] = '\0'; printf("[+] read %zd bytes from %s: %s\n", bytes_read, attr_path, buf); } res = close(fd); if (res < 0) { printf("[-] Failed to close %s: %s\n", attr_path, strerror(errno)); exit(1); } printf("[+] close %s successful.\n", attr_path); /* Allow kernel lockdep logs to flush */ sleep(1); printf("[+] Reproduction sequence completed successfully.\n"); return 0; }