// https://syzkaller.appspot.com/bug?id=a7eee69d4952ae94bd4595910a2da91c5a1655f4 #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #define BLOCK_SIZE 1024 #define NUM_BLOCKS 1024 uint8_t image[NUM_BLOCKS * BLOCK_SIZE]; static void set32(int offset, uint32_t val) { *(uint32_t *)(image + offset) = val; } static void set16(int offset, uint16_t val) { *(uint16_t *)(image + offset) = val; } static void set64(int offset, uint64_t val) { *(uint64_t *)(image + offset) = val; } void create_image() { memset(image, 0, sizeof(image)); // 1. Superblock at offset 1024 int sb = 1024; set32(sb + 0, 16); // s_inodes_count set32(sb + 4, NUM_BLOCKS); // s_blocks_count_lo set32(sb + 12, NUM_BLOCKS - 9); // s_free_blocks_count_lo set32(sb + 16, 16 - 10); // s_free_inodes_count set32(sb + 20, 1); // s_first_data_block set32(sb + 24, 0); // s_log_block_size (1024) set32(sb + 28, 0); // s_log_cluster_size set32(sb + 32, 8192); // s_blocks_per_group set32(sb + 36, 8192); // s_clusters_per_group set32(sb + 40, 16); // s_inodes_per_group set16(sb + 56, 0xEF53); // s_magic set16(sb + 58, 1); // s_state set16(sb + 60, 1); // s_errors set32(sb + 76, 1); // s_rev_level set32(sb + 84, 11); // s_first_ino set16(sb + 88, 128); // s_inode_size set32(sb + 96, 0x0102); // s_feature_incompat (EXT4_FEATURE_INCOMPAT_MMP | EXT4_FEATURE_INCOMPAT_FILETYPE) for (int i = 0; i < 16; i++) image[sb + 104 + i] = i + 1; // s_uuid set16(sb + 254, 32); // s_desc_size set16(sb + 358, 1); // s_mmp_update_interval (1 second, but kernel enforces 5s min) set64(sb + 360, 8); // s_mmp_block // 2. Group descriptor at block 2 (offset 2048) int gd = 2048; set32(gd + 0, 3); // bg_block_bitmap_lo set32(gd + 4, 4); // bg_inode_bitmap_lo set32(gd + 8, 5); // bg_inode_table_lo set16(gd + 12, NUM_BLOCKS - 9); // bg_free_blocks_count_lo set16(gd + 14, 16 - 10); // bg_free_inodes_count_lo set16(gd + 16, 1); // bg_used_dirs_count_lo set16(gd + 18, 0x0004); // bg_flags (EXT4_BG_INODE_ZEROED) // 3. Block bitmap at block 3 (offset 3072) image[3072] = 0xFF; // Blocks 0-7 used image[3073] = 0x01; // Block 8 used // Block bitmap padding (blocks 1024 to 8191) memset(&image[3072 + 128], 0xFF, 1024 - 128); // 4. Inode bitmap at block 4 (offset 4096) image[4096] = 0xFF; // Inodes 1-8 used image[4097] = 0x03; // Inodes 9-10 used // Inode bitmap padding (inodes 17 to 8192) memset(&image[4096 + 2], 0xFF, 1024 - 2); // 5. Inode table at block 5 (offset 5120) int ino2 = 5120 + 128; // Inode 2 (Root) set16(ino2 + 0, 0x41ED); // i_mode (S_IFDIR | 0755) set32(ino2 + 4, 1024); // i_size_lo set16(ino2 + 26, 2); // i_links_count set32(ino2 + 28, 2); // i_blocks_lo (2 sectors) set32(ino2 + 40, 7); // i_block[0] // 6. Root dir data at block 7 (offset 7168) int dir = 7168; set32(dir + 0, 2); // inode (.) set16(dir + 4, 12); // rec_len image[dir + 6] = 1; // name_len image[dir + 7] = 2; // file_type image[dir + 8] = '.'; set32(dir + 12, 2); // inode (..) set16(dir + 16, 1024 - 12); // rec_len image[dir + 18] = 2; // name_len image[dir + 19] = 2; // file_type image[dir + 20] = '.'; image[dir + 21] = '.'; // 7. MMP block at block 8 (offset 8192) int mmp = 8192; set32(mmp + 0, 0x004D4D50); // mmp_magic (EXT4_MMP_MAGIC) set32(mmp + 4, 0xFF4D4D50); // mmp_seq (EXT4_MMP_SEQ_CLEAN) - Skips wait time set16(mmp + 112, 1); // mmp_check_interval int fd = open("ext4.img", O_CREAT | O_RDWR | O_TRUNC, 0666); if (fd < 0) { printf("[-] Failed to open ext4.img: %s\n", strerror(errno)); exit(1); } if (write(fd, image, sizeof(image)) != sizeof(image)) { printf("[-] Failed to write ext4.img: %s\n", strerror(errno)); exit(1); } close(fd); printf("[+] Created ext4.img\n"); } char loop_dev[64]; int setup_loop() { int ctl = open("/dev/loop-control", O_RDWR); if (ctl < 0) { printf("[-] Failed to open /dev/loop-control: %s\n", strerror(errno)); exit(1); } int devnr = ioctl(ctl, LOOP_CTL_GET_FREE); close(ctl); if (devnr < 0) { printf("[-] Failed to get free loop device: %s\n", strerror(errno)); exit(1); } sprintf(loop_dev, "/dev/loop%d", devnr); int loop_fd = open(loop_dev, O_RDWR); if (loop_fd < 0) { if (mknod(loop_dev, S_IFBLK | 0666, makedev(7, devnr)) < 0) { printf("[-] Failed to mknod %s: %s\n", loop_dev, strerror(errno)); exit(1); } loop_fd = open(loop_dev, O_RDWR); } if (loop_fd < 0) { printf("[-] Failed to open %s: %s\n", loop_dev, strerror(errno)); exit(1); } int img = open("ext4.img", O_RDWR); if (img < 0) { printf("[-] Failed to open ext4.img: %s\n", strerror(errno)); close(loop_fd); exit(1); } if (ioctl(loop_fd, LOOP_SET_FD, img) < 0) { printf("[-] Failed to LOOP_SET_FD: %s\n", strerror(errno)); close(img); close(loop_fd); exit(1); } close(img); // Close loop_fd before mount to avoid -EBUSY close(loop_fd); printf("[+] Loop device %s setup successfully.\n", loop_dev); return 0; } int read_fd = -1; volatile int stop_threads = 0; void *reader_thread(void *arg) { char buf[512]; while (!stop_threads) { // Continuously read the MMP block (block 8 * 1024 = 8192) if (pread(read_fd, buf, 512, 8192) < 0) { // Ignore read errors } } return NULL; } int main() { setvbuf(stdout, NULL, _IONBF, 0); printf("[*] Starting reproducer...\n"); create_image(); setup_loop(); if (mkdir("/mnt", 0777) < 0 && errno != EEXIST) { printf("[-] Failed to mkdir /mnt: %s\n", strerror(errno)); exit(1); } printf("[+] Created /mnt\n"); // Open the loop device for reading BEFORE mount read_fd = open(loop_dev, O_RDONLY); if (read_fd < 0) { printf("[-] Failed to open %s for reading: %s\n", loop_dev, strerror(errno)); exit(1); } printf("[+] Opened %s for reading\n", loop_dev); // Pre-load the MMP block into the page cache to avoid race with the loop driver char dummy[512]; if (pread(read_fd, dummy, 512, 8192) < 0) { printf("[-] Failed to pread MMP block: %s\n", strerror(errno)); exit(1); } printf("[+] Pre-loaded MMP block into page cache\n"); // Spawn 16 reader threads to race against kmmpd pthread_t threads[16]; for (int i = 0; i < 16; i++) { if (pthread_create(&threads[i], NULL, reader_thread, NULL) != 0) { printf("[-] Failed to create thread %d\n", i); exit(1); } } printf("[+] Spawned reader threads\n"); // Loop mount and unmount to trigger the race multiple times for (int i = 0; i < 200; i++) { if (mount(loop_dev, "/mnt", "ext4", 0, "") == 0) { if (umount("/mnt") != 0) { printf("[-] umount failed: %s\n", strerror(errno)); } } else { printf("[-] mount failed: %s\n", strerror(errno)); } } // Cleanup stop_threads = 1; for (int i = 0; i < 16; i++) { pthread_join(threads[i], NULL); } printf("[+] Joined reader threads\n"); close(read_fd); // Clear loop device int loop_fd = open(loop_dev, O_RDWR); if (loop_fd >= 0) { ioctl(loop_fd, LOOP_CLR_FD, 0); close(loop_fd); } printf("[+] Reproducer finished.\n"); return 0; }