// https://syzkaller.appspot.com/bug?id=f1f12cd146f61eb399d3d188703913f53a1cb159 #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef FSCONFIG_CMD_RECONFIGURE #define FSCONFIG_CMD_RECONFIGURE 7 #endif #ifndef __NR_fsconfig #define __NR_fsconfig 431 #endif #ifndef __NR_fspick #define __NR_fspick 433 #endif #ifndef AT_FDCWD #define AT_FDCWD -100 #endif #ifndef MEMERASE struct erase_info_user { unsigned int start; unsigned int length; }; #define MEMERASE _IOW('M', 2, struct erase_info_user) #endif #define TARGET_DIR "/tmp/jffs2_target" #define FILE_COUNT 16 #define FILE_SIZE 3072 int main(void) { setvbuf(stdout, NULL, _IONBF, 0); printf("[*] Starting JFFS2 summary NULL-pointer dereference reproducer...\n"); /* 1. Ensure /tmp/jffs2_target exists and is clean */ int res = mkdir(TARGET_DIR, 0755); if (res < 0 && errno != EEXIST) { printf("[-] Failed to create mountpoint %s: %s\n", TARGET_DIR, strerror(errno)); exit(1); } printf("[+] Mountpoint %s ready.\n", TARGET_DIR); /* Clean up any potential stale mount */ res = umount2(TARGET_DIR, MNT_FORCE | MNT_DETACH); if (res < 0) { printf("[*] Cleanup umount2 on %s returned: %s (expected if not mounted)\n", TARGET_DIR, strerror(errno)); } else { printf("[+] Cleaned up stale mount on %s.\n", TARGET_DIR); } /* 2. Ensure /dev/mtd0 character device node exists */ if (access("/dev/mtd0", F_OK) != 0) { printf("[*] Creating /dev/mtd0 device node (major 90, minor 0)...\n"); res = mknod("/dev/mtd0", S_IFCHR | 0660, makedev(90, 0)); if (res < 0 && errno != EEXIST) { printf("[-] Failed to mknod /dev/mtd0: %s\n", strerror(errno)); exit(1); } printf("[+] /dev/mtd0 device node created.\n"); } else { printf("[+] /dev/mtd0 device node exists.\n"); } /* 3. Run reproduction iterations */ for (int iter = 0; iter < 10; iter++) { printf("[*] === Starting iteration %d ===\n", iter); /* Erase mtd0 to reset flash state to 0xFF */ int mtd_fd = open("/dev/mtd0", O_RDWR); if (mtd_fd < 0) { printf("[-] Failed to open /dev/mtd0 for reset: %s\n", strerror(errno)); exit(1); } struct erase_info_user ei = { .start = 0, .length = 0x20000 }; res = ioctl(mtd_fd, MEMERASE, &ei); if (res < 0) { printf("[-] Warning: ioctl MEMERASE on /dev/mtd0 failed: %s\n", strerror(errno)); } else { printf("[+] Erased /dev/mtd0 (0x20000 bytes).\n"); } res = close(mtd_fd); if (res < 0) { printf("[-] Failed to close /dev/mtd0: %s\n", strerror(errno)); exit(1); } /* Mount mtd0 read-write */ res = mount("mtd0", TARGET_DIR, "jffs2", 0, NULL); if (res < 0) { printf("[-] Failed to mount mtd0 RW: %s\n", strerror(errno)); exit(1); } printf("[+] Mounted mtd0 RW on %s.\n", TARGET_DIR); /* Create files to populate and consume eraseblocks */ for (int f = 0; f < FILE_COUNT; f++) { char filepath[128]; snprintf(filepath, sizeof(filepath), "%s/file_%d", TARGET_DIR, f); int fd = open(filepath, O_CREAT | O_WRONLY | O_TRUNC, 0644); if (fd < 0) { printf("[-] Failed to open file %s: %s\n", filepath, strerror(errno)); exit(1); } char buf[FILE_SIZE]; memset(buf, 'A' + (f % 26), sizeof(buf)); ssize_t written = write(fd, buf, sizeof(buf)); if (written < 0) { printf("[-] Failed to write to file %s: %s\n", filepath, strerror(errno)); close(fd); exit(1); } res = close(fd); if (res < 0) { printf("[-] Failed to close file %s: %s\n", filepath, strerror(errno)); exit(1); } } printf("[+] Created %d files on JFFS2 filesystem.\n", FILE_COUNT); /* Delete all created files to obsolete their eraseblocks */ for (int f = 0; f < FILE_COUNT; f++) { char filepath[128]; snprintf(filepath, sizeof(filepath), "%s/file_%d", TARGET_DIR, f); res = unlink(filepath); if (res < 0) { printf("[-] Failed to unlink %s: %s\n", filepath, strerror(errno)); exit(1); } } sync(); printf("[+] Unlinked files and synced (eraseblocks marked obsolete).\n"); /* Unmount cleanly */ res = umount2(TARGET_DIR, MNT_FORCE); if (res < 0) { printf("[-] Failed to unmount RW filesystem: %s\n", strerror(errno)); exit(1); } printf("[+] Unmounted RW filesystem cleanly.\n"); /* Mount read-only: scanning leaves dirty blocks in erase_pending_list, * but does not start the GC thread because sb_rdonly(sb) is true. */ res = mount("mtd0", TARGET_DIR, "jffs2", MS_RDONLY, NULL); if (res < 0) { printf("[-] Failed to mount mtd0 RO: %s\n", strerror(errno)); exit(1); } printf("[+] Mounted mtd0 RO on %s.\n", TARGET_DIR); /* Pick the mount using fspick */ int pfd = syscall(__NR_fspick, AT_FDCWD, TARGET_DIR, 0); if (pfd < 0) { printf("[-] Failed to fspick %s: %s\n", TARGET_DIR, strerror(errno)); exit(1); } printf("[+] fspick successful (fd=%d).\n", pfd); /* Call fsconfig with FSCONFIG_CMD_RECONFIGURE. * jffs2_do_remount_fs() sees !(fc->sb_flags & SB_RDONLY) and spawns jffs2_gcd_mtd0. * However, reconfigure_super() does not clear SB_RDONLY from sb->s_flags because * fc->sb_flags_mask is 0. * The GC thread wakes up (erase_pending_list is not empty) and enters its 50ms pause. */ res = syscall(__NR_fsconfig, pfd, FSCONFIG_CMD_RECONFIGURE, NULL, NULL, 0); if (res < 0) { printf("[-] Failed fsconfig reconfigure: %s\n", strerror(errno)); close(pfd); exit(1); } printf("[+] fsconfig reconfigure successful (GC thread started).\n"); res = close(pfd); if (res < 0) { printf("[-] Failed to close fspick fd: %s\n", strerror(errno)); exit(1); } /* Immediately unmount during the GC thread's 50ms pause. * jffs2_kill_sb() sees sb_rdonly(sb) == true and bypasses jffs2_stop_garbage_collect_thread(). * kill_mtd_super() -> jffs2_put_super() executes jffs2_sum_exit(c), setting c->summary = NULL. */ res = umount2(TARGET_DIR, MNT_FORCE); if (res < 0) { printf("[-] Failed to umount2 during GC window: %s\n", strerror(errno)); exit(1); } printf("[+] umount2 executed while GC thread was running.\n"); /* Wait for the GC thread's 50ms timer to expire. * When jffs2_garbage_collect_pass() calls jffs2_mark_erased_block() -> jffs2_sum_add_kvec(), * c->summary is NULL, triggering a general protection fault / KASAN null-ptr-deref. */ printf("[*] Waiting 200ms for asynchronous GC thread execution...\n"); usleep(200000); printf("[+] Sleep finished without immediate crash on iteration %d.\n", iter); } printf("[*] Reproducer loop finished.\n"); return 0; }