Commit 693b296b17 for qemu.org
commit 693b296b176d1829b10855d9831bd2ad21b2cdcf
Author: Christian Schoenebeck <qemu_oss@crudebyte.com>
Date: Sat Jun 13 16:55:49 2026 +0200
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>
diff --git a/fsdev/file-op-9p.h b/fsdev/file-op-9p.h
index e8d0661c4b..454761d81d 100644
--- a/fsdev/file-op-9p.h
+++ b/fsdev/file-op-9p.h
@@ -81,6 +81,11 @@ typedef struct ExtendedOps {
#define V9FS_SEC_MASK 0x0000003C
+/*
+ * Limits the maximum amount of simultaneously open xattr FIDs to prevent
+ * host memory exhaustion (as each xattr FID contains a xattr value buffer).
+ */
+#define V9FS_MAX_XATTR_DEFAULT 1024
typedef struct FileOperations FileOperations;
typedef struct XattrOperations XattrOperations;
@@ -109,6 +114,10 @@ struct FsContext {
void *private;
mode_t fmode;
mode_t dmode;
+ /* max. amount of simultaneously open xattr FIDs */
+ uint32_t xattr_fid_limit;
+ /* current amount of open xattr FIDs */
+ uint32_t xattr_fid_count;
};
struct V9fsPath {
diff --git a/hw/9pfs/9p.c b/hw/9pfs/9p.c
index 09454a5404..e737629581 100644
--- a/hw/9pfs/9p.c
+++ b/hw/9pfs/9p.c
@@ -265,6 +265,31 @@ static size_t v9fs_string_size(V9fsString *str)
return str->size;
}
+static int xattr_fid_count_inc(V9fsPDU *pdu)
+{
+ V9fsState *s = pdu->s;
+
+ if (s->ctx.xattr_fid_limit > 0 &&
+ s->ctx.xattr_fid_count >= s->ctx.xattr_fid_limit) {
+ error_report_once("9pfs: xattr_fid_count limit exceeded "
+ "(configurable by option 'max_xattr').");
+ return -ENOSPC;
+ }
+ s->ctx.xattr_fid_count++;
+ return 0;
+}
+
+static void xattr_fid_count_decr(V9fsPDU *pdu)
+{
+ V9fsState *s = pdu->s;
+
+ if (s->ctx.xattr_fid_count > 0) {
+ s->ctx.xattr_fid_count--;
+ } else {
+ error_report_once("9pfs: xattr_fid_count underflow detected");
+ }
+}
+
/*
* returns 0 if fid got re-opened, 1 if not, < 0 on error
*/
@@ -397,6 +422,7 @@ static int coroutine_fn free_fid(V9fsPDU *pdu, V9fsFidState *fidp)
}
} else if (fidp->fid_type == P9_FID_XATTR) {
retval = v9fs_xattr_fid_clunk(pdu, fidp);
+ xattr_fid_count_decr(pdu);
}
v9fs_path_free(&fidp->path);
g_free(fidp);
@@ -634,6 +660,14 @@ static void coroutine_fn virtfs_reset(V9fsPDU *pdu)
fidp->clunked = true;
put_fid(pdu, fidp);
}
+
+ /*
+ * Explicitly reset the xattr FID counter.
+ *
+ * free_fid() already decrements the counter for each P9_FID_XATTR, so the
+ * counter should already be zero, hence this is just a defensive measure.
+ */
+ s->ctx.xattr_fid_count = 0;
}
#define P9_QID_TYPE_DIR 0x80
@@ -4023,6 +4057,14 @@ static void coroutine_fn v9fs_xattrwalk(void *opaque)
clunk_fid(s, xattr_fidp->fid);
goto out;
}
+
+ /* Check xattr FID limit */
+ err = xattr_fid_count_inc(pdu);
+ if (err < 0) {
+ clunk_fid(s, xattr_fidp->fid);
+ goto out;
+ }
+
/*
* Read the xattr value
*/
@@ -4030,6 +4072,7 @@ static void coroutine_fn v9fs_xattrwalk(void *opaque)
xattr_fidp->fid_type = P9_FID_XATTR;
xattr_fidp->fs.xattr.xattrwalk_fid = true;
xattr_fidp->fs.xattr.value = g_malloc0(size);
+
if (size) {
err = v9fs_co_llistxattr(pdu, &xattr_fidp->path,
xattr_fidp->fs.xattr.value,
@@ -4056,6 +4099,14 @@ static void coroutine_fn v9fs_xattrwalk(void *opaque)
clunk_fid(s, xattr_fidp->fid);
goto out;
}
+
+ /* Check xattr FID limit */
+ err = xattr_fid_count_inc(pdu);
+ if (err < 0) {
+ clunk_fid(s, xattr_fidp->fid);
+ goto out;
+ }
+
/*
* Read the xattr value
*/
@@ -4063,6 +4114,7 @@ static void coroutine_fn v9fs_xattrwalk(void *opaque)
xattr_fidp->fid_type = P9_FID_XATTR;
xattr_fidp->fs.xattr.xattrwalk_fid = true;
xattr_fidp->fs.xattr.value = g_malloc0(size);
+
if (size) {
err = v9fs_co_lgetxattr(pdu, &xattr_fidp->path,
&name, xattr_fidp->fs.xattr.value,
@@ -4164,6 +4216,12 @@ static void coroutine_fn v9fs_xattrcreate(void *opaque)
goto out_put_fid;
}
+ /* Check xattr FID limit */
+ err = xattr_fid_count_inc(pdu);
+ if (err < 0) {
+ goto out_put_fid;
+ }
+
/* Make the file fid point to xattr */
xattr_fidp = file_fidp;
xattr_fidp->fid_type = P9_FID_XATTR;
@@ -4425,6 +4483,10 @@ int v9fs_device_realize_common(V9fsState *s, const V9fsTransport *t,
s->reclaiming = false;
+ /* init xattr FID limit */
+ s->ctx.xattr_fid_limit = V9FS_MAX_XATTR_DEFAULT;
+ s->ctx.xattr_fid_count = 0;
+
rc = 0;
out:
if (rc) {