AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/09/27 17:52 flow patch-triage
3m Args:
null

Results:
{
  "EnableConfigs": [
    "KVM",
    "KVM_INTEL",
    "KVM_AMD",
    "KVM_GUEST_MEMFD",
    "TMPFS"
  ],
  "FocusSymbols": [
    "kvm_gmem_create",
    "kvm_gmem_attach_resource",
    "kvm_gmem_invalidate_folio",
    "shmem_gmem_attach",
    "shmem_gmem_alloc_folio",
    "shmem_gmem_release",
    "shmem_gmem_invalidate_folio"
  ],
  "KMSANReasoning": "The patch adds support for backing KVM guest_memfd with external memory resource providers (specifically tmpfs/shmem mounts) via a new flag GUEST_MEMFD_FLAG_USE_RESOURCE and resource_fd field in struct kvm_create_guest_memfd.\n\n1. Info-leak analysis: Although struct kvm_create_guest_memfd has been updated with a 32-bit pad field, the ioctl handler KVM_CREATE_GUEST_MEMFD only performs copy_from_user() and never copies any struct or data back to user space (the ioctl returns a file descriptor integer on success). Thus, no uninitialized struct fields or padding can leak to user space.\n2. Uninitialized memory analysis: The allocated folios from the external resource provider (shmem_gmem_alloc_folio) are added to the page cache and, when accessed via faulting or pfn lookup (__kvm_gmem_get_pfn / kvm_gmem_fault_user_mapping), are verified for the uptodate flag and cleared via clear_highpage() before being exposed. All new fields in struct gmem_inode (provider, provider_ops) are explicitly initialized to NULL upon inode allocation. All local variables and pointers are initialized before use.\n3. Bug profile: Any potential bugs introduced by this patch series relate to filesystem mount validation, file reference counting (fget_raw/fput), vfsmount pinning (mntget/mntput), per-cpu counter tracking (used_blocks), or page cache folio lifecycle management. These error classes (use-after-free, reference leaks, locking/concurrency) are well-covered by KASAN, refcount tracking, and LOCKDEP. KMSAN is not applicable.",
  "NeedsKMSAN": false,
  "Reasoning": "The patch introduces external resource provider support (specifically backed by tmpfs/shmem) for KVM guest_memfd files via the new GUEST_MEMFD_FLAG_USE_RESOURCE flag and resource_fd argument in KVM_CREATE_GUEST_MEMFD. This modifies reachable KVM memory management logic and mm/shmem code, adding new folio allocation/invalidation paths, inode attachment operations, and a runtime assertion (WARN_ON_ONCE). This is fully reachable from userspace and warrants fuzzing.",
  "WorthFuzzing": true
}

1/1 2026/09/27 17:52 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 8efef62a5b63da8cba61b5108ce8cc6afd6887b6\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Sun Sep 27 17:52:31 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst\nindex 67f0f290797ab..b48df2827a2c3 100644\n--- a/Documentation/virt/kvm/api.rst\n+++ b/Documentation/virt/kvm/api.rst\n@@ -6470,7 +6470,9 @@ and cannot be resized  (guest_memfd files do however support PUNCH_HOLE).\n   struct kvm_create_guest_memfd {\n \t__u64 size;\n \t__u64 flags;\n-\t__u64 reserved[6];\n+\t__s32 resource_fd;\n+\t__u32 pad;\n+\t__u64 reserved[5];\n   };\n \n Conceptually, the inode backing a guest_memfd file represents physical memory,\n@@ -6492,15 +6494,21 @@ a single guest_memfd file, but the bound ranges must not overlap).\n The capability KVM_CAP_GUEST_MEMFD_FLAGS enumerates the `flags` that can be\n specified via KVM_CREATE_GUEST_MEMFD.  Currently defined flags:\n \n-  ============================ ================================================\n-  GUEST_MEMFD_FLAG_MMAP        Enable using mmap() on the guest_memfd file\n-                               descriptor.\n-  GUEST_MEMFD_FLAG_INIT_SHARED Make all memory in the file shared during\n-                               KVM_CREATE_GUEST_MEMFD (memory files created\n-                               without INIT_SHARED will be marked private).\n-                               Shared memory can be faulted into host userspace\n-                               page tables. Private memory cannot.\n-  ============================ ================================================\n+  ============================= ================================================\n+  GUEST_MEMFD_FLAG_MMAP         Enable using mmap() on the guest_memfd file\n+                                descriptor.\n+  GUEST_MEMFD_FLAG_INIT_SHARED  Make all memory in the file shared during\n+                                KVM_CREATE_GUEST_MEMFD (memory files created\n+                                without INIT_SHARED will be marked private).\n+                                Shared memory can be faulted into host userspace\n+                                page tables. Private memory cannot.\n+  GUEST_MEMFD_FLAG_USE_RESOURCE Consume resource_fd as an external memory\n+                                provider or resource pool. When not set,\n+                                resource_fd must be '0'. When set, the provider\n+                                must be a supported resource (e.g. tmpfs mount\n+                                root directory). Currently, tmpfs resource pools\n+                                require noswap and huge=never mount options.\n+  ============================= ================================================\n \n When the KVM MMU performs a PFN lookup to service a guest fault, the fault will\n always be consumed from guest_memfd, regardless of whether it is a shared or a\ndiff --git a/include/linux/fs/super_types.h b/include/linux/fs/super_types.h\nindex ecd96aeb1cee7..29e502439648a 100644\n--- a/include/linux/fs/super_types.h\n+++ b/include/linux/fs/super_types.h\n@@ -37,6 +37,7 @@ struct workqueue_struct;\n struct writeback_control;\n struct xattr_handler;\n struct fserror_event;\n+struct guest_memfd_provider_operations;\n \n extern struct super_block *blockdev_superblock;\n \n@@ -130,6 +131,9 @@ struct super_operations {\n \n \t/* Report a filesystem error */\n \tvoid (*report_error)(const struct fserror_event *event);\n+#ifdef CONFIG_KVM_GUEST_MEMFD\n+\tconst struct guest_memfd_provider_operations *gmem_provider_ops;\n+#endif\n };\n \n struct super_block {\ndiff --git a/include/linux/guest_memfd.h b/include/linux/guest_memfd.h\nnew file mode 100644\nindex 0000000000000..60eb4f008c246\n--- /dev/null\n+++ b/include/linux/guest_memfd.h\n@@ -0,0 +1,29 @@\n+/* SPDX-License-Identifier: GPL-2.0-only */\n+#ifndef _LINUX_GUEST_MEMFD_H\n+#define _LINUX_GUEST_MEMFD_H\n+\n+#include \u003clinux/types.h\u003e\n+\n+struct file;\n+struct folio;\n+struct mempolicy;\n+\n+/**\n+ * struct guest_memfd_provider_operations - Operations for external memory providers\n+ * @attach: Attach to a resource file, perform filesystem-specific validation,\n+ *          and return an opaque provider context.\n+ * @release: Release provider context and unpin any resources.\n+ * @alloc_folio: Allocate an uninserted folio for a given index and NUMA policy.\n+ * @invalidate_folio: Notify provider that a folio has been invalidated.\n+ *\n+ * Used by filesystems and device drivers that provide memory for guest_memfd.\n+ */\n+struct guest_memfd_provider_operations {\n+\tvoid *(*attach)(struct file *resource_file);\n+\tvoid (*release)(void *provider);\n+\tstruct folio *(*alloc_folio)(void *provider, pgoff_t index,\n+\t\t\t\t     struct mempolicy *mpol);\n+\tvoid (*invalidate_folio)(void *provider, struct folio *folio);\n+};\n+\n+#endif /* _LINUX_GUEST_MEMFD_H */\ndiff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h\nindex 485f18454eb45..f4241c05650e9 100644\n--- a/include/linux/kvm_host.h\n+++ b/include/linux/kvm_host.h\n@@ -2588,7 +2588,7 @@ bool kvm_arch_supports_gmem_init_shared(struct kvm *kvm);\n \n static inline u64 kvm_gmem_get_supported_flags(struct kvm *kvm)\n {\n-\tu64 flags = GUEST_MEMFD_FLAG_MMAP;\n+\tu64 flags = GUEST_MEMFD_FLAG_MMAP | GUEST_MEMFD_FLAG_USE_RESOURCE;\n \n \tif (!kvm || kvm_arch_supports_gmem_init_shared(kvm))\n \t\tflags |= GUEST_MEMFD_FLAG_INIT_SHARED;\ndiff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h\nindex 8dff2fc1972e9..f4108bf7e3192 100644\n--- a/include/uapi/linux/kvm.h\n+++ b/include/uapi/linux/kvm.h\n@@ -1674,11 +1674,14 @@ struct kvm_memory_attributes2 {\n #define KVM_CREATE_GUEST_MEMFD\t_IOWR(KVMIO,  0xd4, struct kvm_create_guest_memfd)\n #define GUEST_MEMFD_FLAG_MMAP\t\t(1ULL \u003c\u003c 0)\n #define GUEST_MEMFD_FLAG_INIT_SHARED\t(1ULL \u003c\u003c 1)\n+#define GUEST_MEMFD_FLAG_USE_RESOURCE\t(1ULL \u003c\u003c 2)\n \n struct kvm_create_guest_memfd {\n \t__u64 size;\n \t__u64 flags;\n-\t__u64 reserved[6];\n+\t__s32 resource_fd;\n+\t__u32 pad;\n+\t__u64 reserved[5];\n };\n \n #define KVM_PRE_FAULT_MEMORY\t_IOWR(KVMIO, 0xd5, struct kvm_pre_fault_memory)\ndiff --git a/mm/shmem.c b/mm/shmem.c\nindex 897fa2b61346f..279f29a9861c1 100644\n--- a/mm/shmem.c\n+++ b/mm/shmem.c\n@@ -38,6 +38,7 @@\n #include \u003clinux/uio.h\u003e\n #include \u003clinux/hugetlb.h\u003e\n #include \u003clinux/fs_parser.h\u003e\n+#include \u003clinux/guest_memfd.h\u003e\n #include \u003clinux/swapfile.h\u003e\n #include \u003clinux/iversion.h\u003e\n #include \u003clinux/unicode.h\u003e\n@@ -5234,6 +5235,114 @@ static const struct inode_operations shmem_special_inode_operations = {\n #endif\n };\n \n+#ifdef CONFIG_KVM_GUEST_MEMFD\n+static void *shmem_gmem_attach(struct file *resource_file)\n+{\n+\tstruct inode *inode = file_inode(resource_file);\n+\tstruct shmem_sb_info *sbinfo;\n+\n+\tif (!S_ISDIR(inode-\u003ei_mode))\n+\t\treturn ERR_PTR(-ENOTDIR);\n+\n+\t/*\n+\t * Would like comments: Requiring the root directory of the mount\n+\t * provides a less confusing interface, although this is not strictly\n+\t * necessary.\n+\t */\n+\tif (resource_file-\u003ef_path.dentry != resource_file-\u003ef_path.mnt-\u003emnt_root)\n+\t\treturn ERR_PTR(-EINVAL);\n+\n+\tif (__mnt_is_readonly(resource_file-\u003ef_path.mnt))\n+\t\treturn ERR_PTR(-EROFS);\n+\n+\tif (inode_permission(file_mnt_idmap(resource_file), inode,\n+\t\t\t     MAY_WRITE | MAY_EXEC))\n+\t\treturn ERR_PTR(-EACCES);\n+\n+\t/* Enforce noswap because guest_memfd does not support swapping. */\n+\tsbinfo = SHMEM_SB(inode-\u003ei_sb);\n+\tif (!sbinfo-\u003enoswap)\n+\t\treturn ERR_PTR(-EINVAL);\n+\n+#ifdef CONFIG_TRANSPARENT_HUGEPAGE\n+\t/*\n+\t * guest_memfd does not support huge pages yet; this restriction can be\n+\t * relaxed in the future.\n+\t *\n+\t * TODO: Block rebinding with huge page support.\n+\t */\n+\tif (sbinfo-\u003ehuge != SHMEM_HUGE_NEVER)\n+\t\treturn ERR_PTR(-EINVAL);\n+#endif\n+\n+\treturn (void *)mntget(resource_file-\u003ef_path.mnt);\n+}\n+\n+static void shmem_gmem_release(void *provider)\n+{\n+\tstruct vfsmount *mnt = provider;\n+\n+\tmntput(mnt);\n+}\n+\n+/*\n+ * TODO: Consider refactoring shmem allocation helpers to share code between\n+ * internal tmpfs folio allocation and guest_memfd provider allocation.\n+ */\n+static struct folio *shmem_gmem_alloc_folio(void *provider, pgoff_t index,\n+\t\t\t\t\t    struct mempolicy *mpol)\n+{\n+\tstruct mempolicy *sb_mpol = NULL;\n+\tstruct vfsmount *mnt = provider;\n+\tstruct shmem_sb_info *sbinfo;\n+\tstruct folio *folio;\n+\n+\tsbinfo = SHMEM_SB(mnt-\u003emnt_sb);\n+\tif (sbinfo-\u003emax_blocks \u0026\u0026\n+\t    !percpu_counter_limited_add(\u0026sbinfo-\u003eused_blocks,\n+\t\t\t\t\tsbinfo-\u003emax_blocks, 1))\n+\t\treturn ERR_PTR(-ENOSPC);\n+\n+\tif (!mpol) {\n+\t\tsb_mpol = shmem_get_sbmpol(sbinfo);\n+\t\tmpol = sb_mpol;\n+\t}\n+\n+\tif (mpol)\n+\t\tfolio = folio_alloc_mpol(GFP_HIGHUSER_MOVABLE, 0, mpol, index,\n+\t\t\t\t\t numa_node_id());\n+\telse\n+\t\tfolio = folio_alloc(GFP_HIGHUSER_MOVABLE, 0);\n+\n+\tmpol_cond_put(sb_mpol);\n+\n+\tif (!folio) {\n+\t\tif (sbinfo-\u003emax_blocks)\n+\t\t\tpercpu_counter_sub(\u0026sbinfo-\u003eused_blocks, 1);\n+\t\treturn ERR_PTR(-ENOMEM);\n+\t}\n+\n+\treturn folio;\n+}\n+\n+static void shmem_gmem_invalidate_folio(void *provider, struct folio *folio)\n+{\n+\tstruct vfsmount *mnt = provider;\n+\tstruct shmem_sb_info *sbinfo;\n+\n+\tsbinfo = SHMEM_SB(mnt-\u003emnt_sb);\n+\tif (sbinfo-\u003emax_blocks)\n+\t\tpercpu_counter_sub(\u0026sbinfo-\u003eused_blocks, folio_nr_pages(folio));\n+}\n+\n+static const struct guest_memfd_provider_operations shmem_gmem_provider_ops = {\n+\t.attach\t\t\t= shmem_gmem_attach,\n+\t.release\t\t= shmem_gmem_release,\n+\t.alloc_folio\t\t= shmem_gmem_alloc_folio,\n+\t.invalidate_folio\t= shmem_gmem_invalidate_folio,\n+};\n+#endif /* CONFIG_KVM_GUEST_MEMFD */\n+\n static const struct super_operations shmem_ops = {\n \t.alloc_inode\t= shmem_alloc_inode,\n \t.free_inode\t= shmem_free_in_core_inode,\n@@ -5252,6 +5361,9 @@ static const struct super_operations shmem_ops = {\n \t.nr_cached_objects\t= shmem_unused_huge_count,\n \t.free_cached_objects\t= shmem_unused_huge_scan,\n #endif\n+#ifdef CONFIG_KVM_GUEST_MEMFD\n+\t.gmem_provider_ops\t= \u0026shmem_gmem_provider_ops,\n+#endif\n };\n \n static const struct vm_operations_struct shmem_vm_ops = {\ndiff --git a/tools/include/uapi/linux/kvm.h b/tools/include/uapi/linux/kvm.h\nindex 419011097fa8e..66c1f40b50c29 100644\n--- a/tools/include/uapi/linux/kvm.h\n+++ b/tools/include/uapi/linux/kvm.h\n@@ -1654,11 +1654,14 @@ struct kvm_memory_attributes {\n #define KVM_CREATE_GUEST_MEMFD\t_IOWR(KVMIO,  0xd4, struct kvm_create_guest_memfd)\n #define GUEST_MEMFD_FLAG_MMAP\t\t(1ULL \u003c\u003c 0)\n #define GUEST_MEMFD_FLAG_INIT_SHARED\t(1ULL \u003c\u003c 1)\n+#define GUEST_MEMFD_FLAG_USE_RESOURCE\t(1ULL \u003c\u003c 2)\n \n struct kvm_create_guest_memfd {\n \t__u64 size;\n \t__u64 flags;\n-\t__u64 reserved[6];\n+\t__s32 resource_fd;\n+\t__u32 pad;\n+\t__u64 reserved[5];\n };\n \n #define KVM_PRE_FAULT_MEMORY\t_IOWR(KVMIO, 0xd5, struct kvm_pre_fault_memory)\ndiff --git a/tools/testing/selftests/kvm/guest_memfd_test.c b/tools/testing/selftests/kvm/guest_memfd_test.c\nindex 2233d871a38f4..4e5d00df899a6 100644\n--- a/tools/testing/selftests/kvm/guest_memfd_test.c\n+++ b/tools/testing/selftests/kvm/guest_memfd_test.c\n@@ -10,12 +10,16 @@\n #include \u003cerrno.h\u003e\n #include \u003cstdio.h\u003e\n #include \u003cfcntl.h\u003e\n+#include \u003climits.h\u003e\n \n #include \u003clinux/bitmap.h\u003e\n #include \u003clinux/falloc.h\u003e\n+#include \u003clinux/mount.h\u003e\n #include \u003clinux/sizes.h\u003e\n+#include \u003csys/mount.h\u003e\n #include \u003csys/types.h\u003e\n #include \u003csys/stat.h\u003e\n+#include \u003csys/statvfs.h\u003e\n \n #include \"kvm_syscalls.h\"\n #include \"kvm_util.h\"\n@@ -404,6 +408,8 @@ static void test_guest_memfd_flags(struct kvm_vm *vm)\n \tint fd;\n \n \tfor (flag = BIT(0); flag; flag \u003c\u003c= 1) {\n+\t\tif (flag == GUEST_MEMFD_FLAG_USE_RESOURCE)\n+\t\t\tcontinue;\n \t\tfd = __vm_create_guest_memfd(vm, page_size, flag);\n \t\tif (flag \u0026 valid_flags) {\n \t\t\tTEST_ASSERT(fd \u003e= 0,\n@@ -418,55 +424,294 @@ static void test_guest_memfd_flags(struct kvm_vm *vm)\n \t}\n }\n \n-#define ____gmem_test(__test, __vm, __flags, __gmem_size, args...)\t\\\n-do {\t\t\t\t\t\t\t\t\t\\\n-\tint fd = vm_create_guest_memfd(__vm, __gmem_size, __flags);\t\\\n-\t\t\t\t\t\t\t\t\t\\\n-\ttest_##__test(args);\t\t\t\t\t\t\\\n-\tclose(fd);\t\t\t\t\t\t\t\\\n+static void test_resource_fd_invalid(struct kvm_vm *vm)\n+{\n+\tint fd;\n+\n+\t/* Non-zero resource_fd without GUEST_MEMFD_FLAG_USE_RESOURCE fails */\n+\tfd = __vm_create_guest_memfd_resource(vm, page_size, 0, 1);\n+\tTEST_ASSERT(fd \u003c 0, \"guest_memfd with resource_fd but without flag should fail\");\n+\tTEST_ASSERT_EQ(errno, EINVAL);\n+\n+\t/* Bad file descriptor with GUEST_MEMFD_FLAG_USE_RESOURCE fails */\n+\tfd = __vm_create_guest_memfd_resource(vm, page_size,\n+\t\t\t\t\t      GUEST_MEMFD_FLAG_USE_RESOURCE, -1);\n+\tTEST_ASSERT(fd \u003c 0, \"guest_memfd with -1 resource_fd should fail\");\n+\tTEST_ASSERT_EQ(errno, EBADF);\n+}\n+\n+static void test_resource_fd_unsupported_fs(struct kvm_vm *vm)\n+{\n+\tint fd, file_fd;\n+\n+\t/*\n+\t * Passing an FD from an unsupported filesystem without provider ops\n+\t * (e.g. the /proc mount root directory) is rejected by KVM with\n+\t * EOPNOTSUPP.\n+\t */\n+\tfile_fd = open(\"/proc\", O_DIRECTORY | O_RDONLY);\n+\tTEST_ASSERT(file_fd \u003e= 0, \"open /proc failed\");\n+\n+\tfd = __vm_create_guest_memfd_resource(vm, page_size,\n+\t\t\t\t\t      GUEST_MEMFD_FLAG_USE_RESOURCE,\n+\t\t\t\t\t      file_fd);\n+\tTEST_ASSERT(fd \u003c 0, \"guest_memfd with /proc fd should fail\");\n+\tTEST_ASSERT_EQ(errno, EOPNOTSUPP);\n+\n+\tclose(file_fd);\n+}\n+\n+static void test_resource_fd_tmpfs_file(struct kvm_vm *vm)\n+{\n+\tint fd, file_fd;\n+\n+\t/*\n+\t * Passing a regular file from tmpfs (e.g. via memfd) is rejected by\n+\t * the provider attach callback with ENOTDIR because only directory\n+\t * FDs representing the mount root are supported resource pools.\n+\t */\n+\tfile_fd = memfd_create(\"test_tmpfs_file\", 0);\n+\tTEST_ASSERT(file_fd \u003e= 0, \"memfd_create failed\");\n+\n+\tfd = __vm_create_guest_memfd_resource(vm, page_size,\n+\t\t\t\t\t      GUEST_MEMFD_FLAG_USE_RESOURCE,\n+\t\t\t\t\t      file_fd);\n+\tTEST_ASSERT(fd \u003c 0, \"guest_memfd with tmpfs file fd should fail\");\n+\tTEST_ASSERT_EQ(errno, ENOTDIR);\n+\tclose(file_fd);\n+}\n+\n+static int create_tmpfs_pool_fd(const char *huge, bool noswap, size_t size)\n+{\n+\tchar size_str[32];\n+\tint fs_fd, mnt_fd;\n+\n+\tTEST_ASSERT(size \u0026\u0026 IS_ALIGNED(size, page_size),\n+\t\t    \"Pool size '0x%zx' must be positive and page-aligned\", size);\n+\n+\tfs_fd = syscall(__NR_fsopen, \"tmpfs\", FSOPEN_CLOEXEC);\n+\tTEST_ASSERT(fs_fd \u003e= 0, \"fsopen failed\");\n+\n+\tif (noswap)\n+\t\tTEST_ASSERT(!syscall(__NR_fsconfig, fs_fd, FSCONFIG_SET_FLAG,\n+\t\t\t\t     \"noswap\", NULL, 0), \"fsconfig noswap failed\");\n+\n+\tif (huge)\n+\t\tTEST_ASSERT(!syscall(__NR_fsconfig, fs_fd, FSCONFIG_SET_STRING,\n+\t\t\t\t     \"huge\", huge, 0), \"fsconfig huge failed\");\n+\n+\tsnprintf(size_str, sizeof(size_str), \"%zu\", size);\n+\tTEST_ASSERT(!syscall(__NR_fsconfig, fs_fd, FSCONFIG_SET_STRING,\n+\t\t\t     \"size\", size_str, 0), \"fsconfig size failed\");\n+\n+\tTEST_ASSERT(!syscall(__NR_fsconfig, fs_fd, FSCONFIG_CMD_CREATE,\n+\t\t\t     NULL, NULL, 0), \"fsconfig create failed\");\n+\n+\tmnt_fd = syscall(__NR_fsmount, fs_fd, FSMOUNT_CLOEXEC, 0);\n+\tTEST_ASSERT(mnt_fd \u003e 0, \"fsmount failed\");\n+\n+\tclose(fs_fd);\n+\treturn mnt_fd;\n+}\n+\n+static void test_resource_fd_tmpfs_swap(struct kvm_vm *vm)\n+{\n+\tint pool_fd, fd;\n+\n+\tpool_fd = create_tmpfs_pool_fd(\"never\", false, page_size);\n+\n+\tfd = __vm_create_guest_memfd_resource(vm, page_size,\n+\t\t\t\t\t      GUEST_MEMFD_FLAG_USE_RESOURCE,\n+\t\t\t\t\t      pool_fd);\n+\tTEST_ASSERT(fd \u003c 0, \"guest_memfd with swap-enabled tmpfs should fail\");\n+\tTEST_ASSERT_EQ(errno, EINVAL);\n+\n+\tclose(pool_fd);\n+}\n+\n+static void test_resource_fd_tmpfs_huge(struct kvm_vm *vm)\n+{\n+\tint pool_fd, fd;\n+\n+\tpool_fd = create_tmpfs_pool_fd(\"always\", true, page_size);\n+\n+\tfd = __vm_create_guest_memfd_resource(vm, page_size,\n+\t\t\t\t\t      GUEST_MEMFD_FLAG_USE_RESOURCE,\n+\t\t\t\t\t      pool_fd);\n+\tTEST_ASSERT(fd \u003c 0, \"guest_memfd with huge-enabled tmpfs should fail\");\n+\tTEST_ASSERT_EQ(errno, EINVAL);\n+\n+\tclose(pool_fd);\n+}\n+\n+static void test_resource_fd_tmpfs_shared_pool(struct kvm_vm *vm)\n+{\n+\tint pool_fd, fd1, fd2, ret;\n+\tstruct statvfs svfs;\n+\tstruct stat st1, st2;\n+\n+\tpool_fd = create_tmpfs_pool_fd(\"never\", true, page_size * 2);\n+\tif (pool_fd \u003c 0)\n+\t\tTEST_REQUIRE(false);\n+\n+\tfd1 = vm_create_guest_memfd_resource(vm, page_size,\n+\t\t\t\t\t     GUEST_MEMFD_FLAG_USE_RESOURCE,\n+\t\t\t\t\t     pool_fd);\n+\n+\tfd2 = vm_create_guest_memfd_resource(vm, page_size * 2,\n+\t\t\t\t\t     GUEST_MEMFD_FLAG_USE_RESOURCE,\n+\t\t\t\t\t     pool_fd);\n+\n+\tTEST_ASSERT(!fstat(fd1, \u0026st1), \"fstat on fd1 should succeed\");\n+\tTEST_ASSERT(st1.st_size == page_size,\n+\t\t    \"fd1 st_size (%lu) should match requested size (%lu)\",\n+\t\t    st1.st_size, page_size);\n+\n+\tTEST_ASSERT(!fstat(fd2, \u0026st2), \"fstat on fd2 should succeed\");\n+\tTEST_ASSERT(st2.st_size == page_size * 2,\n+\t\t    \"fd2 st_size (%lu) should match requested size (%lu)\",\n+\t\t    st2.st_size, page_size * 2);\n+\n+\tTEST_ASSERT(st1.st_ino != st2.st_ino,\n+\t\t    \"different guest_memfd instances should have distinct inodes\");\n+\n+\tret = fallocate(fd1, FALLOC_FL_KEEP_SIZE, 0, page_size);\n+\tTEST_ASSERT(!ret, \"fallocate on fd1 should succeed\");\n+\n+\tTEST_ASSERT(!fstatvfs(pool_fd, \u0026svfs), \"fstatvfs on pool_fd should succeed\");\n+\tTEST_ASSERT_EQ(svfs.f_blocks - svfs.f_bfree, 1);\n+\n+\tret = fallocate(fd2, FALLOC_FL_KEEP_SIZE, 0, page_size);\n+\tTEST_ASSERT(!ret, \"fallocate on fd2 should succeed\");\n+\n+\tTEST_ASSERT(!fstatvfs(pool_fd, \u0026svfs), \"fstatvfs on pool_fd should succeed\");\n+\tTEST_ASSERT_EQ(svfs.f_blocks - svfs.f_bfree, 2);\n+\n+\tret = fallocate(fd2, FALLOC_FL_KEEP_SIZE, page_size, page_size);\n+\tTEST_ASSERT(ret \u003c 0, \"fallocate exceeding pool capacity should fail\");\n+\tTEST_ASSERT_EQ(errno, ENOSPC);\n+\n+\tclose(fd2);\n+\tclose(fd1);\n+\tclose(pool_fd);\n+}\n+\n+enum gmem_pool_type {\n+\tGMEM_POOL_NONE,\n+\tGMEM_POOL_FSMOUNT,\n+\tGMEM_POOL_MOUNTED_DIR,\n+};\n+\n+struct gmem_pool {\n+\tint fd;\n+\tchar path[PATH_MAX];\n+\tbool is_mounted;\n+};\n+\n+static struct gmem_pool create_gmem_pool(size_t size, enum gmem_pool_type type)\n+{\n+\tstruct gmem_pool pool = { .fd = -1, .is_mounted = false };\n+\tint mnt_fd;\n+\n+\tif (type == GMEM_POOL_NONE)\n+\t\treturn pool;\n+\n+\tmnt_fd = create_tmpfs_pool_fd(\"never\", true, size);\n+\tTEST_REQUIRE(mnt_fd \u003e= 0);\n+\n+\tif (type == GMEM_POOL_FSMOUNT) {\n+\t\tpool.fd = mnt_fd;\n+\t\treturn pool;\n+\t}\n+\n+\tstrcpy(pool.path, \"/tmp/gmem_test_dir_XXXXXX\");\n+\tTEST_ASSERT(mkdtemp(pool.path), \"mkdtemp failed\");\n+\n+\tif (syscall(__NR_move_mount, mnt_fd, \"\", AT_FDCWD, pool.path,\n+\t\t    MOVE_MOUNT_F_EMPTY_PATH)) {\n+\t\tclose(mnt_fd);\n+\t\trmdir(pool.path);\n+\t\tTEST_REQUIRE(false);\n+\t}\n+\tclose(mnt_fd);\n+\n+\tpool.fd = open(pool.path, O_RDONLY | O_DIRECTORY);\n+\tTEST_ASSERT(pool.fd \u003e= 0, \"open mounted tmpfs root failed\");\n+\tpool.is_mounted = true;\n+\n+\treturn pool;\n+}\n+\n+static void destroy_gmem_pool(struct gmem_pool *pool)\n+{\n+\tif (pool-\u003efd \u003e= 0)\n+\t\tclose(pool-\u003efd);\n+\tif (pool-\u003eis_mounted) {\n+\t\tTEST_ASSERT(!umount(pool-\u003epath), \"umount failed\");\n+\t\tTEST_ASSERT(!rmdir(pool-\u003epath), \"rmdir failed\");\n+\t}\n+}\n+\n+#define ____gmem_test(__test, __vm, __flags, __pool_type, __gmem_size, args...)\t\\\n+do {\t\t\t\t\t\t\t\t\t\t\\\n+\tstruct gmem_pool pool = { .fd = -1 };\t\t\t\t\t\\\n+\tint fd;\t\t\t\t\t\t\t\t\t\\\n+\t\t\t\t\t\t\t\t\t\t\\\n+\tif ((__flags) \u0026 GUEST_MEMFD_FLAG_USE_RESOURCE) {\t\t\t\\\n+\t\tpool = create_gmem_pool(__gmem_size, __pool_type);\t\t\\\n+\t\tfd = vm_create_guest_memfd_resource(__vm, __gmem_size,\t\t\\\n+\t\t\t\t\t\t    __flags, pool.fd);\t\t\\\n+\t} else {\t\t\t\t\t\t\t\t\\\n+\t\tfd = vm_create_guest_memfd(__vm, __gmem_size, __flags);\t\\\n+\t}\t\t\t\t\t\t\t\t\t\\\n+\t\t\t\t\t\t\t\t\t\t\\\n+\ttest_##__test(args);\t\t\t\t\t\t\t\\\n+\tclose(fd);\t\t\t\t\t\t\t\t\\\n+\tdestroy_gmem_pool(\u0026pool);\t\t\t\t\t\t\\\n } while (0)\n \n-#define __gmem_test(__test, __vm, __flags, __gmem_size)\t\t\t\\\n-\t____gmem_test(__test, __vm, __flags, __gmem_size, fd, __gmem_size)\n+#define __gmem_test(__test, __vm, __flags, __pool_type, __gmem_size)\t\t\\\n+\t____gmem_test(__test, __vm, __flags, __pool_type, __gmem_size, fd, __gmem_size)\n \n-#define gmem_test(__test, __vm, __flags)\t\t\t\t\\\n-\t__gmem_test(__test, __vm, __flags, page_size * 4)\n+#define gmem_test(__test, __vm, __flags, __pool_type)\t\t\t\t\\\n+\t__gmem_test(__test, __vm, __flags, __pool_type, page_size * 4)\n \n-#define __gmem_test_vm(__test, __vm, __flags, __gmem_size)\t\t\\\n-\t____gmem_test(__test, __vm, __flags, __gmem_size, __vm, fd, __gmem_size)\n+#define __gmem_test_vm(__test, __vm, __flags, __pool_type, __gmem_size)\t\\\n+\t____gmem_test(__test, __vm, __flags, __pool_type, __gmem_size, __vm, fd, __gmem_size)\n \n-#define gmem_test_vm(__test, __vm, __flags)\t\t\t\t\\\n-\t__gmem_test_vm(__test, __vm, __flags, page_size * 4)\n+#define gmem_test_vm(__test, __vm, __flags, __pool_type)\t\t\t\\\n+\t__gmem_test_vm(__test, __vm, __flags, __pool_type, page_size * 4)\n \n-static void __test_guest_memfd(struct kvm_vm *vm, u64 flags)\n+static void __test_guest_memfd(struct kvm_vm *vm, u64 flags,\n+\t\t\t       enum gmem_pool_type pool_type)\n {\n \ttest_create_guest_memfd_multiple(vm);\n \ttest_create_guest_memfd_invalid_sizes(vm, flags);\n \n-\tgmem_test(file_read_write, vm, flags);\n+\tgmem_test(file_read_write, vm, flags, pool_type);\n \n \tif (flags \u0026 GUEST_MEMFD_FLAG_MMAP) {\n \t\tif (flags \u0026 GUEST_MEMFD_FLAG_INIT_SHARED) {\n \t\t\tsize_t pmd_size = get_trans_hugepagesz();\n \n-\t\t\tgmem_test(mmap_supported, vm, flags);\n-\t\t\tgmem_test(fault_overflow, vm, flags);\n-\t\t\tgmem_test(numa_allocation, vm, flags);\n-\t\t\t__gmem_test(collapse, vm, flags, pmd_size);\n+\t\t\tgmem_test(mmap_supported, vm, flags, pool_type);\n+\t\t\tgmem_test(fault_overflow, vm, flags, pool_type);\n+\t\t\tgmem_test(numa_allocation, vm, flags, pool_type);\n+\t\t\t__gmem_test(collapse, vm, flags, pool_type, pmd_size);\n \t\t} else {\n-\t\t\tgmem_test(fault_private, vm, flags);\n+\t\t\tgmem_test(fault_private, vm, flags, pool_type);\n \t\t}\n \n-\t\tgmem_test(mmap_cow, vm, flags);\n-\t\tgmem_test(mbind, vm, flags);\n+\t\tgmem_test(mmap_cow, vm, flags, pool_type);\n+\t\tgmem_test(mbind, vm, flags, pool_type);\n \t} else {\n-\t\tgmem_test(mmap_not_supported, vm, flags);\n+\t\tgmem_test(mmap_not_supported, vm, flags, pool_type);\n \t}\n \n-\tgmem_test(file_size, vm, flags);\n-\tgmem_test(fallocate, vm, flags);\n-\tgmem_test(invalid_punch_hole, vm, flags);\n-\tgmem_test_vm(invalid_binding, vm, flags);\n+\tgmem_test(file_size, vm, flags, pool_type);\n+\tgmem_test(fallocate, vm, flags, pool_type);\n+\tgmem_test(invalid_punch_hole, vm, flags, pool_type);\n+\tgmem_test_vm(invalid_binding, vm, flags, pool_type);\n }\n \n static void test_guest_memfd(unsigned long vm_type)\n@@ -476,16 +721,52 @@ static void test_guest_memfd(unsigned long vm_type)\n \n \ttest_guest_memfd_flags(vm);\n \n-\t__test_guest_memfd(vm, 0);\n+\t/*\n+\t * TODO: Test that guest_memfd creation rejects a read-only tmpfs mount\n+\t * (e.g. MOUNT_ATTR_RDONLY) with EROFS.\n+\t *\n+\t * TODO: Test that guest_memfd creation rejects an ID-mapped tmpfs mount\n+\t * (e.g. MOUNT_ATTR_IDMAP) lacking write or execute/search permissions\n+\t * with EACCES.\n+\t *\n+\t * TODO: Test mpol fallback to mount config.\n+\t */\n+\ttest_resource_fd_invalid(vm);\n+\ttest_resource_fd_unsupported_fs(vm);\n+\ttest_resource_fd_tmpfs_file(vm);\n+\ttest_resource_fd_tmpfs_swap(vm);\n+\ttest_resource_fd_tmpfs_huge(vm);\n+\ttest_resource_fd_tmpfs_shared_pool(vm);\n+\n+\t__test_guest_memfd(vm, 0, GMEM_POOL_NONE);\n+\t__test_guest_memfd(vm, GUEST_MEMFD_FLAG_USE_RESOURCE, GMEM_POOL_FSMOUNT);\n+\t__test_guest_memfd(vm, GUEST_MEMFD_FLAG_USE_RESOURCE, GMEM_POOL_MOUNTED_DIR);\n \n \tflags = vm_check_cap(vm, KVM_CAP_GUEST_MEMFD_FLAGS);\n-\tif (flags \u0026 GUEST_MEMFD_FLAG_MMAP)\n-\t\t__test_guest_memfd(vm, GUEST_MEMFD_FLAG_MMAP);\n+\tif (flags \u0026 GUEST_MEMFD_FLAG_MMAP) {\n+\t\t__test_guest_memfd(vm, GUEST_MEMFD_FLAG_MMAP, GMEM_POOL_NONE);\n+\t\t__test_guest_memfd(vm, GUEST_MEMFD_FLAG_MMAP |\n+\t\t\t\t       GUEST_MEMFD_FLAG_USE_RESOURCE,\n+\t\t\t\t   GMEM_POOL_FSMOUNT);\n+\t\t__test_guest_memfd(vm, GUEST_MEMFD_FLAG_MMAP |\n+\t\t\t\t       GUEST_MEMFD_FLAG_USE_RESOURCE,\n+\t\t\t\t   GMEM_POOL_MOUNTED_DIR);\n+\t}\n \n \t/* MMAP should always be supported if INIT_SHARED is supported. */\n-\tif (flags \u0026 GUEST_MEMFD_FLAG_INIT_SHARED)\n+\tif (flags \u0026 GUEST_MEMFD_FLAG_INIT_SHARED) {\n+\t\t__test_guest_memfd(vm, GUEST_MEMFD_FLAG_MMAP |\n+\t\t\t\t       GUEST_MEMFD_FLAG_INIT_SHARED,\n+\t\t\t\t   GMEM_POOL_NONE);\n \t\t__test_guest_memfd(vm, GUEST_MEMFD_FLAG_MMAP |\n-\t\t\t\t       GUEST_MEMFD_FLAG_INIT_SHARED);\n+\t\t\t\t       GUEST_MEMFD_FLAG_INIT_SHARED |\n+\t\t\t\t       GUEST_MEMFD_FLAG_USE_RESOURCE,\n+\t\t\t\t   GMEM_POOL_FSMOUNT);\n+\t\t__test_guest_memfd(vm, GUEST_MEMFD_FLAG_MMAP |\n+\t\t\t\t       GUEST_MEMFD_FLAG_INIT_SHARED |\n+\t\t\t\t       GUEST_MEMFD_FLAG_USE_RESOURCE,\n+\t\t\t\t   GMEM_POOL_MOUNTED_DIR);\n+\t}\n \n \tkvm_vm_free(vm);\n }\n@@ -558,6 +839,62 @@ static void test_guest_memfd_guest(void)\n \tkvm_vm_free(vm);\n }\n \n+static void test_guest_memfd_guest_resource(void)\n+{\n+\tconst gpa_t gpa = SZ_4G;\n+\tconst int slot = 1;\n+\tstruct kvm_vcpu *vcpu;\n+\tstruct kvm_vm *vm;\n+\tint pool_fd, fd, i;\n+\tsize_t size;\n+\tu8 *mem;\n+\n+\tif (!kvm_check_cap(KVM_CAP_GUEST_MEMFD_FLAGS))\n+\t\treturn;\n+\n+\tpool_fd = create_tmpfs_pool_fd(\"never\", true, page_size);\n+\tif (pool_fd \u003c 0)\n+\t\tTEST_REQUIRE(false);\n+\n+\tvm = __vm_create_shape_with_one_vcpu(VM_SHAPE_DEFAULT, \u0026vcpu, 1, guest_code);\n+\n+\tTEST_ASSERT(vm_check_cap(vm, KVM_CAP_GUEST_MEMFD_FLAGS) \u0026 GUEST_MEMFD_FLAG_MMAP,\n+\t\t    \"Default VM type should support MMAP, supported flags = 0x%x\",\n+\t\t    vm_check_cap(vm, KVM_CAP_GUEST_MEMFD_FLAGS));\n+\tTEST_ASSERT(vm_check_cap(vm, KVM_CAP_GUEST_MEMFD_FLAGS) \u0026 GUEST_MEMFD_FLAG_INIT_SHARED,\n+\t\t    \"Default VM type should support INIT_SHARED, supported flags = 0x%x\",\n+\t\t    vm_check_cap(vm, KVM_CAP_GUEST_MEMFD_FLAGS));\n+\n+\tsize = max_t(size_t, vm-\u003epage_size, page_size);\n+\tfd = __vm_create_guest_memfd_resource(vm, size,\n+\t\t\t\t\t      GUEST_MEMFD_FLAG_MMAP |\n+\t\t\t\t\t      GUEST_MEMFD_FLAG_INIT_SHARED |\n+\t\t\t\t\t      GUEST_MEMFD_FLAG_USE_RESOURCE,\n+\t\t\t\t\t      pool_fd);\n+\tTEST_ASSERT(fd \u003e= 0, \"guest_memfd with tmpfs pool should succeed\");\n+\n+\tvm_set_user_memory_region2(vm, slot, KVM_MEM_GUEST_MEMFD, gpa, size, NULL, fd, 0);\n+\n+\tmem = kvm_mmap(size, PROT_READ | PROT_WRITE, MAP_SHARED, fd);\n+\tmemset(mem, 0xaa, size);\n+\tkvm_munmap(mem, size);\n+\n+\tvirt_map(vm, gpa, gpa, size / vm-\u003epage_size);\n+\tvcpu_args_set(vcpu, 2, gpa, size);\n+\tvcpu_run(vcpu);\n+\n+\tTEST_ASSERT_EQ(get_ucall(vcpu, NULL), UCALL_DONE);\n+\n+\tmem = kvm_mmap(size, PROT_READ | PROT_WRITE, MAP_SHARED, fd);\n+\tfor (i = 0; i \u003c size; i++)\n+\t\tTEST_ASSERT_EQ(mem[i], 0xff);\n+\n+\tclose(fd);\n+\tclose(pool_fd);\n+\tkvm_vm_free(vm);\n+}\n+\n+\n int main(int argc, char *argv[])\n {\n \tunsigned long vm_types, vm_type;\n@@ -578,4 +915,5 @@ int main(int argc, char *argv[])\n \t\ttest_guest_memfd(vm_type);\n \n \ttest_guest_memfd_guest();\n+\ttest_guest_memfd_guest_resource();\n }\ndiff --git a/tools/testing/selftests/kvm/include/kvm_util.h b/tools/testing/selftests/kvm/include/kvm_util.h\nindex 777fa3dbf88d6..9eeaefe5a1839 100644\n--- a/tools/testing/selftests/kvm/include/kvm_util.h\n+++ b/tools/testing/selftests/kvm/include/kvm_util.h\n@@ -767,26 +767,39 @@ static inline bool is_smt_on(void)\n \n void vm_create_irqchip(struct kvm_vm *vm);\n \n-static inline int __vm_create_guest_memfd(struct kvm_vm *vm, u64 size,\n-\t\t\t\t\t  u64 flags)\n+static inline int __vm_create_guest_memfd_resource(struct kvm_vm *vm, u64 size,\n+\t\t\t\t\t\t   u64 flags, int resource_fd)\n {\n \tstruct kvm_create_guest_memfd guest_memfd = {\n \t\t.size = size,\n \t\t.flags = flags,\n+\t\t.resource_fd = resource_fd,\n \t};\n \n \treturn __vm_ioctl(vm, KVM_CREATE_GUEST_MEMFD, \u0026guest_memfd);\n }\n \n-static inline int vm_create_guest_memfd(struct kvm_vm *vm, u64 size,\n-\t\t\t\t\tu64 flags)\n+static inline int __vm_create_guest_memfd(struct kvm_vm *vm, u64 size,\n+\t\t\t\t\t  u64 flags)\n+{\n+\treturn __vm_create_guest_memfd_resource(vm, size, flags, 0);\n+}\n+\n+static inline int vm_create_guest_memfd_resource(struct kvm_vm *vm, u64 size,\n+\t\t\t\t\t\t u64 flags, int resource_fd)\n {\n-\tint fd = __vm_create_guest_memfd(vm, size, flags);\n+\tint fd = __vm_create_guest_memfd_resource(vm, size, flags, resource_fd);\n \n \tTEST_ASSERT(fd \u003e= 0, KVM_IOCTL_ERROR(KVM_CREATE_GUEST_MEMFD, fd));\n \treturn fd;\n }\n \n+static inline int vm_create_guest_memfd(struct kvm_vm *vm, u64 size,\n+\t\t\t\t\tu64 flags)\n+{\n+\treturn vm_create_guest_memfd_resource(vm, size, flags, 0);\n+}\n+\n void vm_set_user_memory_region(struct kvm_vm *vm, u32 slot, u32 flags,\n \t\t\t       gpa_t gpa, u64 size, void *hva);\n int __vm_set_user_memory_region(struct kvm_vm *vm, u32 slot, u32 flags,\ndiff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c\nindex a13445c26d9d6..0e0d7e93f4148 100644\n--- a/virt/kvm/guest_memfd.c\n+++ b/virt/kvm/guest_memfd.c\n@@ -3,6 +3,7 @@\n #include \u003clinux/backing-dev.h\u003e\n #include \u003clinux/falloc.h\u003e\n #include \u003clinux/fs.h\u003e\n+#include \u003clinux/guest_memfd.h\u003e\n #include \u003clinux/kvm_host.h\u003e\n #include \u003clinux/maple_tree.h\u003e\n #include \u003clinux/mempolicy.h\u003e\n@@ -35,6 +36,9 @@ struct gmem_inode {\n \tstruct inode vfs_inode;\n \tstruct list_head gmem_file_list;\n \n+\tvoid *provider;\n+\tconst struct guest_memfd_provider_operations *provider_ops;\n+\n \tu64 flags;\n \t/*\n \t * Every index in this inode, whether memory is populated or\n@@ -50,6 +54,29 @@ static __always_inline struct gmem_inode *GMEM_I(struct inode *inode)\n \treturn container_of(inode, struct gmem_inode, vfs_inode);\n }\n \n+static inline struct folio *gmem_provider_alloc_folio(struct gmem_inode *gi,\n+\t\t\t\t\t\t      pgoff_t index,\n+\t\t\t\t\t\t      struct mempolicy *mpol)\n+{\n+\tif (!gi-\u003eprovider_ops || !gi-\u003eprovider_ops-\u003ealloc_folio)\n+\t\treturn ERR_PTR(-EOPNOTSUPP);\n+\n+\treturn gi-\u003eprovider_ops-\u003ealloc_folio(gi-\u003eprovider, index, mpol);\n+}\n+\n+static inline void gmem_provider_invalidate_folio(struct gmem_inode *gi,\n+\t\t\t\t\t\t  struct folio *folio)\n+{\n+\tif (gi-\u003eprovider_ops \u0026\u0026 gi-\u003eprovider_ops-\u003einvalidate_folio)\n+\t\tgi-\u003eprovider_ops-\u003einvalidate_folio(gi-\u003eprovider, folio);\n+}\n+\n+static inline void gmem_provider_release(struct gmem_inode *gi)\n+{\n+\tif (gi-\u003eprovider_ops \u0026\u0026 gi-\u003eprovider_ops-\u003erelease)\n+\t\tgi-\u003eprovider_ops-\u003erelease(gi-\u003eprovider);\n+}\n+\n #define kvm_gmem_for_each_file(f, inode) \\\n \tlist_for_each_entry(f, \u0026GMEM_I(inode)-\u003egmem_file_list, entry)\n \n@@ -128,6 +155,7 @@ static bool kvm_gmem_range_has_attributes(struct inode *inode,\n  */\n static struct folio *kvm_gmem_get_folio(struct inode *inode, pgoff_t index)\n {\n+\tstruct gmem_inode *gi = GMEM_I(inode);\n \t/* TODO: Support huge pages. */\n \tstruct mempolicy *policy;\n \tstruct folio *folio;\n@@ -140,10 +168,30 @@ static struct folio *kvm_gmem_get_folio(struct inode *inode, pgoff_t index)\n \tif (!IS_ERR(folio))\n \t\treturn folio;\n \n-\tpolicy = mpol_shared_policy_lookup(\u0026GMEM_I(inode)-\u003epolicy, index);\n-\tfolio = __filemap_get_folio_mpol(inode-\u003ei_mapping, index,\n-\t\t\t\t\t FGP_LOCK | FGP_CREAT,\n-\t\t\t\t\t mapping_gfp_mask(inode-\u003ei_mapping), policy);\n+\tpolicy = mpol_shared_policy_lookup(\u0026gi-\u003epolicy, index);\n+\t/*\n+\t * TODO: Refactor the internal PAGE_SIZE gmem could be an internal\n+\t * provider with internal provider_ops.\n+\t */\n+\tif (gi-\u003eprovider_ops) {\n+\t\tfolio = gmem_provider_alloc_folio(gi, index, policy);\n+\t\tif (!IS_ERR(folio)) {\n+\t\t\tint r = filemap_add_folio(inode-\u003ei_mapping, folio,\n+\t\t\t\t\t\t  index, GFP_KERNEL);\n+\t\t\tif (r) {\n+\t\t\t\tgmem_provider_invalidate_folio(gi, folio);\n+\t\t\t\tfolio_put(folio);\n+\t\t\t\tfolio = ERR_PTR(r);\n+\t\t\t} else {\n+\t\t\t\tfolio_mark_accessed(folio);\n+\t\t\t}\n+\t\t}\n+\t} else {\n+\t\tfolio = __filemap_get_folio_mpol(inode-\u003ei_mapping, index,\n+\t\t\t\t\t\t FGP_LOCK | FGP_CREAT,\n+\t\t\t\t\t\t mapping_gfp_mask(inode-\u003ei_mapping),\n+\t\t\t\t\t\t policy);\n+\t}\n \tmpol_cond_put(policy);\n \n \t/*\n@@ -859,10 +907,24 @@ static void kvm_gmem_free_folio(struct folio *folio)\n }\n #endif\n \n+static void kvm_gmem_invalidate_folio(struct folio *folio, size_t offset,\n+\t\t\t\t      size_t len)\n+{\n+\tstruct inode *inode = folio-\u003emapping-\u003ehost;\n+\tstruct gmem_inode *gi = GMEM_I(inode);\n+\n+\t/* guest_memfd only allows truncating full folios. */\n+\tif (WARN_ON_ONCE(offset != 0 || len != folio_size(folio)))\n+\t\treturn;\n+\n+\tgmem_provider_invalidate_folio(gi, folio);\n+}\n+\n static const struct address_space_operations kvm_gmem_aops = {\n \t.dirty_folio = noop_dirty_folio,\n \t.migrate_folio\t= kvm_gmem_migrate_folio,\n \t.error_remove_folio = kvm_gmem_error_folio,\n+\t.invalidate_folio = kvm_gmem_invalidate_folio,\n #ifdef CONFIG_HAVE_KVM_ARCH_GMEM_RECLAIM\n \t.free_folio = kvm_gmem_free_folio,\n #endif\n@@ -901,6 +963,7 @@ static int kvm_gmem_init_inode(struct inode *inode, loff_t size, u64 flags)\n \t */\n \tmapping_set_inaccessible(inode-\u003ei_mapping);\n \tWARN_ON_ONCE(!mapping_unevictable(inode-\u003ei_mapping));\n+\tmapping_set_release_always(inode-\u003ei_mapping);\n \n \tgi-\u003eflags = flags;\n \n@@ -927,7 +990,37 @@ static int kvm_gmem_init_inode(struct inode *inode, loff_t size, u64 flags)\n \treturn r;\n }\n \n-static int __kvm_gmem_create(struct kvm *kvm, loff_t size, u64 flags)\n+static int kvm_gmem_attach_resource(struct inode *inode, int resource_fd)\n+{\n+\tstruct file *resource_file;\n+\tstruct inode *res_inode;\n+\tconst struct guest_memfd_provider_operations *ops = NULL;\n+\tvoid *provider;\n+\n+\tresource_file = fget_raw(resource_fd);\n+\tif (!resource_file)\n+\t\treturn -EBADF;\n+\n+\tres_inode = file_inode(resource_file);\n+\tops = res_inode-\u003ei_sb-\u003es_op-\u003egmem_provider_ops;\n+\n+\tif (!ops || !ops-\u003eattach || !ops-\u003ealloc_folio) {\n+\t\tfput(resource_file);\n+\t\treturn -EOPNOTSUPP;\n+\t}\n+\n+\tprovider = ops-\u003eattach(resource_file);\n+\tfput(resource_file);\n+\tif (IS_ERR(provider))\n+\t\treturn PTR_ERR(provider);\n+\n+\tGMEM_I(inode)-\u003eprovider = provider;\n+\tGMEM_I(inode)-\u003eprovider_ops = ops;\n+\n+\treturn 0;\n+}\n+\n+static int __kvm_gmem_create(struct kvm *kvm, loff_t size, u64 flags, int resource_fd)\n {\n \tstatic const char *name = \"[kvm-gmem]\";\n \tstruct gmem_file *f;\n@@ -961,6 +1054,12 @@ static int __kvm_gmem_create(struct kvm *kvm, loff_t size, u64 flags)\n \tif (err)\n \t\tgoto err_inode;\n \n+\tif (flags \u0026 GUEST_MEMFD_FLAG_USE_RESOURCE) {\n+\t\terr = kvm_gmem_attach_resource(inode, resource_fd);\n+\t\tif (err)\n+\t\t\tgoto err_inode;\n+\t}\n+\n \tfile = alloc_file_pseudo(inode, kvm_gmem_mnt, name, O_RDWR, \u0026kvm_gmem_fops);\n \tif (IS_ERR(file)) {\n \t\terr = PTR_ERR(file);\n@@ -1000,7 +1099,10 @@ int kvm_gmem_create(struct kvm *kvm, struct kvm_create_guest_memfd *args)\n \tif (size \u003c= 0 || !PAGE_ALIGNED(size))\n \t\treturn -EINVAL;\n \n-\treturn __kvm_gmem_create(kvm, size, flags);\n+\tif (!(flags \u0026 GUEST_MEMFD_FLAG_USE_RESOURCE) \u0026\u0026 args-\u003eresource_fd)\n+\t\treturn -EINVAL;\n+\n+\treturn __kvm_gmem_create(kvm, size, flags, args-\u003eresource_fd);\n }\n \n int kvm_gmem_prepare_memory_region(struct kvm *kvm, struct kvm_memory_slot *slot,\n@@ -1334,6 +1436,8 @@ static struct inode *kvm_gmem_alloc_inode(struct super_block *sb)\n \tmt_init_flags(\u0026gi-\u003eattributes, MT_FLAGS_LOCK_EXTERN | MT_FLAGS_USE_RCU);\n \n \tgi-\u003eflags = 0;\n+\tgi-\u003eprovider = NULL;\n+\tgi-\u003eprovider_ops = NULL;\n \tINIT_LIST_HEAD(\u0026gi-\u003egmem_file_list);\n \treturn \u0026gi-\u003evfs_inode;\n }\n@@ -1342,6 +1446,8 @@ static void kvm_gmem_destroy_inode(struct inode *inode)\n {\n \tstruct gmem_inode *gi = GMEM_I(inode);\n \n+\tgmem_provider_release(gi);\n+\n \tmpol_free_shared_policy(\u0026gi-\u003epolicy);\n \n \t/*\n"
}

