Constrain max_count in v9fs_readdir() to transport's current, real
response buffer size before calling v9fs_do_readdir() to prevent
excessive host memory allocation by bad clients.
Client may send a Treaddir request with a large 'count' parameter, and
while the negotiated 'msize' provides some limit, it accounts for guest
being somewhat faithful on the negotiated 'msize' value throughout the
session.
A bad guest client could have negotiated a large 'msize' but provide a
small reply buffer for Treaddir request, causing QEMU to allocate host
memory proportional to 'msize' before discovering the reply cannot fit.
Possible consequence was a potential DoS by a priviliged guest, causing
a disconnection of guest communication due to transport device being
marked as "broken", however QEMU process would have continued to run with
potentially giant host memory allocation, which might have negative
impact on other services running on host.
Fixes: CVE-2026-9238
Fixes: 2149675b195f ("9pfs: add new function v9fs_co_readdir_many()")
Reported-by: Feifan Qian <[email protected]>
Signed-off-by: Christian Schoenebeck <[email protected]>
---
hw/9pfs/9p.c | 18 ++++++++++++++++--
1 file changed, 16 insertions(+), 2 deletions(-)
diff --git a/hw/9pfs/9p.c b/hw/9pfs/9p.c
index 2bb42dfc2e..fa8c7243a7 100644
--- a/hw/9pfs/9p.c
+++ b/hw/9pfs/9p.c
@@ -2652,6 +2652,7 @@ static void coroutine_fn v9fs_readdir(void *opaque)
uint32_t max_count;
V9fsPDU *pdu = opaque;
V9fsState *s = pdu->s;
+ size_t max_resp_sz;
retval = pdu_unmarshal(pdu, offset, "dqd", &fid,
&initial_offset, &max_count);
@@ -2660,9 +2661,22 @@ static void coroutine_fn v9fs_readdir(void *opaque)
}
trace_v9fs_readdir(pdu->tag, pdu->id, fid, initial_offset, max_count);
+ max_resp_sz = s->msize;
+
+ /*
+ * Constrain max_count to transport's current, actual response buffer size.
+ * A bad client might provide a response buffer < msize.
+ */
+ if (s->transport->response_buffer_size) {
+ size_t buf_size = s->transport->response_buffer_size(pdu);
+ if (max_resp_sz > buf_size) {
+ max_resp_sz = buf_size;
+ }
+ }
+
/* Enough space for a R_readdir header: size[4] Rreaddir tag[2] count[4] */
- if (max_count > s->msize - 11) {
- max_count = s->msize - 11;
+ if (max_count > max_resp_sz - 11) {
+ max_count = max_resp_sz - 11;
warn_report_once(
"9p: bad client: T_readdir with count > msize - 11"
);
--
2.47.3