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) {