2/1 2026/09/27 17:52 agent patch-evaluator
1m Model:
core

Args:
null

Results:
{
  "EnableConfigs": [
    "KVM",
    "KVM_INTEL",
    "KVM_AMD",
    "KVM_GUEST_MEMFD",
    "TMPFS"
  ],
  "FocusSymbols": [
    "kvm_gmem_create",
    "kvm_gmem_attach_resource",
    "kvm_gmem_invalidate_folio",
    "shmem_gmem_attach",
    "shmem_gmem_alloc_folio",
    "shmem_gmem_release",
    "shmem_gmem_invalidate_folio"
  ],
  "Reasoning": "The patch introduces external resource provider support (specifically backed by tmpfs/shmem) for KVM guest_memfd files via the new GUEST_MEMFD_FLAG_USE_RESOURCE flag and resource_fd argument in KVM_CREATE_GUEST_MEMFD. This modifies reachable KVM memory management logic and mm/shmem code, adding new folio allocation/invalidation paths, inode attachment operations, and a runtime assertion (WARN_ON_ONCE). This is fully reachable from userspace and warrants fuzzing.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.

================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.

- REACHABILITY IS THE PRIMARY GATE:
  Fuzzing can only discover bugs in code that can actually execute in standard virtualized
  environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
  If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
  regardless of whether it adds assertions or complex logic.

- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
  Patch authors routinely label changes as "cleanups", "refactorings", or state
  "No functional change intended". Do NOT take these claims at face value.
  Code refactorings that rearrange logic, introduce helper functions, or alter state management
  in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
  If reachable executable code is modified or refactored, it MUST be fuzzed.

- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
  When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
  BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
  Even if the author believes the invariant always holds, fuzzing is essential to verify whether
  an unusual sequence of operations can violate it.

================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:

- Non-kernel and non-executable changes:
  * Modifications to Documentation/, comments, or spelling fixes.
  * User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
    that do not affect the compiled kernel image (vmlinux) or kernel modules.
  * Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
    that do not alter control flow or data structures.
  * Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
  * Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
    ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
  * Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
    physical PCI hot-unplug or manual sysfs driver unbinding.
  * Code paths exclusive to architectures other than the target architecture.

================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
  * Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
    BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
  * Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
  * Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
  * Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).

================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:

- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
  Do NOT list generic, ubiquitous functions called by almost every program in the corpus
  (including, but not limited to: general memory allocators and deallocators, page fault
  and trap handlers, or core synchronization primitives; this is not an exhaustive list).
  Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
  which severely dilutes fuzzing effort away from the actual changes.

- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
  List functions that specifically implement the logic being added or altered, or direct API entrypoints
  for the subsystem feature under review.

- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
  Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
  distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
  If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
  functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).

================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit 8efef62a5b63da8cba61b5108ce8cc6afd6887b6
Author: syz-cluster <triage@syzkaller.com>
Date:   Sun Sep 27 17:52:31 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
index 67f0f290797ab..b48df2827a2c3 100644
--- a/Documentation/virt/kvm/api.rst
+++ b/Documentation/virt/kvm/api.rst
@@ -6470,7 +6470,9 @@ and cannot be resized  (guest_memfd files do however support PUNCH_HOLE).
   struct kvm_create_guest_memfd {
 	__u64 size;
 	__u64 flags;
-	__u64 reserved[6];
+	__s32 resource_fd;
+	__u32 pad;
+	__u64 reserved[5];
   };
 
 Conceptually, the inode backing a guest_memfd file represents physical memory,
@@ -6492,15 +6494,21 @@ a single guest_memfd file, but the bound ranges must not overlap).
 The capability KVM_CAP_GUEST_MEMFD_FLAGS enumerates the `flags` that can be
 specified via KVM_CREATE_GUEST_MEMFD.  Currently defined flags:
 
-  ============================ ================================================
-  GUEST_MEMFD_FLAG_MMAP        Enable using mmap() on the guest_memfd file
-                               descriptor.
-  GUEST_MEMFD_FLAG_INIT_SHARED Make all memory in the file shared during
-                               KVM_CREATE_GUEST_MEMFD (memory files created
-                               without INIT_SHARED will be marked private).
-                               Shared memory can be faulted into host userspace
-                               page tables. Private memory cannot.
-  ============================ ================================================
+  ============================= ================================================
+  GUEST_MEMFD_FLAG_MMAP         Enable using mmap() on the guest_memfd file
+                                descriptor.
+  GUEST_MEMFD_FLAG_INIT_SHARED  Make all memory in the file shared during
+                                KVM_CREATE_GUEST_MEMFD (memory files created
+                                without INIT_SHARED will be marked private).
+                                Shared memory can be faulted into host userspace
+                                page tables. Private memory cannot.
+  GUEST_MEMFD_FLAG_USE_RESOURCE Consume resource_fd as an external memory
+                                provider or resource pool. When not set,
+                                resource_fd must be '0'. When set, the provider
+                                must be a supported resource (e.g. tmpfs mount
+                                root directory). Currently, tmpfs resource pools
+                                require noswap and huge=never mount options.
+  ============================= ================================================
 
 When the KVM MMU performs a PFN lookup to service a guest fault, the fault will
 always be consumed from guest_memfd, regardless of whether it is a shared or a
diff --git a/include/linux/fs/super_types.h b/include/linux/fs/super_types.h
index ecd96aeb1cee7..29e502439648a 100644
--- a/include/linux/fs/super_types.h
+++ b/include/linux/fs/super_types.h
@@ -37,6 +37,7 @@ struct workqueue_struct;
 struct writeback_control;
 struct xattr_handler;
 struct fserror_event;
+struct guest_memfd_provider_operations;
 
 extern struct super_block *blockdev_superblock;
 
