@samitouri / QOSamiQemu / commits / 693b296b17

hw/9pfs: add xattr FID limit to prevent memory exhaustion

Add a limit on the number of simultaneously open xattr FIDs to prevent host memory exhaustion attacks. Each xattr FID contains a buffer for the xattr value, and without a limit, a malicious priviliged guest with direct communication access to 9p server could create a huge number of xattr FIDs until host memory is eventually exhausted. Fix this by: - add xattr_fid_limit to struct FsContext for the max. amount - add xattr_fid_count to struct FsContext for the current amount - init xattr_fid_limit with 1024 - init xattr_fid_count with 0 - add function xattr_fid_count_inc() to increment the count - add function xattr_fid_count_decr() to decrement the count - call xattr_fid_count_inc() in Txattrcreate handler - call xattr_fid_count_inc() in Txattrwalk handler - call xattr_fid_count_decr() when a xattr FID is freed Additionally: - reset the xattr FID counter in virtfs_reset() When the limit is reached then xattr_fid_count_inc() returns -ENOSPC and the request handler is aborted on its error path without turning the FID into an xattr type and without allocating memory for the xattr. The default value of 1024 was chosen, as (sane usage of) xattr requests in the 9p protocol are usually very short-lived, and even machines with 128 cores with very high xattr activity should have plenty of head room without ever hitting this limit. Fixes: 10b468bdc5 ("virtio-9p: Implement TXATTRCREATE") Fixes: CVE-2026-8348 Reported-by: Feifan Qian <bea1e@proton.me> Link: https://lore.kernel.org/qemu-devel/eb3787869745d47234fb662600187bf773e1ef8a.1781361555.git.qemu_oss@crudebyte.com Signed-off-by: Christian Schoenebeck <qemu_oss@crudebyte.com>

