From 0e377bec23ca71b7152a78b665f003f4ae277461 Mon Sep 17 00:00:00 2001 From: usualtreee <1340399194@qq.com> Date: Thu, 22 Oct 2026 19:30:19 +0800 Subject: [PATCH] [dfs][9pfs] fix: clamp server-controlled lengths to the received reply The 9PFS client trusted server-controlled length fields: - dfs_9pfs_read() copied READ3resok.count bytes into the caller's buffer without comparing count with the requested length or the decoded data.data_len, and advanced the buffer by the request length instead of the copied byte count. - dfs_9pfs_getdents() discarded the number of bytes actually received and then trusted the in-packet count plus every per-record stat_size and name length, so a crafted reply made the parser walk past the receive buffer (ASan use-after-poison, read 8.3 MB into an 8 MB heap region). Pass the actually-received byte count to p9_transaction() and clamp every wire-supplied length against it; reject replies shorter than the count field. In getdents, stop when the remaining bytes cannot hold the next record, when a record overruns the received data, or when a name overruns its record. Verified on bsp/simulator (x86-64, ASan) with a fake 9P server replying to Tread with an 11-byte packet whose count field claims ~2 GB: before the fix the reader aborts inside get_rx_value16_of()/dfs_9pfs_getdents(); after the fix the reply is rejected and the process survives (no ASan report, empty listing), while a well-formed listing still parses correctly. Refs: #11289 --- .../dfs/dfs_v1/filesystems/9pfs/dfs_9pfs.c | 62 +++++++++++++++++-- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/components/dfs/dfs_v1/filesystems/9pfs/dfs_9pfs.c b/components/dfs/dfs_v1/filesystems/9pfs/dfs_9pfs.c index 6e842f51c858..835c23651ef0 100644 --- a/components/dfs/dfs_v1/filesystems/9pfs/dfs_9pfs.c +++ b/components/dfs/dfs_v1/filesystems/9pfs/dfs_9pfs.c @@ -682,7 +682,7 @@ static ssize_t dfs_9pfs_read(struct dfs_file *fd, void *buf, size_t count) { int rc; size_t tcount = 0; - rt_uint32_t max_count, set, size, got; + rt_uint32_t max_count, set, size, got, rx_size = 0; struct p9_file *p9f = fd->vnode->data; struct p9_connection *conn = p9f->connection; @@ -704,10 +704,26 @@ static ssize_t dfs_9pfs_read(struct dfs_file *fd, void *buf, size_t count) size = put_value32(conn, size, set); put_tx_value32_of(conn, P9_MSG_SIZE, size); - if (!(rc = p9_transaction(conn, size, RT_NULL))) + if (!(rc = p9_transaction(conn, size, &rx_size))) { + if (rx_size < P9_MSG_READ_DATA) + { + /* the reply does not even hold the count field */ + tcount = p9_to_fs_err(P9_READ_UNEXPECTED_DATA); + break; + } + got = get_rx_value32_of(conn, P9_MSG_READ_COUNT); + /* + * Never trust the wire: the file data must fit into the data + * that was actually received. + */ + if (got > rx_size - P9_MSG_READ_DATA) + { + got = rx_size - P9_MSG_READ_DATA; + } + if (got == 0) { break; @@ -824,7 +840,7 @@ static int dfs_9pfs_getdents(struct dfs_file *fd, struct dirent *dirp, uint32_t { int rc; rt_uint64_t off; - rt_uint32_t ret, end, stat_size, size; + rt_uint32_t ret, end, stat_size, size, rx_size = 0; struct p9_file *p9f = fd->vnode->data; struct p9_connection *conn = p9f->connection; @@ -849,23 +865,56 @@ static int dfs_9pfs_getdents(struct dfs_file *fd, struct dirent *dirp, uint32_t size = put_value32(conn, size, fd->vnode->size); put_tx_value32_of(conn, P9_MSG_SIZE, size); - if (!(rc = p9_transaction(conn, size, RT_NULL))) + if (!(rc = p9_transaction(conn, size, &rx_size))) { + if (rx_size < P9_MSG_READ_DATA) + { + /* the reply does not even hold the count field */ + rt_mutex_release(&conn->lock); + return -EIO; + } + ret = get_rx_value32_of(conn, P9_MSG_READ_COUNT); + + /* + * Never trust the wire: the directory entries must fit into the + * data that was actually received. + */ + if (ret > rx_size - P9_MSG_READ_DATA) + { + ret = rx_size - P9_MSG_READ_DATA; + } off = P9_MSG_READ_DATA - P9_MSG_STAT_SIZE; for (int i = 0; (rt_int32_t)ret > 0; off += stat_size, ret -= stat_size, ++i) { + if (ret < sizeof(rt_uint16_t)) + { + /* not even the size field of the next record is left */ + break; + } + /* * size[2] type[2] dev[4] qid[1+4+8] mode[4] atime[4] mtime[4] length[8] */ stat_size = get_rx_value16_of(conn, off + P9_MSG_STAT_SIZE) + sizeof(rt_uint16_t); + if (stat_size > ret) + { + /* the record overruns the received data */ + break; + } if (i < fd->pos) { continue; } + if (stat_size < P9_MSG_STAT_NAME_LEN - P9_MSG_STAT_SIZE + sizeof(rt_uint16_t)) + { + /* the record cannot hold the fields up to the name length */ + break; + } + if (get_rx_value16_of(conn, off + P9_MSG_STAT_TYPE) & P9_STAT_MODE_DIR) { dirp->d_type = DT_DIR; @@ -886,6 +935,11 @@ static int dfs_9pfs_getdents(struct dfs_file *fd, struct dirent *dirp, uint32_t * n_muid[4] */ dirp->d_namlen = get_rx_value16_of(conn, off + P9_MSG_STAT_NAME_LEN); + if (P9_MSG_STAT_NAME - P9_MSG_STAT_SIZE + dirp->d_namlen > stat_size) + { + /* the name overruns this record */ + break; + } dirp->d_reclen = (rt_uint16_t)sizeof(struct dirent); rt_strncpy(dirp->d_name, (void *)conn->rx_buffer + off + P9_MSG_STAT_NAME,