[PATCH 1/5] 9p/virtio: clamp the reply size of a zero-copy request

From: Lianjie Wang

Date: Mon Nov 09 2026 - 05:56:34 EST


req_done() takes the used length from the device as the reply size
without checking it against the posted buffers, and p9_check_errors()
skips its size check for zero-copy requests, so reading an oversized
error reply to a zero-copy request can overrun the posted buffers:

BUG: KASAN: slab-out-of-bounds in handle_rerror+0x144/0x230
Read of size 8 at addr ffff888003065768 by task zc9p/157
Call Trace:
handle_rerror+0x144/0x230
p9_virtio_zc_request+0xb8e/0xd70
p9_client_zc_rpc+0x21c/0x970
p9_client_read_once+0x237/0x6c0
The buggy address is located 0 bytes to the right of
allocated 8-byte region [ffff888003065760, ffff888003065768)

Also, handle_rerror() returns before truncating the reply size of a
zero-copy TWRITE, which posts no reply pages, so p9_check_errors() can
parse the error string of an oversized error reply past the end of
rc.sdata.

Fix this by clamping the reply size to the posted buffers upon getting
the reply.

This patch is assisted by Claude Opus 5.5.

Reported-by: Yizhou Zhao <zhaoyz24@xxxxxxxxxxxxxxxxxxxxx>
Closes: https://lore.kernel.org/all/20260607140603.24342-1-zhaoyz24@xxxxxxxxxxxxxxxxxxxxx/
Suggested-by: Dominique Martinet <asmadeus@xxxxxxxxxxxxx>
Fixes: f615625a44c4 ("9p: handling Rerror without copy_from_iter_full()")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Lianjie Wang <lian@xxxxxxxxxxxxxxxxxx>
---
net/9p/trans_virtio.c | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/net/9p/trans_virtio.c b/net/9p/trans_virtio.c
index 9fa63e3ebf673..53046668c16b1 100644
--- a/net/9p/trans_virtio.c
+++ b/net/9p/trans_virtio.c
@@ -537,10 +537,14 @@ p9_virtio_zc_request(struct p9_client *client, struct p9_req_t *req,
* threads - echoes to wmb() in the callback
*/
smp_rmb();
- // RERROR needs reply (== error string) in static data
- if (READ_ONCE(req->status) == REQ_STATUS_RCVD &&
- unlikely(req->rc.sdata[4] == P9_RERROR))
- handle_rerror(req, in_hdr_len, offs, in_pages);
+ if (READ_ONCE(req->status) == REQ_STATUS_RCVD) {
+ // The device may report more than the buffers we posted
+ req->rc.size = umin(req->rc.size, in_hdr_len + inlen);
+
+ // RERROR needs reply (== error string) in static data
+ if (unlikely(req->rc.sdata[4] == P9_RERROR))
+ handle_rerror(req, in_hdr_len, offs, in_pages);
+ }

/*
* Non kernel buffers are pinned, unpin them
--
2.56.0