Christian Schoenebeck committed Jun 13, 2026 at 16:55 UTC 693b296b176d1829b10855d9831bd2ad21b2cdcf
2 files changed +71
fsdev/file-op-9p.h
+9
@@ -81,6 +81,11 @@ typedef struct ExtendedOps {
81
82 #define V9FS_SEC_MASK 0x0000003C
83
84 +/*
85 + * Limits the maximum amount of simultaneously open xattr FIDs to prevent
86 + * host memory exhaustion (as each xattr FID contains a xattr value buffer).
87 + */
88 +#define V9FS_MAX_XATTR_DEFAULT 1024
89
90 typedef struct FileOperations FileOperations;
91 typedef struct XattrOperations XattrOperations;
@@ -109,6 +114,10 @@ struct FsContext {
114 void *private;
115 mode_t fmode;
116 mode_t dmode;
117 + /* max. amount of simultaneously open xattr FIDs */
118 + uint32_t xattr_fid_limit;
119 + /* current amount of open xattr FIDs */
120 + uint32_t xattr_fid_count;
121 };
122
123 struct V9fsPath {
hw/9pfs/9p.c
+62
@@ -265,6 +265,31 @@ static size_t v9fs_string_size(V9fsString *str)
265 return str->size;
266 }
267
268 +static int xattr_fid_count_inc(V9fsPDU *pdu)
269 +{
270 + V9fsState *s = pdu->s;
271 +
272 + if (s->ctx.xattr_fid_limit > 0 &&
273 + s->ctx.xattr_fid_count >= s->ctx.xattr_fid_limit) {
274 + error_report_once("9pfs: xattr_fid_count limit exceeded "
275 + "(configurable by option 'max_xattr').");
276 + return -ENOSPC;
277 + }
278 + s->ctx.xattr_fid_count++;
279 + return 0;
280 +}
281 +
282 +static void xattr_fid_count_decr(V9fsPDU *pdu)
283 +{
284 + V9fsState *s = pdu->s;
285 +
286 + if (s->ctx.xattr_fid_count > 0) {
287 + s->ctx.xattr_fid_count--;
288 + } else {
289 + error_report_once("9pfs: xattr_fid_count underflow detected");
290 + }
291 +}
292 +
293 /*
294 * returns 0 if fid got re-opened, 1 if not, < 0 on error
295 */
@@ -397,6 +422,7 @@ static int coroutine_fn free_fid(V9fsPDU *pdu, V9fsFidState *fidp)
422 }
423 } else if (fidp->fid_type == P9_FID_XATTR) {
424 retval = v9fs_xattr_fid_clunk(pdu, fidp);
425 + xattr_fid_count_decr(pdu);
426 }
427 v9fs_path_free(&fidp->path);
428 g_free(fidp);
@@ -634,6 +660,14 @@ static void coroutine_fn virtfs_reset(V9fsPDU *pdu)
660 fidp->clunked = true;
661 put_fid(pdu, fidp);
662 }
663 +
664 + /*
665 + * Explicitly reset the xattr FID counter.
666 + *
667 + * free_fid() already decrements the counter for each P9_FID_XATTR, so the
668 + * counter should already be zero, hence this is just a defensive measure.
669 + */
670 + s->ctx.xattr_fid_count = 0;
671 }
672
673 #define P9_QID_TYPE_DIR 0x80
@@ -4023,6 +4057,14 @@ static void coroutine_fn v9fs_xattrwalk(void *opaque)
4057 clunk_fid(s, xattr_fidp->fid);
4058 goto out;
4059 }
4060 +
4061 + /* Check xattr FID limit */
4062 + err = xattr_fid_count_inc(pdu);
4063 + if (err < 0) {
4064 + clunk_fid(s, xattr_fidp->fid);
4065 + goto out;
4066 + }
4067 +
4068 /*
4069 * Read the xattr value
4070 */
@@ -4030,6 +4072,7 @@ static void coroutine_fn v9fs_xattrwalk(void *opaque)
4072 xattr_fidp->fid_type = P9_FID_XATTR;
4073 xattr_fidp->fs.xattr.xattrwalk_fid = true;
4074 xattr_fidp->fs.xattr.value = g_malloc0(size);
4075 +
4076 if (size) {
4077 err = v9fs_co_llistxattr(pdu, &xattr_fidp->path,
4078 xattr_fidp->fs.xattr.value,
@@ -4056,6 +4099,14 @@ static void coroutine_fn v9fs_xattrwalk(void *opaque)
4099 clunk_fid(s, xattr_fidp->fid);
4100 goto out;
4101 }
4102 +
4103 + /* Check xattr FID limit */
4104 + err = xattr_fid_count_inc(pdu);
4105 + if (err < 0) {
4106 + clunk_fid(s, xattr_fidp->fid);
4107 + goto out;
4108 + }
4109 +
4110 /*
4111 * Read the xattr value
4112 */
@@ -4063,6 +4114,7 @@ static void coroutine_fn v9fs_xattrwalk(void *opaque)
4114 xattr_fidp->fid_type = P9_FID_XATTR;
4115 xattr_fidp->fs.xattr.xattrwalk_fid = true;
4116 xattr_fidp->fs.xattr.value = g_malloc0(size);
4117 +
4118 if (size) {
4119 err = v9fs_co_lgetxattr(pdu, &xattr_fidp->path,
4120 &name, xattr_fidp->fs.xattr.value,
@@ -4164,6 +4216,12 @@ static void coroutine_fn v9fs_xattrcreate(void *opaque)
4216 goto out_put_fid;
4217 }
4218
4219 + /* Check xattr FID limit */
4220 + err = xattr_fid_count_inc(pdu);
4221 + if (err < 0) {
4222 + goto out_put_fid;
4223 + }
4224 +
4225 /* Make the file fid point to xattr */
4226 xattr_fidp = file_fidp;
4227 xattr_fidp->fid_type = P9_FID_XATTR;
@@ -4425,6 +4483,10 @@ int v9fs_device_realize_common(V9fsState *s, const V9fsTransport *t,
4483
4484 s->reclaiming = false;
4485
4486 + /* init xattr FID limit */
4487 + s->ctx.xattr_fid_limit = V9FS_MAX_XATTR_DEFAULT;
4488 + s->ctx.xattr_fid_count = 0;
4489 +
4490 rc = 0;
4491 out:
4492 if (rc) {