@@ -130,6 +131,9 @@ struct super_operations {
 
 	/* Report a filesystem error */
 	void (*report_error)(const struct fserror_event *event);
+#ifdef CONFIG_KVM_GUEST_MEMFD
+	const struct guest_memfd_provider_operations *gmem_provider_ops;
+#endif
 };
 
 struct super_block {
diff --git a/include/linux/guest_memfd.h b/include/linux/guest_memfd.h
new file mode 100644
index 0000000000000..60eb4f008c246
--- /dev/null
+++ b/include/linux/guest_memfd.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _LINUX_GUEST_MEMFD_H
+#define _LINUX_GUEST_MEMFD_H
+
+#include <linux/types.h>
+
+struct file;
+struct folio;
+struct mempolicy;
+
+/**
+ * struct guest_memfd_provider_operations - Operations for external memory providers
+ * @attach: Attach to a resource file, perform filesystem-specific validation,
+ *          and return an opaque provider context.
+ * @release: Release provider context and unpin any resources.
+ * @alloc_folio: Allocate an uninserted folio for a given index and NUMA policy.
+ * @invalidate_folio: Notify provider that a folio has been invalidated.
+ *
+ * Used by filesystems and device drivers that provide memory for guest_memfd.
+ */
+struct guest_memfd_provider_operations {
+	void *(*attach)(struct file *resource_file);
+	void (*release)(void *provider);
+	struct folio *(*alloc_folio)(void *provider, pgoff_t index,
+				     struct mempolicy *mpol);
+	void (*invalidate_folio)(void *provider, struct folio *folio);
+};
+
+#endif /* _LINUX_GUEST_MEMFD_H */
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index 485f18454eb45..f4241c05650e9 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -2588,7 +2588,7 @@ bool kvm_arch_supports_gmem_init_shared(struct kvm *kvm);
 
 static inline u64 kvm_gmem_get_supported_flags(struct kvm *kvm)
 {
-	u64 flags = GUEST_MEMFD_FLAG_MMAP;
+	u64 flags = GUEST_MEMFD_FLAG_MMAP | GUEST_MEMFD_FLAG_USE_RESOURCE;
 
 	if (!kvm || kvm_arch_supports_gmem_init_shared(kvm))
 		flags |= GUEST_MEMFD_FLAG_INIT_SHARED;
diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index 8dff2fc1972e9..f4108bf7e3192 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -1674,11 +1674,14 @@ struct kvm_memory_attributes2 {
 #define KVM_CREATE_GUEST_MEMFD	_IOWR(KVMIO,  0xd4, struct kvm_create_guest_memfd)
 #define GUEST_MEMFD_FLAG_MMAP		(1ULL << 0)
 #define GUEST_MEMFD_FLAG_INIT_SHARED	(1ULL << 1)
+#define GUEST_MEMFD_FLAG_USE_RESOURCE	(1ULL << 2)
 
 struct kvm_create_guest_memfd {
 	__u64 size;
 	__u64 flags;
-	__u64 reserved[6];
+	__s32 resource_fd;
+	__u32 pad;
+	__u64 reserved[5];
 };
 
 #define KVM_PRE_FAULT_MEMORY	_IOWR(KVMIO, 0xd5, struct kvm_pre_fault_memory)
diff --git a/mm/shmem.c b/mm/shmem.c
index 897fa2b61346f..279f29a9861c1 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -38,6 +38,7 @@
 #include <linux/uio.h>
 #include <linux/hugetlb.h>
 #include <linux/fs_parser.h>
+#include <linux/guest_memfd.h>
 #include <linux/swapfile.h>
 #include <linux/iversion.h>
 #include <linux/unicode.h>
@@ -5234,6 +5235,114 @@ static const struct inode_operations shmem_special_inode_operations = {
 #endif
 };
 
+#ifdef CONFIG_KVM_GUEST_MEMFD
+static void *shmem_gmem_attach(struct file *resource_file)
+{
+	struct inode *inode = file_inode(resource_file);
+	struct shmem_sb_info *sbinfo;
+
+	if (!S_ISDIR(inode->i_mode))
+		return ERR_PTR(-ENOTDIR);
+
+	/*
+	 * Would like comments: Requiring the root directory of the mount
+	 * provides a less confusing interface, although this is not strictly
+	 * necessary.
+	 */
+	if (resource_file->f_path.dentry != resource_file->f_path.mnt->mnt_root)
+		return ERR_PTR(-EINVAL);
+
+	if (__mnt_is_readonly(resource_file->f_path.mnt))
+		return ERR_PTR(-EROFS);
+
+	if (inode_permission(file_mnt_idmap(resource_file), inode,
+			     MAY_WRITE | MAY_EXEC))
+		return ERR_PTR(-EACCES);
+
+	/* Enforce noswap because guest_memfd does not support swapping. */
+	sbinfo = SHMEM_SB(inode->i_sb);
+	if (!sbinfo->noswap)
+		return ERR_PTR(-EINVAL);
+
+#ifdef CONFIG_TRANSPARENT_HUGEPAGE
+	/*
+	 * guest_memfd does not support huge pages yet; this restriction can be
+	 * relaxed in the future.
+	 *
+	 * TODO: Block rebinding with huge page support.
+	 */
+	if (sbinfo->huge != SHMEM_HUGE_NEVER)
+		return ERR_PTR(-EINVAL);
+#endif
+
+	return (void *)mntget(resource_file->f_path.mnt);
+}
+
+static void shmem_gmem_release(void *provider)
+{
+	struct vfsmount *mnt = provider;
+
+	mntput(mnt);
+}
+
+/*
+ * TODO: Consider refactoring shmem allocation helpers to share code between
+ * internal tmpfs folio allocation and guest_memfd provider allocation.
+ */
+static struct folio *shmem_gmem_alloc_folio(void *provider, pgoff_t index,
+					    struct mempolicy *mpol)
+{
+	struct mempolicy *sb_mpol = NULL;
+	struct vfsmount *mnt = provider;
+	struct shmem_sb_info *sbinfo;
+	struct folio *folio;
+
+	sbinfo = SHMEM_SB(mnt->mnt_sb);
+	if (sbinfo->max_blocks &&
+	    !percpu_counter_limited_add(&sbinfo->used_blocks,
+					sbinfo->max_blocks, 1))
+		return ERR_PTR(-ENOSPC);
+
+	if (!mpol) {
+		sb_mpol = shmem_get_sbmpol(sbinfo);
+		mpol = sb_mpol;
+	}
+
+	if (mpol)
+		folio = folio_alloc_mpol(GFP_HIGHUSER_MOVABLE, 0, mpol, index,
+					 numa_node_id());
+	else
+		folio = folio_alloc(GFP_HIGHUSER_MOVABLE, 0);
+
+	mpol_cond_put(sb_mpol);
+
+	if (!folio) {
+		if (sbinfo->max_blocks)
+			percpu_counter_sub(&sbinfo->used_blocks, 1);
+		return ERR_PTR(-ENOMEM);
+	}
+
+	return folio;
+}
+
+static void shmem_gmem_invalidate_folio(void *provider, struct folio *folio)
+{
+	struct vfsmount *mnt = provider;
+	struct shmem_sb_info *sbinfo;
+
+	sbinfo = SHMEM_SB(mnt->mnt_sb);
+	if (sbinfo->max_blocks)
+		percpu_counter_sub(&sbinfo->used_blocks, folio_nr_pages(folio));
+}
+
+static const struct guest_memfd_provider_operations shmem_gmem_provider_ops = {
+	.attach			= shmem_gmem_attach,
+	.release		= shmem_gmem_release,
+	.alloc_folio		= shmem_gmem_alloc_folio,
+	.invalidate_folio	= shmem_gmem_invalidate_folio,
+};
+#endif /* CONFIG_KVM_GUEST_MEMFD */
+
 static const struct super_operations shmem_ops = {
 	.alloc_inode	= shmem_alloc_inode,
 	.free_inode	= shmem_free_in_core_inode,
@@ -5252,6 +5361,9 @@ static const struct super_operations shmem_ops = {
 	.nr_cached_objects	= shmem_unused_huge_count,
 	.free_cached_objects	= shmem_unused_huge_scan,
 #endif
+#ifdef CONFIG_KVM_GUEST_MEMFD
+	.gmem_provider_ops	= &shmem_gmem_provider_ops,
+#endif
 };
 
 static const struct vm_operations_struct shmem_vm_ops = {
diff --git a/tools/include/uapi/linux/kvm.h b/tools/include/uapi/linux/kvm.h
index 419011097fa8e..66c1f40b50c29 100644
--- a/tools/include/uapi/linux/kvm.h
+++ b/tools/include/uapi/linux/kvm.h
@@ -1654,11 +1654,14 @@ struct kvm_memory_attributes {
 #define KVM_CREATE_GUEST_MEMFD	_IOWR(KVMIO,  0xd4, struct kvm_create_guest_memfd)
 #define GUEST_MEMFD_FLAG_MMAP		(1ULL << 0)
 #define GUEST_MEMFD_FLAG_INIT_SHARED	(1ULL << 1)
+#define GUEST_MEMFD_FLAG_USE_RESOURCE	(1ULL << 2)
 
 struct kvm_create_guest_memfd {
 	__u64 size;
 	__u64 flags;
-	__u64 reserved[6];
+	__s32 resource_fd;
+	__u32 pad;
+	__u64 reserved[5];
 };
 
 #define KVM_PRE_FAULT_MEMORY	_IOWR(KVMIO, 0xd5, struct kvm_pre_fault_memory)
diff --git a/tools/testing/selftests/kvm/guest_memfd_test.c b/tools/testing/selftests/kvm/guest_memfd_test.c
index 2233d871a38f4..4e5d00df899a6 100644
--- a/tools/testing/selftests/kvm/guest_memfd_test.c
+++ b/tools/testing/selftests/kvm/guest_memfd_test.c
@@ -10,12 +10,16 @@
 #include <errno.h>
 #include <stdio.h>
 #include <fcntl.h>
+#include <limits.h>
 
 #include <linux/bitmap.h>
 #include <linux/falloc.h>
+#include <linux/mount.h>
 #include <linux/sizes.h>
+#include <sys/mount.h>
 #include <sys/types.h>
 #include <sys/stat.h>
+#include <sys/statvfs.h>
 
 #include "kvm_syscalls.h"
 #include "kvm_util.h"
@@ -404,6 +408,8 @@ static void test_guest_memfd_flags(struct kvm_vm *vm)
 	int fd;
 
 	for (flag = BIT(0); flag; flag <<= 1) {
+		if (flag == GUEST_MEMFD_FLAG_USE_RESOURCE)
+			continue;
 		fd = __vm_create_guest_memfd(vm, page_size, flag);
 		if (flag & valid_flags) {
 			TEST_ASSERT(fd >= 0,
@@ -418,55 +424,294 @@ static void test_guest_memfd_flags(struct kvm_vm *vm)
 	}
 }
 
-#define ____gmem_test(__test, __vm, __flags, __gmem_size, args...)	\
-do {									\
-	int fd = vm_create_guest_memfd(__vm, __gmem_size, __flags);	\
-									\
-	test_##__test(args);						\
-	close(fd);							\
+static void test_resource_fd_invalid(struct kvm_vm *vm)
+{
+	int fd;
+
+	/* Non-zero resource_fd without GUEST_MEMFD_FLAG_USE_RESOURCE fails */
+	fd = __vm_create_guest_memfd_resource(vm, page_size, 0, 1);
+	TEST_ASSERT(fd < 0, "guest_memfd with resource_fd but without flag should fail");
+	TEST_ASSERT_EQ(errno, EINVAL);
+
+	/* Bad file descriptor with GUEST_MEMFD_FLAG_USE_RESOURCE fails */
+	fd = __vm_create_guest_memfd_resource(vm, page_size,
+					      GUEST_MEMFD_FLAG_USE_RESOURCE, -1);
+	TEST_ASSERT(fd < 0, "guest_memfd with -1 resource_fd should fail");
+	TEST_ASSERT_EQ(errno, EBADF);
+}
+
+static void test_resource_fd_unsupported_fs(struct kvm_vm *vm)
+{
+	int fd, file_fd;
+
+	/*
+	 * Passing an FD from an unsupported filesystem without provider ops
+	 * (e.g. the /proc mount root directory) is rejected by KVM with
+	 * EOPNOTSUPP.
+	 */
+	file_fd = open("/proc", O_DIRECTORY | O_RDONLY);
+	TEST_ASSERT(file_fd >= 0, "open /proc failed");
+
+	fd = __vm_create_guest_memfd_resource(vm, page_size,
+					      GUEST_MEMFD_FLAG_USE_RESOURCE,
+					      file_fd);
+	TEST_ASSERT(fd < 0, "guest_memfd with /proc fd should fail");
+	TEST_ASSERT_EQ(errno, EOPNOTSUPP);
+
+	close(file_fd);
+}
+
+static void test_resource_fd_tmpfs_file(struct kvm_vm *vm)
+{
+	int fd, file_fd;
+
+	/*
+	 * Passing a regular file from tmpfs (e.g. via memfd) is rejected by
+	 * the provider attach callback with ENOTDIR because only directory
+	 * FDs representing the mount root are supported resource pools.
+	 */
+	file_fd = memfd_create("test_tmpfs_file", 0);
+	TEST_ASSERT(file_fd >= 0, "memfd_create failed");
+
+	fd = __vm_create_guest_memfd_resource(vm, page_size,
+					      GUEST_MEMFD_FLAG_USE_RESOURCE,
+					      file_fd);
+	TEST_ASSERT(fd < 0, "guest_memfd with tmpfs file fd should fail");
+	TEST_ASSERT_EQ(errno, ENOTDIR);
+	close(file_fd);
+}
+
+static int create_tmpfs_pool_fd(const char *huge, bool noswap, size_t size)
+{
+	char size_str[32];
+	int fs_fd, mnt_fd;
+
+	TEST_ASSERT(size && IS_ALIGNED(size, page_size),
+		    "Pool size '0x%zx' must be positive and page-aligned", size);
+
+	fs_fd = syscall(__NR_fsopen, "tmpfs", FSOPEN_CLOEXEC);
+	TEST_ASSERT(fs_fd >= 0, "fsopen failed");
+
+	if (noswap)
+		TEST_ASSERT(!syscall(__NR_fsconfig, fs_fd, FSCONFIG_SET_FLAG,
+				     "noswap", NULL, 0), "fsconfig noswap failed");
+
+	if (huge)
+		TEST_ASSERT(!syscall(__NR_fsconfig, fs_fd, FSCONFIG_SET_STRING,
+				     "huge", huge, 0), "fsconfig huge failed");
+
+	snprintf(size_str, sizeof(size_str), "%zu", size);
+	TEST_ASSERT(!syscall(__NR_fsconfig, fs_fd, FSCONFIG_SET_STRING,
+			     "size", size_str, 0), "fsconfig size failed");
+
+	TEST_ASSERT(!syscall(__NR_fsconfig, fs_fd, FSCONFIG_CMD_CREATE,
+			     NULL, NULL, 0), "fsconfig create failed");
+
+	mnt_fd = syscall(__NR_fsmount, fs_fd, FSMOUNT_CLOEXEC, 0);
+	TEST_ASSERT(mnt_fd > 0, "fsmount failed");
+
+	close(fs_fd);
+	return mnt_fd;
+}
+
+static void test_resource_fd_tmpfs_swap(struct kvm_vm *vm)
+{
+	int pool_fd, fd;
+
+	pool_fd = create_tmpfs_pool_fd("never", false, page_size);
+
+	fd = __vm_create_guest_memfd_resource(vm, page_size,
+					      GUEST_MEMFD_FLAG_USE_RESOURCE,
+					      pool_fd);
+	TEST_ASSERT(fd < 0, "guest_memfd with swap-enabled tmpfs should fail");
+	TEST_ASSERT_EQ(errno, EINVAL);
+
+	close(pool_fd);
+}
+
+static void test_resource_fd_tmpfs_huge(struct kvm_vm *vm)
+{
+	int pool_fd, fd;
+
+	pool_fd = create_tmpfs_pool_fd("always", true, page_size);
+
+	fd = __vm_create_guest_memfd_resource(vm, page_size,
+					      GUEST_MEMFD_FLAG_USE_RESOURCE,
+					      pool_fd);
+	TEST_ASSERT(fd < 0, "guest_memfd with huge-enabled tmpfs should fail");
+	TEST_ASSERT_EQ(errno, EINVAL);
+
+	close(pool_fd);
+}
+
+static void test_resource_fd_tmpfs_shared_pool(struct kvm_vm *vm)
+{
+	int pool_fd, fd1, fd2, ret;
+	struct statvfs svfs;
+	struct stat st1, st2;
+
+	pool_fd = create_tmpfs_pool_fd("never", true, page_size * 2);
+	if (pool_fd < 0)
+		TEST_REQUIRE(false);
+
+	fd1 = vm_create_guest_memfd_resource(vm, page_size,
+					     GUEST_MEMFD_FLAG_USE_RESOURCE,
+					     pool_fd);
+
+	fd2 = vm_create_guest_memfd_resource(vm, page_size * 2,
+					     GUEST_MEMFD_FLAG_USE_RESOURCE,
+					     pool_fd);
+
+	TEST_ASSERT(!fstat(fd1, &st1), "fstat on fd1 should succeed");
+	TEST_ASSERT(st1.st_size == page_size,
+		    "fd1 st_size (%lu) should match requested size (%lu)",
+		    st1.st_size, page_size);
+
+	TEST_ASSERT(!fstat(fd2, &st2), "fstat on fd2 should succeed");
+	TEST_ASSERT(st2.st_size == page_size * 2,
+		    "fd2 st_size (%lu) should match requested size (%lu)",
+		    st2.st_size, page_size * 2);
+
+	TEST_ASSERT(st1.st_ino != st2.st_ino,
+		    "different guest_memfd instances should have distinct inodes");
+
+	ret = fallocate(fd1, FALLOC_FL_KEEP_SIZE, 0, page_size);
+	TEST_ASSERT(!ret, "fallocate on fd1 should succeed");
+
+	TEST_ASSERT(!fstatvfs(pool_fd, &svfs), "fstatvfs on pool_fd should succeed");
+	TEST_ASSERT_EQ(svfs.f_blocks - svfs.f_bfree, 1);
+
+	ret = fallocate(fd2, FALLOC_FL_KEEP_SIZE, 0, page_size);
+	TEST_ASSERT(!ret, "fallocate on fd2 should succeed");
+
+	TEST_ASSERT(!fstatvfs(pool_fd, &svfs), "fstatvfs on pool_fd should succeed");
+	TEST_ASSERT_EQ(svfs.f_blocks - svfs.f_bfree, 2);
+
+	ret = fallocate(fd2, FALLOC_FL_KEEP_SIZE, page_size, page_size);
+	TEST_ASSERT(ret < 0, "fallocate exceeding pool capacity should fail");
+	TEST_ASSERT_EQ(errno, ENOSPC);
+
+	close(fd2);
+	close(fd1);
+	close(pool_fd);
+}
+
+enum gmem_pool_type {
+	GMEM_POOL_NONE,
+	GMEM_POOL_FSMOUNT,
+	GMEM_POOL_MOUNTED_DIR,
+};
+
+struct gmem_pool {
+	int fd;
+	char path[PATH_MAX];
+	bool is_mounted;
+};
+
+static struct gmem_pool create_gmem_pool(size_t size, enum gmem_pool_type type)
+{
+	struct gmem_pool pool = { .fd = -1, .is_mounted = false };
+	int mnt_fd;
+
+	if (type == GMEM_POOL_NONE)
+		return pool;
+
+	mnt_fd = create_tmpfs_pool_fd("never", true, size);
+	TEST_REQUIRE(mnt_fd >= 0);
+
+	if (type == GMEM_POOL_FSMOUNT) {
+		pool.fd = mnt_fd;
+		return pool;
+	}
+
+	strcpy(pool.path, "/tmp/gmem_test_dir_XXXXXX");
+	TEST_ASSERT(mkdtemp(pool.path), "mkdtemp failed");
+
+	if (syscall(__NR_move_mount, mnt_fd, "", AT_FDCWD, pool.path,
+		    MOVE_MOUNT_F_EMPTY_PATH)) {
+		close(mnt_fd);
+		rmdir(pool.path);
+		TEST_REQUIRE(false);
+	}
+	close(mnt_fd);
+
+	pool.fd = open(pool.path, O_RDONLY | O_DIRECTORY);
+	TEST_ASSERT(pool.fd >= 0, "open mounted tmpfs root failed");
+	pool.is_mounted = true;
+
+	return pool;
+}
+
+static void destroy_gmem_pool(struct gmem_pool *pool)
+{
+	if (pool->fd >= 0)
+		close(pool->fd);
+	if (pool->is_mounted) {
+		TEST_ASSERT(!umount(pool->path), "umount failed");
+		TEST_ASSERT(!rmdir(pool->path), "rmdir failed");
+	}
+}
+
+#define ____gmem_test(__test, __vm, __flags, __pool_type, __gmem_size, args...)	\
+do {										\
+	struct gmem_pool pool = { .fd = -1 };					\
+	int fd;									\
+										\
+	if ((__flags) & GUEST_MEMFD_FLAG_USE_RESOURCE) {			\
+		pool = create_gmem_pool(__gmem_size, __pool_type);		\
+		fd = vm_create_guest_memfd_resource(__vm, __gmem_size,		\
+						    __flags, pool.fd);		\
+	} else {								\
+		fd = vm_create_guest_memfd(__vm, __gmem_size, __flags);	\
+	}									\
+										\
+	test_##__test(args);							\
+	close(fd);								\
+	destroy_gmem_pool(&pool);						\
 } while (0)
 
-#define __gmem_test(__test, __vm, __flags, __gmem_size)			\
-	____gmem_test(__test, __vm, __flags, __gmem_size, fd, __gmem_size)
+#define __gmem_test(__test, __vm, __flags, __pool_type, __gmem_size)		\
+	____gmem_test(__test, __vm, __flags, __pool_type, __gmem_size, fd, __gmem_size)
 
-#define gmem_test(__test, __vm, __flags)				\
-	__gmem_test(__test, __vm, __flags, page_size * 4)
+#define gmem_test(__test, __vm, __flags, __pool_type)				\
+	__gmem_test(__test, __vm, __flags, __pool_type, page_size * 4)
 
-#define __gmem_test_vm(__test, __vm, __flags, __gmem_size)		\
-	____gmem_test(__test, __vm, __flags, __gmem_size, __vm, fd, __gmem_size)
+#define __gmem_test_vm(__test, __vm, __flags, __pool_type, __gmem_size)	\
+	____gmem_test(__test, __vm, __flags, __pool_type, __gmem_size, __vm, fd, __gmem_size)
 
-#define gmem_test_vm(__test, __vm, __flags)				\
-	__gmem_test_vm(__test, __vm, __flags, page_size * 4)
+#define gmem_test_vm(__test, __vm, __flags, __pool_type)			\
+	__gmem_test_vm(__test, __vm, __flags, __pool_type, page_size * 4)
 
-static void __test_guest_memfd(struct kvm_vm *vm, u64 flags)
+static void __test_guest_memfd(struct kvm_vm *vm, u64 flags,
+			       enum gmem_pool_type pool_type)
 {
 	test_create_guest_memfd_multiple(vm);
 	test_create_guest_memfd_invalid_sizes(vm, flags);
 
-	gmem_test(file_read_write, vm, flags);
+	gmem_test(file_read_write, vm, flags, pool_type);
 
 	if (flags & GUEST_MEMFD_FLAG_MMAP) {
 		if (flags & GUEST_MEMFD_FLAG_INIT_SHARED) {
 			size_t pmd_size = get_trans_hugepagesz();
 
-			gmem_test(mmap_supported, vm, flags);
-			gmem_test(fault_overflow, vm, flags);
-			gmem_test(numa_allocation, vm, flags);
-			__gmem_test(collapse, vm, flags, pmd_size);
+			gmem_test(mmap_supported, vm, flags, pool_type);
+			gmem_test(fault_overflow, vm, flags, pool_type);
+			gmem_test(numa_allocation, vm, flags, pool_type);
+			__gmem_test(collapse, vm, flags, pool_type, pmd_size);
 		} else {
-			gmem_test(fault_private, vm, flags);
+			gmem_test(fault_private, vm, flags, pool_type);
 		}
 
-		gmem_test(mmap_cow, vm, flags);
-		gmem_test(mbind, vm, flags);
+		gmem_test(mmap_cow, vm, flags, pool_type);
+		gmem_test(mbind, vm, flags, pool_type);
 	} else {
-		gmem_test(mmap_not_supported, vm, flags);
+		gmem_test(mmap_not_supported, vm, flags, pool_type);
 	}
 
-	gmem_test(file_size, vm, flags);
-	gmem_test(fallocate, vm, flags);
-	gmem_test(invalid_punch_hole, vm, flags);
-	gmem_test_vm(invalid_binding, vm, flags);
+	gmem_test(file_size, vm, flags, pool_type);
+	gmem_test(fallocate, vm, flags, pool_type);
+	gmem_test(invalid_punch_hole, vm, flags, pool_type);
+	gmem_test_vm(invalid_binding, vm, flags, pool_type);
 }
 
 static void test_guest_memfd(unsigned long vm_type)
@@ -476,16 +721,52 @@ static void test_guest_memfd(unsigned long vm_type)
 
 	test_guest_memfd_flags(vm);
 
-	__test_guest_memfd(vm, 0);
+	/*
+	 * TODO: Test that guest_memfd creation rejects a read-only tmpfs mount
+	 * (e.g. MOUNT_ATTR_RDONLY) with EROFS.
+	 *
+	 * TODO: Test that guest_memfd creation rejects an ID-mapped tmpfs mount
+	 * (e.g. MOUNT_ATTR_IDMAP) lacking write or execute/search permissions
+	 * with EACCES.
+	 *
+	 * TODO: Test mpol fallback to mount config.
+	 */
+	test_resource_fd_invalid(vm);
+	test_resource_fd_unsupported_fs(vm);
+	test_resource_fd_tmpfs_file(vm);
+	test_resource_fd_tmpfs_swap(vm);
+	test_resource_fd_tmpfs_huge(vm);
+	test_resource_fd_tmpfs_shared_pool(vm);
+
+	__test_guest_memfd(vm, 0, GMEM_POOL_NONE);
+	__test_guest_memfd(vm, GUEST_MEMFD_FLAG_USE_RESOURCE, GMEM_POOL_FSMOUNT);
+	__test_guest_memfd(vm, GUEST_MEMFD_FLAG_USE_RESOURCE, GMEM_POOL_MOUNTED_DIR);
 
 	flags = vm_check_cap(vm, KVM_CAP_GUEST_MEMFD_FLAGS);
-	if (flags & GUEST_MEMFD_FLAG_MMAP)
-		__test_guest_memfd(vm, GUEST_MEMFD_FLAG_MMAP);
+	if (flags & GUEST_MEMFD_FLAG_MMAP) {
+		__test_guest_memfd(vm, GUEST_MEMFD_FLAG_MMAP, GMEM_POOL_NONE);
+		__test_guest_memfd(vm, GUEST_MEMFD_FLAG_MMAP |
+				       GUEST_MEMFD_FLAG_USE_RESOURCE,
+				   GMEM_POOL_FSMOUNT);
+		__test_guest_memfd(vm, GUEST_MEMFD_FLAG_MMAP |
+				       GUEST_MEMFD_FLAG_USE_RESOURCE,
+				   GMEM_POOL_MOUNTED_DIR);
+	}
 
 	/* MMAP should always be supported if INIT_SHARED is supported. */
-	if (flags & GUEST_MEMFD_FLAG_INIT_SHARED)
+	if (flags & GUEST_MEMFD_FLAG_INIT_SHARED) {
+		__test_guest_memfd(vm, GUEST_MEMFD_FLAG_MMAP |
+				       GUEST_MEMFD_FLAG_INIT_SHARED,
+				   GMEM_POOL_NONE);
 		__test_guest_memfd(vm, GUEST_MEMFD_FLAG_MMAP |
-				       GUEST_MEMFD_FLAG_INIT_SHARED);
+				       GUEST_MEMFD_FLAG_INIT_SHARED |
+				       GUEST_MEMFD_FLAG_USE_RESOURCE,
+				   GMEM_POOL_FSMOUNT);
+		__test_guest_memfd(vm, GUEST_MEMFD_FLAG_MMAP |
+				       GUEST_MEMFD_FLAG_INIT_SHARED |
+				       GUEST_MEMFD_FLAG_USE_RESOURCE,
+				   GMEM_POOL_MOUNTED_DIR);
+	}
 
 	kvm_vm_free(vm);
 }
@@ -558,6 +839,62 @@ static void test_guest_memfd_guest(void)
 	kvm_vm_free(vm);
 }
 
+static void test_guest_memfd_guest_resource(void)
+{
+	const gpa_t gpa = SZ_4G;
+	const int slot = 1;
+	struct kvm_vcpu *vcpu;
+	struct kvm_vm *vm;
+	int pool_fd, fd, i;
+	size_t size;
+	u8 *mem;
+
+	if (!kvm_check_cap(KVM_CAP_GUEST_MEMFD_FLAGS))
+		return;
+
+	pool_fd = create_tmpfs_pool_fd("never", true, page_size);
+	if (pool_fd < 0)
+		TEST_REQUIRE(false);
+
+	vm = __vm_create_shape_with_one_vcpu(VM_SHAPE_DEFAULT, &vcpu, 1, guest_code);
+
+	TEST_ASSERT(vm_check_cap(vm, KVM_CAP_GUEST_MEMFD_FLAGS) & GUEST_MEMFD_FLAG_MMAP,
+		    "Default VM type should support MMAP, supported flags = 0x%x",
+		    vm_check_cap(vm, KVM_CAP_GUEST_MEMFD_FLAGS));
+	TEST_ASSERT(vm_check_cap(vm, KVM_CAP_GUEST_MEMFD_FLAGS) & GUEST_MEMFD_FLAG_INIT_SHARED,
+		    "Default VM type should support INIT_SHARED, supported flags = 0x%x",
+		    vm_check_cap(vm, KVM_CAP_GUEST_MEMFD_FLAGS));
+
+	size = max_t(size_t, vm->page_size, page_size);
+	fd = __vm_create_guest_memfd_resource(vm, size,
+					      GUEST_MEMFD_FLAG_MMAP |
+					      GUEST_MEMFD_FLAG_INIT_SHARED |
+					      GUEST_MEMFD_FLAG_USE_RESOURCE,
+					      pool_fd);
+	TEST_ASSERT(fd >= 0, "guest_memfd with tmpfs pool should succeed");
+
+	vm_set_user_memory_region2(vm, slot, KVM_MEM_GUEST_MEMFD, gpa, size, NULL, fd, 0);
+
+	mem = kvm_mmap(size, PROT_READ | PROT_WRITE, MAP_SHARED, fd);
+	memset(mem, 0xaa, size);
+	kvm_munmap(mem, size);
+
+	virt_map(vm, gpa, gpa, size / vm->page_size);
+	vcpu_args_set(vcpu, 2, gpa, size);
+	vcpu_run(vcpu);
+
+	TEST_ASSERT_EQ(get_ucall(vcpu, NULL), UCALL_DONE);
+
+	mem = kvm_mmap(size, PROT_READ | PROT_WRITE, MAP_SHARED, fd);
+	for (i = 0; i < size; i++)
+		TEST_ASSERT_EQ(mem[i], 0xff);
+
+	close(fd);
+	close(pool_fd);
+	kvm_vm_free(vm);
+}
+
+
 int main(int argc, char *argv[])
 {
 	unsigned long vm_types, vm_type;
@@ -578,4 +915,5 @@ int main(int argc, char *argv[])
 		test_guest_memfd(vm_type);
 
 	test_guest_memfd_guest();
+	test_guest_memfd_guest_resource();
 }
diff --git a/tools/testing/selftests/kvm/include/kvm_util.h b/tools/testing/selftests/kvm/include/kvm_util.h
index 777fa3dbf88d6..9eeaefe5a1839 100644
--- a/tools/testing/selftests/kvm/include/kvm_util.h
+++ b/tools/testing/selftests/kvm/include/kvm_util.h
@@ -767,26 +767,39 @@ static inline bool is_smt_on(void)
 
 void vm_create_irqchip(struct kvm_vm *vm);
 
-static inline int __vm_create_guest_memfd(struct kvm_vm *vm, u64 size,
-					  u64 flags)
+static inline int __vm_create_guest_memfd_resource(struct kvm_vm *vm, u64 size,
+						   u64 flags, int resource_fd)
 {
 	struct kvm_create_guest_memfd guest_memfd = {
 		.size = size,
 		.flags = flags,
+		.resource_fd = resource_fd,
 	};
 
 	return __vm_ioctl(vm, KVM_CREATE_GUEST_MEMFD, &guest_memfd);
 }
 
-static inline int vm_create_guest_memfd(struct kvm_vm *vm, u64 size,
-					u64 flags)
+static inline int __vm_create_guest_memfd(struct kvm_vm *vm, u64 size,
+					  u64 flags)
+{
+	return __vm_create_guest_memfd_resource(vm, size, flags, 0);
+}
+
+static inline int vm_create_guest_memfd_resource(struct kvm_vm *vm, u64 size,
+						 u64 flags, int resource_fd)
 {
-	int fd = __vm_create_guest_memfd(vm, size, flags);
+	int fd = __vm_create_guest_memfd_resource(vm, size, flags, resource_fd);
 
 	TEST_ASSERT(fd >= 0, KVM_IOCTL_ERROR(KVM_CREATE_GUEST_MEMFD, fd));
 	return fd;
 }
 
+static inline int vm_create_guest_memfd(struct kvm_vm *vm, u64 size,
+					u64 flags)
+{
+	return vm_create_guest_memfd_resource(vm, size, flags, 0);
+}
+
 void vm_set_user_memory_region(struct kvm_vm *vm, u32 slot, u32 flags,
 			       gpa_t gpa, u64 size, void *hva);
 int __vm_set_user_memory_region(struct kvm_vm *vm, u32 slot, u32 flags,
diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index a13445c26d9d6..0e0d7e93f4148 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -3,6 +3,7 @@
 #include <linux/backing-dev.h>
 #include <linux/falloc.h>
 #include <linux/fs.h>
+#include <linux/guest_memfd.h>
 #include <linux/kvm_host.h>
 #include <linux/maple_tree.h>
 #include <linux/mempolicy.h>
@@ -35,6 +36,9 @@ struct gmem_inode {
 	struct inode vfs_inode;
 	struct list_head gmem_file_list;
 
+	void *provider;
+	const struct guest_memfd_provider_operations *provider_ops;
+
 	u64 flags;
 	/*
 	 * Every index in this inode, whether memory is populated or
@@ -50,6 +54,29 @@ static __always_inline struct gmem_inode *GMEM_I(struct inode *inode)
 	return container_of(inode, struct gmem_inode, vfs_inode);
 }
 
+static inline struct folio *gmem_provider_alloc_folio(struct gmem_inode *gi,
+						      pgoff_t index,
+						      struct mempolicy *mpol)
+{
+	if (!gi->provider_ops || !gi->provider_ops->alloc_folio)
+		return ERR_PTR(-EOPNOTSUPP);
+
+	return gi->provider_ops->alloc_folio(gi->provider, index, mpol);
+}
+
+static inline void gmem_provider_invalidate_folio(struct gmem_inode *gi,
+						  struct folio *folio)
+{
+	if (gi->provider_ops && gi->provider_ops->invalidate_folio)
+		gi->provider_ops->invalidate_folio(gi->provider, folio);
+}
+
+static inline void gmem_provider_release(struct gmem_inode *gi)
+{
+	if (gi->provider_ops && gi->provider_ops->release)
+		gi->provider_ops->release(gi->provider);
+}
+
 #define kvm_gmem_for_each_file(f, inode) \
 	list_for_each_entry(f, &GMEM_I(inode)->gmem_file_list, entry)
 
@@ -128,6 +155,7 @@ static bool kvm_gmem_range_has_attributes(struct inode *inode,
  */
 static struct folio *kvm_gmem_get_folio(struct inode *inode, pgoff_t index)
 {
+	struct gmem_inode *gi = GMEM_I(inode);
 	/* TODO: Support huge pages. */
 	struct mempolicy *policy;
 	struct folio *folio;
@@ -140,10 +168,30 @@ static struct folio *kvm_gmem_get_folio(struct inode *inode, pgoff_t index)
 	if (!IS_ERR(folio))
 		return folio;
 
-	policy = mpol_shared_policy_lookup(&GMEM_I(inode)->policy, index);
-	folio = __filemap_get_folio_mpol(inode->i_mapping, index,
-					 FGP_LOCK | FGP_CREAT,
-					 mapping_gfp_mask(inode->i_mapping), policy);
+	policy = mpol_shared_policy_lookup(&gi->policy, index);
+	/*
+	 * TODO: Refactor the internal PAGE_SIZE gmem could be an internal
+	 * provider with internal provider_ops.
+	 */
+	if (gi->provider_ops) {
+		folio = gmem_provider_alloc_folio(gi, index, policy);
+		if (!IS_ERR(folio)) {
+			int r = filemap_add_folio(inode->i_mapping, folio,
+						  index, GFP_KERNEL);
+			if (r) {
+				gmem_provider_invalidate_folio(gi, folio);
+				folio_put(folio);
+				folio = ERR_PTR(r);
+			} else {
+				folio_mark_accessed(folio);
+			}
+		}
+	} else {
+		folio = __filemap_get_folio_mpol(inode->i_mapping, index,
+						 FGP_LOCK | FGP_CREAT,
+						 mapping_gfp_mask(inode->i_mapping),
+						 policy);
+	}
 	mpol_cond_put(policy);
 
 	/*
@@ -859,10 +907,24 @@ static void kvm_gmem_free_folio(struct folio *folio)
 }
 #endif
 
+static void kvm_gmem_invalidate_folio(struct folio *folio, size_t offset,
+				      size_t len)
+{
+	struct inode *inode = folio->mapping->host;
+	struct gmem_inode *gi = GMEM_I(inode);
+
+	/* guest_memfd only allows truncating full folios. */
+	if (WARN_ON_ONCE(offset != 0 || len != folio_size(folio)))
+		return;
+
+	gmem_provider_invalidate_folio(gi, folio);
+}
+
 static const struct address_space_operations kvm_gmem_aops = {
 	.dirty_folio = noop_dirty_folio,
 	.migrate_folio	= kvm_gmem_migrate_folio,
 	.error_remove_folio = kvm_gmem_error_folio,
+	.invalidate_folio = kvm_gmem_invalidate_folio,
 #ifdef CONFIG_HAVE_KVM_ARCH_GMEM_RECLAIM
 	.free_folio = kvm_gmem_free_folio,
 #endif
@@ -901,6 +963,7 @@ static int kvm_gmem_init_inode(struct inode *inode, loff_t size, u64 flags)
 	 */
 	mapping_set_inaccessible(inode->i_mapping);
 	WARN_ON_ONCE(!mapping_unevictable(inode->i_mapping));
+	mapping_set_release_always(inode->i_mapping);
 
 	gi->flags = flags;
 
@@ -927,7 +990,37 @@ static int kvm_gmem_init_inode(struct inode *inode, loff_t size, u64 flags)
 	return r;
 }
 
-static int __kvm_gmem_create(struct kvm *kvm, loff_t size, u64 flags)
+static int kvm_gmem_attach_resource(struct inode *inode, int resource_fd)
+{
+	struct file *resource_file;
+	struct inode *res_inode;
+	const struct guest_memfd_provider_operations *ops = NULL;
+	void *provider;
+
+	resource_file = fget_raw(resource_fd);
+	if (!resource_file)
+		return -EBADF;
+
+	res_inode = file_inode(resource_file);
+	ops = res_inode->i_sb->s_op->gmem_provider_ops;
+
+	if (!ops || !ops->attach || !ops->alloc_folio) {
+		fput(resource_file);
+		return -EOPNOTSUPP;
+	}
+
+	provider = ops->attach(resource_file);
+	fput(resource_file);
+	if (IS_ERR(provider))
+		return PTR_ERR(provider);
+
+	GMEM_I(inode)->provider = provider;
+	GMEM_I(inode)->provider_ops = ops;
+
+	return 0;
+}
+
+static int __kvm_gmem_create(struct kvm *kvm, loff_t size, u64 flags, int resource_fd)
 {
 	static const char *name = "[kvm-gmem]";
 	struct gmem_file *f;
@@ -961,6 +1054,12 @@ static int __kvm_gmem_create(struct kvm *kvm, loff_t size, u64 flags)
 	if (err)
 		goto err_inode;
 
+	if (flags & GUEST_MEMFD_FLAG_USE_RESOURCE) {
+		err = kvm_gmem_attach_resource(inode, resource_fd);
+		if (err)
+			goto err_inode;
+	}
+
 	file = alloc_file_pseudo(inode, kvm_gmem_mnt, name, O_RDWR, &kvm_gmem_fops);
 	if (IS_ERR(file)) {
 		err = PTR_ERR(file);
@@ -1000,7 +1099,10 @@ int kvm_gmem_create(struct kvm *kvm, struct kvm_create_guest_memfd *args)
 	if (size <= 0 || !PAGE_ALIGNED(size))
 		return -EINVAL;
 
-	return __kvm_gmem_create(kvm, size, flags);
+	if (!(flags & GUEST_MEMFD_FLAG_USE_RESOURCE) && args->resource_fd)
+		return -EINVAL;
+
+	return __kvm_gmem_create(kvm, size, flags, args->resource_fd);
 }
 
 int kvm_gmem_prepare_memory_region(struct kvm *kvm, struct kvm_memory_slot *slot,
@@ -1334,6 +1436,8 @@ static struct inode *kvm_gmem_alloc_inode(struct super_block *sb)
 	mt_init_flags(&gi->attributes, MT_FLAGS_LOCK_EXTERN | MT_FLAGS_USE_RCU);
 
 	gi->flags = 0;
+	gi->provider = NULL;
+	gi->provider_ops = NULL;
 	INIT_LIST_HEAD(&gi->gmem_file_list);
 	return &gi->vfs_inode;
 }
@@ -1342,6 +1446,8 @@ static void kvm_gmem_destroy_inode(struct inode *inode)
 {
 	struct gmem_inode *gi = GMEM_I(inode);
 
+	gmem_provider_release(gi);
+
 	mpol_free_shared_policy(&gi->policy);
 
 	/*

3/2 2026/09/27 17:52 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 16442
							output: 23
							thoughts: 745
						

Thoughts:
**Analyzing the Guest Memfd Patch Series**

Okay, let's dive into this patch series. My initial thought is to break down the changes and understand their implications within the KVM ecosystem. The target architecture is amd64, which is expected. The modified files point towards core KVM components, including the API documentation, headers, memory management (shmem), and testing infrastructure. That's a solid indication of a significant feature.

Now, let's look at the diff. The introduction of `GUEST_MEMFD_FLAG_USE_RESOURCE` to the `KVM_CREATE_GUEST_MEMFD` ioctl is the core of this. This is how the magic will be enabled. The ability to pass a `resource_fd` to `kvm_create_guest_memfd` opens up the possibility of backing guest memory with a provider filesystem, specifically tmpfs, via the `struct guest_memfd_provider_operations`. Very elegant. The key operations, the "shmem_gmem" functions, in `mm/shmem.c` are being added, along with configuring the `shmem_ops` struct. That provides the backend for tmpfs to provide backing pages.

In `virt/kvm/guest_memfd.c`, it's clear how this all ties together: `kvm_gmem_attach_resource` handling the resource file descriptor, the folio allocation using the provider's functions (`kvm_gmem_get_folio`), invalidation, and inode destruction. The `kvm_gmem_create` function validating the `GUEST_MEMFD_FLAG_USE_RESOURCE` flag is crucial for enabling the feature.

Now, the important question is: is this reachable code? Will this work as expected? Yes! This is a standard and common setup. KVM (`/dev/kvm`) and guest_memfd mechanisms, along with tmpfs, are fully accessible from userspace. QEMU and KVM tests (and potentially nested virtualization or fuzzing tools like syzkaller) will be able to validate this. In particular, syzkaller actively fuzzes KVM ioctls, including `KVM_CREATE_GUEST_MEMFD`, which suggests it will provide robust testing of this functionality. That's a very good sign that the feature is likely well-tested.

Finally, the `CONFIG_KVM_GUEST_MEMFD` Kconfig option is critical. I'll need to run a `grepper` command to understand its dependencies and configuration, to know where it's defined and what other features it enables. That's the next step, to check if the new functionality will make it into common kernels, and what the dependencies might be.



4/2 2026/09/27 17:52 tool grepper
0m Args:
{
  "Expression": "config KVM_GUEST_MEMFD"
}

Results:
{
  "Output": "virt/kvm/Kconfig=100=config KVM_MMU_LOCKLESS_AGING\n--\nvirt/kvm/Kconfig-102-\nvirt/kvm/Kconfig:103:config KVM_GUEST_MEMFD\nvirt/kvm/Kconfig-104-       select XARRAY_MULTI\n"
}

5/2 2026/09/27 17:52 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 17882
							output: 34
							thoughts: 24
						

Thoughts:
Here's a summary of my thought process, tailored for an expert like you:

**Investigating KVM Configuration**

Okay, so I need to dive into the KVM configuration. The first thing that comes to mind is to examine `virt/kvm/Kconfig`. Based on my experience, line 103 seems like a likely spot to find something interesting, so I'll reach for a utility like `read-file` to zero in on that section. I'm assuming that this Kconfig file should offer valuable information regarding available features. By viewing this specific area, I'm hoping to quickly identify the configuration options or dependencies that I need to understand better.



6/2 2026/09/27 17:52 tool read-file
0m Args:
{
  "File": "virt/kvm/Kconfig",
  "FirstLine": 95,
  "LineCount": 30
}

Results:
{
  "Contents": "  95:\t       bool\n  96:\t\n  97:\tconfig KVM_ELIDE_TLB_FLUSH_IF_YOUNG\n  98:\t       bool\n  99:\t\n 100:\tconfig KVM_MMU_LOCKLESS_AGING\n 101:\t       bool\n 102:\t\n 103:\tconfig KVM_GUEST_MEMFD\n 104:\t       select XARRAY_MULTI\n 105:\t       bool\n 106:\t\n 107:\tconfig HAVE_KVM_ARCH_GMEM_CONVERT\n 108:\t       bool\n 109:\t       depends on KVM_GUEST_MEMFD\n 110:\t\n 111:\tconfig HAVE_KVM_ARCH_GMEM_RECLAIM\n 112:\t       bool\n 113:\t       depends on KVM_GUEST_MEMFD\n 114:\t\n 115:\tconfig HAVE_KVM_ARCH_GMEM_INVALIDATE\n 116:\t       bool\n 117:\t       depends on KVM_GUEST_MEMFD\n 118:\t\n 119:\tconfig HAVE_KVM_ARCH_GMEM_POPULATE\n 120:\t       bool\n 121:\t       depends on KVM_GUEST_MEMFD\n"
}

7/2 2026/09/27 17:52 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 18435
							output: 23
							thoughts: 18
						

Thoughts:
Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person:

**Investigating the `KVM_GUEST_MEMFD` Footprint**

Alright, so the question is, who's actually using, or even enabling, the `KVM_GUEST_MEMFD` feature? It's time to dig into the codebase and figure out the breadth of its impact. I need to understand its dependencies and how it's being leveraged, or if it’s even being used at all. This might be a relatively quick investigation, or it could potentially open a rabbit hole of calls through other features. My aim is to build a mental map of its usage, understand the reasoning behind its inclusion, and ensure we're not carrying unnecessary complexity. Let's see what turns up.



8/2 2026/09/27 17:52 tool grepper
0m Args:
{
  "Expression": "select KVM_GUEST_MEMFD"
}

Results:
{
  "Output": "arch/arm64/kvm/Kconfig=20=menuconfig KVM\n--\narch/arm64/kvm/Kconfig-38-\tselect GUEST_PERF_EVENTS if PERF_EVENTS\narch/arm64/kvm/Kconfig:39:\tselect KVM_GUEST_MEMFD\narch/arm64/kvm/Kconfig-40-\thelp\n--\narch/x86/kvm/Kconfig=20=config KVM_X86\n--\narch/x86/kvm/Kconfig-49-\tselect KVM_WERROR if WERROR\narch/x86/kvm/Kconfig:50:\tselect KVM_GUEST_MEMFD if X86_64\narch/x86/kvm/Kconfig-51-\n"
}

9/2 2026/09/27 17:52 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 18863
							output: 37
							thoughts: 74
						

Thoughts:
Here's my analysis of the `Kconfig` files regarding KVM on x86:

**Delving into KVM_X86 Configuration**

Okay, so I'm digging into the Kconfig files, specifically `arch/x86/kvm/Kconfig`. My initial focus is on `KVM_X86`. I see that `KVM_X86` selects `KVM_GUEST_MEMFD` *conditionally*, but only if the architecture is `X86_64`. That's important. I need to keep that dependency in mind.  Now, the crucial question is, what *enables* `KVM_X86` in the first place? To truly understand the configuration flow, I need to trace back and determine the conditions and dependencies that determine the inclusion of `KVM_X86`. Back to the same `arch/x86/kvm/Kconfig` file, then. I'll need to work backward from this point, looking at what other configurations might trigger its selection.



10/2 2026/09/27 17:52 tool read-file
0m Args:
{
  "File": "arch/x86/kvm/Kconfig",
  "FirstLine": 1,
  "LineCount": 40
}

Results:
{
  "Contents": "   1:\t# SPDX-License-Identifier: GPL-2.0\n   2:\t#\n   3:\t# KVM configuration\n   4:\t#\n   5:\t\n   6:\tsource \"virt/kvm/Kconfig\"\n   7:\t\n   8:\tmenuconfig VIRTUALIZATION\n   9:\t\tbool \"Virtualization\"\n  10:\t\tdefault y\n  11:\t\thelp\n  12:\t\t  Say Y here to get to see options for using your Linux host to run other\n  13:\t\t  operating systems inside virtual machines (guests).\n  14:\t\t  This option alone does not add any kernel code.\n  15:\t\n  16:\t\t  If you say N, all options in this submenu will be skipped and disabled.\n  17:\t\n  18:\tif VIRTUALIZATION\n  19:\t\n  20:\tconfig KVM_X86\n  21:\t\tdef_tristate KVM if (KVM_INTEL != n || KVM_AMD != n)\n  22:\t\tselect KVM_COMMON\n  23:\t\tselect KVM_ELIDE_TLB_FLUSH_IF_YOUNG\n  24:\t\tselect KVM_MMU_LOCKLESS_AGING\n  25:\t\tselect HAVE_KVM_IRQCHIP\n  26:\t\tselect HAVE_KVM_PFNCACHE\n  27:\t\tselect HAVE_KVM_DIRTY_RING_TSO\n  28:\t\tselect HAVE_KVM_DIRTY_RING_ACQ_REL\n  29:\t\tselect HAVE_KVM_IRQ_BYPASS\n  30:\t\tselect HAVE_KVM_IRQ_ROUTING\n  31:\t\tselect HAVE_KVM_READONLY_MEM\n  32:\t\tselect VHOST_TASK\n  33:\t\tselect KVM_ASYNC_PF\n  34:\t\tselect USER_RETURN_NOTIFIER\n  35:\t\tselect KVM_MMIO\n  36:\t\tselect SCHED_INFO\n  37:\t\tselect PERF_EVENTS\n  38:\t\tselect GUEST_PERF_EVENTS\n  39:\t\tselect PERF_GUEST_MEDIATED_PMU\n  40:\t\tselect HAVE_KVM_MSI\n"
}

11/2 2026/09/27 17:52 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 19752
							output: 17
							thoughts: 100
						

Thoughts:
**Investigating Shared Memory Options in the Kernel**

Okay, so I'm looking at different shared memory solutions within the kernel, specifically in the context of `KVM`. My primary focus is on `KVM` itself (and potentially `KVM_INTEL` or `KVM_AMD`, depending on the hardware) and how it interfaces with shared memory primitives. I'm trying to figure out the most suitable approach for guest memory sharing, and I'm thinking about `KVM_GUEST_MEMFD` as a potential candidate, along with other possibilities like `TMPFS` or `SHMEM`.

Right now, I'm leaning towards inspecting the Kconfig options related to `SHMEM` and `TMPFS`. The first logical step is to check the kernel configuration files. The `mm/shmem.c` file is a good starting point, as it should contain the relevant configuration symbols like `CONFIG_SHMEM` and `CONFIG_TMPFS`. From there, if necessary, I will move on to exploring `mm/Kconfig` or `init/Kconfig` to trace the origins or relationships of `SHMEM` within the kernel configuration system. I want to build a clear understanding of the configuration options, the underlying implementations, and their suitability for KVM-based shared memory use cases.



12/2 2026/09/27 17:52 tool grepper
0m Args:
{
  "Expression": "config SHMEM"
}

Results:
{
  "Output": "init/Kconfig=1932=config EVENTFD\n--\ninit/Kconfig-1940-\ninit/Kconfig:1941:config SHMEM\ninit/Kconfig-1942-\tbool \"Use full shmem filesystem\" if EXPERT\n"
}

13/2 2026/09/27 17:52 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 20220
							output: 26
							thoughts: 20
						

Thoughts:
Here's my thought process, as if I were considering this myself:

**Investigating the Build Condition of `mm/shmem.c`**

Okay, so I need to understand why this specific module is or isn't being built. I'm going to start by looking into the build rules. The most likely place for build condition logic would be within the `mm` directory's `Makefile`. So, first things first, I'll need to locate and examine the `mm/Makefile`. I'm specifically looking for any conditional statements, variables, or macros that determine whether or not `mm/shmem.o` (or the equivalent object file name) is included in the build process. I'll be particularly vigilant for things like `ifdef`, `ifneq`, or any defined variables that impact source file inclusions. This should give me a good handle on what the dependencies or compile-time flags are that influence this build.



14/2 2026/09/27 17:53 tool grepper
0m Args:
{
  "Expression": "shmem.o",
  "PathPrefix": "mm/"
}

Results:
{
  "Output": "mm/Makefile=51=obj-y\t\t\t:= filemap.o mempool.o oom_kill.o fadvise.o \\\n--\nmm/Makefile-53-\t\t\t   readahead.o folio.o truncate.o vmscan.o shrinker.o \\\nmm/Makefile:54:\t\t\t   shmem.o util.o mmzone.o vmstat.o backing-dev.o \\\nmm/Makefile-55-\t\t\t   mm_init.o percpu.o slab_common.o \\\n--\nmm/gup.c=3408=EXPORT_SYMBOL(pin_user_pages_unlocked);\n--\nmm/gup.c-3419- * Attempt to pin folios associated with a memfd in the contiguous range\nmm/gup.c:3420: * [start, end]. Given that a memfd is either backed by shmem or hugetlb,\nmm/gup.c-3421- * the folios can either be found in the page cache or need to be allocated\n--\nmm/huge_memory.c=3922=int folio_check_splittable(struct folio *folio, unsigned int new_order,\n--\nmm/huge_memory.c-3930-\t * TODO: this will also currently refuse folios without a mapping in the\nmm/huge_memory.c:3931:\t * swapcache (shmem or to-be-anon folios).\nmm/huge_memory.c-3932-\t */\n--\nmm/memory.c=6514=static vm_fault_t handle_pte_fault(struct vm_fault *vmf)\n--\nmm/memory.c-6532-\t\t * pmd by anon khugepaged, since that takes mmap_lock in write\nmm/memory.c:6533:\t\t * mode; but shmem or file collapse to THP could still morph\nmm/memory.c-6534-\t\t * it into a huge pmd: just retry later if so.\n--\nmm/shmem.c=107=struct shmem_falloc {\n--\nmm/shmem.c-114-\nmm/shmem.c:115:struct shmem_options {\nmm/shmem.c-116-\tunsigned long long blocks;\n--\nmm/shmem.c-139-#ifdef CONFIG_TRANSPARENT_HUGEPAGE\nmm/shmem.c:140:static unsigned long huge_shmem_orders_always __read_mostly;\nmm/shmem.c:141:static unsigned long huge_shmem_orders_madvise __read_mostly;\nmm/shmem.c:142:static unsigned long huge_shmem_orders_inherit __read_mostly;\nmm/shmem.c:143:static unsigned long huge_shmem_orders_within_size __read_mostly;\nmm/shmem.c:144:static bool shmem_orders_configured __initdata;\nmm/shmem.c-145-#endif\n--\nmm/shmem.c=256=static void shmem_inode_unacct_blocks(struct inode *inode, long pages)\n--\nmm/shmem.c-268-\nmm/shmem.c:269:static const struct super_operations shmem_ops;\nmm/shmem.c-270-static const struct address_space_operations shmem_aops;\n--\nmm/shmem.c=522=static int shmem_confirm_swap(struct address_space *mapping, pgoff_t index,\n--\nmm/shmem.c-571-#ifdef CONFIG_TRANSPARENT_HUGEPAGE\nmm/shmem.c:572:/* ifdef here to avoid bloating shmem.o when not necessary */\nmm/shmem.c-573-\n--\nmm/shmem.c=672=static int shmem_parse_huge(const char *str)\n--\nmm/shmem.c-699-\tif (huge == SHMEM_HUGE_FORCE \u0026\u0026\nmm/shmem.c:700:\t    huge_shmem_orders_inherit != BIT(HPAGE_PMD_ORDER))\nmm/shmem.c-701-\t\treturn -EINVAL;\n--\nmm/shmem.c=976=static long shmem_free_swap(struct address_space *mapping,\n--\nmm/shmem.c-1002-/*\nmm/shmem.c:1003: * Determine (in bytes) how many of the shmem object's pages mapped by the\nmm/shmem.c-1004- * given offsets are swapped out.\n--\nmm/shmem.c=1009=unsigned long shmem_partial_swap_usage(struct address_space *mapping,\n--\nmm/shmem.c-1035-/*\nmm/shmem.c:1036: * Determine (in bytes) how many of the shmem object's pages mapped by the\nmm/shmem.c-1037- * given vma is swapped out.\n--\nmm/shmem.c=1042=unsigned long shmem_swap_usage(struct vm_area_struct *vma)\n--\nmm/shmem.c-1054-\t/*\nmm/shmem.c:1055:\t * The easier cases are when the shmem object has nothing in swap, or\nmm/shmem.c-1056-\t * the vma maps it whole. Then we can simply use the stats that we\n--\nmm/shmem.c=1820=bool shmem_hpage_pmd_enabled(void)\n--\nmm/shmem.c-1823-\t\treturn false;\nmm/shmem.c:1824:\tif (test_bit(HPAGE_PMD_ORDER, \u0026huge_shmem_orders_always))\nmm/shmem.c-1825-\t\treturn true;\nmm/shmem.c:1826:\tif (test_bit(HPAGE_PMD_ORDER, \u0026huge_shmem_orders_madvise))\nmm/shmem.c-1827-\t\treturn true;\nmm/shmem.c:1828:\tif (test_bit(HPAGE_PMD_ORDER, \u0026huge_shmem_orders_within_size))\nmm/shmem.c-1829-\t\treturn true;\nmm/shmem.c:1830:\tif (test_bit(HPAGE_PMD_ORDER, \u0026huge_shmem_orders_inherit) \u0026\u0026\nmm/shmem.c-1831-\t    shmem_huge != SHMEM_HUGE_NEVER)\n--\nmm/shmem.c=1837=unsigned long shmem_allowable_huge_orders(struct inode *inode,\n--\nmm/shmem.c-1840-{\nmm/shmem.c:1841:\tunsigned long mask = READ_ONCE(huge_shmem_orders_always);\nmm/shmem.c:1842:\tunsigned long within_size_orders = READ_ONCE(huge_shmem_orders_within_size);\nmm/shmem.c-1843-\tvm_flags_t vm_flags = vma ? vma-\u003evm_flags : 0;\n--\nmm/shmem.c-1866-\tif (shmem_huge == SHMEM_HUGE_FORCE)\nmm/shmem.c:1867:\t\treturn READ_ONCE(huge_shmem_orders_inherit);\nmm/shmem.c-1868-\n--\nmm/shmem.c-1872-\tif (vm_flags \u0026 VM_HUGEPAGE)\nmm/shmem.c:1873:\t\tmask |= READ_ONCE(huge_shmem_orders_madvise);\nmm/shmem.c-1874-\nmm/shmem.c-1875-\tif (global_orders \u003e 0)\nmm/shmem.c:1876:\t\tmask |= READ_ONCE(huge_shmem_orders_inherit);\nmm/shmem.c-1877-\n--\nmm/shmem.c=2741=unsigned long shmem_get_unmapped_area(struct file *file,\n--\nmm/shmem.c-2801-#ifdef CONFIG_TRANSPARENT_HUGEPAGE\nmm/shmem.c:2802:\t\t\thpage_orders = READ_ONCE(huge_shmem_orders_always);\nmm/shmem.c:2803:\t\t\thpage_orders |= READ_ONCE(huge_shmem_orders_within_size);\nmm/shmem.c:2804:\t\t\thpage_orders |= READ_ONCE(huge_shmem_orders_madvise);\nmm/shmem.c-2805-\t\t\tif (SHMEM_SB(sb)-\u003ehuge != SHMEM_HUGE_NEVER)\nmm/shmem.c:2806:\t\t\t\thpage_orders |= READ_ONCE(huge_shmem_orders_inherit);\nmm/shmem.c-2807-\n--\nmm/shmem.c=4502=static int shmem_parse_opt_casefold(struct fs_context *fc, struct fs_parameter *param,\n--\nmm/shmem.c-4504-{\nmm/shmem.c:4505:\tstruct shmem_options *ctx = fc-\u003efs_private;\nmm/shmem.c-4506-\tint version = UTF8_LATEST;\n--\nmm/shmem.c=4543=static int shmem_parse_one(struct fs_context *fc, struct fs_parameter *param)\nmm/shmem.c-4544-{\nmm/shmem.c:4545:\tstruct shmem_options *ctx = fc-\u003efs_private;\nmm/shmem.c-4546-\tstruct fs_parse_result result;\n--\nmm/shmem.c=4756=static int shmem_reconfigure(struct fs_context *fc)\nmm/shmem.c-4757-{\nmm/shmem.c:4758:\tstruct shmem_options *ctx = fc-\u003efs_private;\nmm/shmem.c-4759-\tstruct shmem_sb_info *sbinfo = SHMEM_SB(fc-\u003eroot-\u003ed_sb);\n--\nmm/shmem.c=4955=static int shmem_fill_super(struct super_block *sb, struct fs_context *fc)\nmm/shmem.c-4956-{\nmm/shmem.c:4957:\tstruct shmem_options *ctx = fc-\u003efs_private;\nmm/shmem.c-4958-\tstruct inode *inode;\n--\nmm/shmem.c-5039-\tsb-\u003es_magic = TMPFS_MAGIC;\nmm/shmem.c:5040:\tsb-\u003es_op = \u0026shmem_ops;\nmm/shmem.c-5041-\tsb-\u003es_time_gran = 1;\n--\nmm/shmem.c=5091=static void shmem_free_fc(struct fs_context *fc)\nmm/shmem.c-5092-{\nmm/shmem.c:5093:\tstruct shmem_options *ctx = fc-\u003efs_private;\nmm/shmem.c-5094-\n--\nmm/shmem.c=5338=static const struct guest_memfd_provider_operations shmem_gmem_provider_ops = {\n--\nmm/shmem.c-5345-\nmm/shmem.c:5346:static const struct super_operations shmem_ops = {\nmm/shmem.c-5347-\t.alloc_inode\t= shmem_alloc_inode,\n--\nmm/shmem.c=5393=int shmem_init_fs_context(struct fs_context *fc)\nmm/shmem.c-5394-{\nmm/shmem.c:5395:\tstruct shmem_options *ctx;\nmm/shmem.c-5396-\nmm/shmem.c:5397:\tctx = kzalloc_obj(struct shmem_options);\nmm/shmem.c-5398-\tif (!ctx)\n--\nmm/shmem.c=5488=void __init shmem_init(void)\n--\nmm/shmem.c-5528-\t */\nmm/shmem.c:5529:\tif (!shmem_orders_configured)\nmm/shmem.c:5530:\t\thuge_shmem_orders_inherit = BIT(HPAGE_PMD_ORDER);\nmm/shmem.c-5531-#endif\n--\nmm/shmem.c=5594=struct kobj_attribute shmem_enabled_attr = __ATTR_RW(shmem_enabled);\nmm/shmem.c:5595:static DEFINE_SPINLOCK(huge_shmem_orders_lock);\nmm/shmem.c-5596-\n--\nmm/shmem.c=5613=static unsigned long * const huge_mode_orders[] = {\nmm/shmem.c:5614:\t[HUGE_SHMEM_ENABLED_ALWAYS]      = \u0026huge_shmem_orders_always,\nmm/shmem.c:5615:\t[HUGE_SHMEM_ENABLED_INHERIT]     = \u0026huge_shmem_orders_inherit,\nmm/shmem.c:5616:\t[HUGE_SHMEM_ENABLED_WITHIN_SIZE] = \u0026huge_shmem_orders_within_size,\nmm/shmem.c:5617:\t[HUGE_SHMEM_ENABLED_ADVISE]      = \u0026huge_shmem_orders_madvise,\nmm/shmem.c-5618-};\n--\nmm/shmem.c=5650=static bool set_shmem_enabled_mode(int order, enum huge_mode mode)\n--\nmm/shmem.c-5654-\nmm/shmem.c:5655:\tspin_lock(\u0026huge_shmem_orders_lock);\nmm/shmem.c-5656-\tfor (idx = 0; idx \u003c ARRAY_SIZE(huge_mode_orders); idx++) {\n--\nmm/shmem.c-5661-\t}\nmm/shmem.c:5662:\tspin_unlock(\u0026huge_shmem_orders_lock);\nmm/shmem.c-5663-\n--\nmm/shmem.c=5736=static int __init setup_thp_shmem(char *str)\n--\nmm/shmem.c-5747-\nmm/shmem.c:5748:\talways = huge_shmem_orders_always;\nmm/shmem.c:5749:\tinherit = huge_shmem_orders_inherit;\nmm/shmem.c:5750:\tmadvise = huge_shmem_orders_madvise;\nmm/shmem.c:5751:\twithin_size = huge_shmem_orders_within_size;\nmm/shmem.c-5752-\tp = str_dup;\n--\nmm/shmem.c-5822-\nmm/shmem.c:5823:\thuge_shmem_orders_always = always;\nmm/shmem.c:5824:\thuge_shmem_orders_madvise = madvise;\nmm/shmem.c:5825:\thuge_shmem_orders_inherit = inherit;\nmm/shmem.c:5826:\thuge_shmem_orders_within_size = within_size;\nmm/shmem.c:5827:\tshmem_orders_configured = true;\nmm/shmem.c-5828-\treturn 1;\n--\nmm/vmscan.c=621=static pageout_t pageout(struct swap_io_ctx *ctx, struct address_space *mapping,\n--\nmm/vmscan.c-639-\t *\nmm/vmscan.c:640:\t * A freeable shmem or swapcache folio is referenced only by the\nmm/vmscan.c-641-\t * caller that isolated the folio and the page cache.\n"
}

15/2 2026/09/27 17:53 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 24524
							output: 29
							thoughts: 89
						

Thoughts:
Okay, let's see. Here's how I'm thinking through this code structure...

**Digging into Shared Memory and KVM Guest Memory File Descriptors**

Right, so `mm/shmem.c` is definitely in the build, the standard configuration has `CONFIG_SHMEM` enabled, which is pretty much expected, as it's the standard default.

Now, that `CONFIG_KVM_GUEST_MEMFD` guard… that's got my attention. It's obviously conditional. I need to figure out *how* it's brought into the build. That's a classic build dependency and configuration issue. Let's see... looking at the source of `virt/kvm/guest_memfd.c`, it's clearly a KVM-specific feature. Time to look into `virt/kvm/Makefile` and see what the build system is doing to tie things together. I'm assuming it's probably using conditional compilation based on that `CONFIG_KVM_GUEST_MEMFD` define, but let's confirm.



16/2 2026/09/27 17:53 tool grepper
0m Args:
{
  "Expression": "guest_memfd",
  "PathPrefix": "virt/kvm/"
}

Results:
{
  "Output": "virt/kvm/Makefile.kvm=14=kvm-$(CONFIG_HAVE_KVM_PFNCACHE) += $(KVM)/pfncache.o\nvirt/kvm/Makefile.kvm:15:kvm-$(CONFIG_KVM_GUEST_MEMFD) += $(KVM)/guest_memfd.o\n--\nvirt/kvm/guest_memfd.c-5-#include \u003clinux/fs.h\u003e\nvirt/kvm/guest_memfd.c:6:#include \u003clinux/guest_memfd.h\u003e\nvirt/kvm/guest_memfd.c-7-#include \u003clinux/kvm_host.h\u003e\n--\nvirt/kvm/guest_memfd.c-14-#include \"kvm_mm.h\"\nvirt/kvm/guest_memfd.c:15:#include \"guest_memfd.h\"\nvirt/kvm/guest_memfd.c-16-\nvirt/kvm/guest_memfd.c=17=static struct vfsmount *kvm_gmem_mnt;\n--\nvirt/kvm/guest_memfd.c-19-/*\nvirt/kvm/guest_memfd.c:20: * A guest_memfd instance can be associated multiple VMs, each with its own\nvirt/kvm/guest_memfd.c-21- * \"view\" of the underlying physical memory.\n--\nvirt/kvm/guest_memfd.c=34=struct gmem_inode {\n--\nvirt/kvm/guest_memfd.c-39-\tvoid *provider;\nvirt/kvm/guest_memfd.c:40:\tconst struct guest_memfd_provider_operations *provider_ops;\nvirt/kvm/guest_memfd.c-41-\n--\nvirt/kvm/guest_memfd.c=156=static struct folio *kvm_gmem_get_folio(struct inode *inode, pgoff_t index)\n--\nvirt/kvm/guest_memfd.c-198-\t * External interfaces like kvm_gmem_get_pfn() support dealing\nvirt/kvm/guest_memfd.c:199:\t * with hugepages to a degree, but internally, guest_memfd currently\nvirt/kvm/guest_memfd.c-200-\t * assumes that all folios are order-0 and handling would need\n--\nvirt/kvm/guest_memfd.c=589=bool kvm_gmem_is_private_gfn(struct kvm *kvm, gfn_t gfn)\n--\nvirt/kvm/guest_memfd.c-606-\t * caller _must_ protect consumption of private vs. shared either by\nvirt/kvm/guest_memfd.c:607:\t * holding guest_memfd's invalidate lock for the entire duration, or by\nvirt/kvm/guest_memfd.c-608-\t * checking mmu_invalidate_retry_gfn() under mmu_lock to serialize\n--\nvirt/kvm/guest_memfd.c=733=static int __kvm_gmem_set_attributes(struct inode *inode, pgoff_t start,\n--\nvirt/kvm/guest_memfd.c-778-\t/*\nvirt/kvm/guest_memfd.c:779:\t * From this point on guest_memfd has performed necessary\nvirt/kvm/guest_memfd.c-780-\t * checks and can proceed to do guest-breaking changes.\n--\nvirt/kvm/guest_memfd.c=910=static void kvm_gmem_invalidate_folio(struct folio *folio, size_t offset,\n--\nvirt/kvm/guest_memfd.c-915-\nvirt/kvm/guest_memfd.c:916:\t/* guest_memfd only allows truncating full folios. */\nvirt/kvm/guest_memfd.c-917-\tif (WARN_ON_ONCE(offset != 0 || len != folio_size(folio)))\n--\nvirt/kvm/guest_memfd.c=947=static int kvm_gmem_init_inode(struct inode *inode, loff_t size, u64 flags)\n--\nvirt/kvm/guest_memfd.c-960-\t/*\nvirt/kvm/guest_memfd.c:961:\t * guest_memfd memory is neither migratable nor swappable: set\nvirt/kvm/guest_memfd.c-962-\t * inaccessible to gate off both.\n--\nvirt/kvm/guest_memfd.c=993=static int kvm_gmem_attach_resource(struct inode *inode, int resource_fd)\n--\nvirt/kvm/guest_memfd.c-996-\tstruct inode *res_inode;\nvirt/kvm/guest_memfd.c:997:\tconst struct guest_memfd_provider_operations *ops = NULL;\nvirt/kvm/guest_memfd.c-998-\tvoid *provider;\n--\nvirt/kvm/guest_memfd.c=1023=static int __kvm_gmem_create(struct kvm *kvm, loff_t size, u64 flags, int resource_fd)\n--\nvirt/kvm/guest_memfd.c-1090-\nvirt/kvm/guest_memfd.c:1091:int kvm_gmem_create(struct kvm *kvm, struct kvm_create_guest_memfd *args)\nvirt/kvm/guest_memfd.c-1092-{\n--\nvirt/kvm/guest_memfd.c=1404=static void kvm_gmem_init_inode_once(void *__gi)\n--\nvirt/kvm/guest_memfd.c-1409-\t * Note!  Don't initialize the inode with anything specific to the\nvirt/kvm/guest_memfd.c:1410:\t * guest_memfd instance, or that might be specific to how the inode is\nvirt/kvm/guest_memfd.c-1411-\t * used (from the VFS-layer's perspective).  This hook is called only\n--\nvirt/kvm/guest_memfd.c=1445=static void kvm_gmem_destroy_inode(struct inode *inode)\n--\nvirt/kvm/guest_memfd.c-1456-\t * initialized, i.e. if the inode is being destroyed before\nvirt/kvm/guest_memfd.c:1457:\t * guest_memfd can set the external lock, lockdep would find\nvirt/kvm/guest_memfd.c-1458-\t * that the tree's internal ma_lock was not held.\n--\nvirt/kvm/guest_memfd.c=1496=static struct file_system_type kvm_gmem_fs = {\nvirt/kvm/guest_memfd.c:1497:\t.name\t\t = \"guest_memfd\",\nvirt/kvm/guest_memfd.c-1498-\t.init_fs_context = kvm_gmem_init_fs_context,\n--\nvirt/kvm/guest_memfd.h=9=void kvm_gmem_exit(void);\nvirt/kvm/guest_memfd.h:10:int kvm_gmem_create(struct kvm *kvm, struct kvm_create_guest_memfd *args);\nvirt/kvm/guest_memfd.h-11-int kvm_gmem_prepare_memory_region(struct kvm *kvm, struct kvm_memory_slot *slot,\n--\nvirt/kvm/kvm_main.c-58-#include \"async_pf.h\"\nvirt/kvm/kvm_main.c:59:#include \"guest_memfd.h\"\nvirt/kvm/kvm_main.c-60-#include \"kvm_mm.h\"\n--\nvirt/kvm/kvm_main.c=1705=static void kvm_commit_memory_region(struct kvm *kvm,\n--\nvirt/kvm/kvm_main.c-1738-\t\t/*\nvirt/kvm/kvm_main.c:1739:\t\t * Moving a guest_memfd memslot isn't supported, and will never\nvirt/kvm/kvm_main.c-1740-\t\t * be supported.\n--\nvirt/kvm/kvm_main.c-1753-\t\t * TODO: Drop the WARN and do the unbind() call only for MOVE\nvirt/kvm/kvm_main.c:1754:\t\t * when dirty logging guest_memfd memslots is supported.  Until\nvirt/kvm/kvm_main.c:1755:\t\t * then, flags-only changes on guest_memfd slots should also be\nvirt/kvm/kvm_main.c-1756-\t\t * impossible; unbind the old memslot for defense-in-depth.\n--\nvirt/kvm/kvm_main.c=2013=static int kvm_set_memory_region(struct kvm *kvm,\n--\nvirt/kvm/kvm_main.c-2045-\tif (mem-\u003eflags \u0026 KVM_MEM_GUEST_MEMFD \u0026\u0026\nvirt/kvm/kvm_main.c:2046:\t    (mem-\u003eguest_memfd_offset \u0026 (PAGE_SIZE - 1) ||\nvirt/kvm/kvm_main.c:2047:\t     mem-\u003eguest_memfd_offset + mem-\u003ememory_size \u003c mem-\u003eguest_memfd_offset))\nvirt/kvm/kvm_main.c-2048-\t\treturn -EINVAL;\n--\nvirt/kvm/kvm_main.c-2126-\tif (change == KVM_MR_CREATE \u0026\u0026 (mem-\u003eflags \u0026 KVM_MEM_GUEST_MEMFD)) {\nvirt/kvm/kvm_main.c:2127:\t\tr = kvm_gmem_prepare_memory_region(kvm, new, mem-\u003eguest_memfd,\nvirt/kvm/kvm_main.c:2128:\t\t\t\t\t\t   mem-\u003eguest_memfd_offset);\nvirt/kvm/kvm_main.c-2129-\t\tif (r)\n--\nvirt/kvm/kvm_main.c=5225=static long kvm_vm_ioctl(struct file *filp,\n--\nvirt/kvm/kvm_main.c-5446-\tcase KVM_CREATE_GUEST_MEMFD: {\nvirt/kvm/kvm_main.c:5447:\t\tstruct kvm_create_guest_memfd guest_memfd;\nvirt/kvm/kvm_main.c-5448-\nvirt/kvm/kvm_main.c-5449-\t\tr = -EFAULT;\nvirt/kvm/kvm_main.c:5450:\t\tif (copy_from_user(\u0026guest_memfd, argp, sizeof(guest_memfd)))\nvirt/kvm/kvm_main.c-5451-\t\t\tgoto out;\nvirt/kvm/kvm_main.c-5452-\nvirt/kvm/kvm_main.c:5453:\t\tr = kvm_gmem_create(kvm, \u0026guest_memfd);\nvirt/kvm/kvm_main.c-5454-\t\tbreak;\n"
}

17/2 2026/09/27 17:53 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 27725
							output: 17
							thoughts: 822
						

Thoughts:
**KVM Guest Memory Fuzzing - A Deep Dive**

Okay, let's break this down. My initial scan of `virt/kvm/kvm_main.c` has flagged `KVM_CREATE_GUEST_MEMFD` and the interaction with `kvm_gmem_create` as a prime target for fuzzing, especially after seeing the introduction of the new `GUEST_MEMFD_FLAG_USE_RESOURCE` flag. The fact that userspace can now provide a `resource_fd` linked to a tmpfs directory is a significant architectural shift. This introduces a whole new attack surface! Operations like `fallocate`, reads, writes, memory faulting (both KVM MMU and guest execution contexts), hole punching, VM/memfd closure, and destruction all now interact with this external memory provider logic.

The sheer number of functions involved immediately screams "WorthFuzzing=true": `shmem_gmem_attach`, `shmem_gmem_release`, `shmem_gmem_alloc_folio`, `shmem_gmem_invalidate_folio`, `kvm_gmem_attach_resource`, `kvm_gmem_invalidate_folio`, `kvm_gmem_get_folio`, `kvm_gmem_create`, and `kvm_gmem_destroy_inode`. It's a comprehensive integration!

My attention is also drawn to `kvm_gmem_invalidate_folio` and that `WARN_ON_ONCE` within it.  That's a potential assertion right there, and according to the fuzzing guidelines, *any* new or modified assertions in reachable code are a red flag and need immediate fuzzing attention! The restriction on full-folio invalidation is a classic example of where fuzzers can shine.

Now, let's confirm the criteria:

1.  **WorthFuzzing**: Absolutely. This patch is fundamentally changing how KVM manages guest memory, integrating it with external providers, and introducing new constraints and checks.  This definitely falls under "NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED."

2.  **EnableConfigs**: We'll need to make sure the fuzzer is properly configured.
    *   `KVM` (or `KVM_X86`) is a no-brainer.
    *   `KVM_GUEST_MEMFD` is essential, and it's conditionally selected by `KVM_X86 if X86_64`.
    *   We also *must* have `TMPFS` or `SHMEM` enabled because of the `resource_fd` being tied to a tmpfs root directory. We'll need to identify the exact config option for tmpfs or shmem, and verify that it's selected.

Therefore, this needs to be fuzzed, and we need to determine the precise configuration requirements for our fuzzing campaign!



18/2 2026/09/27 17:53 tool grepper
0m Args:
{
  "Expression": "config TMPFS"
}

Results:
{
  "Output": "fs/Kconfig=165=source \"fs/sysfs/Kconfig\"\nfs/Kconfig-166-\nfs/Kconfig:167:config TMPFS\nfs/Kconfig-168-\tbool \"Tmpfs virtual memory file system support (former shm fs)\"\n--\nfs/Kconfig-180-\nfs/Kconfig:181:config TMPFS_POSIX_ACL\nfs/Kconfig-182-\tbool \"Tmpfs POSIX Access Control Lists\"\n--\nfs/Kconfig-198-\nfs/Kconfig:199:config TMPFS_XATTR\nfs/Kconfig-200-\tbool \"Tmpfs extended attributes\"\n--\nfs/Kconfig-213-\nfs/Kconfig:214:config TMPFS_INODE64\nfs/Kconfig-215-\tbool \"Use 64-bit ino_t by default in tmpfs\"\n--\nfs/Kconfig-234-\nfs/Kconfig:235:config TMPFS_QUOTA\nfs/Kconfig-236-\tbool \"Tmpfs quota support\"\n"
}

19/2 2026/09/27 17:53 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 29473
							output: 32
							thoughts: 15
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience:

**Examining Kconfig for Filesystem Configuration**

Right, let's dive into this. Based on my memory, if we're dealing with filesystem configuration options, `fs/Kconfig` is usually a good starting point. I suspect there may be a specific configuration I'm trying to locate, and I need to hone in on it. I'll need to double check around line 167 in that file to see if the required feature is defined. This may or may not be directly related to the issue, it is worth a double check.



20/2 2026/09/27 17:53 tool read-file
0m Args:
{
  "File": "fs/Kconfig",
  "FirstLine": 165,
  "LineCount": 25
}

Results:
{
  "Contents": " 165:\tsource \"fs/sysfs/Kconfig\"\n 166:\t\n 167:\tconfig TMPFS\n 168:\t\tbool \"Tmpfs virtual memory file system support (former shm fs)\"\n 169:\t\tdepends on SHMEM\n 170:\t\tselect MEMFD_CREATE\n 171:\t\thelp\n 172:\t\t  Tmpfs is a file system which keeps all files in virtual memory.\n 173:\t\n 174:\t\t  Everything in tmpfs is temporary in the sense that no files will be\n 175:\t\t  created on your hard drive. The files live in memory and swap\n 176:\t\t  space. If you unmount a tmpfs instance, everything stored therein is\n 177:\t\t  lost.\n 178:\t\n 179:\t\t  See \u003cfile:Documentation/filesystems/tmpfs.rst\u003e for details.\n 180:\t\n 181:\tconfig TMPFS_POSIX_ACL\n 182:\t\tbool \"Tmpfs POSIX Access Control Lists\"\n 183:\t\tdepends on TMPFS\n 184:\t\tselect TMPFS_XATTR\n 185:\t\tselect FS_POSIX_ACL\n 186:\t\thelp\n 187:\t\t  POSIX Access Control Lists (ACLs) support additional access rights\n 188:\t\t  for users and groups beyond the standard owner/group/world scheme,\n 189:\t\t  and this option selects support for ACLs specifically for tmpfs\n"
}

21/2 2026/09/27 17:53 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 30037
							output: 30
							thoughts: 92
						

Thoughts:
Okay, let's dive into this. Here's my thought process on this compilation issue:

**Tracing the Compilation of `shmem.c`**

Right, I need to see how `shmem.c` is compiled within the `mm` directory's `Makefile`. Okay, let me quickly navigate to that file and zero in on it... Got it. Let's start with what I expect to be the key line, line 54... Yes, there it is! Confirmed: `shmem.o` is indeed listed within `obj-y`. Alright, that confirms it is statically compiled...or does it? Now, the question is, is this the only object file involved? Could there be a conditional inclusion? Let's verify that quickly, I have seen it before. Let me scan around line 50 to see if there is any condition related to `shmem_tiny.o`. I am looking for a conditional to see if `shmem.o` is always built, or if there is a `shmem_tiny.o` involved.



22/2 2026/09/27 17:53 tool read-file
0m Args:
{
  "File": "mm/Makefile",
  "FirstLine": 45,
  "LineCount": 25
}

Results:
{
  "Contents": "  45:\tendif\n  46:\t\n  47:\tifdef CONFIG_64BIT\n  48:\tmmu-$(CONFIG_MMU)\t+= mseal.o\n  49:\tendif\n  50:\t\n  51:\tobj-y\t\t\t:= filemap.o mempool.o oom_kill.o fadvise.o \\\n  52:\t\t\t\t   maccess.o page-writeback.o folio-compat.o \\\n  53:\t\t\t\t   readahead.o folio.o truncate.o vmscan.o shrinker.o \\\n  54:\t\t\t\t   shmem.o util.o mmzone.o vmstat.o backing-dev.o \\\n  55:\t\t\t\t   mm_init.o percpu.o slab_common.o \\\n  56:\t\t\t\t   compaction.o show_mem.o \\\n  57:\t\t\t\t   interval_tree.o list_lru.o workingset.o \\\n  58:\t\t\t\t   debug.o gup.o mmap_lock.o vma_init.o $(mmu-y)\n  59:\t\n  60:\t# Give 'page_alloc' its own module-parameter namespace\n  61:\tpage-alloc-y := page_alloc.o\n  62:\tpage-alloc-$(CONFIG_SHUFFLE_PAGE_ALLOCATOR) += shuffle.o\n  63:\t\n  64:\t# Give 'memory_hotplug' its own module-parameter namespace\n  65:\tmemory-hotplug-$(CONFIG_MEMORY_HOTPLUG) += memory_hotplug.o\n  66:\t\n  67:\tobj-y += page-alloc.o\n  68:\tobj-y += page_frag_cache.o\n  69:\tobj-y += init-mm.o\n"
}

23/2 2026/09/27 17:53 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 30835
							output: 34
							thoughts: 1275
						

Thoughts:
**Analyzing KVM Guest Memfd Symbol Introductions and Configuration Requirements**

Okay, so I'm diving into this `mm/shmem.c` file and see the use of `#ifdef CONFIG_KVM_GUEST_MEMFD`.  That immediately triggers an investigation.  I check `include/linux/fs/super_types.h` and confirm that, yes, this config option enables a `guest_memfd_provider_operations` struct pointer within the `super_block` structure.  That tells me this is all about integrating a guest-side memfd implementation.

Next, I look at how the code is compiled, seeing that `kvm-$(CONFIG_KVM_GUEST_MEMFD) += $(KVM)/guest_memfd.o` is used, so it compiles into a separate object file.  Looking further, I see that `KVM_GUEST_MEMFD` is selected if `KVM_X86` is set and the architecture is x86-64. Then I see the dependency tree.  `KVM_X86` depends on `VIRTUALIZATION` and `KVM` (or `KVM_INTEL` or `KVM_AMD`).  Importantly, I note that `TMPFS` is needed if tmpfs is mounted and used for `resource_fd`.  Therefore, if I want to *use* this feature, I need to configure the kernel with: `KVM`, `KVM_INTEL` or `KVM_AMD`, `KVM_GUEST_MEMFD`, and `TMPFS`.  I always make sure I cover my bases.

I then check to see if `CONFIG_KVM_GUEST_MEMFD` is a boolean or a tristate, to see if there's any prompting.  I find in `virt/kvm/Kconfig` that it is a boolean, with no prompt.  So it's selected by `KVM_X86` (when `X86_64` is set) or can be selected if needed.  This means that, to enable everything, providing `KVM`, `KVM_INTEL`, `KVM_AMD`, `KVM_GUEST_MEMFD`, `TMPFS` is thorough and precise, a good starting point.

Now, for the interesting part: identifying what symbols are actually *introduced* or *modified* by this patch. I dig into the functions themselves. In `virt/kvm/guest_memfd.c` I see:

*   `kvm_gmem_create` is modified to take a `resource_fd` and checks `GUEST_MEMFD_FLAG_USE_RESOURCE`, then calls `__kvm_gmem_create`.
*   `__kvm_gmem_create` (a static function) modified to call `kvm_gmem_attach_resource`.
*   `kvm_gmem_attach_resource` (a new, static function).
*   `kvm_gmem_get_folio` is modified to call provider ops: `gmem_provider_alloc_folio`.
*   `kvm_gmem_invalidate_folio` (a new, static function in `kvm_gmem_aops`).
*   `kvm_gmem_destroy_inode` (a static function) modified to call `gmem_provider_release`.

Then I move to `mm/shmem.c`, where I see the shmem provider functions:

*   `shmem_gmem_attach` (static)
*   `shmem_gmem_release` (static)
*   `shmem_gmem_alloc_folio` (static)
*   `shmem_gmem_invalidate_folio` (static)

And this is important, I must make a mental note, are these `shmem_gmem_*` functions inlined by the compiler? Because their addresses are taken to populate the `shmem_gmem_provider_ops` struct table. They cannot be inlined by the compiler! So they will be distinct symbols in the `vmlinux` image. I need to be sure the linker does not discard these.

Similarly, I confirm that:

*   `kvm_gmem_create` is non-static, called from `kvm_vm_ioctl` in `virt/kvm/kvm_main.c`.
*   `kvm_gmem_invalidate_folio`'s address is stored in `kvm_gmem_aops.invalidate_folio`, so its address is taken and it will exist as a distinct symbol in `vmlinux`.
*   `kvm_gmem


24/2 2026/09/27 17:53 tool grepper
0m Args:
{
  "Expression": "kvm_gmem_get_folio",
  "PathPrefix": "virt/kvm/"
}

Results:
{
  "Output": "virt/kvm/guest_memfd.c=129=static bool kvm_gmem_range_has_attributes(struct inode *inode,\n--\nvirt/kvm/guest_memfd.c-155- */\nvirt/kvm/guest_memfd.c:156:static struct folio *kvm_gmem_get_folio(struct inode *inode, pgoff_t index)\nvirt/kvm/guest_memfd.c-157-{\n--\nvirt/kvm/guest_memfd.c=315=static long kvm_gmem_allocate(struct inode *inode, loff_t offset, loff_t len)\n--\nvirt/kvm/guest_memfd.c-338-\nvirt/kvm/guest_memfd.c:339:\t\tfolio = kvm_gmem_get_folio(inode, index);\nvirt/kvm/guest_memfd.c-340-\t\tif (IS_ERR(folio)) {\n--\nvirt/kvm/guest_memfd.c=493=static vm_fault_t kvm_gmem_fault_user_mapping(struct vm_fault *vmf)\n--\nvirt/kvm/guest_memfd.c-503-\tif (kvm_gmem_is_shared_mem(inode, vmf-\u003epgoff))\nvirt/kvm/guest_memfd.c:504:\t\tfolio = kvm_gmem_get_folio(inode, vmf-\u003epgoff);\nvirt/kvm/guest_memfd.c-505-\telse\n--\nvirt/kvm/guest_memfd.c=1230=static struct folio *__kvm_gmem_get_pfn(struct file *file,\n--\nvirt/kvm/guest_memfd.c-1248-\nvirt/kvm/guest_memfd.c:1249:\tfolio = kvm_gmem_get_folio(file_inode(file), index);\nvirt/kvm/guest_memfd.c-1250-\tif (IS_ERR(folio))\n"
}

25/2 2026/09/27 17:53 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 33696
							output: 252
							thoughts: 336
						

Thoughts:
**Analysis of Kernel Memory Management Targets**

Okay, let's break this down. From what I see, `kvm_gmem_get_folio` is a fairly sizable function in guest_memfd.c, and it's called from a few different spots. Even if parts of it are inlined into allocate/fault/get_pfn, I think that tracking the function itself, or the calls to it, could prove to be quite valuable. But beyond that, I see a clear set of other functions that warrant closer attention. `kvm_gmem_create`, `kvm_gmem_attach_resource`, `kvm_gmem_invalidate_folio`, `shmem_gmem_attach`, `shmem_gmem_alloc_folio`, `shmem_gmem_release`, and `shmem_gmem_invalidate_folio` are all excellent candidates for focused fuzzing efforts.

Looking at the proposed `set-results` parameters, it all checks out. The `EnableConfigs` list looks right; it covers all the necessary configurations like KVM, and guest memfd support, and the TMPFS dependency is noted. Then, the `FocusSymbols` list, that's exactly what I've identified as key functions to target. The `Reasoning` section, while concise, adequately explains the rationale – targeting those specific functions, and the `WorthFuzzing` flag being set to `true` is completely appropriate given what we've identified. Everything is good, it all matches my mental map of the system, time to execute `set-results`.



26/2 2026/09/27 17:53 tool set-results
0m Args:
{
  "EnableConfigs": [
    "KVM",
    "KVM_INTEL",
    "KVM_AMD",
    "KVM_GUEST_MEMFD",
    "TMPFS"
  ],
  "FocusSymbols": [
    "kvm_gmem_create",
    "kvm_gmem_attach_resource",
    "kvm_gmem_invalidate_folio",
    "shmem_gmem_attach",
    "shmem_gmem_alloc_folio",
    "shmem_gmem_release",
    "shmem_gmem_invalidate_folio"
  ],
  "Reasoning": "The patch introduces external resource provider support (specifically backed by tmpfs/shmem) for KVM guest_memfd files via the new GUEST_MEMFD_FLAG_USE_RESOURCE flag and resource_fd argument in KVM_CREATE_GUEST_MEMFD. This modifies reachable KVM memory management logic and mm/shmem code, adding new folio allocation/invalidation paths, inode attachment operations, and a runtime assertion (WARN_ON_ONCE). This is fully reachable from userspace and warrants fuzzing.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "KVM",
    "KVM_INTEL",
    "KVM_AMD",
    "KVM_GUEST_MEMFD",
    "TMPFS"
  ],
  "FocusSymbols": [
    "kvm_gmem_create",
    "kvm_gmem_attach_resource",
    "kvm_gmem_invalidate_folio",
    "shmem_gmem_attach",
    "shmem_gmem_alloc_folio",
    "shmem_gmem_release",
    "shmem_gmem_invalidate_folio"
  ],
  "Reasoning": "The patch introduces external resource provider support (specifically backed by tmpfs/shmem) for KVM guest_memfd files via the new GUEST_MEMFD_FLAG_USE_RESOURCE flag and resource_fd argument in KVM_CREATE_GUEST_MEMFD. This modifies reachable KVM memory management logic and mm/shmem code, adding new folio allocation/invalidation paths, inode attachment operations, and a runtime assertion (WARN_ON_ONCE). This is fully reachable from userspace and warrants fuzzing.",
  "WorthFuzzing": true
}

27/1 2026/09/27 17:53 action If
2m Args:
{
  "WorthFuzzing": true
}

Results:
null

28/2 2026/09/27 17:53 agent kmsan-evaluator
2m Model:
core

Args:
null

Results:
{
  "KMSANReasoning": "The patch adds support for backing KVM guest_memfd with external memory resource providers (specifically tmpfs/shmem mounts) via a new flag GUEST_MEMFD_FLAG_USE_RESOURCE and resource_fd field in struct kvm_create_guest_memfd.\n\n1. Info-leak analysis: Although struct kvm_create_guest_memfd has been updated with a 32-bit pad field, the ioctl handler KVM_CREATE_GUEST_MEMFD only performs copy_from_user() and never copies any struct or data back to user space (the ioctl returns a file descriptor integer on success). Thus, no uninitialized struct fields or padding can leak to user space.\n2. Uninitialized memory analysis: The allocated folios from the external resource provider (shmem_gmem_alloc_folio) are added to the page cache and, when accessed via faulting or pfn lookup (__kvm_gmem_get_pfn / kvm_gmem_fault_user_mapping), are verified for the uptodate flag and cleared via clear_highpage() before being exposed. All new fields in struct gmem_inode (provider, provider_ops) are explicitly initialized to NULL upon inode allocation. All local variables and pointers are initialized before use.\n3. Bug profile: Any potential bugs introduced by this patch series relate to filesystem mount validation, file reference counting (fget_raw/fput), vfsmount pinning (mntget/mntput), per-cpu counter tracking (used_blocks), or page cache folio lifecycle management. These error classes (use-after-free, reference leaks, locking/concurrency) are well-covered by KASAN, refcount tracking, and LOCKDEP. KMSAN is not applicable.",
  "NeedsKMSAN": false
}

Instruction:
You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.

CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
  a comprehensive suite of debugging tools and sanitizers, including KASAN
  (out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
  (locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
  or page allocations) and kernel-to-user memory info-leaks.

Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.

Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.

Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
   attributes, ioctl output arguments, socket options, or BPF buffers) where fields
   or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
   or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
   could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
   uninitialized bytes of existing buffers.

Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
  dereferences, locking deadlocks, or use-after-free bugs (these are already caught
  by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
  or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.

Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit 8efef62a5b63da8cba61b5108ce8cc6afd6887b6
Author: syz-cluster <triage@syzkaller.com>
Date:   Sun Sep 27 17:52:31 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
index 67f0f290797ab..b48df2827a2c3 100644
--- a/Documentation/virt/kvm/api.rst
+++ b/Documentation/virt/kvm/api.rst
@@ -6470,7 +6470,9 @@ and cannot be resized  (guest_memfd files do however support PUNCH_HOLE).
   struct kvm_create_guest_memfd {
 	__u64 size;
 	__u64 flags;
-	__u64 reserved[6];
+	__s32 resource_fd;
+	__u32 pad;
+	__u64 reserved[5];
   };
 
 Conceptually, the inode backing a guest_memfd file represents physical memory,
@@ -6492,15 +6494,21 @@ a single guest_memfd file, but the bound ranges must not overlap).
 The capability KVM_CAP_GUEST_MEMFD_FLAGS enumerates the `flags` that can be
 specified via KVM_CREATE_GUEST_MEMFD.  Currently defined flags:
 
-  ============================ ================================================
-  GUEST_MEMFD_FLAG_MMAP        Enable using mmap() on the guest_memfd file
-                               descriptor.
-  GUEST_MEMFD_FLAG_INIT_SHARED Make all memory in the file shared during
-                               KVM_CREATE_GUEST_MEMFD (memory files created
-                               without INIT_SHARED will be marked private).
-                               Shared memory can be faulted into host userspace
-                               page tables. Private memory cannot.
-  ============================ ================================================
+  ============================= ================================================
+  GUEST_MEMFD_FLAG_MMAP         Enable using mmap() on the guest_memfd file
+                                descriptor.
+  GUEST_MEMFD_FLAG_INIT_SHARED  Make all memory in the file shared during
+                                KVM_CREATE_GUEST_MEMFD (memory files created
+                                without INIT_SHARED will be marked private).
+                                Shared memory can be faulted into host userspace
+                                page tables. Private memory cannot.
+  GUEST_MEMFD_FLAG_USE_RESOURCE Consume resource_fd as an external memory
+                                provider or resource pool. When not set,
+                                resource_fd must be '0'. When set, the provider
+                                must be a supported resource (e.g. tmpfs mount
+                                root directory). Currently, tmpfs resource pools
+                                require noswap and huge=never mount options.
+  ============================= ================================================
 
 When the KVM MMU performs a PFN lookup to service a guest fault, the fault will
 always be consumed from guest_memfd, regardless of whether it is a shared or a
diff --git a/include/linux/fs/super_types.h b/include/linux/fs/super_types.h
index ecd96aeb1cee7..29e502439648a 100644
--- a/include/linux/fs/super_types.h
+++ b/include/linux/fs/super_types.h
@@ -37,6 +37,7 @@ struct workqueue_struct;
 struct writeback_control;
 struct xattr_handler;
 struct fserror_event;
+struct guest_memfd_provider_operations;
 
 extern struct super_block *blockdev_superblock;
 
@@ -130,6 +131,9 @@ struct super_operations {
 
 	/* Report a filesystem error */
 	void (*report_error)(const struct fserror_event *event);
+#ifdef CONFIG_KVM_GUEST_MEMFD
+	const struct guest_memfd_provider_operations *gmem_provider_ops;
+#endif
 };
 
 struct super_block {
diff --git a/include/linux/guest_memfd.h b/include/linux/guest_memfd.h
new file mode 100644
index 0000000000000..60eb4f008c246
--- /dev/null
+++ b/include/linux/guest_memfd.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _LINUX_GUEST_MEMFD_H
+#define _LINUX_GUEST_MEMFD_H
+
+#include <linux/types.h>
+
+struct file;
+struct folio;
+struct mempolicy;
+
+/**
+ * struct guest_memfd_provider_operations - Operations for external memory providers
+ * @attach: Attach to a resource file, perform filesystem-specific validation,
+ *          and return an opaque provider context.
+ * @release: Release provider context and unpin any resources.
+ * @alloc_folio: Allocate an uninserted folio for a given index and NUMA policy.
+ * @invalidate_folio: Notify provider that a folio has been invalidated.
+ *
+ * Used by filesystems and device drivers that provide memory for guest_memfd.
+ */
+struct guest_memfd_provider_operations {
+	void *(*attach)(struct file *resource_file);
+	void (*release)(void *provider);
+	struct folio *(*alloc_folio)(void *provider, pgoff_t index,
+				     struct mempolicy *mpol);
+	void (*invalidate_folio)(void *provider, struct folio *folio);
+};
+
+#endif /* _LINUX_GUEST_MEMFD_H */
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index 485f18454eb45..f4241c05650e9 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -2588,7 +2588,7 @@ bool kvm_arch_supports_gmem_init_shared(struct kvm *kvm);
 
 static inline u64 kvm_gmem_get_supported_flags(struct kvm *kvm)
 {
-	u64 flags = GUEST_MEMFD_FLAG_MMAP;
+	u64 flags = GUEST_MEMFD_FLAG_MMAP | GUEST_MEMFD_FLAG_USE_RESOURCE;
 
 	if (!kvm || kvm_arch_supports_gmem_init_shared(kvm))
 		flags |= GUEST_MEMFD_FLAG_INIT_SHARED;
diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index 8dff2fc1972e9..f4108bf7e3192 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -1674,11 +1674,14 @@ struct kvm_memory_attributes2 {
 #define KVM_CREATE_GUEST_MEMFD	_IOWR(KVMIO,  0xd4, struct kvm_create_guest_memfd)
 #define GUEST_MEMFD_FLAG_MMAP		(1ULL << 0)
 #define GUEST_MEMFD_FLAG_INIT_SHARED	(1ULL << 1)
+#define GUEST_MEMFD_FLAG_USE_RESOURCE	(1ULL << 2)
 
 struct kvm_create_guest_memfd {
 	__u64 size;
 	__u64 flags;
-	__u64 reserved[6];
+	__s32 resource_fd;
+	__u32 pad;
+	__u64 reserved[5];
 };
 
 #define KVM_PRE_FAULT_MEMORY	_IOWR(KVMIO, 0xd5, struct kvm_pre_fault_memory)
diff --git a/mm/shmem.c b/mm/shmem.c
index 897fa2b61346f..279f29a9861c1 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -38,6 +38,7 @@
 #include <linux/uio.h>
 #include <linux/hugetlb.h>
 #include <linux/fs_parser.h>
+#include <linux/guest_memfd.h>
 #include <linux/swapfile.h>
 #include <linux/iversion.h>
 #include <linux/unicode.h>
@@ -5234,6 +5235,114 @@ static const struct inode_operations shmem_special_inode_operations = {
 #endif
 };
 
+#ifdef CONFIG_KVM_GUEST_MEMFD
+static void *shmem_gmem_attach(struct file *resource_file)
+{
+	struct inode *inode = file_inode(resource_file);
+	struct shmem_sb_info *sbinfo;
+
+	if (!S_ISDIR(inode->i_mode))
+		return ERR_PTR(-ENOTDIR);
+
+	/*
+	 * Would like comments: Requiring the root directory of the mount
+	 * provides a less confusing interface, although this is not strictly
+	 * necessary.
+	 */
+	if (resource_file->f_path.dentry != resource_file->f_path.mnt->mnt_root)
+		return ERR_PTR(-EINVAL);
+
+	if (__mnt_is_readonly(resource_file->f_path.mnt))
+		return ERR_PTR(-EROFS);
+
+	if (inode_permission(file_mnt_idmap(resource_file), inode,
+			     MAY_WRITE | MAY_EXEC))
+		return ERR_PTR(-EACCES);
+
+	/* Enforce noswap because guest_memfd does not support swapping. */
+	sbinfo = SHMEM_SB(inode->i_sb);
+	if (!sbinfo->noswap)
+		return ERR_PTR(-EINVAL);
+
+#ifdef CONFIG_TRANSPARENT_HUGEPAGE
+	/*
+	 * guest_memfd does not support huge pages yet; this restriction can be
+	 * relaxed in the future.
+	 *
+	 * TODO: Block rebinding with huge page support.
+	 */
+	if (sbinfo->huge != SHMEM_HUGE_NEVER)
+		return ERR_PTR(-EINVAL);
+#endif
+
+	return (void *)mntget(resource_file->f_path.mnt);
+}
+
+static void shmem_gmem_release(void *provider)
+{
+	struct vfsmount *mnt = provider;
+
+	mntput(mnt);
+}
+
+/*
+ * TODO: Consider refactoring shmem allocation helpers to share code between
+ * internal tmpfs folio allocation and guest_memfd provider allocation.
+ */
+static struct folio *shmem_gmem_alloc_folio(void *provider, pgoff_t index,
+					    struct mempolicy *mpol)
+{
+	struct mempolicy *sb_mpol = NULL;
+	struct vfsmount *mnt = provider;
+	struct shmem_sb_info *sbinfo;
+	struct folio *folio;
+
+	sbinfo = SHMEM_SB(mnt->mnt_sb);
+	if (sbinfo->max_blocks &&
+	    !percpu_counter_limited_add(&sbinfo->used_blocks,
+					sbinfo->max_blocks, 1))
+		return ERR_PTR(-ENOSPC);
+
+	if (!mpol) {
+		sb_mpol = shmem_get_sbmpol(sbinfo);
+		mpol = sb_mpol;
+	}
+
+	if (mpol)
+		folio = folio_alloc_mpol(GFP_HIGHUSER_MOVABLE, 0, mpol, index,
+					 numa_node_id());
+	else
+		folio = folio_alloc(GFP_HIGHUSER_MOVABLE, 0);
+
+	mpol_cond_put(sb_mpol);
+
+	if (!folio) {
+		if (sbinfo->max_blocks)
+			percpu_counter_sub(&sbinfo->used_blocks, 1);
+		return ERR_PTR(-ENOMEM);
+	}
+
+	return folio;
+}
+
+static void shmem_gmem_invalidate_folio(void *provider, struct folio *folio)
+{
+	struct vfsmount *mnt = provider;
+	struct shmem_sb_info *sbinfo;
+
+	sbinfo = SHMEM_SB(mnt->mnt_sb);
+	if (sbinfo->max_blocks)
+		percpu_counter_sub(&sbinfo->used_blocks, folio_nr_pages(folio));
+}
+
+static const struct guest_memfd_provider_operations shmem_gmem_provider_ops = {
+	.attach			= shmem_gmem_attach,
+	.release		= shmem_gmem_release,
+	.alloc_folio		= shmem_gmem_alloc_folio,
+	.invalidate_folio	= shmem_gmem_invalidate_folio,
+};
+#endif /* CONFIG_KVM_GUEST_MEMFD */
+
 static const struct super_operations shmem_ops = {
 	.alloc_inode	= shmem_alloc_inode,
 	.free_inode	= shmem_free_in_core_inode,
@@ -5252,6 +5361,9 @@ static const struct super_operations shmem_ops = {
 	.nr_cached_objects	= shmem_unused_huge_count,
 	.free_cached_objects	= shmem_unused_huge_scan,
 #endif
+#ifdef CONFIG_KVM_GUEST_MEMFD
+	.gmem_provider_ops	= &shmem_gmem_provider_ops,
+#endif
 };
 
 static const struct vm_operations_struct shmem_vm_ops = {
diff --git a/tools/include/uapi/linux/kvm.h b/tools/include/uapi/linux/kvm.h
index 419011097fa8e..66c1f40b50c29 100644
--- a/tools/include/uapi/linux/kvm.h
+++ b/tools/include/uapi/linux/kvm.h
@@ -1654,11 +1654,14 @@ struct kvm_memory_attributes {
 #define KVM_CREATE_GUEST_MEMFD	_IOWR(KVMIO,  0xd4, struct kvm_create_guest_memfd)
 #define GUEST_MEMFD_FLAG_MMAP		(1ULL << 0)
 #define GUEST_MEMFD_FLAG_INIT_SHARED	(1ULL << 1)
+#define GUEST_MEMFD_FLAG_USE_RESOURCE	(1ULL << 2)
 
 struct kvm_create_guest_memfd {
 	__u64 size;
 	__u64 flags;
-	__u64 reserved[6];
+	__s32 resource_fd;
+	__u32 pad;
+	__u64 reserved[5];
 };
 
 #define KVM_PRE_FAULT_MEMORY	_IOWR(KVMIO, 0xd5, struct kvm_pre_fault_memory)
diff --git a/tools/testing/selftests/kvm/guest_memfd_test.c b/tools/testing/selftests/kvm/guest_memfd_test.c
index 2233d871a38f4..4e5d00df899a6 100644
--- a/tools/testing/selftests/kvm/guest_memfd_test.c
+++ b/tools/testing/selftests/kvm/guest_memfd_test.c
@@ -10,12 +10,16 @@
 #include <errno.h>
 #include <stdio.h>
 #include <fcntl.h>
+#include <limits.h>
 
 #include <linux/bitmap.h>
 #include <linux/falloc.h>
+#include <linux/mount.h>
 #include <linux/sizes.h>
+#include <sys/mount.h>
 #include <sys/types.h>
 #include <sys/stat.h>
+#include <sys/statvfs.h>
 
 #include "kvm_syscalls.h"
 #include "kvm_util.h"
@@ -404,6 +408,8 @@ static void test_guest_memfd_flags(struct kvm_vm *vm)
 	int fd;
 
 	for (flag = BIT(0); flag; flag <<= 1) {
+		if (flag == GUEST_MEMFD_FLAG_USE_RESOURCE)
+			continue;
 		fd = __vm_create_guest_memfd(vm, page_size, flag);
 		if (flag & valid_flags) {
 			TEST_ASSERT(fd >= 0,
@@ -418,55 +424,294 @@ static void test_guest_memfd_flags(struct kvm_vm *vm)
 	}
 }
 
-#define ____gmem_test(__test, __vm, __flags, __gmem_size, args...)	\
-do {									\
-	int fd = vm_create_guest_memfd(__vm, __gmem_size, __flags);	\
-									\
-	test_##__test(args);						\
-	close(fd);							\
+static void test_resource_fd_invalid(struct kvm_vm *vm)
+{
+	int fd;
+
+	/* Non-zero resource_fd without GUEST_MEMFD_FLAG_USE_RESOURCE fails */
+	fd = __vm_create_guest_memfd_resource(vm, page_size, 0, 1);
+	TEST_ASSERT(fd < 0, "guest_memfd with resource_fd but without flag should fail");
+	TEST_ASSERT_EQ(errno, EINVAL);
+
+	/* Bad file descriptor with GUEST_MEMFD_FLAG_USE_RESOURCE fails */
+	fd = __vm_create_guest_memfd_resource(vm, page_size,
+					      GUEST_MEMFD_FLAG_USE_RESOURCE, -1);
+	TEST_ASSERT(fd < 0, "guest_memfd with -1 resource_fd should fail");
+	TEST_ASSERT_EQ(errno, EBADF);
+}
+
+static void test_resource_fd_unsupported_fs(struct kvm_vm *vm)
+{
+	int fd, file_fd;
+
+	/*
+	 * Passing an FD from an unsupported filesystem without provider ops
+	 * (e.g. the /proc mount root directory) is rejected by KVM with
+	 * EOPNOTSUPP.
+	 */
+	file_fd = open("/proc", O_DIRECTORY | O_RDONLY);
+	TEST_ASSERT(file_fd >= 0, "open /proc failed");
+
+	fd = __vm_create_guest_memfd_resource(vm, page_size,
+					      GUEST_MEMFD_FLAG_USE_RESOURCE,
+					      file_fd);
+	TEST_ASSERT(fd < 0, "guest_memfd with /proc fd should fail");
+	TEST_ASSERT_EQ(errno, EOPNOTSUPP);
+
+	close(file_fd);
+}
+
+static void test_resource_fd_tmpfs_file(struct kvm_vm *vm)
+{
+	int fd, file_fd;
+
+	/*
+	 * Passing a regular file from tmpfs (e.g. via memfd) is rejected by
+	 * the provider attach callback with ENOTDIR because only directory
+	 * FDs representing the mount root are supported resource pools.
+	 */
+	file_fd = memfd_create("test_tmpfs_file", 0);
+	TEST_ASSERT(file_fd >= 0, "memfd_create failed");
+
+	fd = __vm_create_guest_memfd_resource(vm, page_size,
+					      GUEST_MEMFD_FLAG_USE_RESOURCE,
+					      file_fd);
+	TEST_ASSERT(fd < 0, "guest_memfd with tmpfs file fd should fail");
+	TEST_ASSERT_EQ(errno, ENOTDIR);
+	close(file_fd);
+}
+
+static int create_tmpfs_pool_fd(const char *huge, bool noswap, size_t size)
+{
+	char size_str[32];
+	int fs_fd, mnt_fd;
+
+	TEST_ASSERT(size && IS_ALIGNED(size, page_size),
+		    "Pool size '0x%zx' must be positive and page-aligned", size);
+
+	fs_fd = syscall(__NR_fsopen, "tmpfs", FSOPEN_CLOEXEC);
+	TEST_ASSERT(fs_fd >= 0, "fsopen failed");
+
+	if (noswap)
+		TEST_ASSERT(!syscall(__NR_fsconfig, fs_fd, FSCONFIG_SET_FLAG,
+				     "noswap", NULL, 0), "fsconfig noswap failed");
+
+	if (huge)
+		TEST_ASSERT(!syscall(__NR_fsconfig, fs_fd, FSCONFIG_SET_STRING,
+				     "huge", huge, 0), "fsconfig huge failed");
+
+	snprintf(size_str, sizeof(size_str), "%zu", size);
+	TEST_ASSERT(!syscall(__NR_fsconfig, fs_fd, FSCONFIG_SET_STRING,
+			     "size", size_str, 0), "fsconfig size failed");
+
+	TEST_ASSERT(!syscall(__NR_fsconfig, fs_fd, FSCONFIG_CMD_CREATE,
+			     NULL, NULL, 0), "fsconfig create failed");
+
+	mnt_fd = syscall(__NR_fsmount, fs_fd, FSMOUNT_CLOEXEC, 0);
+	TEST_ASSERT(mnt_fd > 0, "fsmount failed");
+
+	close(fs_fd);
+	return mnt_fd;
+}
+
+static void test_resource_fd_tmpfs_swap(struct kvm_vm *vm)
+{
+	int pool_fd, fd;
+
+	pool_fd = create_tmpfs_pool_fd("never", false, page_size);
+
+	fd = __vm_create_guest_memfd_resource(vm, page_size,
+					      GUEST_MEMFD_FLAG_USE_RESOURCE,
+					      pool_fd);
+	TEST_ASSERT(fd < 0, "guest_memfd with swap-enabled tmpfs should fail");
+	TEST_ASSERT_EQ(errno, EINVAL);
+
+	close(pool_fd);
+}
+
+static void test_resource_fd_tmpfs_huge(struct kvm_vm *vm)
+{
+	int pool_fd, fd;
+
+	pool_fd = create_tmpfs_pool_fd("always", true, page_size);
+
+	fd = __vm_create_guest_memfd_resource(vm, page_size,
+					      GUEST_MEMFD_FLAG_USE_RESOURCE,
+					      pool_fd);
+	TEST_ASSERT(fd < 0, "guest_memfd with huge-enabled tmpfs should fail");
+	TEST_ASSERT_EQ(errno, EINVAL);
+
+	close(pool_fd);
+}
+
+static void test_resource_fd_tmpfs_shared_pool(struct kvm_vm *vm)
+{
+	int pool_fd, fd1, fd2, ret;
+	struct statvfs svfs;
+	struct stat st1, st2;
+
+	pool_fd = create_tmpfs_pool_fd("never", true, page_size * 2);
+	if (pool_fd < 0)
+		TEST_REQUIRE(false);
+
+	fd1 = vm_create_guest_memfd_resource(vm, page_size,
+					     GUEST_MEMFD_FLAG_USE_RESOURCE,
+					     pool_fd);
+
+	fd2 = vm_create_guest_memfd_resource(vm, page_size * 2,
+					     GUEST_MEMFD_FLAG_USE_RESOURCE,
+					     pool_fd);
+
+	TEST_ASSERT(!fstat(fd1, &st1), "fstat on fd1 should succeed");
+	TEST_ASSERT(st1.st_size == page_size,
+		    "fd1 st_size (%lu) should match requested size (%lu)",
+		    st1.st_size, page_size);
+
+	TEST_ASSERT(!fstat(fd2, &st2), "fstat on fd2 should succeed");
+	TEST_ASSERT(st2.st_size == page_size * 2,
+		    "fd2 st_size (%lu) should match requested size (%lu)",
+		    st2.st_size, page_size * 2);
+
+	TEST_ASSERT(st1.st_ino != st2.st_ino,
+		    "different guest_memfd instances should have distinct inodes");
+
+	ret = fallocate(fd1, FALLOC_FL_KEEP_SIZE, 0, page_size);
+	TEST_ASSERT(!ret, "fallocate on fd1 should succeed");
+
+	TEST_ASSERT(!fstatvfs(pool_fd, &svfs), "fstatvfs on pool_fd should succeed");
+	TEST_ASSERT_EQ(svfs.f_blocks - svfs.f_bfree, 1);
+
+	ret = fallocate(fd2, FALLOC_FL_KEEP_SIZE, 0, page_size);
+	TEST_ASSERT(!ret, "fallocate on fd2 should succeed");
+
+	TEST_ASSERT(!fstatvfs(pool_fd, &svfs), "fstatvfs on pool_fd should succeed");
+	TEST_ASSERT_EQ(svfs.f_blocks - svfs.f_bfree, 2);
+
+	ret = fallocate(fd2, FALLOC_FL_KEEP_SIZE, page_size, page_size);
+	TEST_ASSERT(ret < 0, "fallocate exceeding pool capacity should fail");
+	TEST_ASSERT_EQ(errno, ENOSPC);
+
+	close(fd2);
+	close(fd1);
+	close(pool_fd);
+}
+
+enum gmem_pool_type {
+	GMEM_POOL_NONE,
+	GMEM_POOL_FSMOUNT,
+	GMEM_POOL_MOUNTED_DIR,
+};
+
+struct gmem_pool {
+	int fd;
+	char path[PATH_MAX];
+	bool is_mounted;
+};
+
+static struct gmem_pool create_gmem_pool(size_t size, enum gmem_pool_type type)
+{
+	struct gmem_pool pool = { .fd = -1, .is_mounted = false };
+	int mnt_fd;
+
+	if (type == GMEM_POOL_NONE)
+		return pool;
+
+	mnt_fd = create_tmpfs_pool_fd("never", true, size);
+	TEST_REQUIRE(mnt_fd >= 0);
+
+	if (type == GMEM_POOL_FSMOUNT) {
+		pool.fd = mnt_fd;
+		return pool;
+	}
+
+	strcpy(pool.path, "/tmp/gmem_test_dir_XXXXXX");
+	TEST_ASSERT(mkdtemp(pool.path), "mkdtemp failed");
+
+	if (syscall(__NR_move_mount, mnt_fd, "", AT_FDCWD, pool.path,
+		    MOVE_MOUNT_F_EMPTY_PATH)) {
+		close(mnt_fd);
+		rmdir(pool.path);
+		TEST_REQUIRE(false);
+	}
+	close(mnt_fd);
+
+	pool.fd = open(pool.path, O_RDONLY | O_DIRECTORY);
+	TEST_ASSERT(pool.fd >= 0, "open mounted tmpfs root failed");
+	pool.is_mounted = true;
+
+	return pool;
+}
+
+static void destroy_gmem_pool(struct gmem_pool *pool)
+{
+	if (pool->fd >= 0)
+		close(pool->fd);
+	if (pool->is_mounted) {
+		TEST_ASSERT(!umount(pool->path), "umount failed");
+		TEST_ASSERT(!rmdir(pool->path), "rmdir failed");
+	}
+}
+
+#define ____gmem_test(__test, __vm, __flags, __pool_type, __gmem_size, args...)	\
+do {										\
+	struct gmem_pool pool = { .fd = -1 };					\
+	int fd;									\
+										\
+	if ((__flags) & GUEST_MEMFD_FLAG_USE_RESOURCE) {			\
+		pool = create_gmem_pool(__gmem_size, __pool_type);		\
+		fd = vm_create_guest_memfd_resource(__vm, __gmem_size,		\
+						    __flags, pool.fd);		\
+	} else {								\
+		fd = vm_create_guest_memfd(__vm, __gmem_size, __flags);	\
+	}									\
+										\
+	test_##__test(args);							\
+	close(fd);								\
+	destroy_gmem_pool(&pool);						\
 } while (0)
 
-#define __gmem_test(__test, __vm, __flags, __gmem_size)			\
-	____gmem_test(__test, __vm, __flags, __gmem_size, fd, __gmem_size)
+#define __gmem_test(__test, __vm, __flags, __pool_type, __gmem_size)		\
+	____gmem_test(__test, __vm, __flags, __pool_type, __gmem_size, fd, __gmem_size)
 
-#define gmem_test(__test, __vm, __flags)				\
-	__gmem_test(__test, __vm, __flags, page_size * 4)
+#define gmem_test(__test, __vm, __flags, __pool_type)				\
+	__gmem_test(__test, __vm, __flags, __pool_type, page_size * 4)
 
-#define __gmem_test_vm(__test, __vm, __flags, __gmem_size)		\
-	____gmem_test(__test, __vm, __flags, __gmem_size, __vm, fd, __gmem_size)
+#define __gmem_test_vm(__test, __vm, __flags, __pool_type, __gmem_size)	\
+	____gmem_test(__test, __vm, __flags, __pool_type, __gmem_size, __vm, fd, __gmem_size)
 
-#define gmem_test_vm(__test, __vm, __flags)				\
-	__gmem_test_vm(__test, __vm, __flags, page_size * 4)
+#define gmem_test_vm(__test, __vm, __flags, __pool_type)			\
+	__gmem_test_vm(__test, __vm, __flags, __pool_type, page_size * 4)
 
-static void __test_guest_memfd(struct kvm_vm *vm, u64 flags)
+static void __test_guest_memfd(struct kvm_vm *vm, u64 flags,
+			       enum gmem_pool_type pool_type)
 {
 	test_create_guest_memfd_multiple(vm);
 	test_create_guest_memfd_invalid_sizes(vm, flags);
 
-	gmem_test(file_read_write, vm, flags);
+	gmem_test(file_read_write, vm, flags, pool_type);
 
 	if (flags & GUEST_MEMFD_FLAG_MMAP) {
 		if (flags & GUEST_MEMFD_FLAG_INIT_SHARED) {
 			size_t pmd_size = get_trans_hugepagesz();
 
-			gmem_test(mmap_supported, vm, flags);
-			gmem_test(fault_overflow, vm, flags);
-			gmem_test(numa_allocation, vm, flags);
-			__gmem_test(collapse, vm, flags, pmd_size);
+			gmem_test(mmap_supported, vm, flags, pool_type);
+			gmem_test(fault_overflow, vm, flags, pool_type);
+			gmem_test(numa_allocation, vm, flags, pool_type);
+			__gmem_test(collapse, vm, flags, pool_type, pmd_size);
 		} else {
-			gmem_test(fault_private, vm, flags);
+			gmem_test(fault_private, vm, flags, pool_type);
 		}
 
-		gmem_test(mmap_cow, vm, flags);
-		gmem_test(mbind, vm, flags);
+		gmem_test(mmap_cow, vm, flags, pool_type);
+		gmem_test(mbind, vm, flags, pool_type);
 	} else {
-		gmem_test(mmap_not_supported, vm, flags);
+		gmem_test(mmap_not_supported, vm, flags, pool_type);
 	}
 
-	gmem_test(file_size, vm, flags);
-	gmem_test(fallocate, vm, flags);
-	gmem_test(invalid_punch_hole, vm, flags);
-	gmem_test_vm(invalid_binding, vm, flags);
+	gmem_test(file_size, vm, flags, pool_type);
+	gmem_test(fallocate, vm, flags, pool_type);
+	gmem_test(invalid_punch_hole, vm, flags, pool_type);
+	gmem_test_vm(invalid_binding, vm, flags, pool_type);
 }
 
 static void test_guest_memfd(unsigned long vm_type)
@@ -476,16 +721,52 @@ static void test_guest_memfd(unsigned long vm_type)
 
 	test_guest_memfd_flags(vm);
 
-	__test_guest_memfd(vm, 0);
+	/*
+	 * TODO: Test that guest_memfd creation rejects a read-only tmpfs mount
+	 * (e.g. MOUNT_ATTR_RDONLY) with EROFS.
+	 *
+	 * TODO: Test that guest_memfd creation rejects an ID-mapped tmpfs mount
+	 * (e.g. MOUNT_ATTR_IDMAP) lacking write or execute/search permissions
+	 * with EACCES.
+	 *
+	 * TODO: Test mpol fallback to mount config.
+	 */
+	test_resource_fd_invalid(vm);
+	test_resource_fd_unsupported_fs(vm);
+	test_resource_fd_tmpfs_file(vm);
+	test_resource_fd_tmpfs_swap(vm);
+	test_resource_fd_tmpfs_huge(vm);
+	test_resource_fd_tmpfs_shared_pool(vm);
+
+	__test_guest_memfd(vm, 0, GMEM_POOL_NONE);
+	__test_guest_memfd(vm, GUEST_MEMFD_FLAG_USE_RESOURCE, GMEM_POOL_FSMOUNT);
+	__test_guest_memfd(vm, GUEST_MEMFD_FLAG_USE_RESOURCE, GMEM_POOL_MOUNTED_DIR);
 
 	flags = vm_check_cap(vm, KVM_CAP_GUEST_MEMFD_FLAGS);
-	if (flags & GUEST_MEMFD_FLAG_MMAP)
-		__test_guest_memfd(vm, GUEST_MEMFD_FLAG_MMAP);
+	if (flags & GUEST_MEMFD_FLAG_MMAP) {
+		__test_guest_memfd(vm, GUEST_MEMFD_FLAG_MMAP, GMEM_POOL_NONE);
+		__test_guest_memfd(vm, GUEST_MEMFD_FLAG_MMAP |
+				       GUEST_MEMFD_FLAG_USE_RESOURCE,
+				   GMEM_POOL_FSMOUNT);
+		__test_guest_memfd(vm, GUEST_MEMFD_FLAG_MMAP |
+				       GUEST_MEMFD_FLAG_USE_RESOURCE,
+				   GMEM_POOL_MOUNTED_DIR);
+	}
 
 	/* MMAP should always be supported if INIT_SHARED is supported. */
-	if (flags & GUEST_MEMFD_FLAG_INIT_SHARED)
+	if (flags & GUEST_MEMFD_FLAG_INIT_SHARED) {
+		__test_guest_memfd(vm, GUEST_MEMFD_FLAG_MMAP |
+				       GUEST_MEMFD_FLAG_INIT_SHARED,
+				   GMEM_POOL_NONE);
 		__test_guest_memfd(vm, GUEST_MEMFD_FLAG_MMAP |
-				       GUEST_MEMFD_FLAG_INIT_SHARED);
+				       GUEST_MEMFD_FLAG_INIT_SHARED |
+				       GUEST_MEMFD_FLAG_USE_RESOURCE,
+				   GMEM_POOL_FSMOUNT);
+		__test_guest_memfd(vm, GUEST_MEMFD_FLAG_MMAP |
+				       GUEST_MEMFD_FLAG_INIT_SHARED |
+				       GUEST_MEMFD_FLAG_USE_RESOURCE,
+				   GMEM_POOL_MOUNTED_DIR);
+	}
 
 	kvm_vm_free(vm);
 }
@@ -558,6 +839,62 @@ static void test_guest_memfd_guest(void)
 	kvm_vm_free(vm);
 }
 
+static void test_guest_memfd_guest_resource(void)
+{
+	const gpa_t gpa = SZ_4G;
+	const int slot = 1;
+	struct kvm_vcpu *vcpu;
+	struct kvm_vm *vm;
+	int pool_fd, fd, i;
+	size_t size;
+	u8 *mem;
+
+	if (!kvm_check_cap(KVM_CAP_GUEST_MEMFD_FLAGS))
+		return;
+
+	pool_fd = create_tmpfs_pool_fd("never", true, page_size);
+	if (pool_fd < 0)
+		TEST_REQUIRE(false);
+
+	vm = __vm_create_shape_with_one_vcpu(VM_SHAPE_DEFAULT, &vcpu, 1, guest_code);
+
+	TEST_ASSERT(vm_check_cap(vm, KVM_CAP_GUEST_MEMFD_FLAGS) & GUEST_MEMFD_FLAG_MMAP,
+		    "Default VM type should support MMAP, supported flags = 0x%x",
+		    vm_check_cap(vm, KVM_CAP_GUEST_MEMFD_FLAGS));
+	TEST_ASSERT(vm_check_cap(vm, KVM_CAP_GUEST_MEMFD_FLAGS) & GUEST_MEMFD_FLAG_INIT_SHARED,
+		    "Default VM type should support INIT_SHARED, supported flags = 0x%x",
+		    vm_check_cap(vm, KVM_CAP_GUEST_MEMFD_FLAGS));
+
+	size = max_t(size_t, vm->page_size, page_size);
+	fd = __vm_create_guest_memfd_resource(vm, size,
+					      GUEST_MEMFD_FLAG_MMAP |
+					      GUEST_MEMFD_FLAG_INIT_SHARED |
+					      GUEST_MEMFD_FLAG_USE_RESOURCE,
+					      pool_fd);
+	TEST_ASSERT(fd >= 0, "guest_memfd with tmpfs pool should succeed");
+
+	vm_set_user_memory_region2(vm, slot, KVM_MEM_GUEST_MEMFD, gpa, size, NULL, fd, 0);
+
+	mem = kvm_mmap(size, PROT_READ | PROT_WRITE, MAP_SHARED, fd);
+	memset(mem, 0xaa, size);
+	kvm_munmap(mem, size);
+
+	virt_map(vm, gpa, gpa, size / vm->page_size);
+	vcpu_args_set(vcpu, 2, gpa, size);
+	vcpu_run(vcpu);
+
+	TEST_ASSERT_EQ(get_ucall(vcpu, NULL), UCALL_DONE);
+
+	mem = kvm_mmap(size, PROT_READ | PROT_WRITE, MAP_SHARED, fd);
+	for (i = 0; i < size; i++)
+		TEST_ASSERT_EQ(mem[i], 0xff);
+
+	close(fd);
+	close(pool_fd);
+	kvm_vm_free(vm);
+}
+
+
 int main(int argc, char *argv[])
 {
 	unsigned long vm_types, vm_type;
@@ -578,4 +915,5 @@ int main(int argc, char *argv[])
 		test_guest_memfd(vm_type);
 
 	test_guest_memfd_guest();
+	test_guest_memfd_guest_resource();
 }
diff --git a/tools/testing/selftests/kvm/include/kvm_util.h b/tools/testing/selftests/kvm/include/kvm_util.h
index 777fa3dbf88d6..9eeaefe5a1839 100644
--- a/tools/testing/selftests/kvm/include/kvm_util.h
+++ b/tools/testing/selftests/kvm/include/kvm_util.h
@@ -767,26 +767,39 @@ static inline bool is_smt_on(void)
 
 void vm_create_irqchip(struct kvm_vm *vm);
 
-static inline int __vm_create_guest_memfd(struct kvm_vm *vm, u64 size,
-					  u64 flags)
+static inline int __vm_create_guest_memfd_resource(struct kvm_vm *vm, u64 size,
+						   u64 flags, int resource_fd)
 {
 	struct kvm_create_guest_memfd guest_memfd = {
 		.size = size,
 		.flags = flags,
+		.resource_fd = resource_fd,
 	};
 
 	return __vm_ioctl(vm, KVM_CREATE_GUEST_MEMFD, &guest_memfd);
 }
 
-static inline int vm_create_guest_memfd(struct kvm_vm *vm, u64 size,
-					u64 flags)
+static inline int __vm_create_guest_memfd(struct kvm_vm *vm, u64 size,
+					  u64 flags)
+{
+	return __vm_create_guest_memfd_resource(vm, size, flags, 0);
+}
+
+static inline int vm_create_guest_memfd_resource(struct kvm_vm *vm, u64 size,
+						 u64 flags, int resource_fd)
 {
-	int fd = __vm_create_guest_memfd(vm, size, flags);
+	int fd = __vm_create_guest_memfd_resource(vm, size, flags, resource_fd);
 
 	TEST_ASSERT(fd >= 0, KVM_IOCTL_ERROR(KVM_CREATE_GUEST_MEMFD, fd));
 	return fd;
 }
 
+static inline int vm_create_guest_memfd(struct kvm_vm *vm, u64 size,
+					u64 flags)
+{
+	return vm_create_guest_memfd_resource(vm, size, flags, 0);
+}
+
 void vm_set_user_memory_region(struct kvm_vm *vm, u32 slot, u32 flags,
 			       gpa_t gpa, u64 size, void *hva);
 int __vm_set_user_memory_region(struct kvm_vm *vm, u32 slot, u32 flags,
diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index a13445c26d9d6..0e0d7e93f4148 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -3,6 +3,7 @@
 #include <linux/backing-dev.h>
 #include <linux/falloc.h>
 #include <linux/fs.h>
+#include <linux/guest_memfd.h>
 #include <linux/kvm_host.h>
 #include <linux/maple_tree.h>
 #include <linux/mempolicy.h>
@@ -35,6 +36,9 @@ struct gmem_inode {
 	struct inode vfs_inode;
 	struct list_head gmem_file_list;
 
+	void *provider;
+	const struct guest_memfd_provider_operations *provider_ops;
+
 	u64 flags;
 	/*
 	 * Every index in this inode, whether memory is populated or
@@ -50,6 +54,29 @@ static __always_inline struct gmem_inode *GMEM_I(struct inode *inode)
 	return container_of(inode, struct gmem_inode, vfs_inode);
 }
 
+static inline struct folio *gmem_provider_alloc_folio(struct gmem_inode *gi,
+						      pgoff_t index,
+						      struct mempolicy *mpol)
+{
+	if (!gi->provider_ops || !gi->provider_ops->alloc_folio)
+		return ERR_PTR(-EOPNOTSUPP);
+
+	return gi->provider_ops->alloc_folio(gi->provider, index, mpol);
+}
+
+static inline void gmem_provider_invalidate_folio(struct gmem_inode *gi,
+						  struct folio *folio)
+{
+	if (gi->provider_ops && gi->provider_ops->invalidate_folio)
+		gi->provider_ops->invalidate_folio(gi->provider, folio);
+}
+
+static inline void gmem_provider_release(struct gmem_inode *gi)
+{
+	if (gi->provider_ops && gi->provider_ops->release)
+		gi->provider_ops->release(gi->provider);
+}
+
 #define kvm_gmem_for_each_file(f, inode) \
 	list_for_each_entry(f, &GMEM_I(inode)->gmem_file_list, entry)
 
@@ -128,6 +155,7 @@ static bool kvm_gmem_range_has_attributes(struct inode *inode,
  */
 static struct folio *kvm_gmem_get_folio(struct inode *inode, pgoff_t index)
 {
+	struct gmem_inode *gi = GMEM_I(inode);
 	/* TODO: Support huge pages. */
 	struct mempolicy *policy;
 	struct folio *folio;
@@ -140,10 +168,30 @@ static struct folio *kvm_gmem_get_folio(struct inode *inode, pgoff_t index)
 	if (!IS_ERR(folio))
 		return folio;
 
-	policy = mpol_shared_policy_lookup(&GMEM_I(inode)->policy, index);
-	folio = __filemap_get_folio_mpol(inode->i_mapping, index,
-					 FGP_LOCK | FGP_CREAT,
-					 mapping_gfp_mask(inode->i_mapping), policy);
+	policy = mpol_shared_policy_lookup(&gi->policy, index);
+	/*
+	 * TODO: Refactor the internal PAGE_SIZE gmem could be an internal
+	 * provider with internal provider_ops.
+	 */
+	if (gi->provider_ops) {
+		folio = gmem_provider_alloc_folio(gi, index, policy);
+		if (!IS_ERR(folio)) {
+			int r = filemap_add_folio(inode->i_mapping, folio,
+						  index, GFP_KERNEL);
+			if (r) {
+				gmem_provider_invalidate_folio(gi, folio);
+				folio_put(folio);
+				folio = ERR_PTR(r);
+			} else {
+				folio_mark_accessed(folio);
+			}
+		}
+	} else {
+		folio = __filemap_get_folio_mpol(inode->i_mapping, index,
+						 FGP_LOCK | FGP_CREAT,
+						 mapping_gfp_mask(inode->i_mapping),
+						 policy);
+	}
 	mpol_cond_put(policy);
 
 	/*
@@ -859,10 +907,24 @@ static void kvm_gmem_free_folio(struct folio *folio)
 }
 #endif
 
+static void kvm_gmem_invalidate_folio(struct folio *folio, size_t offset,
+				      size_t len)
+{
+	struct inode *inode = folio->mapping->host;
+	struct gmem_inode *gi = GMEM_I(inode);
+
+	/* guest_memfd only allows truncating full folios. */
+	if (WARN_ON_ONCE(offset != 0 || len != folio_size(folio)))
+		return;
+
+	gmem_provider_invalidate_folio(gi, folio);
+}
+
 static const struct address_space_operations kvm_gmem_aops = {
 	.dirty_folio = noop_dirty_folio,
 	.migrate_folio	= kvm_gmem_migrate_folio,
 	.error_remove_folio = kvm_gmem_error_folio,
+	.invalidate_folio = kvm_gmem_invalidate_folio,
 #ifdef CONFIG_HAVE_KVM_ARCH_GMEM_RECLAIM
 	.free_folio = kvm_gmem_free_folio,
 #endif
@@ -901,6 +963,7 @@ static int kvm_gmem_init_inode(struct inode *inode, loff_t size, u64 flags)
 	 */
 	mapping_set_inaccessible(inode->i_mapping);
 	WARN_ON_ONCE(!mapping_unevictable(inode->i_mapping));
+	mapping_set_release_always(inode->i_mapping);
 
 	gi->flags = flags;
 
@@ -927,7 +990,37 @@ static int kvm_gmem_init_inode(struct inode *inode, loff_t size, u64 flags)
 	return r;
 }
 
-static int __kvm_gmem_create(struct kvm *kvm, loff_t size, u64 flags)
+static int kvm_gmem_attach_resource(struct inode *inode, int resource_fd)
+{
+	struct file *resource_file;
+	struct inode *res_inode;
+	const struct guest_memfd_provider_operations *ops = NULL;
+	void *provider;
+
+	resource_file = fget_raw(resource_fd);
+	if (!resource_file)
+		return -EBADF;
+
+	res_inode = file_inode(resource_file);
+	ops = res_inode->i_sb->s_op->gmem_provider_ops;
+
+	if (!ops || !ops->attach || !ops->alloc_folio) {
+		fput(resource_file);
+		return -EOPNOTSUPP;
+	}
+
+	provider = ops->attach(resource_file);
+	fput(resource_file);
+	if (IS_ERR(provider))
+		return PTR_ERR(provider);
+
+	GMEM_I(inode)->provider = provider;
+	GMEM_I(inode)->provider_ops = ops;
+
+	return 0;
+}
+
+static int __kvm_gmem_create(struct kvm *kvm, loff_t size, u64 flags, int resource_fd)
 {
 	static const char *name = "[kvm-gmem]";
 	struct gmem_file *f;
@@ -961,6 +1054,12 @@ static int __kvm_gmem_create(struct kvm *kvm, loff_t size, u64 flags)
 	if (err)
 		goto err_inode;
 
+	if (flags & GUEST_MEMFD_FLAG_USE_RESOURCE) {
+		err = kvm_gmem_attach_resource(inode, resource_fd);
+		if (err)
+			goto err_inode;
+	}
+
 	file = alloc_file_pseudo(inode, kvm_gmem_mnt, name, O_RDWR, &kvm_gmem_fops);
 	if (IS_ERR(file)) {
 		err = PTR_ERR(file);
@@ -1000,7 +1099,10 @@ int kvm_gmem_create(struct kvm *kvm, struct kvm_create_guest_memfd *args)
 	if (size <= 0 || !PAGE_ALIGNED(size))
 		return -EINVAL;
 
-	return __kvm_gmem_create(kvm, size, flags);
+	if (!(flags & GUEST_MEMFD_FLAG_USE_RESOURCE) && args->resource_fd)
+		return -EINVAL;
+
+	return __kvm_gmem_create(kvm, size, flags, args->resource_fd);
 }
 
 int kvm_gmem_prepare_memory_region(struct kvm *kvm, struct kvm_memory_slot *slot,
@@ -1334,6 +1436,8 @@ static struct inode *kvm_gmem_alloc_inode(struct super_block *sb)
 	mt_init_flags(&gi->attributes, MT_FLAGS_LOCK_EXTERN | MT_FLAGS_USE_RCU);
 
 	gi->flags = 0;
+	gi->provider = NULL;
+	gi->provider_ops = NULL;
 	INIT_LIST_HEAD(&gi->gmem_file_list);
 	return &gi->vfs_inode;
 }
@@ -1342,6 +1446,8 @@ static void kvm_gmem_destroy_inode(struct inode *inode)
 {
 	struct gmem_inode *gi = GMEM_I(inode);
 
+	gmem_provider_release(gi);
+
 	mpol_free_shared_policy(&gi->policy);
 
 	/*

29/3 2026/09/27 17:53 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 15654
							output: 24
							thoughts: 389
						

Thoughts:
**Analyzing the Patch for KMSAN Implications**

Okay, let's break this down. My task is to figure out whether this patch warrants a dedicated KMSAN fuzzing session, beyond the standard KASAN fuzzing. The key here is: does this change introduce or reveal *uninitialized memory* risks that KMSAN, and *only* KMSAN, would catch? If so, we set `NeedsKMSAN=true`; otherwise, `NeedsKMSAN=false`.

Alright, first, I need to understand what changed. The core modification seems to be within the `struct kvm_create_guest_memfd`, specifically in `include/uapi/linux/kvm.h`. The `reserved[6]` field is now replaced by `resource_fd`, `pad`, and `reserved[5]`. The size of the struct hasn't changed. A quick calculation confirms that the total size in bytes remains 48: `resource_fd` (4 bytes) + `pad` (4 bytes) + `reserved[5]` (40 bytes). The alignment remains the same.

Now, the crucial part: how is this struct *used*? I need to understand where `kvm_create_guest_memfd` is employed within the kernel's code. I'll need to trace the usage of this struct and the function `kvm_gmem_create`. Let's use grep to locate all calls to `kvm_gmem_create` and all references to `KVM_CREATE_GUEST_MEMFD`. This will expose how arguments are passed, copied, and handled. I'm looking for any indication of uninitialized memory being read or used in a way that KMSAN would highlight, but KASAN might miss.



30/3 2026/09/27 17:53 tool grepper
0m Args:
{
  "Expression": "KVM_CREATE_GUEST_MEMFD"
}

Results:
{
  "Output": "Documentation/virt/kvm/api.rst=6357=in flags to have KVM bind the memory region to a given guest_memfd range of\nDocumentation/virt/kvm/api.rst-6358-[guest_memfd_offset, guest_memfd_offset + memory_size].  The target guest_memfd\nDocumentation/virt/kvm/api.rst:6359:must point at a file created via KVM_CREATE_GUEST_MEMFD on the current VM, and\nDocumentation/virt/kvm/api.rst-6360-the target range must not be bound to any other memory region.  All standard\n--\nDocumentation/virt/kvm/api.rst=6386=shared vs. private based on the state in guest_memfd, which is the sole\nDocumentation/virt/kvm/api.rst:6387:authority on private vs. shared memory.  See :ref:`KVM_CREATE_GUEST_MEMFD` to\nDocumentation/virt/kvm/api.rst-6388-find out more about the creation-time shared/private status.  Userspace can\n--\nDocumentation/virt/kvm/api.rst=6447=See also: :ref:`KVM_SET_MEMORY_ATTRIBUTES2`.\nDocumentation/virt/kvm/api.rst-6448-\nDocumentation/virt/kvm/api.rst:6449:.. _KVM_CREATE_GUEST_MEMFD:\nDocumentation/virt/kvm/api.rst-6450-\nDocumentation/virt/kvm/api.rst:6451:4.142 KVM_CREATE_GUEST_MEMFD\nDocumentation/virt/kvm/api.rst-6452-----------------------------\n--\nDocumentation/virt/kvm/api.rst-6459-\nDocumentation/virt/kvm/api.rst:6460:KVM_CREATE_GUEST_MEMFD creates an anonymous file and returns a file descriptor\nDocumentation/virt/kvm/api.rst-6461-that refers to it.  guest_memfd files are roughly analogous to files created\n--\nDocumentation/virt/kvm/api.rst=6494=The capability KVM_CAP_GUEST_MEMFD_FLAGS enumerates the `flags` that can be\nDocumentation/virt/kvm/api.rst:6495:specified via KVM_CREATE_GUEST_MEMFD.  Currently defined flags:\nDocumentation/virt/kvm/api.rst-6496-\n--\nDocumentation/virt/kvm/api.rst-6500-  GUEST_MEMFD_FLAG_INIT_SHARED  Make all memory in the file shared during\nDocumentation/virt/kvm/api.rst:6501:                                KVM_CREATE_GUEST_MEMFD (memory files created\nDocumentation/virt/kvm/api.rst-6502-                                without INIT_SHARED will be marked private).\n--\narch/x86/kvm/Kconfig=95=config KVM_SW_PROTECTED_VM\n--\narch/x86/kvm/Kconfig-101-\t  protected VMs are purely a development and testing vehicle for\narch/x86/kvm/Kconfig:102:\t  KVM_CREATE_GUEST_MEMFD.  Attempting to run a \"real\" VM workload as a\narch/x86/kvm/Kconfig-103-\t  software-protected VM will fail miserably.\n--\ninclude/uapi/linux/kvm.h=1660=struct kvm_memory_attributes2 {\n--\ninclude/uapi/linux/kvm.h-1673-\ninclude/uapi/linux/kvm.h:1674:#define KVM_CREATE_GUEST_MEMFD\t_IOWR(KVMIO,  0xd4, struct kvm_create_guest_memfd)\ninclude/uapi/linux/kvm.h-1675-#define GUEST_MEMFD_FLAG_MMAP\t\t(1ULL \u003c\u003c 0)\n--\ntools/include/uapi/linux/kvm.h=1645=struct kvm_memory_attributes {\n--\ntools/include/uapi/linux/kvm.h-1653-\ntools/include/uapi/linux/kvm.h:1654:#define KVM_CREATE_GUEST_MEMFD\t_IOWR(KVMIO,  0xd4, struct kvm_create_guest_memfd)\ntools/include/uapi/linux/kvm.h-1655-#define GUEST_MEMFD_FLAG_MMAP\t\t(1ULL \u003c\u003c 0)\n--\ntools/testing/selftests/kvm/include/kvm_util.h=770=static inline int __vm_create_guest_memfd_resource(struct kvm_vm *vm, u64 size,\n--\ntools/testing/selftests/kvm/include/kvm_util.h-778-\ntools/testing/selftests/kvm/include/kvm_util.h:779:\treturn __vm_ioctl(vm, KVM_CREATE_GUEST_MEMFD, \u0026guest_memfd);\ntools/testing/selftests/kvm/include/kvm_util.h-780-}\n--\ntools/testing/selftests/kvm/include/kvm_util.h=788=static inline int vm_create_guest_memfd_resource(struct kvm_vm *vm, u64 size,\n--\ntools/testing/selftests/kvm/include/kvm_util.h-792-\ntools/testing/selftests/kvm/include/kvm_util.h:793:\tTEST_ASSERT(fd \u003e= 0, KVM_IOCTL_ERROR(KVM_CREATE_GUEST_MEMFD, fd));\ntools/testing/selftests/kvm/include/kvm_util.h-794-\treturn fd;\n--\nvirt/kvm/kvm_main.c=5225=static long kvm_vm_ioctl(struct file *filp,\n--\nvirt/kvm/kvm_main.c-5445-#ifdef CONFIG_KVM_GUEST_MEMFD\nvirt/kvm/kvm_main.c:5446:\tcase KVM_CREATE_GUEST_MEMFD: {\nvirt/kvm/kvm_main.c-5447-\t\tstruct kvm_create_guest_memfd guest_memfd;\n"
}

31/3 2026/09/27 17:53 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 18024
							output: 40
							thoughts: 25
						

Thoughts:
Here's a first-person summary, tailored for an expert audience, as requested:

**Diving into KVM's Core**

Okay, time to get my hands dirty. Let's zero in on `virt/kvm/kvm_main.c`, specifically around line 5445. I want to see what's happening in that area, likely related to core KVM functionality. I'm probably looking at something crucial, maybe related to VM entry/exit, interrupt handling, or even a specific hypercall. I need to refresh my memory on what was being worked on or debugged related to kvm_main in the last few weeks. This specific location warrants a deep dive.



32/3 2026/09/27 17:54 tool read-file
0m Args:
{
  "File": "virt/kvm/kvm_main.c",
  "FirstLine": 5440,
  "LineCount": 25
}

Results:
{
  "Contents": "5440:\t\t\tr = kvm_vm_ioctl_reset_dirty_pages(kvm);\n5441:\t\t\tbreak;\n5442:\t\tcase KVM_GET_STATS_FD:\n5443:\t\t\tr = kvm_vm_ioctl_get_stats_fd(kvm);\n5444:\t\t\tbreak;\n5445:\t#ifdef CONFIG_KVM_GUEST_MEMFD\n5446:\t\tcase KVM_CREATE_GUEST_MEMFD: {\n5447:\t\t\tstruct kvm_create_guest_memfd guest_memfd;\n5448:\t\n5449:\t\t\tr = -EFAULT;\n5450:\t\t\tif (copy_from_user(\u0026guest_memfd, argp, sizeof(guest_memfd)))\n5451:\t\t\t\tgoto out;\n5452:\t\n5453:\t\t\tr = kvm_gmem_create(kvm, \u0026guest_memfd);\n5454:\t\t\tbreak;\n5455:\t\t}\n5456:\t#endif\n5457:\t\tdefault:\n5458:\t\t\tr = kvm_arch_vm_ioctl(filp, ioctl, arg);\n5459:\t\t}\n5460:\tout:\n5461:\t\treturn r;\n5462:\t}\n5463:\t\n5464:\t#ifdef CONFIG_KVM_COMPAT\n"
}

33/3 2026/09/27 17:54 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 18610
							output: 40
							thoughts: 171
						

Thoughts:
**Analyzing the KVM Guest Memory File Descriptor Creation**

Okay, so I'm looking closely at how `KVM_CREATE_GUEST_MEMFD` is handled. Specifically, it seems to involve a `copy_from_user` operation to get the `guest_memfd` structure from userspace. However, it's immediately clear that *nothing* is copied back to userspace after the call to `kvm_gmem_create`. It seems the kernel only returns the file descriptor (`r`) obtained, likely from `__kvm_gmem_create` which leads to `get_unused_fd_flags(O_CLOEXEC)`. That's important; it implies the userspace process is responsible for the file descriptor lifecycle, which is consistent with the standard file descriptor paradigm. No data transfer.

I need to understand what `kvm_gmem_create` is actually *doing* with that `guest_memfd` structure. This is where the real interesting stuff must be happening. Time to dive into `virt/kvm/guest_memfd.c` and zero in on that `kvm_gmem_create` function. I have to see how it's setting up the underlying structures and memory mappings. This is where the magic happens, and what will determine the true behavior and any implications of this.



34/3 2026/09/27 17:54 tool read-file
0m Args:
{
  "File": "virt/kvm/guest_memfd.c",
  "FirstLine": 1085,
  "LineCount": 35
}

Results:
{
  "Contents": "1085:\t\tkfree(f);\n1086:\terr_fd:\n1087:\t\tput_unused_fd(fd);\n1088:\t\treturn err;\n1089:\t}\n1090:\t\n1091:\tint kvm_gmem_create(struct kvm *kvm, struct kvm_create_guest_memfd *args)\n1092:\t{\n1093:\t\tloff_t size = args-\u003esize;\n1094:\t\tu64 flags = args-\u003eflags;\n1095:\t\n1096:\t\tif (flags \u0026 ~kvm_gmem_get_supported_flags(kvm))\n1097:\t\t\treturn -EINVAL;\n1098:\t\n1099:\t\tif (size \u003c= 0 || !PAGE_ALIGNED(size))\n1100:\t\t\treturn -EINVAL;\n1101:\t\n1102:\t\tif (!(flags \u0026 GUEST_MEMFD_FLAG_USE_RESOURCE) \u0026\u0026 args-\u003eresource_fd)\n1103:\t\t\treturn -EINVAL;\n1104:\t\n1105:\t\treturn __kvm_gmem_create(kvm, size, flags, args-\u003eresource_fd);\n1106:\t}\n1107:\t\n1108:\tint kvm_gmem_prepare_memory_region(struct kvm *kvm, struct kvm_memory_slot *slot,\n1109:\t\t\t\t\t   unsigned int fd, uoff_t offset)\n1110:\t{\n1111:\t\tuoff_t size = slot-\u003enpages \u003c\u003c PAGE_SHIFT;\n1112:\t\tstruct gmem_file *f;\n1113:\t\tstruct inode *inode;\n1114:\t\tstruct file *file;\n1115:\t\n1116:\t\n1117:\t\tBUILD_BUG_ON(sizeof(gpa_t) != sizeof(offset));\n1118:\t\tBUILD_BUG_ON(sizeof(gfn_t) != sizeof(slot-\u003egmem.pgoff));\n1119:\t\n"
}

35/3 2026/09/27 17:54 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 19649
							output: 40
							thoughts: 34
						

Thoughts:
Okay, let's dive into this. Here's how I'd approach this diff, focusing on `virt/kvm/guest_memfd.c`:

**Focusing on Guest MemFD Creation in KVM**

My immediate instinct is to home in on `__kvm_gmem_create`. It's the core of this, isn't it? The other parts of the diff are likely related to this function or use it. My priority is to understand how memory file descriptors are being created within the KVM virtual machine guest context, likely through this function. Are there any assumptions being made? Any potential race conditions? How does this integrate with the larger memory management strategy? I also need to understand how other parts of the diff are interacting to make these changes. Are there any implications for security or performance? I'll probably also need to consider any potential issues. Understanding the use and impact of `guest_memfd.c` is the primary task right now, as `__kvm_gmem_create` is most likely at the heart of the change.



36/3 2026/09/27 17:54 tool read-file
0m Args:
{
  "File": "virt/kvm/guest_memfd.c",
  "FirstLine": 1040,
  "LineCount": 55
}

Results:
{
  "Contents": "1040:\t\n1041:\t\t/* __fput() will take care of fops_put(). */\n1042:\t\tif (!fops_get(\u0026kvm_gmem_fops)) {\n1043:\t\t\terr = -ENOENT;\n1044:\t\t\tgoto err_gmem;\n1045:\t\t}\n1046:\t\n1047:\t\tinode = anon_inode_make_secure_inode(kvm_gmem_mnt-\u003emnt_sb, name, NULL);\n1048:\t\tif (IS_ERR(inode)) {\n1049:\t\t\terr = PTR_ERR(inode);\n1050:\t\t\tgoto err_fops;\n1051:\t\t}\n1052:\t\n1053:\t\terr = kvm_gmem_init_inode(inode, size, flags);\n1054:\t\tif (err)\n1055:\t\t\tgoto err_inode;\n1056:\t\n1057:\t\tif (flags \u0026 GUEST_MEMFD_FLAG_USE_RESOURCE) {\n1058:\t\t\terr = kvm_gmem_attach_resource(inode, resource_fd);\n1059:\t\t\tif (err)\n1060:\t\t\t\tgoto err_inode;\n1061:\t\t}\n1062:\t\n1063:\t\tfile = alloc_file_pseudo(inode, kvm_gmem_mnt, name, O_RDWR, \u0026kvm_gmem_fops);\n1064:\t\tif (IS_ERR(file)) {\n1065:\t\t\terr = PTR_ERR(file);\n1066:\t\t\tgoto err_inode;\n1067:\t\t}\n1068:\t\n1069:\t\tfile-\u003ef_flags |= O_LARGEFILE;\n1070:\t\tfile-\u003eprivate_data = f;\n1071:\t\n1072:\t\tkvm_get_kvm(kvm);\n1073:\t\tf-\u003ekvm = kvm;\n1074:\t\txa_init(\u0026f-\u003ebindings);\n1075:\t\tlist_add(\u0026f-\u003eentry, \u0026GMEM_I(inode)-\u003egmem_file_list);\n1076:\t\n1077:\t\tfd_install(fd, file);\n1078:\t\treturn fd;\n1079:\t\n1080:\terr_inode:\n1081:\t\tiput(inode);\n1082:\terr_fops:\n1083:\t\tfops_put(\u0026kvm_gmem_fops);\n1084:\terr_gmem:\n1085:\t\tkfree(f);\n1086:\terr_fd:\n1087:\t\tput_unused_fd(fd);\n1088:\t\treturn err;\n1089:\t}\n1090:\t\n1091:\tint kvm_gmem_create(struct kvm *kvm, struct kvm_create_guest_memfd *args)\n1092:\t{\n1093:\t\tloff_t size = args-\u003esize;\n1094:\t\tu64 flags = args-\u003eflags;\n"
}

37/3 2026/09/27 17:54 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 20757
							output: 33
							thoughts: 1320
						

Thoughts:
**Analyzing `mm/shmem.c` for `CONFIG_KVM_GUEST_MEMFD` Changes**

Okay, let's dive into this patch concerning `mm/shmem.c` and specifically the `CONFIG_KVM_GUEST_MEMFD` section. I need to understand how the new `guest_memfd` functionality integrates with shared memory (shmem). I'm seeing the addition of a `shmem_gmem_*` set of functions which appear to provide shmem backing to the guest memfd.

First, I'm noting `shmem_gmem_attach`, `shmem_gmem_release`, `shmem_gmem_alloc_folio` and `shmem_gmem_invalidate_folio`. It appears to be establishing the interface for guest access to shmem. There is also a `shmem_gmem_provider_ops` which are then registered to the `guest_memfd` framework. Good. Let's see what each function does.

`shmem_gmem_attach` is about attaching a shmem resource to guest memfd. I see it checks for the mountpoint and performs permission checks. It also enforces `noswap` and rejects `huge` pages, presumably because those are not yet supported or cause issues. Makes sense.

`shmem_gmem_release` is straightforward – it simply puts the mount.

Now, `shmem_gmem_alloc_folio` is where it gets interesting, as it appears to implement the folio allocation. It checks for resource limits (`max_blocks`), gets the memory policy, and then calls `folio_alloc_mpol` or `folio_alloc`. Hmm, the allocation path looks pretty standard but I need to pay close attention to the `GFP` flags. The comment does note that this should be refactored with the internal shmem allocation. Let me get back to that.

The next thought I had was about the `folio` allocation, and I immediately wonder about zeroing the memory. *Is* `GFP_HIGHUSER_MOVABLE` being zeroed? I don't see `__GFP_ZERO` being passed, which is a key flag for that. I think this might be a subtle but critical detail, as uninitialized memory can leak sensitive information. This needs further scrutiny.

Now I need to contrast what the previous code did: `__filemap_get_folio_mpol`. What was `mapping_gfp_mask(inode->i_mapping)` set to for `guest_memfd`? Let's check `guest_memfd.c` to see how the mapping is set up. This is a potential area for subtle differences in behavior or security implications.



38/3 2026/09/27 17:55 tool grepper
0m Args:
{
  "Expression": "mapping_set_gfp_mask",
  "PathPrefix": "virt/kvm/"
}

Results:
{
  "Output": "virt/kvm/guest_memfd.c=947=static int kvm_gmem_init_inode(struct inode *inode, loff_t size, u64 flags)\n--\nvirt/kvm/guest_memfd.c-957-\tinode-\u003ei_size = size;\nvirt/kvm/guest_memfd.c:958:\tmapping_set_gfp_mask(inode-\u003ei_mapping, GFP_HIGHUSER);\nvirt/kvm/guest_memfd.c-959-\n"
}

39/3 2026/09/27 17:55 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 22830
							output: 39
							thoughts: 68
						

Thoughts:
Here's my analysis of what's going on:

**Kernel Memory Allocation and Folio Management - A Quick Peek**

Alright, let's break this down. First, I'm seeing the `mapping_set_gfp_mask(inode->i_mapping, GFP_HIGHUSER)` call. My initial thought is this is setting the GFP mask for an inode's mapping. Okay, nothing immediately groundbreaking there, but worth noting the *HIGHUSER* flag. We're likely dealing with memory that's accessible from user space, at least potentially.

Now, the interesting bit: I need to quickly refresh on how `kvm_gmem_get_folio` initializes a folio, since that seems related to the memory allocation. That means I need to dig into `kvm_gmem_get_folio`'s implementation to see how it's setting up the folio, especially with `GFP_HIGHUSER` implications. Is it directly allocating pages? Using a mempool? Are there any special considerations for KVM guest memory management here, such as the use of huge pages or specific NUMA node affinity? Getting that detail is key to fully understanding the context of that `mapping_set_gfp_mask` call.



40/3 2026/09/27 17:55 tool read-file
0m Args:
{
  "File": "virt/kvm/guest_memfd.c",
  "FirstLine": 165,
  "LineCount": 50
}

Results:
{
  "Contents": " 165:\t\t * policy_lookup.\n 166:\t\t */\n 167:\t\tfolio = filemap_lock_folio(inode-\u003ei_mapping, index);\n 168:\t\tif (!IS_ERR(folio))\n 169:\t\t\treturn folio;\n 170:\t\n 171:\t\tpolicy = mpol_shared_policy_lookup(\u0026gi-\u003epolicy, index);\n 172:\t\t/*\n 173:\t\t * TODO: Refactor the internal PAGE_SIZE gmem could be an internal\n 174:\t\t * provider with internal provider_ops.\n 175:\t\t */\n 176:\t\tif (gi-\u003eprovider_ops) {\n 177:\t\t\tfolio = gmem_provider_alloc_folio(gi, index, policy);\n 178:\t\t\tif (!IS_ERR(folio)) {\n 179:\t\t\t\tint r = filemap_add_folio(inode-\u003ei_mapping, folio,\n 180:\t\t\t\t\t\t\t  index, GFP_KERNEL);\n 181:\t\t\t\tif (r) {\n 182:\t\t\t\t\tgmem_provider_invalidate_folio(gi, folio);\n 183:\t\t\t\t\tfolio_put(folio);\n 184:\t\t\t\t\tfolio = ERR_PTR(r);\n 185:\t\t\t\t} else {\n 186:\t\t\t\t\tfolio_mark_accessed(folio);\n 187:\t\t\t\t}\n 188:\t\t\t}\n 189:\t\t} else {\n 190:\t\t\tfolio = __filemap_get_folio_mpol(inode-\u003ei_mapping, index,\n 191:\t\t\t\t\t\t\t FGP_LOCK | FGP_CREAT,\n 192:\t\t\t\t\t\t\t mapping_gfp_mask(inode-\u003ei_mapping),\n 193:\t\t\t\t\t\t\t policy);\n 194:\t\t}\n 195:\t\tmpol_cond_put(policy);\n 196:\t\n 197:\t\t/*\n 198:\t\t * External interfaces like kvm_gmem_get_pfn() support dealing\n 199:\t\t * with hugepages to a degree, but internally, guest_memfd currently\n 200:\t\t * assumes that all folios are order-0 and handling would need\n 201:\t\t * to be updated for anything otherwise (e.g. page-clearing\n 202:\t\t * operations).\n 203:\t\t */\n 204:\t\tWARN_ON_ONCE(!IS_ERR(folio) \u0026\u0026 folio_order(folio));\n 205:\t\n 206:\t\treturn folio;\n 207:\t}\n 208:\t\n 209:\tstatic enum kvm_gfn_range_filter kvm_gmem_get_all_gfns_filter(struct inode *inode)\n 210:\t{\n 211:\t\tif (gmem_in_place_conversion)\n 212:\t\t\treturn KVM_FILTER_SHARED | KVM_FILTER_PRIVATE;\n 213:\t\n 214:\t\tif (GMEM_I(inode)-\u003eflags \u0026 GUEST_MEMFD_FLAG_INIT_SHARED)\n"
}

41/3 2026/09/27 17:55 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 23977
							output: 34
							thoughts: 33
						

Thoughts:
Okay, here's my take on those thoughts, framed as an expert's internal monologue:

**Investigating `kvm_gmem_get_folio` Usage**

Alright, I'm diving into this `kvm_gmem_get_folio` function. I need to understand its role within the kernel's memory management for KVM. My immediate question is, where is this function actually being *used*? I need to find its callers. Knowing the calling contexts will give me vital clues about its purpose and how it fits into the bigger picture. Time to fire up the tools and trace those calls! Let's see what the code reveals. This will be key to understanding the allocation strategies, potential contention points, and any optimization opportunities within that area of the KVM code.



42/3 2026/09/27 17:55 tool grepper
0m Args:
{
  "Expression": "kvm_gmem_get_folio",
  "PathPrefix": "virt/kvm/"
}

Results:
{
  "Output": "virt/kvm/guest_memfd.c=129=static bool kvm_gmem_range_has_attributes(struct inode *inode,\n--\nvirt/kvm/guest_memfd.c-155- */\nvirt/kvm/guest_memfd.c:156:static struct folio *kvm_gmem_get_folio(struct inode *inode, pgoff_t index)\nvirt/kvm/guest_memfd.c-157-{\n--\nvirt/kvm/guest_memfd.c=315=static long kvm_gmem_allocate(struct inode *inode, loff_t offset, loff_t len)\n--\nvirt/kvm/guest_memfd.c-338-\nvirt/kvm/guest_memfd.c:339:\t\tfolio = kvm_gmem_get_folio(inode, index);\nvirt/kvm/guest_memfd.c-340-\t\tif (IS_ERR(folio)) {\n--\nvirt/kvm/guest_memfd.c=493=static vm_fault_t kvm_gmem_fault_user_mapping(struct vm_fault *vmf)\n--\nvirt/kvm/guest_memfd.c-503-\tif (kvm_gmem_is_shared_mem(inode, vmf-\u003epgoff))\nvirt/kvm/guest_memfd.c:504:\t\tfolio = kvm_gmem_get_folio(inode, vmf-\u003epgoff);\nvirt/kvm/guest_memfd.c-505-\telse\n--\nvirt/kvm/guest_memfd.c=1230=static struct folio *__kvm_gmem_get_pfn(struct file *file,\n--\nvirt/kvm/guest_memfd.c-1248-\nvirt/kvm/guest_memfd.c:1249:\tfolio = kvm_gmem_get_folio(file_inode(file), index);\nvirt/kvm/guest_memfd.c-1250-\tif (IS_ERR(folio))\n"
}

43/3 2026/09/27 17:55 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 24738
							output: 39
							thoughts: 51
						

Thoughts:
Here's a summary of my thought process, as requested:

**Diving into Guest Memory Management in KVM**

Okay, so I need to get a handle on how KVM manages guest memory using memfds, and I'm going to start by targeting specific code sections. My immediate focus is on `virt/kvm/guest_memfd.c`. I believe the key to understanding the mechanism will be found between lines 315-370 and lines 493-550. I'm guessing these areas will cover core functionality like allocation, mapping, or potentially error handling related to memfd usage within the KVM guest context. I'll need to carefully parse the code, paying close attention to data structures, function calls, and any relevant locking mechanisms to build a clear picture of how guest memory is set up and managed using memfds. I expect these ranges to be self-contained but I will watch the context of adjacent lines.



44/3 2026/09/27 17:55 tool read-file
0m Args:
{
  "File": "virt/kvm/guest_memfd.c",
  "FirstLine": 315,
  "LineCount": 55
}

Results:
{
  "Contents": " 315:\tstatic long kvm_gmem_allocate(struct inode *inode, loff_t offset, loff_t len)\n 316:\t{\n 317:\t\tstruct address_space *mapping = inode-\u003ei_mapping;\n 318:\t\tpgoff_t start, index, end;\n 319:\t\tint r;\n 320:\t\n 321:\t\t/* Dedicated guest is immutable by default. */\n 322:\t\tif (offset + len \u003e i_size_read(inode))\n 323:\t\t\treturn -EINVAL;\n 324:\t\n 325:\t\tfilemap_invalidate_lock_shared(mapping);\n 326:\t\n 327:\t\tstart = offset \u003e\u003e PAGE_SHIFT;\n 328:\t\tend = (offset + len) \u003e\u003e PAGE_SHIFT;\n 329:\t\n 330:\t\tr = 0;\n 331:\t\tfor (index = start; index \u003c end; ) {\n 332:\t\t\tstruct folio *folio;\n 333:\t\n 334:\t\t\tif (signal_pending(current)) {\n 335:\t\t\t\tr = -EINTR;\n 336:\t\t\t\tbreak;\n 337:\t\t\t}\n 338:\t\n 339:\t\t\tfolio = kvm_gmem_get_folio(inode, index);\n 340:\t\t\tif (IS_ERR(folio)) {\n 341:\t\t\t\tr = PTR_ERR(folio);\n 342:\t\t\t\tbreak;\n 343:\t\t\t}\n 344:\t\n 345:\t\t\tindex = folio_next_index(folio);\n 346:\t\n 347:\t\t\tfolio_unlock(folio);\n 348:\t\t\tfolio_put(folio);\n 349:\t\n 350:\t\t\t/* 64-bit only, wrapping the index should be impossible. */\n 351:\t\t\tif (WARN_ON_ONCE(!index))\n 352:\t\t\t\tbreak;\n 353:\t\n 354:\t\t\tcond_resched();\n 355:\t\t}\n 356:\t\n 357:\t\tfilemap_invalidate_unlock_shared(mapping);\n 358:\t\n 359:\t\treturn r;\n 360:\t}\n 361:\t\n 362:\tstatic long kvm_gmem_fallocate(struct file *file, int mode, loff_t offset,\n 363:\t\t\t\t       loff_t len)\n 364:\t{\n 365:\t\tint ret;\n 366:\t\n 367:\t\tif (!(mode \u0026 FALLOC_FL_KEEP_SIZE))\n 368:\t\t\treturn -EOPNOTSUPP;\n 369:\t\n"
}

45/3 2026/09/27 17:55 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 25747
							output: 39
							thoughts: 47
						

Thoughts:
Okay, let's dive into this.

**Examining KVM Guest Memory Handling**

Right, so I'm focusing in on `kvm_gmem_fault_user_mapping` and `__kvm_gmem_get_pfn`. My intuition tells me these are central to how KVM handles guest memory mapped with a `memfd`, specifically in that guest_memfd.c file. I need to understand how these functions interact to see the entire lifecycle of how the guest accesses the memory backing the `memfd`. I need to figure out where the faults are triggered and how the physical page frame numbers (PFNs) are obtained, to better understand any potential performance bottlenecks or security implications. I'll need to trace the data flow through these functions and see how they are implemented.



46/3 2026/09/27 17:55 tool read-file
0m Args:
{
  "File": "virt/kvm/guest_memfd.c",
  "FirstLine": 490,
  "LineCount": 50
}

Results:
{
  "Contents": " 490:\t\treturn GMEM_I(inode)-\u003eflags \u0026 GUEST_MEMFD_FLAG_MMAP;\n 491:\t}\n 492:\t\n 493:\tstatic vm_fault_t kvm_gmem_fault_user_mapping(struct vm_fault *vmf)\n 494:\t{\n 495:\t\tstruct inode *inode = file_inode(vmf-\u003evma-\u003evm_file);\n 496:\t\tstruct folio *folio;\n 497:\t\tvm_fault_t ret = VM_FAULT_LOCKED;\n 498:\t\n 499:\t\tif (((loff_t)vmf-\u003epgoff \u003c\u003c PAGE_SHIFT) \u003e= i_size_read(inode))\n 500:\t\t\treturn VM_FAULT_SIGBUS;\n 501:\t\n 502:\t\tfilemap_invalidate_lock_shared(inode-\u003ei_mapping);\n 503:\t\tif (kvm_gmem_is_shared_mem(inode, vmf-\u003epgoff))\n 504:\t\t\tfolio = kvm_gmem_get_folio(inode, vmf-\u003epgoff);\n 505:\t\telse\n 506:\t\t\tfolio = ERR_PTR(-EACCES);\n 507:\t\tfilemap_invalidate_unlock_shared(inode-\u003ei_mapping);\n 508:\t\n 509:\t\tif (IS_ERR(folio)) {\n 510:\t\t\tif (PTR_ERR(folio) == -EAGAIN)\n 511:\t\t\t\treturn VM_FAULT_RETRY;\n 512:\t\n 513:\t\t\treturn vmf_error(PTR_ERR(folio));\n 514:\t\t}\n 515:\t\n 516:\t\tif (WARN_ON_ONCE(folio_test_large(folio))) {\n 517:\t\t\tret = VM_FAULT_SIGBUS;\n 518:\t\t\tgoto out_folio;\n 519:\t\t}\n 520:\t\n 521:\t\tif (!folio_test_uptodate(folio)) {\n 522:\t\t\tclear_highpage(folio_page(folio, 0));\n 523:\t\t\tfolio_mark_uptodate(folio);\n 524:\t\t}\n 525:\t\n 526:\t\tvmf-\u003epage = folio_file_page(folio, vmf-\u003epgoff);\n 527:\t\n 528:\tout_folio:\n 529:\t\tif (ret != VM_FAULT_LOCKED) {\n 530:\t\t\tfolio_unlock(folio);\n 531:\t\t\tfolio_put(folio);\n 532:\t\t}\n 533:\t\n 534:\t\treturn ret;\n 535:\t}\n 536:\t\n 537:\t#ifdef CONFIG_NUMA\n 538:\tstatic int kvm_gmem_set_policy(struct vm_area_struct *vma, struct mempolicy *mpol)\n 539:\t{\n"
}

47/3 2026/09/27 17:55 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 26775
							output: 40
							thoughts: 97
						

Thoughts:
Here's my analysis of the provided code snippet, keeping in mind my expertise:

**Examining Folio Update and Physical Page Retrieval**

Okay, let's zero in on this specific section of code, lines 521-524. I see a conditional check using `folio_test_uptodate(folio)`. If the folio isn't marked as "uptodate," which implies some data freshness or validity check is failing, the code proceeds to `clear_highpage(folio_page(folio, 0))`. That would imply that the first page within the folio needs to be invalidated or sanitized, likely to get rid of stale data. The subsequent `folio_mark_uptodate(folio)` function then updates the folio to reflect the change. This is typical of a write path. I wonder what kind of memory model or caching scheme this code is interacting with.

My immediate follow-up is to determine what mechanisms are in place that impact whether a `folio` is considered "uptodate".

Now, let's shift gears and look at the role of `__kvm_gmem_get_pfn`. That looks like a key function in a virtual memory system, based on the naming, it's very likely interacting with a guest physical memory region. I anticipate it's responsible for fetching a specific physical frame number (PFN) for a given guest memory address. It may also perform some level of validation or permission checks. Given its importance, I need to understand its behavior in more detail; let's check lines 1230-1280 to see how it operates.



48/3 2026/09/27 17:55 tool read-file
0m Args:
{
  "File": "virt/kvm/guest_memfd.c",
  "FirstLine": 1230,
  "LineCount": 55
}

Results:
{
  "Contents": "1230:\tstatic struct folio *__kvm_gmem_get_pfn(struct file *file,\n1231:\t\t\t\t\t\tstruct kvm_memory_slot *slot,\n1232:\t\t\t\t\t\tpgoff_t index, kvm_pfn_t *pfn,\n1233:\t\t\t\t\t\tint *max_order)\n1234:\t{\n1235:\t\tstruct file *slot_file = READ_ONCE(slot-\u003egmem.file);\n1236:\t\tstruct gmem_file *f = file-\u003eprivate_data;\n1237:\t\tstruct folio *folio;\n1238:\t\n1239:\t\tif (file != slot_file) {\n1240:\t\t\tWARN_ON_ONCE(slot_file);\n1241:\t\t\treturn ERR_PTR(-EFAULT);\n1242:\t\t}\n1243:\t\n1244:\t\tif (xa_load(\u0026f-\u003ebindings, index) != slot) {\n1245:\t\t\tWARN_ON_ONCE(xa_load(\u0026f-\u003ebindings, index));\n1246:\t\t\treturn ERR_PTR(-EIO);\n1247:\t\t}\n1248:\t\n1249:\t\tfolio = kvm_gmem_get_folio(file_inode(file), index);\n1250:\t\tif (IS_ERR(folio))\n1251:\t\t\treturn folio;\n1252:\t\n1253:\t\tif (folio_test_hwpoison(folio)) {\n1254:\t\t\tfolio_unlock(folio);\n1255:\t\t\tfolio_put(folio);\n1256:\t\t\treturn ERR_PTR(-EHWPOISON);\n1257:\t\t}\n1258:\t\n1259:\t\tif (!folio_test_uptodate(folio)) {\n1260:\t\t\tclear_highpage(folio_page(folio, 0));\n1261:\t\t\tfolio_mark_uptodate(folio);\n1262:\t\t}\n1263:\t\n1264:\t\t*pfn = folio_file_pfn(folio, index);\n1265:\t\tif (max_order)\n1266:\t\t\t*max_order = 0;\n1267:\t\n1268:\t\treturn folio;\n1269:\t}\n1270:\t\n1271:\tint kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot,\n1272:\t\t\t     gfn_t gfn, kvm_pfn_t *pfn, int *max_order)\n1273:\t{\n1274:\t\tpgoff_t index = kvm_gmem_get_index(slot, gfn);\n1275:\t\tstruct folio *folio;\n1276:\t\tint r = 0, __order;\n1277:\t\n1278:\t\tmax_order = max_order ?: \u0026__order;\n1279:\t\n1280:\t\tCLASS(gmem_get_file, file)(slot);\n1281:\t\tif (!file)\n1282:\t\t\treturn -EFAULT;\n1283:\t\n1284:\t\tfilemap_invalidate_lock_shared(file_inode(file)-\u003ei_mapping);\n"
}

49/3 2026/09/27 17:55 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 28100
							output: 29
							thoughts: 554
						

Thoughts:
**My Analysis of Guest Memory Initialization**

Okay, so I'm looking at how guest memory is initialized, specifically focusing on the interactions between `kvm_gmem_fault_user_mapping` and `__kvm_gmem_get_pfn`. My initial thought is, both these functions check if a folio is uptodate and, if not, they clear it using `clear_highpage` and then mark it as uptodate using `folio_mark_uptodate`. This seems like standard operating procedure, especially since `shmem_gmem_alloc_folio` will allocate a folio that is *not* uptodate. This means that, when a guest attempts to access the memory, the code should zero out the page before the guest can see it.

But... hang on. What about the changes related to `struct kvm_create_guest_memfd`? Let me check that structure definition:

```c
struct kvm_create_guest_memfd {
	__u64 size;
	__u64 flags;
	__s32 resource_fd;
	__u32 pad;
	__u64 reserved[5];
};
```

I'm starting to think about whether the `pad` or `reserved` fields might be copied back to userspace somehow. Maybe there's a potential vulnerability there? Okay, let's look at the ioctl handler in `virt/kvm/kvm_main.c` where `KVM_CREATE_GUEST_MEMFD` is handled:

```c
	#ifdef CONFIG_KVM_GUEST_MEMFD
		case KVM_CREATE_GUEST_MEMFD: {
			struct kvm_create_guest_memfd guest_memfd;
	
			r = -EFAULT;
			if (copy_from_user(&guest_memfd, argp, sizeof(guest_memfd)))
				goto out;
	
			r = kvm_gmem_create(kvm, &guest_memfd);
			break;
		}
	#endif
```

Aha! It only uses `copy_from_user`! There's no `copy_to_user` call here. That's good.

Wait a minute, why is `KVM_CREATE_GUEST_MEMFD` defined with `_IOWR`? That seems suspicious. Let's look at the macro:

```c
#define KVM_CREATE_GUEST_MEMFD	_IOWR(KVMIO,  0xd4, struct kvm_create_guest_memfd)
```

Right, even though the macro uses `_IOWR`, the actual ioctl handler only uses `copy_from_user`. Okay, that *should* be fine.

But I'm a bit paranoid. Let's be thorough. I need to verify that nothing, *anywhere* in the codebase, copies `guest_memfd` back to userspace. I'll grep for `copy_to_user.*guest_memfd` or just `guest_memfd` to be absolutely certain. It's best to be sure.



50/3 2026/09/27 17:55 tool grepper
0m Args:
{
  "Expression": "guest_memfd",
  "PathPrefix": "virt/kvm/"
}

Results:
{
  "Output": "virt/kvm/Makefile.kvm=14=kvm-$(CONFIG_HAVE_KVM_PFNCACHE) += $(KVM)/pfncache.o\nvirt/kvm/Makefile.kvm:15:kvm-$(CONFIG_KVM_GUEST_MEMFD) += $(KVM)/guest_memfd.o\n--\nvirt/kvm/guest_memfd.c-5-#include \u003clinux/fs.h\u003e\nvirt/kvm/guest_memfd.c:6:#include \u003clinux/guest_memfd.h\u003e\nvirt/kvm/guest_memfd.c-7-#include \u003clinux/kvm_host.h\u003e\n--\nvirt/kvm/guest_memfd.c-14-#include \"kvm_mm.h\"\nvirt/kvm/guest_memfd.c:15:#include \"guest_memfd.h\"\nvirt/kvm/guest_memfd.c-16-\nvirt/kvm/guest_memfd.c=17=static struct vfsmount *kvm_gmem_mnt;\n--\nvirt/kvm/guest_memfd.c-19-/*\nvirt/kvm/guest_memfd.c:20: * A guest_memfd instance can be associated multiple VMs, each with its own\nvirt/kvm/guest_memfd.c-21- * \"view\" of the underlying physical memory.\n--\nvirt/kvm/guest_memfd.c=34=struct gmem_inode {\n--\nvirt/kvm/guest_memfd.c-39-\tvoid *provider;\nvirt/kvm/guest_memfd.c:40:\tconst struct guest_memfd_provider_operations *provider_ops;\nvirt/kvm/guest_memfd.c-41-\n--\nvirt/kvm/guest_memfd.c=156=static struct folio *kvm_gmem_get_folio(struct inode *inode, pgoff_t index)\n--\nvirt/kvm/guest_memfd.c-198-\t * External interfaces like kvm_gmem_get_pfn() support dealing\nvirt/kvm/guest_memfd.c:199:\t * with hugepages to a degree, but internally, guest_memfd currently\nvirt/kvm/guest_memfd.c-200-\t * assumes that all folios are order-0 and handling would need\n--\nvirt/kvm/guest_memfd.c=589=bool kvm_gmem_is_private_gfn(struct kvm *kvm, gfn_t gfn)\n--\nvirt/kvm/guest_memfd.c-606-\t * caller _must_ protect consumption of private vs. shared either by\nvirt/kvm/guest_memfd.c:607:\t * holding guest_memfd's invalidate lock for the entire duration, or by\nvirt/kvm/guest_memfd.c-608-\t * checking mmu_invalidate_retry_gfn() under mmu_lock to serialize\n--\nvirt/kvm/guest_memfd.c=733=static int __kvm_gmem_set_attributes(struct inode *inode, pgoff_t start,\n--\nvirt/kvm/guest_memfd.c-778-\t/*\nvirt/kvm/guest_memfd.c:779:\t * From this point on guest_memfd has performed necessary\nvirt/kvm/guest_memfd.c-780-\t * checks and can proceed to do guest-breaking changes.\n--\nvirt/kvm/guest_memfd.c=910=static void kvm_gmem_invalidate_folio(struct folio *folio, size_t offset,\n--\nvirt/kvm/guest_memfd.c-915-\nvirt/kvm/guest_memfd.c:916:\t/* guest_memfd only allows truncating full folios. */\nvirt/kvm/guest_memfd.c-917-\tif (WARN_ON_ONCE(offset != 0 || len != folio_size(folio)))\n--\nvirt/kvm/guest_memfd.c=947=static int kvm_gmem_init_inode(struct inode *inode, loff_t size, u64 flags)\n--\nvirt/kvm/guest_memfd.c-960-\t/*\nvirt/kvm/guest_memfd.c:961:\t * guest_memfd memory is neither migratable nor swappable: set\nvirt/kvm/guest_memfd.c-962-\t * inaccessible to gate off both.\n--\nvirt/kvm/guest_memfd.c=993=static int kvm_gmem_attach_resource(struct inode *inode, int resource_fd)\n--\nvirt/kvm/guest_memfd.c-996-\tstruct inode *res_inode;\nvirt/kvm/guest_memfd.c:997:\tconst struct guest_memfd_provider_operations *ops = NULL;\nvirt/kvm/guest_memfd.c-998-\tvoid *provider;\n--\nvirt/kvm/guest_memfd.c=1023=static int __kvm_gmem_create(struct kvm *kvm, loff_t size, u64 flags, int resource_fd)\n--\nvirt/kvm/guest_memfd.c-1090-\nvirt/kvm/guest_memfd.c:1091:int kvm_gmem_create(struct kvm *kvm, struct kvm_create_guest_memfd *args)\nvirt/kvm/guest_memfd.c-1092-{\n--\nvirt/kvm/guest_memfd.c=1404=static void kvm_gmem_init_inode_once(void *__gi)\n--\nvirt/kvm/guest_memfd.c-1409-\t * Note!  Don't initialize the inode with anything specific to the\nvirt/kvm/guest_memfd.c:1410:\t * guest_memfd instance, or that might be specific to how the inode is\nvirt/kvm/guest_memfd.c-1411-\t * used (from the VFS-layer's perspective).  This hook is called only\n--\nvirt/kvm/guest_memfd.c=1445=static void kvm_gmem_destroy_inode(struct inode *inode)\n--\nvirt/kvm/guest_memfd.c-1456-\t * initialized, i.e. if the inode is being destroyed before\nvirt/kvm/guest_memfd.c:1457:\t * guest_memfd can set the external lock, lockdep would find\nvirt/kvm/guest_memfd.c-1458-\t * that the tree's internal ma_lock was not held.\n--\nvirt/kvm/guest_memfd.c=1496=static struct file_system_type kvm_gmem_fs = {\nvirt/kvm/guest_memfd.c:1497:\t.name\t\t = \"guest_memfd\",\nvirt/kvm/guest_memfd.c-1498-\t.init_fs_context = kvm_gmem_init_fs_context,\n--\nvirt/kvm/guest_memfd.h=9=void kvm_gmem_exit(void);\nvirt/kvm/guest_memfd.h:10:int kvm_gmem_create(struct kvm *kvm, struct kvm_create_guest_memfd *args);\nvirt/kvm/guest_memfd.h-11-int kvm_gmem_prepare_memory_region(struct kvm *kvm, struct kvm_memory_slot *slot,\n--\nvirt/kvm/kvm_main.c-58-#include \"async_pf.h\"\nvirt/kvm/kvm_main.c:59:#include \"guest_memfd.h\"\nvirt/kvm/kvm_main.c-60-#include \"kvm_mm.h\"\n--\nvirt/kvm/kvm_main.c=1705=static void kvm_commit_memory_region(struct kvm *kvm,\n--\nvirt/kvm/kvm_main.c-1738-\t\t/*\nvirt/kvm/kvm_main.c:1739:\t\t * Moving a guest_memfd memslot isn't supported, and will never\nvirt/kvm/kvm_main.c-1740-\t\t * be supported.\n--\nvirt/kvm/kvm_main.c-1753-\t\t * TODO: Drop the WARN and do the unbind() call only for MOVE\nvirt/kvm/kvm_main.c:1754:\t\t * when dirty logging guest_memfd memslots is supported.  Until\nvirt/kvm/kvm_main.c:1755:\t\t * then, flags-only changes on guest_memfd slots should also be\nvirt/kvm/kvm_main.c-1756-\t\t * impossible; unbind the old memslot for defense-in-depth.\n--\nvirt/kvm/kvm_main.c=2013=static int kvm_set_memory_region(struct kvm *kvm,\n--\nvirt/kvm/kvm_main.c-2045-\tif (mem-\u003eflags \u0026 KVM_MEM_GUEST_MEMFD \u0026\u0026\nvirt/kvm/kvm_main.c:2046:\t    (mem-\u003eguest_memfd_offset \u0026 (PAGE_SIZE - 1) ||\nvirt/kvm/kvm_main.c:2047:\t     mem-\u003eguest_memfd_offset + mem-\u003ememory_size \u003c mem-\u003eguest_memfd_offset))\nvirt/kvm/kvm_main.c-2048-\t\treturn -EINVAL;\n--\nvirt/kvm/kvm_main.c-2126-\tif (change == KVM_MR_CREATE \u0026\u0026 (mem-\u003eflags \u0026 KVM_MEM_GUEST_MEMFD)) {\nvirt/kvm/kvm_main.c:2127:\t\tr = kvm_gmem_prepare_memory_region(kvm, new, mem-\u003eguest_memfd,\nvirt/kvm/kvm_main.c:2128:\t\t\t\t\t\t   mem-\u003eguest_memfd_offset);\nvirt/kvm/kvm_main.c-2129-\t\tif (r)\n--\nvirt/kvm/kvm_main.c=5225=static long kvm_vm_ioctl(struct file *filp,\n--\nvirt/kvm/kvm_main.c-5446-\tcase KVM_CREATE_GUEST_MEMFD: {\nvirt/kvm/kvm_main.c:5447:\t\tstruct kvm_create_guest_memfd guest_memfd;\nvirt/kvm/kvm_main.c-5448-\nvirt/kvm/kvm_main.c-5449-\t\tr = -EFAULT;\nvirt/kvm/kvm_main.c:5450:\t\tif (copy_from_user(\u0026guest_memfd, argp, sizeof(guest_memfd)))\nvirt/kvm/kvm_main.c-5451-\t\t\tgoto out;\nvirt/kvm/kvm_main.c-5452-\nvirt/kvm/kvm_main.c:5453:\t\tr = kvm_gmem_create(kvm, \u0026guest_memfd);\nvirt/kvm/kvm_main.c-5454-\t\tbreak;\n"
}

51/3 2026/09/27 17:55 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 32232
							output: 35
							thoughts: 15
						

Thoughts:
Here's your summary:

**Digging Deeper into IOCTL Compatibility**

Alright, I'm taking a look at this. The initial assessment seems reasonable, but I want to be thorough. I'm going to circle back and specifically examine `compat_ioctl`. It's always a good practice to consider potential compatibility issues, especially when dealing with IOCTLs, which can be a point of contention across different kernel versions or architectures. I want to make absolutely sure there aren't any subtle incompatibilities lurking there that we're missing. Gotta cover all the bases!



52/3 2026/09/27 17:55 tool grepper
0m Args:
{
  "Expression": "KVM_CREATE_GUEST_MEMFD",
  "PathPrefix": "virt/kvm/"
}

Results:
{
  "Output": "virt/kvm/kvm_main.c=5225=static long kvm_vm_ioctl(struct file *filp,\n--\nvirt/kvm/kvm_main.c-5445-#ifdef CONFIG_KVM_GUEST_MEMFD\nvirt/kvm/kvm_main.c:5446:\tcase KVM_CREATE_GUEST_MEMFD: {\nvirt/kvm/kvm_main.c-5447-\t\tstruct kvm_create_guest_memfd guest_memfd;\n"
}

53/3 2026/09/27 17:55 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 32545
							output: 20
							thoughts: 1374
						

Thoughts:
**KVM Guest Memfd Analysis**

Okay, let's break this down. First, `compat_ioctl` leads me to `kvm_vm_ioctl`, and specifically the `KVM_CREATE_GUEST_MEMFD` case. The code copies a `kvm_create_guest_memfd` struct from userspace. Hmm, no `copy_to_user` here. That feels like a potential area for security review, but let's see how the struct is used before raising alarm bells.

The key call is `kvm_gmem_create`, so that's where I need to focus. I notice there's an `alloc_inode` call to `kvm_gmem_alloc_inode`, and that looks like the starting point. Good to see that the `gmem_inode`'s new fields are initialized to `NULL`.

Next, the code goes on to `kvm_gmem_destroy_inode`. This calls `gmem_provider_release` which is the important part here. This in turn calls `gi->provider_ops->release(gi->provider)`. So, the `release` operation is called on the `provider` which is pointed to by the `gmem_inode` when the inode is destroyed. I need to understand when, and how, the `provider` and `provider_ops` are set.

That brings us to `kvm_gmem_attach_resource`. Ah, so a file descriptor, `resource_fd`, is taken from userspace to attach a resource. The code then retrieves the `guest_memfd_provider_operations` through `res_inode->i_sb->s_op->gmem_provider_ops`. The code checks if the `attach` and `alloc_folio` operations are implemented. This looks like the place where the `provider` and `provider_ops` are set.

Now the error handling in `__kvm_gmem_create` is crucial. If the `GUEST_MEMFD_FLAG_USE_RESOURCE` flag is set, `kvm_gmem_attach_resource` is called, and if it fails, the inode is put using `iput(inode)`. This means that `kvm_gmem_destroy_inode` and thus `gmem_provider_release` is called if an error happened. However, if `kvm_gmem_attach_resource` fails *before* it can attach anything, `provider_ops` will still be NULL, which makes `gmem_provider_release` a no-op. But if it succeeds and `alloc_file_pseudo` fails, `gmem_provider_release` is still called, leading to `shmem_gmem_release` and then `mntput(mnt)`.

Hang on, what's going on with `shmem_ops` in `mm/shmem.c`? Ah, it defines `.gmem_provider_ops` if `CONFIG_KVM_GUEST_MEMFD` is enabled. This links the shmem filesystem as a provider for guest memfds. Okay, in the super operations, other filesystems would have `gmem_provider_ops` set to NULL or not defined at all.

Finally, I see the definition of `guest_memfd_provider_operations` in `linux/guest_memfd.h`. This gives me the structure of the provider operations: `attach`, `release`, `alloc_folio` and `invalidate_folio`. It is important to remember what's inside here.

Okay, I'll need to do a full kernel search, to check if the header and operations are used anywhere else. I need to make sure this flow is complete, and secure!



54/3 2026/09/27 17:55 tool grepper
0m Args:
{
  "Expression": "gmem_provider_ops"
}

Results:
{
  "Output": "include/linux/fs/super_types.h=85=struct super_operations {\n--\ninclude/linux/fs/super_types.h-134-#ifdef CONFIG_KVM_GUEST_MEMFD\ninclude/linux/fs/super_types.h:135:\tconst struct guest_memfd_provider_operations *gmem_provider_ops;\ninclude/linux/fs/super_types.h-136-#endif\n--\nmm/shmem.c=5328=static void shmem_gmem_invalidate_folio(void *provider, struct folio *folio)\n--\nmm/shmem.c-5337-\nmm/shmem.c:5338:static const struct guest_memfd_provider_operations shmem_gmem_provider_ops = {\nmm/shmem.c-5339-\t.attach\t\t\t= shmem_gmem_attach,\n--\nmm/shmem.c=5346=static const struct super_operations shmem_ops = {\n--\nmm/shmem.c-5364-#ifdef CONFIG_KVM_GUEST_MEMFD\nmm/shmem.c:5365:\t.gmem_provider_ops\t= \u0026shmem_gmem_provider_ops,\nmm/shmem.c-5366-#endif\n--\nvirt/kvm/guest_memfd.c=993=static int kvm_gmem_attach_resource(struct inode *inode, int resource_fd)\n--\nvirt/kvm/guest_memfd.c-1004-\tres_inode = file_inode(resource_file);\nvirt/kvm/guest_memfd.c:1005:\tops = res_inode-\u003ei_sb-\u003es_op-\u003egmem_provider_ops;\nvirt/kvm/guest_memfd.c-1006-\n"
}

55/3 2026/09/27 17:55 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 35205
							output: 22
							thoughts: 1256
						

Thoughts:
**Analyzing Guest Memfd Folio Invalidation and Allocation**

Okay, let's break this down. My initial focus was on understanding the implications of `mapping_set_release_always` being added to the inode's mapping in `kvm_gmem_init_inode`. That change suggests a different approach to memory management within the guest memfd context. It signals some change in how the kernel handles releasing memory associated with the inode's mapping.

My thinking then shifted to how this relates to folio invalidation. I noticed the introduction of `kvm_gmem_invalidate_folio` in the address space operations (`aops`) for guest memfd. This function, in turn, calls `gmem_provider_invalidate_folio`, which ultimately leads to `shmem_gmem_invalidate_folio`. This suggests this invalidation path is tied closely to the shared memory implementation. The `shmem_gmem_invalidate_folio` function decrements a percpu counter representing used blocks. So, the invalidation path seems to free up resources in a block counter managed in shared memory, triggered by truncation or similar operations.

However, I needed to understand the allocation path to have a complete picture. Digging into `shmem_gmem_alloc_folio` reveals the process of folio allocation. It interacts with mempolicies, using `folio_alloc_mpol` or `folio_alloc` depending on whether a mempolicy is provided. More importantly, it seems that there is a check if the block counter `sbinfo->max_blocks` is reached. If so, an error is returned.

The critical piece seems to be the subsequent use of the allocated folio within `kvm_gmem_get_folio`. Specifically, I was curious about locking semantics. The folio allocation happens through `gmem_provider_alloc_folio`, and it's added to the filemap via `filemap_add_folio`. This implies that the folio is locked at some point. I was wondering whether `shmem_gmem_alloc_folio` (allocating the folio) or `filemap_add_folio` locks the folio. This locking is essential to consider the implications of the change added in `kvm_gmem_init_inode`, which can affect the locking order.



56/3 2026/09/27 17:55 tool grepper
0m Args:
{
  "Expression": "int filemap_add_folio\\("
}

Results:
{
  "Output": "include/linux/pagemap.h=1346=int add_to_page_cache_lru(struct page *page, struct address_space *mapping,\ninclude/linux/pagemap.h-1347-\t\tpgoff_t index, gfp_t gfp);\ninclude/linux/pagemap.h:1348:int filemap_add_folio(struct address_space *mapping, struct folio *folio,\ninclude/linux/pagemap.h-1349-\t\tpgoff_t index, gfp_t gfp);\n--\nmm/filemap.c=953=ALLOW_ERROR_INJECTION(__filemap_add_folio, ERRNO);\nmm/filemap.c-954-\nmm/filemap.c:955:int filemap_add_folio(struct address_space *mapping, struct folio *folio,\nmm/filemap.c-956-\t\t\t\tpgoff_t index, gfp_t gfp)\n"
}

57/3 2026/09/27 17:55 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 37216
							output: 34
							thoughts: 21
						

Thoughts:
Okay, let's dive into this. Here's my take on `filemap_add_folio` within `mm/filemap.c`.

**Diving into `filemap_add_folio`**

Alright, I'm thinking, let's get into the heart of this. I need to understand how we're actually adding folios to a file's page cache. This function, `filemap_add_folio`, seems like the likely candidate to do that. Given my understanding of the memory management subsystem, I anticipate this function plays a crucial role in establishing the relationship between a file's data and the physical memory holding it. I'm expecting to see operations related to creating or referencing page cache entries (or now, folio entries), associating them with the inode and offset, and potentially dealing with locking and synchronization to ensure consistency in a multi-threaded environment. I'm keen to see how it interacts with the underlying block device or other storage mechanisms. What kind of strategies are employed for pre-population, how are dirty pages handled, and how does this handle different types of files and their attributes. Looking forward to see the details.



58/3 2026/09/27 17:55 tool read-file
0m Args:
{
  "File": "mm/filemap.c",
  "FirstLine": 950,
  "LineCount": 25
}

Results:
{
  "Contents": " 950:\t\tfolio_put_refs(folio, nr);\n 951:\t\treturn xas_error(\u0026xas);\n 952:\t}\n 953:\tALLOW_ERROR_INJECTION(__filemap_add_folio, ERRNO);\n 954:\t\n 955:\tint filemap_add_folio(struct address_space *mapping, struct folio *folio,\n 956:\t\t\t\t\tpgoff_t index, gfp_t gfp)\n 957:\t{\n 958:\t\tvoid *shadow = NULL;\n 959:\t\tint ret;\n 960:\t\tstruct mem_cgroup *tmp;\n 961:\t\tbool kernel_file = test_bit(AS_KERNEL_FILE, \u0026mapping-\u003eflags);\n 962:\t\n 963:\t\tif (kernel_file)\n 964:\t\t\ttmp = set_active_memcg(root_mem_cgroup);\n 965:\t\tret = mem_cgroup_charge(folio, NULL, gfp);\n 966:\t\tif (kernel_file)\n 967:\t\t\tset_active_memcg(tmp);\n 968:\t\tif (ret)\n 969:\t\t\treturn ret;\n 970:\t\n 971:\t\t__folio_set_locked(folio);\n 972:\t\tret = __filemap_add_folio(mapping, folio, index, gfp, \u0026shadow);\n 973:\t\tif (unlikely(ret)) {\n 974:\t\t\tmem_cgroup_uncharge(folio);\n"
}

59/3 2026/09/27 17:55 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 37917
							output: 33
							thoughts: 235
						

Thoughts:
**Analyzing Folio Locking and Up-to-Date Status in KVM Memory Management**

Okay, so I'm tracing the folio lifecycle here, particularly within KVM's memory management. I see that `__folio_set_locked(folio);` is getting called within `filemap_add_folio`. Now, the critical question is, what follows that lock acquisition, especially when `kvm_gmem_get_folio` returns the locked folio?

Looking at the caller, `kvm_gmem_allocate`, I observe the subsequent operations: `folio_unlock(folio);` and then `folio_put(folio);`. That's standard folio lifecycle management; acquire, use, and release. But now I'm seeing `kvm_gmem_fault_user_mapping` and `__kvm_gmem_get_pfn` also use that folio, specifically checking its up-to-date status. They call `!folio_test_uptodate(folio)` and if it's not up-to-date, they call `clear_highpage` then `folio_mark_uptodate`. This confirms that we're actively managing potentially stale pages and ensuring they are up-to-date, perhaps after page faulting.

But wait a minute! I need to be absolutely thorough. This folio management touches a *lot* of things... Is this the only place folios are being accessed? I need to consider all potential access points. Let's think about `guest_memfd`. Does it involve any reads or writes from the guest side? Let me investigate if the `guest_memfd` file operations (fops) support `read` or `write` calls. That's the next logical step to ensure a complete understanding of how this works and to catch any potential race conditions or synchronization issues related to folio access.



60/3 2026/09/27 17:56 tool grepper
0m Args:
{
  "Expression": "kvm_gmem_fops",
  "PathPrefix": "virt/kvm/"
}

Results:
{
  "Output": "virt/kvm/guest_memfd.c=843=static long kvm_gmem_ioctl(struct file *file, unsigned int ioctl,\n--\nvirt/kvm/guest_memfd.c-856-\nvirt/kvm/guest_memfd.c:857:static struct file_operations kvm_gmem_fops = {\nvirt/kvm/guest_memfd.c-858-\t.mmap\t\t= kvm_gmem_mmap,\n--\nvirt/kvm/guest_memfd.c=1023=static int __kvm_gmem_create(struct kvm *kvm, loff_t size, u64 flags, int resource_fd)\n--\nvirt/kvm/guest_memfd.c-1041-\t/* __fput() will take care of fops_put(). */\nvirt/kvm/guest_memfd.c:1042:\tif (!fops_get(\u0026kvm_gmem_fops)) {\nvirt/kvm/guest_memfd.c-1043-\t\terr = -ENOENT;\n--\nvirt/kvm/guest_memfd.c-1062-\nvirt/kvm/guest_memfd.c:1063:\tfile = alloc_file_pseudo(inode, kvm_gmem_mnt, name, O_RDWR, \u0026kvm_gmem_fops);\nvirt/kvm/guest_memfd.c-1064-\tif (IS_ERR(file)) {\n--\nvirt/kvm/guest_memfd.c-1082-err_fops:\nvirt/kvm/guest_memfd.c:1083:\tfops_put(\u0026kvm_gmem_fops);\nvirt/kvm/guest_memfd.c-1084-err_gmem:\n--\nvirt/kvm/guest_memfd.c=1108=int kvm_gmem_prepare_memory_region(struct kvm *kvm, struct kvm_memory_slot *slot,\n--\nvirt/kvm/guest_memfd.c-1126-\nvirt/kvm/guest_memfd.c:1127:\tif (file-\u003ef_op != \u0026kvm_gmem_fops)\nvirt/kvm/guest_memfd.c-1128-\t\tgoto err;\n--\nvirt/kvm/guest_memfd.c=1161=int kvm_gmem_commit_memory_region(struct kvm *kvm, struct kvm_memory_slot *slot)\n--\nvirt/kvm/guest_memfd.c-1167-\nvirt/kvm/guest_memfd.c:1168:\tif (WARN_ON_ONCE(slot-\u003egmem.file-\u003ef_op != \u0026kvm_gmem_fops))\nvirt/kvm/guest_memfd.c-1169-\t\treturn -EIO;\n--\nvirt/kvm/guest_memfd.c=1513=int kvm_gmem_init(struct module *module)\n--\nvirt/kvm/guest_memfd.c-1520-\nvirt/kvm/guest_memfd.c:1521:\tkvm_gmem_fops.owner = module;\nvirt/kvm/guest_memfd.c-1522-\tkvm_gmem_inode_cachep = kmem_cache_create(\"kvm_gmem_inode_cache\",\n"
}

61/3 2026/09/27 17:56 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 39452
							output: 39
							thoughts: 30
						

Thoughts:
Here's my thinking process:

**Diving into `kvm_gmem_fops`**

Okay, so I'm looking into `kvm_gmem_fops` and I know it's related to KVM's guest memory file operations. This usually means it handles how guest memory is managed through file descriptors, likely using `memfd` or something similar for shared memory regions. I'm going to head into `virt/kvm/guest_memfd.c` and take a look at the implementation of this specific set of file operations. I expect I'll be looking for things like `open`, `mmap`, `read`, `write`, `close`, and how they interact with the guest's memory space and the host's underlying mechanisms. I'll be particularly interested in any synchronization or protection mechanisms used to ensure memory safety between the host and guest, and how they handle access permissions and memory sharing. Ultimately, I'm expecting to learn more about how `guest_memfd` and KVM work together. Let's see what's actually in there.



62/3 2026/09/27 17:56 tool read-file
0m Args:
{
  "File": "virt/kvm/guest_memfd.c",
  "FirstLine": 850,
  "LineCount": 20
}

Results:
{
  "Contents": " 850:\t\n 851:\t\t\treturn kvm_gmem_set_attributes(file, (void __user *)arg);\n 852:\t\tdefault:\n 853:\t\t\treturn -ENOTTY;\n 854:\t\t}\n 855:\t}\n 856:\t\n 857:\tstatic struct file_operations kvm_gmem_fops = {\n 858:\t\t.mmap\t\t= kvm_gmem_mmap,\n 859:\t\t.open\t\t= generic_file_open,\n 860:\t\t.release\t= kvm_gmem_release,\n 861:\t\t.fallocate\t= kvm_gmem_fallocate,\n 862:\t\t.unlocked_ioctl\t= kvm_gmem_ioctl,\n 863:\t};\n 864:\t\n 865:\tstatic int kvm_gmem_migrate_folio(struct address_space *mapping,\n 866:\t\t\t\t\t  struct folio *dst, struct folio *src,\n 867:\t\t\t\t\t  enum migrate_mode mode)\n 868:\t{\n 869:\t\tWARN_ON_ONCE(1);\n"
}

63/3 2026/09/27 17:56 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 40067
							output: 401
							thoughts: 2633
						

Thoughts:
**KMSAN Analysis: Guest Memfd Patch**

Okay, so looking at this patch, my initial thought was to dive into the memory access patterns, especially given the introduction of new structures and operations. The first thing that jumps out is that `kvm_gmem_fops` *doesn't* have standard file operations like read/write, and instead, page data is checked for freshness and then zeroed out when mapped or faulted in. That's a good starting point for safety.

The core question is whether this patch could lead to uninitialized memory access. I went through the changes step-by-step: checking the documentation, the new header files defining `guest_memfd_provider_operations`, changes to `kvm_host.h` (adding the `GUEST_MEMFD_FLAG_USE_RESOURCE` flag), and the definition of `GUEST_MEMFD_FLAG_USE_RESOURCE` in `kvm.h`. The splitting of `reserved[6]` into `resource_fd`, `pad`, and `reserved[5]` immediately caught my eye.

Next, I looked at `mm/shmem.c`, where new functions like `shmem_gmem_attach`, `shmem_gmem_release`, and `shmem_gmem_alloc_folio` are defined. I checked the assignment of `.gmem_provider_ops`. Then, I looked at the core logic in `virt/kvm/guest_memfd.c`, focusing on the addition of `provider` and `provider_ops` to `struct gmem_inode`, their initialization and cleanup, and the resource attachment logic with `kvm_gmem_attach_resource`. The use of `gmem_provider_alloc_folio` and `kvm_gmem_invalidate_folio` also demanded close inspection. I made sure to note the `mapping_set_release_always` and the `GUEST_MEMFD_FLAG_USE_RESOURCE` check in `kvm_gmem_create`.

Then, I meticulously checked for potential issues:

1.  **Kernel structures sent to userspace:** I looked at whether the patch modified any kernel structures that are sent to userspace, and more specifically I was looking for potential uninitialized values. The only relevant structure is `kvm_create_guest_memfd`, which is *only* used with `copy_from_user` and the ioctl returns only a file descriptor integer. Therefore, nothing is copied back to userspace, meaning no risk of uninitialized memory leaks.
2.  **Conditional logic based on uninitialized data:** I examined the `kvm_gmem_attach_resource` function, ensuring that all variables (`resource_file`, `ops`, `provider`) are properly initialized *before* use. Same goes for `shmem_gmem_alloc_folio`. Nothing appeared to be uninitialized before usage.
3.  **Complex data structures:** I verified that the `gmem_inode` fields `provider` and `provider_ops` are explicitly initialized to `NULL` in `kvm_gmem_alloc_inode`, eliminating any risk here.
4.  **Bounds checks and lengths:** I confirmed that the pages allocated by the provider are added to the page cache, and importantly, zeroed using `clear_highpage` before mapping to userspace or passing to the guest. So no uninitialized memory leaks to userspace.

The next step was to really scrutinize for subtleties, given the specific instructions in the prompt. I re-read the KMSAN guidelines. I then started thinking about potential bugs:
*   **Lifetime/refcount issues:** `mntget`, `mntput`, `filemap_add_folio`, `mpol_cond_put`. I remembered error handling for `ops->attach` with `IS_ERR`, and checked that `kvm_gmem_destroy_inode` is called in all relevant code paths. The per-cpu counter accounting for `used_blocks` in `shmem_gmem_alloc_folio` also caught my eye. These were *all* reference counting, resource leak, or use-after-free issues.
*   **Locking/deadlocks:** I thought about lockdep and how this already detects locking issues.
*   **NULL pointer dereferences:** Specifically I considered a NULL `provider_ops` in `if (!gi->provider_ops || !gi->provider_ops->alloc_folio)` - but I realized this would be caught by standard kernel oops/sanitizers.

I returned to the structure `kvm_create_guest_memfd` and carefully re-verified that `pad` or `reserved` are *never* copied back to user. It's input only. I then understood the purpose of `pad`: to maintain 8


64/3 2026/09/27 17:56 tool set-results
0m Args:
{
  "KMSANReasoning": "The patch adds support for backing KVM guest_memfd with external memory resource providers (specifically tmpfs/shmem mounts) via a new flag GUEST_MEMFD_FLAG_USE_RESOURCE and resource_fd field in struct kvm_create_guest_memfd.\n\n1. Info-leak analysis: Although struct kvm_create_guest_memfd has been updated with a 32-bit pad field, the ioctl handler KVM_CREATE_GUEST_MEMFD only performs copy_from_user() and never copies any struct or data back to user space (the ioctl returns a file descriptor integer on success). Thus, no uninitialized struct fields or padding can leak to user space.\n2. Uninitialized memory analysis: The allocated folios from the external resource provider (shmem_gmem_alloc_folio) are added to the page cache and, when accessed via faulting or pfn lookup (__kvm_gmem_get_pfn / kvm_gmem_fault_user_mapping), are verified for the uptodate flag and cleared via clear_highpage() before being exposed. All new fields in struct gmem_inode (provider, provider_ops) are explicitly initialized to NULL upon inode allocation. All local variables and pointers are initialized before use.\n3. Bug profile: Any potential bugs introduced by this patch series relate to filesystem mount validation, file reference counting (fget_raw/fput), vfsmount pinning (mntget/mntput), per-cpu counter tracking (used_blocks), or page cache folio lifecycle management. These error classes (use-after-free, reference leaks, locking/concurrency) are well-covered by KASAN, refcount tracking, and LOCKDEP. KMSAN is not applicable.",
  "NeedsKMSAN": false
}

Results:
{
  "KMSANReasoning": "The patch adds support for backing KVM guest_memfd with external memory resource providers (specifically tmpfs/shmem mounts) via a new flag GUEST_MEMFD_FLAG_USE_RESOURCE and resource_fd field in struct kvm_create_guest_memfd.\n\n1. Info-leak analysis: Although struct kvm_create_guest_memfd has been updated with a 32-bit pad field, the ioctl handler KVM_CREATE_GUEST_MEMFD only performs copy_from_user() and never copies any struct or data back to user space (the ioctl returns a file descriptor integer on success). Thus, no uninitialized struct fields or padding can leak to user space.\n2. Uninitialized memory analysis: The allocated folios from the external resource provider (shmem_gmem_alloc_folio) are added to the page cache and, when accessed via faulting or pfn lookup (__kvm_gmem_get_pfn / kvm_gmem_fault_user_mapping), are verified for the uptodate flag and cleared via clear_highpage() before being exposed. All new fields in struct gmem_inode (provider, provider_ops) are explicitly initialized to NULL upon inode allocation. All local variables and pointers are initialized before use.\n3. Bug profile: Any potential bugs introduced by this patch series relate to filesystem mount validation, file reference counting (fget_raw/fput), vfsmount pinning (mntget/mntput), per-cpu counter tracking (used_blocks), or page cache folio lifecycle management. These error classes (use-after-free, reference leaks, locking/concurrency) are well-covered by KASAN, refcount tracking, and LOCKDEP. KMSAN is not applicable.",
  "NeedsKMSAN": false
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)