// https://syzkaller.appspot.com/bug?id=eefefd759ef3b48ddad4da086ff8f4b9194576f9 #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef MEMGETINFO struct mtd_info_user { uint8_t type; uint32_t flags; uint32_t size; uint32_t erasesize; uint32_t writesize; uint32_t oobsize; uint64_t padding; }; struct erase_info_user { uint32_t start; uint32_t length; }; #define MEMGETINFO _IOR('M', 1, struct mtd_info_user) #define MEMERASE _IOW('M', 2, struct erase_info_user) #endif _Static_assert(sizeof(struct mtd_info_user) == 32, "struct mtd_info_user must be 32 bytes"); #define JFFS2_MAGIC_BITMASK 0x1985 #define JFFS2_NODETYPE_CLEANMARKER 0x2003 struct jffs2_unknown_node { uint16_t magic; uint16_t nodetype; uint32_t totlen; uint32_t hdr_crc; }; #define NUM_PUSHERS 4 static uint32_t crc32_le_calc(uint32_t crc, const unsigned char *p, size_t len) { while (len--) { crc ^= *p++; for (int i = 0; i < 8; i++) crc = (crc >> 1) ^ ((crc & 1) ? 0xedb88320 : 0); } return crc; } static void write_cleanmarkers(int fd, size_t total_size, size_t erase_size) { struct jffs2_unknown_node marker; marker.magic = JFFS2_MAGIC_BITMASK; marker.nodetype = JFFS2_NODETYPE_CLEANMARKER; marker.totlen = sizeof(marker); marker.hdr_crc = crc32_le_calc(0, (const unsigned char *)&marker, sizeof(marker) - 4); for (size_t offset = 0; offset < total_size; offset += erase_size) { ssize_t written = pwrite(fd, &marker, sizeof(marker), offset); if (written != (ssize_t)sizeof(marker)) { printf("[-] Failed to pwrite cleanmarker at offset %zu: %s\n", offset, strerror(errno)); break; } } } static void write_sysctl(const char *path, const char *val) { int fd = open(path, O_WRONLY); if (fd < 0) { printf("[-] Failed to open sysctl %s: %s\n", path, strerror(errno)); return; } ssize_t len = (ssize_t)strlen(val); ssize_t written = write(fd, val, len); if (written < 0) { printf("[-] Failed to write to sysctl %s: %s\n", path, strerror(errno)); } else { printf("[+] Set sysctl %s to %s", path, val); } close(fd); } static int find_mtd_from_proc(int *mtd_num, size_t *size, size_t *erasesize) { FILE *f = fopen("/proc/mtd", "r"); if (!f) { printf("[-] Failed to open /proc/mtd: %s\n", strerror(errno)); return -1; } char line[256]; int found = 0; while (fgets(line, sizeof(line), f)) { int num; unsigned long long sz; unsigned int esz; if (sscanf(line, "mtd%d: %llx %x", &num, &sz, &esz) == 3) { *mtd_num = num; *size = (size_t)sz; *erasesize = (size_t)esz; found = 1; printf("[+] Discovered MTD device from /proc/mtd: mtd%d (size=%zu, erasesize=%zu)\n", num, (size_t)sz, (size_t)esz); break; } } fclose(f); return found ? 0 : -1; } static int setup_mtd_and_mount_jffs2(const char *target) { int mtd_num = 0; size_t size = 131072; size_t erasesize = 4096; int found = find_mtd_from_proc(&mtd_num, &size, &erasesize); if (found < 0) { printf("[*] /proc/mtd not found or empty, using defaults mtd0 (size=%zu, erasesize=%zu)\n", size, erasesize); } char chr_dev[64], blk_dev[64], mtd_name[32]; snprintf(chr_dev, sizeof(chr_dev), "/dev/mtd%d", mtd_num); snprintf(blk_dev, sizeof(blk_dev), "/dev/mtdblock%d", mtd_num); snprintf(mtd_name, sizeof(mtd_name), "mtd%d", mtd_num); /* Explicitly unlink stale nodes before mknod to avoid ENOTTY or wrong dev types */ unlink(chr_dev); int res = mknod(chr_dev, S_IFCHR | 0666, makedev(90, mtd_num * 2)); if (res < 0) { printf("[-] Failed to mknod %s: %s\n", chr_dev, strerror(errno)); } else { printf("[+] mknod %s (chr 90,%d) successful.\n", chr_dev, mtd_num * 2); } unlink(blk_dev); res = mknod(blk_dev, S_IFBLK | 0666, makedev(31, mtd_num)); if (res < 0) { printf("[-] Failed to mknod %s: %s\n", blk_dev, strerror(errno)); } else { printf("[+] mknod %s (blk 31,%d) successful.\n", blk_dev, mtd_num); } int mtd_fd = open(chr_dev, O_RDWR); if (mtd_fd >= 0) { printf("[+] Opened %s successfully.\n", chr_dev); struct mtd_info_user info; memset(&info, 0, sizeof(info)); if (ioctl(mtd_fd, MEMGETINFO, &info) == 0 && info.size > 0 && info.erasesize > 0) { size = info.size; erasesize = info.erasesize; printf("[+] ioctl MEMGETINFO on %s successful: size=%u, erasesize=%u\n", chr_dev, info.size, info.erasesize); } else { printf("[-] ioctl MEMGETINFO on %s failed: %s (using discovered size=%zu, erasesize=%zu)\n", chr_dev, strerror(errno), size, erasesize); } struct erase_info_user einfo = { .start = 0, .length = (uint32_t)size }; if (ioctl(mtd_fd, MEMERASE, &einfo) == 0) { printf("[+] ioctl MEMERASE on %s successful.\n", chr_dev); } else { printf("[-] ioctl MEMERASE on %s failed: %s\n", chr_dev, strerror(errno)); } write_cleanmarkers(mtd_fd, size, erasesize); printf("[+] Written cleanmarkers to %s.\n", chr_dev); close(mtd_fd); if (mount(mtd_name, target, "jffs2", 0, NULL) == 0) { printf("[+] mount %s on %s successful.\n", mtd_name, target); return 0; } printf("[-] Failed to mount %s on %s: %s\n", mtd_name, target, strerror(errno)); if (mount(blk_dev, target, "jffs2", 0, NULL) == 0) { printf("[+] mount %s on %s successful.\n", blk_dev, target); return 0; } printf("[-] Failed to mount %s on %s: %s\n", blk_dev, target, strerror(errno)); } else { printf("[-] Failed to open %s: %s\n", chr_dev, strerror(errno)); } /* Fallback: loop device + block2mtd */ printf("[*] Attempting fallback via loop device + block2mtd...\n"); const char *img_path = "/tmp/jffs2_fallback.img"; size_t img_size = 8 * 1024 * 1024; size_t erasesize_fb = 65536; int img_fd = open(img_path, O_CREAT | O_RDWR | O_TRUNC, 0666); if (img_fd >= 0) { char buf[4096]; memset(buf, 0xff, sizeof(buf)); for (size_t i = 0; i < img_size / sizeof(buf); i++) { if (write(img_fd, buf, sizeof(buf)) != sizeof(buf)) break; } write_cleanmarkers(img_fd, img_size, erasesize_fb); close(img_fd); int ctl_fd = open("/dev/loop-control", O_RDWR); int loop_idx = (ctl_fd >= 0) ? ioctl(ctl_fd, LOOP_CTL_GET_FREE) : 0; if (ctl_fd >= 0) close(ctl_fd); if (loop_idx < 0) loop_idx = 0; char loop_dev[32]; snprintf(loop_dev, sizeof(loop_dev), "/dev/loop%d", loop_idx); unlink(loop_dev); mknod(loop_dev, S_IFBLK | 0666, makedev(7, loop_idx)); int lfd = open(loop_dev, O_RDWR); img_fd = open(img_path, O_RDWR); if (lfd >= 0 && img_fd >= 0) { ioctl(lfd, LOOP_SET_FD, img_fd); close(img_fd); close(lfd); int b2m = open("/sys/module/block2mtd/parameters/block2mtd", O_WRONLY); if (b2m >= 0) { char param[64]; snprintf(param, sizeof(param), "%s,%zu\n", loop_dev, erasesize_fb); write(b2m, param, strlen(param)); close(b2m); } usleep(100000); } } if (mount("mtd0", target, "jffs2", 0, NULL) == 0) return 0; if (mount("mtd1", target, "jffs2", 0, NULL) == 0) return 0; if (mount("/dev/mtdblock0", target, "jffs2", 0, NULL) == 0) return 0; if (mount("/dev/mtdblock1", target, "jffs2", 0, NULL) == 0) return 0; return -1; } static void run_holder(int pipe_write_fd) { write_sysctl("/proc/self/oom_score_adj", "-1000\n"); alarm(45); struct sysinfo si; if (sysinfo(&si) != 0) { printf("[-] Holder failed to get sysinfo: %s\n", strerror(errno)); _exit(1); } unsigned long unit = si.mem_unit ? si.mem_unit : 1; unsigned long long free_bytes = (unsigned long long)si.freeram * unit; size_t target_hold = 0; if (free_bytes > 200ULL * 1024 * 1024) target_hold = (size_t)(free_bytes - 130ULL * 1024 * 1024); else target_hold = (size_t)(free_bytes / 2); size_t chunk_size = 16 * 1024 * 1024; size_t allocated = 0; while (allocated < target_hold) { if (sysinfo(&si) == 0 && (unsigned long long)si.freeram * unit <= 140ULL * 1024 * 1024) break; size_t to_alloc = chunk_size; if (allocated + to_alloc > target_hold) to_alloc = target_hold - allocated; char *p = mmap(NULL, to_alloc, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (p == MAP_FAILED) break; for (size_t off = 0; off < to_alloc; off += 4096) { p[off] = 0x5a; } allocated += to_alloc; } /* Top up until free RAM is down to ~120-140 MB */ while (sysinfo(&si) == 0 && (unsigned long long)si.freeram * unit > 140ULL * 1024 * 1024) { size_t sub_chunk = 4 * 1024 * 1024; char *p = mmap(NULL, sub_chunk, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (p == MAP_FAILED) break; for (size_t off = 0; off < sub_chunk; off += 4096) { p[off] = 0x5a; } } char ack = 1; if (write(pipe_write_fd, &ack, 1) != 1) { _exit(1); } close(pipe_write_fd); while (1) { pause(); } } static void on_pusher_fault(int sig) { (void)sig; while (1) pause(); } static void run_pusher(void) { signal(SIGSEGV, on_pusher_fault); signal(SIGBUS, on_pusher_fault); write_sysctl("/proc/self/oom_score_adj", "1000\n"); alarm(10); size_t chunk_size = 4 * 1024 * 1024; while (1) { char *p = mmap(NULL, chunk_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (p != MAP_FAILED) { for (size_t off = 0; off < chunk_size; off += 4096) { p[off] = 0x5a; } munmap(p, chunk_size); } else { usleep(1000); } } } static void trigger_2stage_memory_pressure(int round) { printf("[*] Round %d: Setting up sustained 2-stage memory pressure...\n", round); int holder_pipe[2]; int res = pipe(holder_pipe); if (res < 0) { printf("[-] Failed to create holder pipe: %s\n", strerror(errno)); exit(1); } /* Fork Holder child to pin bulk memory */ pid_t holder = fork(); if (holder < 0) { printf("[-] Failed to fork holder: %s\n", strerror(errno)); exit(1); } if (holder == 0) { close(holder_pipe[0]); run_holder(holder_pipe[1]); _exit(0); } close(holder_pipe[1]); /* Wait for Holder child to finish memory consumption */ char ack = 0; res = read(holder_pipe[0], &ack, 1); close(holder_pipe[0]); if (res != 1) { printf("[-] Holder child failed to signal completion.\n"); kill(holder, SIGKILL); waitpid(holder, NULL, 0); return; } printf("[+] Holder child pinned bulk memory. Free RAM is low.\n"); /* Fork pushers only after holder is holding memory */ pid_t pushers[NUM_PUSHERS]; for (int i = 0; i < NUM_PUSHERS; i++) { pid_t p = fork(); if (p < 0) { printf("[-] Failed to fork pusher %d: %s\n", i, strerror(errno)); break; } if (p == 0) { run_pusher(); _exit(0); } pushers[i] = p; } printf("[+] %d pushers forked and actively driving direct reclaim.\n", NUM_PUSHERS); /* Drive direct reclaim and kswapd for 2.5 seconds */ usleep(2500000); for (int i = 0; i < NUM_PUSHERS; i++) { kill(pushers[i], SIGKILL); int status = 0; waitpid(pushers[i], &status, 0); printf("[+] Pusher %d stopped (status=0x%x, signaled=%d).\n", i, status, WIFSIGNALED(status)); } /* Allow kswapd / kernel background reclaim to complete shrinker scans */ usleep(200000); /* Terminate Holder child and release pinned memory */ kill(holder, SIGKILL); waitpid(holder, NULL, 0); printf("[+] Holder child terminated and memory freed.\n"); } int main(void) { setvbuf(stdout, NULL, _IONBF, 0); printf("[*] Starting reproducer for possible deadlock in jffs2_xattr_delete_inode...\n"); const char *jffs2_mnt = "/tmp/jffs2_mnt"; const char *ovl_lower = "/tmp/ovl_lower"; const char *ovl_mnt = "/tmp/ovl_mnt"; const char *upper_dir = "/tmp/jffs2_mnt/upper"; const char *work_dir = "/tmp/jffs2_mnt/work"; umount2(ovl_mnt, MNT_FORCE | MNT_DETACH); umount2(jffs2_mnt, MNT_FORCE | MNT_DETACH); int res = mkdir(jffs2_mnt, 0777); if (res < 0 && errno != EEXIST) { printf("[-] Failed to mkdir %s: %s\n", jffs2_mnt, strerror(errno)); exit(1); } printf("[+] mkdir %s successful.\n", jffs2_mnt); res = mkdir(ovl_lower, 0777); if (res < 0 && errno != EEXIST) { printf("[-] Failed to mkdir %s: %s\n", ovl_lower, strerror(errno)); exit(1); } printf("[+] mkdir %s successful.\n", ovl_lower); res = mkdir(ovl_mnt, 0777); if (res < 0 && errno != EEXIST) { printf("[-] Failed to mkdir %s: %s\n", ovl_mnt, strerror(errno)); exit(1); } printf("[+] mkdir %s successful.\n", ovl_mnt); /* Step 1: Initialize MTD and mount JFFS2 */ if (setup_mtd_and_mount_jffs2(jffs2_mnt) != 0) { printf("[-] setup_mtd_and_mount_jffs2 failed.\n"); exit(1); } printf("[+] JFFS2 mounted successfully on %s.\n", jffs2_mnt); /* Step 2: Register lock dependency &c->xattr_sem -> fs_reclaim via explicit setxattr */ char init_file[128]; snprintf(init_file, sizeof(init_file), "%s/init_xattr", jffs2_mnt); int init_fd = open(init_file, O_CREAT | O_RDWR | O_TRUNC, 0666); if (init_fd < 0) { printf("[-] Failed to open %s: %s\n", init_file, strerror(errno)); exit(1); } printf("[+] open %s successful.\n", init_file); res = fsetxattr(init_fd, "user.init", "deadlock_edge", 13, 0); if (res < 0) { printf("[-] Failed to fsetxattr on %s: %s\n", init_file, strerror(errno)); close(init_fd); exit(1); } printf("[+] fsetxattr on %s successful (dependency &c->xattr_sem -> fs_reclaim registered).\n", init_file); close(init_fd); /* Step 3: Setup OverlayFS with upperdir and workdir on JFFS2 */ res = mkdir(upper_dir, 0777); if (res < 0 && errno != EEXIST) { printf("[-] Failed to mkdir %s: %s\n", upper_dir, strerror(errno)); exit(1); } printf("[+] mkdir %s successful.\n", upper_dir); res = mkdir(work_dir, 0777); if (res < 0 && errno != EEXIST) { printf("[-] Failed to mkdir %s: %s\n", work_dir, strerror(errno)); exit(1); } printf("[+] mkdir %s successful.\n", work_dir); char ovl_opts[512]; snprintf(ovl_opts, sizeof(ovl_opts), "lowerdir=%s,upperdir=%s,workdir=%s", ovl_lower, upper_dir, work_dir); res = mount("overlay", ovl_mnt, "overlay", 0, ovl_opts); if (res < 0) { printf("[-] Failed to mount overlay on %s: %s\n", ovl_mnt, strerror(errno)); exit(1); } printf("[+] mount overlay on %s successful.\n", ovl_mnt); /* Step 4: Configure vfs_cache_pressure and min_free_kbytes */ write_sysctl("/proc/sys/vm/vfs_cache_pressure", "100000\n"); write_sysctl("/proc/sys/vm/min_free_kbytes", "131072\n"); /* Step 5: Create files on OverlayFS with xattrs, then unlink on underlying JFFS2 * so their JFFS2 inodes remain alive with pino_nlink == 0 pinned by oi->__upperdentry. * Also inject 1000 negative dentries into OverlayFS to grant high scan priority. */ printf("[*] Creating files on OverlayFS and unlinking on JFFS2 upper...\n"); for (int i = 0; i < 60; i++) { char ovl_path[128]; snprintf(ovl_path, sizeof(ovl_path), "%s/f_%d", ovl_mnt, i); int fd = open(ovl_path, O_CREAT | O_RDWR | O_TRUNC, 0666); if (fd < 0) { printf("[-] Failed to open %s: %s\n", ovl_path, strerror(errno)); exit(1); } char xattr_name[64]; snprintf(xattr_name, sizeof(xattr_name), "user.x_%d", i); res = fsetxattr(fd, xattr_name, "test_val", 8, 0); if (res < 0) { printf("[-] Failed to fsetxattr on %s: %s\n", ovl_path, strerror(errno)); close(fd); exit(1); } close(fd); char upper_path[128]; snprintf(upper_path, sizeof(upper_path), "%s/f_%d", upper_dir, i); res = unlink(upper_path); if (res < 0) { printf("[-] Failed to unlink %s: %s\n", upper_path, strerror(errno)); exit(1); } } printf("[+] 60 files created on OverlayFS and unlinked on JFFS2 upper.\n"); printf("[*] Injecting 1000 negative dentries into OverlayFS dcache LRU...\n"); for (int i = 0; i < 1000; i++) { char neg_f[128]; snprintf(neg_f, sizeof(neg_f), "%s/neg_%d", ovl_mnt, i); access(neg_f, F_OK); } printf("[+] 1000 negative dentries populated into OverlayFS dcache.\n"); /* Step 6: Trigger 2 rounds of sustained 2-stage memory pressure */ for (int round = 0; round < 2; round++) { trigger_2stage_memory_pressure(round); usleep(100000); } /* Step 7: Wait for asynchronous kswapd and kernel logging before exit */ printf("[*] Waiting for asynchronous kernel tasks...\n"); sleep(3); printf("[+] Reproducer execution completed successfully.\n"); return 0; }