Commit 64c6c7e0df for qemu.org
commit 64c6c7e0df726055fac9ed12b30f712b115193f3
Author: Christian Schoenebeck <qemu_oss@crudebyte.com>
Date: Fri Jun 12 20:22:52 2026 +0200
hw/9pfs: cap Treaddir allocation (CVE-2026-9238)
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 for specific, crafted, huge
directories (large amount of entries) 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 <bea1e@proton.me>
Reviewed-by: Stefano Stabellini <sstabellini@kernel.org>
Link: https://lore.kernel.org/qemu-devel/f81a387a2de4f2172fd5830c5654f49d78102254.1781287774.git.qemu_oss@crudebyte.com
Signed-off-by: Christian Schoenebeck <qemu_oss@crudebyte.com>
diff --git a/hw/9pfs/9p.c b/hw/9pfs/9p.c
index 81d9bb8c6d..09454a5404 100644
--- a/hw/9pfs/9p.c
+++ b/hw/9pfs/9p.c
@@ -2679,6 +2679,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);
@@ -2687,9 +2688,28 @@ 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_resp_sz > 11) {
+ max_resp_sz -= 11;
+ } else {
+ max_resp_sz = 0;
+ }
+
+ if (max_count > max_resp_sz) {
+ max_count = max_resp_sz;
warn_report_once(
"9p: bad client: T_readdir with count > msize - 11"
);