// https://syzkaller.appspot.com/bug?id=960ff74c22ed4df608c42381256e6b9ceaefe332 #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include int fid_to_path[65536]; // Minimal 9P2000.L server to satisfy mount and file creation void *p9_server(void *arg) { int fd = (int)(long)arg; uint8_t buf[8192]; while (1) { uint32_t size; if (read(fd, &size, 4) != 4) break; if (size < 7 || size > 8192) break; uint32_t rem = size - 4; uint32_t done = 0; while (rem > 0) { int n = read(fd, buf + 4 + done, rem); if (n <= 0) break; done += n; rem -= n; } if (rem > 0) break; uint8_t id = buf[4]; uint16_t tag = *(uint16_t *)(buf + 5); uint8_t reply[8192]; uint32_t rsize = 7; reply[4] = id + 1; // Default to R-message *(uint16_t *)(reply + 5) = tag; switch (id) { case 100: // Tversion rsize = 7 + 4 + 2 + 8; *(uint32_t *)(reply + 7) = 8192; *(uint16_t *)(reply + 11) = 8; memcpy(reply + 13, "9P2000.L", 8); break; case 104: // Tattach { uint32_t fid = *(uint32_t *)(buf + 7); fid_to_path[fid & 0xffff] = 1; rsize = 20; reply[7] = 0x80; // QTDIR *(uint32_t *)(reply + 8) = 0; *(uint64_t *)(reply + 12) = 1; } break; case 110: // Twalk { uint32_t fid = *(uint32_t *)(buf + 7); uint32_t newfid = *(uint32_t *)(buf + 11); uint16_t nwname = *(uint16_t *)(buf + 15); if (nwname == 0) { fid_to_path[newfid & 0xffff] = fid_to_path[fid & 0xffff]; } else { fid_to_path[newfid & 0xffff] = 2; } rsize = 9 + nwname * 13; *(uint16_t *)(reply + 7) = nwname; for (int i = 0; i < nwname; i++) { reply[9 + i * 13] = 0; // QTFILE *(uint32_t *)(reply + 10 + i * 13) = 0; *(uint64_t *)(reply + 14 + i * 13) = 2 + i; } } break; case 24: // Tgetattr (9P2000.L) { uint32_t fid = *(uint32_t *)(buf + 7); int path = fid_to_path[fid & 0xffff]; rsize = 160; memset(reply + 7, 0, rsize - 7); *(uint64_t *)(reply + 7) = 0x7ff; // valid mask if (path == 1) { reply[15] = 0x80; // QTDIR *(uint32_t *)(reply + 16) = 0; // vers *(uint64_t *)(reply + 20) = 1; // path *(uint32_t *)(reply + 28) = 040777; // mode } else { reply[15] = 0; // QTFILE *(uint32_t *)(reply + 16) = 0; // vers *(uint64_t *)(reply + 20) = 2; // path *(uint32_t *)(reply + 28) = 0100777; // mode } *(uint64_t *)(reply + 40) = 1; // st_nlink *(uint64_t *)(reply + 56) = 0; // st_size *(uint64_t *)(reply + 64) = 4096; // st_blksize *(uint64_t *)(reply + 72) = 8; // st_blocks } break; case 14: // Tlcreate (9P2000.L) { uint32_t fid = *(uint32_t *)(buf + 7); fid_to_path[fid & 0xffff] = 2; rsize = 24; reply[7] = 0; // QTFILE *(uint32_t *)(reply + 8) = 0; *(uint64_t *)(reply + 12) = 2; *(uint32_t *)(reply + 20) = 4096; // iounit } break; case 12: // Tlopen (9P2000.L) case 112: // Topen rsize = 24; reply[7] = 0; // QTFILE *(uint32_t *)(reply + 8) = 0; *(uint64_t *)(reply + 12) = 2; *(uint32_t *)(reply + 20) = 4096; // iounit break; case 116: // Tread { rsize = 11; *(uint32_t *)(reply + 7) = 0; // Return EOF } break; case 118: // Twrite { uint32_t count = *(uint32_t *)(buf + 19); rsize = 11; *(uint32_t *)(reply + 7) = count; } break; case 122: // Tremove case 120: // Tclunk case 108: // Tflush case 50: // Tfsync (9P2000.L) case 26: // Tsetattr (9P2000.L) rsize = 7; break; case 8: // Tstatfs (9P2000.L) rsize = 67; memset(reply + 7, 0, 60); *(uint32_t *)(reply + 7) = 0x01021997; // type (V9FS_MAGIC) *(uint32_t *)(reply + 11) = 4096; // bsize *(uint64_t *)(reply + 15) = 1024; // blocks *(uint64_t *)(reply + 23) = 1024; // bfree *(uint64_t *)(reply + 31) = 1024; // bavail *(uint64_t *)(reply + 39) = 1024; // files *(uint64_t *)(reply + 47) = 1024; // ffree *(uint64_t *)(reply + 55) = 0; // fsid *(uint32_t *)(reply + 63) = 255; // namelen break; default: reply[4] = 7; // Rlerror (9P2000.L) rsize = 11; *(uint32_t *)(reply + 7) = 22; // EINVAL break; } *(uint32_t *)reply = rsize; if (write(fd, reply, rsize) != rsize) break; } return NULL; } void *cachefiles_reader(void *arg) { int fd = (int)(long)arg; char buf[4096]; while (read(fd, buf, sizeof(buf)) > 0) {} return NULL; } int main(void) { char tmpfs_dir[] = "/tmp/cachetest_XXXXXX"; char lower[512], upper[512], work[512], merged[512]; char mount_opts[2048]; char cmd[1024]; int dev_fd = -1; ssize_t ret; if (!mkdtemp(tmpfs_dir)) { printf("[-] mkdtemp failed: %s\n", strerror(errno)); exit(1); } printf("[+] Created temporary directory: %s\n", tmpfs_dir); // Mount tmpfs without user_xattr if (mount("tmpfs", tmpfs_dir, "tmpfs", 0, "") < 0) { printf("[-] mount tmpfs failed: %s\n", strerror(errno)); exit(1); } printf("[+] Mounted tmpfs on %s\n", tmpfs_dir); snprintf(lower, sizeof(lower), "%s/lower", tmpfs_dir); snprintf(upper, sizeof(upper), "%s/upper", tmpfs_dir); snprintf(work, sizeof(work), "%s/work", tmpfs_dir); snprintf(merged, sizeof(merged), "%s/merged", tmpfs_dir); if (mkdir(lower, 0755) < 0 || mkdir(upper, 0755) < 0 || mkdir(work, 0755) < 0 || mkdir(merged, 0755) < 0) { printf("[-] mkdir overlay subdirectories failed: %s\n", strerror(errno)); exit(1); } // Mount overlayfs with index=on,nfs_export=on to satisfy cachefiles requirements snprintf(mount_opts, sizeof(mount_opts), "lowerdir=%s,upperdir=%s,workdir=%s,index=on,nfs_export=on", lower, upper, work); if (mount("overlay", merged, "overlay", 0, mount_opts) < 0) { printf("[-] mount overlayfs failed: %s\n", strerror(errno)); // Fallback without nfs_export=on just in case snprintf(mount_opts, sizeof(mount_opts), "lowerdir=%s,upperdir=%s,workdir=%s", lower, upper, work); if (mount("overlay", merged, "overlay", 0, mount_opts) < 0) { printf("[-] mount overlayfs fallback failed: %s\n", strerror(errno)); exit(1); } } printf("[+] Mounted overlayfs on %s\n", merged); dev_fd = open("/dev/cachefiles", O_RDWR); if (dev_fd < 0) { printf("[-] open /dev/cachefiles failed: %s\n", strerror(errno)); exit(1); } printf("[+] Opened /dev/cachefiles\n"); snprintf(cmd, sizeof(cmd), "dir %s\n", merged); if (write(dev_fd, cmd, strlen(cmd)) < 0) { printf("[-] write dir command failed: %s\n", strerror(errno)); exit(1); } printf("[+] Sent command: %s", cmd); snprintf(cmd, sizeof(cmd), "tag testcache\n"); if (write(dev_fd, cmd, strlen(cmd)) < 0) { printf("[-] write tag command failed: %s\n", strerror(errno)); exit(1); } printf("[+] Sent command: %s", cmd); snprintf(cmd, sizeof(cmd), "bind\n"); ret = write(dev_fd, cmd, strlen(cmd)); if (ret < 0) { printf("[-] bind command failed: %s (errno=%d)\n", strerror(errno), errno); exit(1); } printf("[+] BIND SUCCESSFUL\n"); pthread_t ctid; pthread_create(&ctid, NULL, cachefiles_reader, (void *)(long)dev_fd); // Setup 9p server int sv[2]; if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0) { printf("[-] socketpair failed: %s\n", strerror(errno)); exit(1); } pthread_t tid; pthread_create(&tid, NULL, p9_server, (void *)(long)sv[1]); // Mount 9p with fscache char p9_dir[] = "/tmp/9p_XXXXXX"; if (!mkdtemp(p9_dir)) { printf("[-] mkdtemp 9p failed: %s\n", strerror(errno)); exit(1); } char p9_mount_opts[256]; snprintf(p9_mount_opts, sizeof(p9_mount_opts), "trans=fd,rfdno=%d,wfdno=%d,cache=fscache,version=9p2000.L", sv[0], sv[0]); if (mount("9p", p9_dir, "9p", 0, p9_mount_opts) < 0) { printf("[-] mount 9p failed: %s\n", strerror(errno)); rmdir(p9_dir); exit(1); } printf("[+] Mounted 9p on %s\n", p9_dir); // Open the root directory and close it to trigger fscache_unuse_cookie int fd = open(p9_dir, O_RDONLY | O_DIRECTORY); if (fd >= 0) { printf("[+] Opened 9p root directory\n"); close(fd); } else { printf("[-] open 9p root directory failed: %s\n", strerror(errno)); } // Also create a file and write to it char test_file[512]; snprintf(test_file, sizeof(test_file), "%s/test.txt", p9_dir); int fd2 = open(test_file, O_CREAT | O_RDWR, 0666); if (fd2 >= 0) { if (write(fd2, "hello", 5) < 0) { printf("[-] write to 9p file failed: %s\n", strerror(errno)); } else { printf("[+] Wrote to 9p file\n"); } close(fd2); } else { printf("[-] open 9p file failed: %s\n", strerror(errno)); } // Unmount 9p to trigger cookie withdrawal (and the out-of-bounds read) if (umount(p9_dir) < 0) { printf("[-] umount 9p failed: %s\n", strerror(errno)); } else { printf("[+] Unmounted 9p\n"); } rmdir(p9_dir); // Allow fscache_cookie_worker workqueue to process the withdrawal printf("[+] Waiting for fscache_cookie_worker...\n"); sleep(2); close(dev_fd); if (umount(merged) < 0) { printf("[-] umount overlay failed: %s\n", strerror(errno)); } rmdir(merged); rmdir(work); rmdir(upper); rmdir(lower); if (umount(tmpfs_dir) < 0) { printf("[-] umount tmpfs failed: %s\n", strerror(errno)); } rmdir(tmpfs_dir); return 0; }