@samitouri / QOSamiQemu / commits / 64c6c7e0df

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>

Christian Schoenebeck committed Jun 12, 2026 at 20:22 UTC 64c6c7e0df726055fac9ed12b30f712b115193f3
1 file changed +22 -2
hw/9pfs/9p.c
+22 -2
@@ -2679,6 +2679,7 @@ static void coroutine_fn v9fs_readdir(void *opaque)
2679 uint32_t max_count;
2680 V9fsPDU *pdu = opaque;
2681 V9fsState *s = pdu->s;
2682 + size_t max_resp_sz;
2683
2684 retval = pdu_unmarshal(pdu, offset, "dqd", &fid,
2685 &initial_offset, &max_count);
@@ -2687,9 +2688,28 @@ static void coroutine_fn v9fs_readdir(void *opaque)
2688 }
2689 trace_v9fs_readdir(pdu->tag, pdu->id, fid, initial_offset, max_count);
2690
2691 + max_resp_sz = s->msize;
2692 +
2693 + /*
2694 + * Constrain max_count to transport's current, actual response buffer size.
2695 + * A bad client might provide a response buffer < msize.
2696 + */
2697 + if (s->transport->response_buffer_size) {
2698 + size_t buf_size = s->transport->response_buffer_size(pdu);
2699 + if (max_resp_sz > buf_size) {
2700 + max_resp_sz = buf_size;
2701 + }
2702 + }
2703 +
2704 /* Enough space for a R_readdir header: size[4] Rreaddir tag[2] count[4] */
2691 - if (max_count > s->msize - 11) {
2692 - max_count = s->msize - 11;
2705 + if (max_resp_sz > 11) {
2706 + max_resp_sz -= 11;
2707 + } else {
2708 + max_resp_sz = 0;
2709 + }
2710 +
2711 + if (max_count > max_resp_sz) {
2712 + max_count = max_resp_sz;
2713 warn_report_once(
2714 "9p: bad client: T_readdir with count > msize - 11"
2715 );