| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/08/13 06:57 | flow | patch-triage |
5mArgs:null Results: {
"EnableConfigs": [
"MMAP_ALLOW_UNINITIALIZED"
],
"FocusSymbols": [
"generic_file_readonly_mmap_prepare",
"generic_file_mmap_prepare"
],
"KMSANReasoning": "The patch series introduces `mmap_prepare` and `mremap` shrinking/growing support specifically for NOMMU architectures (`mm/nommu.c`, `mm/filemap.c` under `#ifndef CONFIG_MMU`, and `drivers/char/mem.c` for NOMMU). The only change to code compiled with `CONFIG_MMU` is in `drivers/char/mem.c`, where an `#ifndef CONFIG_MMU` block was replaced with an `#ifdef CONFIG_MMU` block, resulting in identical behavior for MMU builds.\n\nKMSAN is a dynamic analysis tool that requires compiler instrumentation and is currently only supported on architectures with an MMU (x86_64 and s390). It cannot be enabled on NOMMU builds. Because the functional changes in this patch series are exclusively for NOMMU configurations, they will never be compiled or executed in a KMSAN-enabled kernel.\n\nFurthermore, even within the NOMMU code, the patch carefully initializes newly exposed memory during `mremap` (either by reading from the backing file via `nommu_read_iter` or zero-filling) and correctly handles anonymous mappings, preventing uninitialized memory leaks. Therefore, a dedicated KMSAN fuzzing session is not justified.",
"NeedsKMSAN": false,
"Reasoning": "The patch modifies core memory mapping logic (mmap, munmap, mremap) and file mapping preparation for nommu architectures. These changes are functional and reachable in nommu kernels emulated by QEMU.",
"WorthFuzzing": true
} |
| 1/1 | 2026/08/13 06:57 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 6216c638fbd4b11f0b5b5c76b1b4d376b68eb3e2\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Thu Aug 13 06:57:37 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/Documentation/dev-tools/kselftest.rst b/Documentation/dev-tools/kselftest.rst\nindex 64c0ec7428a27..800b2b688aff0 100644\n--- a/Documentation/dev-tools/kselftest.rst\n+++ b/Documentation/dev-tools/kselftest.rst\n@@ -230,6 +230,18 @@ section::\n \n .. _tar's auto-compress: https://www.gnu.org/software/tar/manual/html_node/gzip.html#auto_002dcompress\n \n+Build and test on nommu target\n+==============================\n+\n+If you (cross-)build kselftests for nommu targets, or run tests on nommu targets, use\n+``NOMMU=1`` as a make variable/environment setting to tell build system to do the additional\n+checks. These nommu targets may differ in several ways, such as not supporting fork(2) or\n+using musl or another libc. Set this variable to apply the necessary build and test adjustments.\n+\n+ $ make ARCH=um NOMMU=1 O=build kselftest\n+ $ make ARCH=um NOMMU=1 -C tools/testing/selftests/mm run_tests\n+ $ NOMMU=1 /tmp/kselftest_install/run_kselftest.sh -s -c mm\n+\n Contributing new tests\n ======================\n \ndiff --git a/drivers/char/mem.c b/drivers/char/mem.c\nindex 63253d1de5d70..dba24d0a7b33d 100644\n--- a/drivers/char/mem.c\n+++ b/drivers/char/mem.c\n@@ -500,11 +500,10 @@ static ssize_t read_zero(struct file *file, char __user *buf,\n \n static int mmap_zero_prepare(struct vm_area_desc *desc)\n {\n-#ifndef CONFIG_MMU\n-\treturn -ENOSYS;\n-#endif\n+#ifdef CONFIG_MMU\n \tif (vma_desc_test(desc, VMA_SHARED_BIT))\n \t\treturn shmem_zero_setup_desc(desc);\n+#endif\n \n \t/*\n \t * This is a highly unique situation where we mark a MAP_PRIVATE mapping\ndiff --git a/mm/filemap.c b/mm/filemap.c\nindex d721986d5f465..cf02faad86aa6 100644\n--- a/mm/filemap.c\n+++ b/mm/filemap.c\n@@ -4077,7 +4077,7 @@ int generic_file_mmap(struct file *file, struct vm_area_struct *vma)\n }\n int generic_file_mmap_prepare(struct vm_area_desc *desc)\n {\n-\treturn -ENOSYS;\n+\treturn 0;\n }\n int generic_file_readonly_mmap(struct file *file, struct vm_area_struct *vma)\n {\n@@ -4085,7 +4085,9 @@ int generic_file_readonly_mmap(struct file *file, struct vm_area_struct *vma)\n }\n int generic_file_readonly_mmap_prepare(struct vm_area_desc *desc)\n {\n-\treturn -ENOSYS;\n+\tif (is_shared_maywrite(\u0026desc-\u003evma_flags))\n+\t\treturn -EINVAL;\n+\treturn generic_file_mmap_prepare(desc);\n }\n #endif /* CONFIG_MMU */\n \ndiff --git a/mm/nommu.c b/mm/nommu.c\nindex ed3934bc2de48..a29a53c1c80a6 100644\n--- a/mm/nommu.c\n+++ b/mm/nommu.c\n@@ -37,6 +37,7 @@\n \n #include \u003clinux/uaccess.h\u003e\n #include \u003clinux/uio.h\u003e\n+#include \u003clinux/major.h\u003e\n #include \u003casm/tlb.h\u003e\n #include \u003casm/tlbflush.h\u003e\n #include \u003casm/mmu_context.h\u003e\n@@ -559,36 +560,51 @@ static void put_nommu_region(struct vm_region *region)\n \t__put_nommu_region(region);\n }\n \n+static void add_vma_to_mapping(struct vm_area_struct *vma)\n+{\n+\tstruct address_space *mapping;\n+\n+\tif (!vma-\u003evm_file)\n+\t\treturn;\n+\n+\tmapping = vma-\u003evm_file-\u003ef_mapping;\n+\ti_mmap_lock_write(mapping);\n+\tflush_dcache_mmap_lock(mapping);\n+\tvma_interval_tree_insert(vma, \u0026mapping-\u003ei_mmap);\n+\tflush_dcache_mmap_unlock(mapping);\n+\ti_mmap_unlock_write(mapping);\n+}\n+\n+static void remove_vma_from_mapping(struct vm_area_struct *vma)\n+{\n+\tstruct address_space *mapping;\n+\n+\tif (!vma-\u003evm_file)\n+\t\treturn;\n+\n+\tmapping = vma-\u003evm_file-\u003ef_mapping;\n+\ti_mmap_lock_write(mapping);\n+\tflush_dcache_mmap_lock(mapping);\n+\tvma_interval_tree_remove(vma, \u0026mapping-\u003ei_mmap);\n+\tflush_dcache_mmap_unlock(mapping);\n+\ti_mmap_unlock_write(mapping);\n+}\n+\n static void setup_vma_to_mm(struct vm_area_struct *vma, struct mm_struct *mm)\n {\n \tvma-\u003evm_mm = mm;\n \n \t/* add the VMA to the mapping */\n-\tif (vma-\u003evm_file) {\n-\t\tstruct address_space *mapping = vma-\u003evm_file-\u003ef_mapping;\n-\n-\t\ti_mmap_lock_write(mapping);\n-\t\tflush_dcache_mmap_lock(mapping);\n-\t\tvma_interval_tree_insert(vma, \u0026mapping-\u003ei_mmap);\n-\t\tflush_dcache_mmap_unlock(mapping);\n-\t\ti_mmap_unlock_write(mapping);\n-\t}\n+\tif (vma-\u003evm_file)\n+\t\tadd_vma_to_mapping(vma);\n }\n \n static void cleanup_vma_from_mm(struct vm_area_struct *vma)\n {\n \tvma-\u003evm_mm-\u003emap_count--;\n \t/* remove the VMA from the mapping */\n-\tif (vma-\u003evm_file) {\n-\t\tstruct address_space *mapping;\n-\t\tmapping = vma-\u003evm_file-\u003ef_mapping;\n-\n-\t\ti_mmap_lock_write(mapping);\n-\t\tflush_dcache_mmap_lock(mapping);\n-\t\tvma_interval_tree_remove(vma, \u0026mapping-\u003ei_mmap);\n-\t\tflush_dcache_mmap_unlock(mapping);\n-\t\ti_mmap_unlock_write(mapping);\n-\t}\n+\tif (vma-\u003evm_file)\n+\t\tremove_vma_from_mapping(vma);\n }\n \n /*\n@@ -841,6 +857,22 @@ static int validate_mmap_request(struct file *file,\n \treturn 0;\n }\n \n+static int is_file_anonymous(struct file *file)\n+{\n+\tif (!file)\n+\t\treturn 1;\n+\n+\tif (file-\u003ef_path.dentry \u0026\u0026 file-\u003ef_path.dentry-\u003ed_inode) {\n+\t\tstruct inode *inode = file-\u003ef_path.dentry-\u003ed_inode;\n+\t\t/* if the device is /dev/zero */\n+\t\tif (S_ISCHR(inode-\u003ei_mode) \u0026\u0026\n+\t\t imajor(inode) == MEM_MAJOR \u0026\u0026 iminor(inode) == 5)\n+\t\t\treturn 1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n /*\n * we've determined that we can make the mapping, now translate what we\n * now know into VMA flags\n@@ -854,7 +886,11 @@ static vm_flags_t determine_vm_flags(struct file *file,\n \n \tvm_flags = calc_vm_prot_bits(prot, 0) | calc_vm_flag_bits(file, flags);\n \n-\tif (!file) {\n+\t/* private and file mapping will be marked anonymous later (do_mmap_private()).\n+\t * and /dev/zero is marked by them at .mmap_prepare,\n+\t * which should be _before_ this point.\n+\t */\n+\tif (is_file_anonymous(file)) {\n \t\t/*\n \t\t * MAP_ANONYMOUS. MAP_SHARED is mapped to MAP_PRIVATE, because\n \t\t * there is no fork().\n@@ -908,6 +944,29 @@ static int do_mmap_shared_file(struct vm_area_struct *vma)\n \treturn -ENODEV;\n }\n \n+static ssize_t nommu_read_iter(struct file *file, void *buf,\n+\t\t\t size_t count, loff_t *pos)\n+{\n+\tstruct iov_iter iter;\n+\tssize_t ret;\n+\tsize_t done = 0;\n+\n+\twhile (done \u003c count) {\n+\t\tstruct kvec iov = {\n+\t\t\t.iov_base = buf + done,\n+\t\t\t.iov_len = min_t(size_t, count - done, MAX_RW_COUNT),\n+\t\t};\n+\n+\t\tiov_iter_kvec(\u0026iter, ITER_DEST, \u0026iov, 1, iov.iov_len);\n+\t\tret = vfs_iter_read(file, \u0026iter, pos, 0);\n+\t\tif (ret \u003c= 0)\n+\t\t\treturn done ? done : ret;\n+\t\tdone += ret;\n+\t}\n+\n+\treturn done;\n+}\n+\n /*\n * set up a private mapping or an anonymous shared mapping\n */\n@@ -978,7 +1037,7 @@ static int do_mmap_private(struct vm_area_struct *vma,\n \t\tfpos = vma-\u003evm_pgoff;\n \t\tfpos \u003c\u003c= PAGE_SHIFT;\n \n-\t\tret = kernel_read(vma-\u003evm_file, base, len, \u0026fpos);\n+\t\tret = nommu_read_iter(vma-\u003evm_file, base, len, \u0026fpos);\n \t\tif (ret \u003c 0)\n \t\t\tgoto error_free;\n \n@@ -1065,6 +1124,28 @@ unsigned long do_mmap(struct file *file,\n \t\tvma-\u003evm_file = get_file(file);\n \t}\n \n+\t/* call mmap_prepare function if any */\n+\tif (!(flags \u0026 MAP_SHARED) \u0026\u0026 !(capabilities \u0026 NOMMU_MAP_DIRECT) \u0026\u0026\n+\t (vma-\u003evm_file \u0026\u0026 vma-\u003evm_file-\u003ef_op-\u003emmap_prepare)) {\n+\t\tstruct vm_area_desc desc;\n+\n+\t\tvma-\u003evm_start = addr;\n+\t\tvma-\u003evm_end = addr + len;\n+\n+\t\tcompat_set_desc_from_vma(\u0026desc, vma-\u003evm_file, vma);\n+\t\tret = vma-\u003evm_file-\u003ef_op-\u003emmap_prepare(\u0026desc);\n+\t\t/* private ramfs/romfs mappings fails with -ENOSYS so,\n+\t\t * fall back to copied mapping.\n+\t\t */\n+\t\tif (ret \u0026\u0026 ret != -ENOSYS)\n+\t\t\tgoto error_mmap_prepare;\n+\n+\t\tret = __compat_vma_mmap(\u0026desc, vma);\n+\t\tif (ret)\n+\t\t\tgoto error_mmap_prepare;\n+\t}\n+\n+\n \tdown_write(\u0026nommu_region_sem);\n \n \t/* if we want to share, we need to check for regions created by other\n@@ -1181,7 +1262,7 @@ unsigned long do_mmap(struct file *file,\n \tadd_nommu_region(region);\n \n \t/* clear anonymous mappings that don't ask for uninitialized data */\n-\tif (!vma-\u003evm_file \u0026\u0026\n+\tif (is_file_anonymous(vma-\u003evm_file) \u0026\u0026\n \t (!IS_ENABLED(CONFIG_MMAP_ALLOW_UNINITIALIZED) ||\n \t !(flags \u0026 MAP_UNINITIALIZED)))\n \t\tmemset((void *)region-\u003evm_start, 0,\n@@ -1232,6 +1313,18 @@ unsigned long do_mmap(struct file *file,\n \tret = -EINVAL;\n \tgoto error;\n \n+error_mmap_prepare:\n+\tif (region-\u003evm_file)\n+\t\tfput(region-\u003evm_file);\n+\tkmem_cache_free(vm_region_jar, region);\n+\tif (vma-\u003evm_file)\n+\t\tfput(vma-\u003evm_file);\n+\tvm_area_free(vma);\n+\n+\tpr_warn(\"mmap_prepare failed for %lu byte allocation from process %d\\n\",\n+\t\t\tlen, current-\u003epid);\n+\treturn ret;\n+\n error_getting_vma:\n \tkmem_cache_free(vm_region_jar, region);\n \tpr_warn(\"Allocation of vma for %lu byte allocation from process %d failed\\n\",\n@@ -1313,7 +1406,7 @@ static int split_vma(struct vma_iterator *vmi, struct vm_area_struct *vma,\n \n \t/* we're only permitted to split anonymous regions (these should have\n \t * only a single usage on the region) */\n-\tif (vma-\u003evm_file)\n+\tif (!vma_is_anonymous(vma))\n \t\treturn -ENOMEM;\n \n \tmm = vma-\u003evm_mm;\n@@ -1351,6 +1444,8 @@ static int split_vma(struct vma_iterator *vmi, struct vm_area_struct *vma,\n \tif (new-\u003evm_ops \u0026\u0026 new-\u003evm_ops-\u003eopen)\n \t\tnew-\u003evm_ops-\u003eopen(new);\n \n+\tremove_vma_from_mapping(vma);\n+\n \tdown_write(\u0026nommu_region_sem);\n \tdelete_nommu_region(vma-\u003evm_region);\n \tif (new_below) {\n@@ -1364,6 +1459,11 @@ static int split_vma(struct vma_iterator *vmi, struct vm_area_struct *vma,\n \tadd_nommu_region(new-\u003evm_region);\n \tup_write(\u0026nommu_region_sem);\n \n+\tif (new-\u003evm_file) {\n+\t\tvma-\u003evm_file = get_file(vma-\u003evm_file);\n+\t\tnew-\u003evm_file = get_file(new-\u003evm_file);\n+\t}\n+\n \tsetup_vma_to_mm(vma, mm);\n \tsetup_vma_to_mm(new, mm);\n \tvma_iter_store_new(vmi, new);\n@@ -1386,16 +1486,20 @@ static int vmi_shrink_vma(struct vma_iterator *vmi,\n \t\t unsigned long from, unsigned long to)\n {\n \tstruct vm_region *region;\n+\tbool has_mapping = !!vma-\u003evm_file;\n+\n+\tif (has_mapping)\n+\t\tremove_vma_from_mapping(vma);\n \n \t/* adjust the VMA's pointers, which may reposition it in the MM's tree\n \t * and list */\n \tif (from \u003e vma-\u003evm_start) {\n \t\tif (vma_iter_clear_gfp(vmi, from, vma-\u003evm_end, GFP_KERNEL))\n-\t\t\treturn -ENOMEM;\n+\t\t\tgoto restore_mapping;\n \t\tvma-\u003evm_end = from;\n \t} else {\n \t\tif (vma_iter_clear_gfp(vmi, vma-\u003evm_start, to, GFP_KERNEL))\n-\t\t\treturn -ENOMEM;\n+\t\t\tgoto restore_mapping;\n \t\tvma-\u003evm_start = to;\n \t}\n \n@@ -1415,7 +1519,15 @@ static int vmi_shrink_vma(struct vma_iterator *vmi,\n \tup_write(\u0026nommu_region_sem);\n \n \tfree_page_series(from, to);\n+\tif (has_mapping)\n+\t\tadd_vma_to_mapping(vma);\n+\n \treturn 0;\n+\n+restore_mapping:\n+\tif (has_mapping)\n+\t\tadd_vma_to_mapping(vma);\n+\treturn -ENOMEM;\n }\n \n /*\n@@ -1450,7 +1562,7 @@ int do_munmap(struct mm_struct *mm, unsigned long start, size_t len, struct list\n \t}\n \n \t/* we're allowed to split an anonymous VMA but not a file-backed one */\n-\tif (vma-\u003evm_file) {\n+\tif (!vma_is_anonymous(vma)) {\n \t\tdo {\n \t\t\tif (start \u003e vma-\u003evm_start)\n \t\t\t\treturn -EINVAL;\n@@ -1544,6 +1656,9 @@ static unsigned long do_mremap(unsigned long addr,\n \t\t\tunsigned long flags, unsigned long new_addr)\n {\n \tstruct vm_area_struct *vma;\n+\tint ret;\n+\n+\tVMA_ITERATOR(vmi, current-\u003emm, addr);\n \n \t/* insanity checks first */\n \told_len = PAGE_ALIGN(old_len);\n@@ -1567,11 +1682,75 @@ static unsigned long do_mremap(unsigned long addr,\n \tif (is_nommu_shared_mapping(vma-\u003evm_flags))\n \t\treturn (unsigned long) -EPERM;\n \n-\tif (new_len \u003e vma-\u003evm_region-\u003evm_end - vma-\u003evm_region-\u003evm_start)\n+\t/* vm_region-\u003evm_top != vm_region-\u003evm_end when sysctl_nr_trim_pages is 0 (default: 1) */\n+\tif (new_len \u003e vma-\u003evm_region-\u003evm_top - vma-\u003evm_region-\u003evm_start)\n \t\treturn (unsigned long) -ENOMEM;\n \n \t/* all checks complete - do it */\n-\tvma-\u003evm_end = vma-\u003evm_start + new_len;\n+\tif (new_len == old_len)\n+\t\treturn vma-\u003evm_start;\n+\n+\t/* shrink only happens addr + new_len and old_len are in different pages */\n+\tif (new_len \u003c old_len) {\n+\t\t/* like do_munmap(), we're allowed to shrink an anonymous VMA but not\n+\t\t * a file-backed one\n+\t\t */\n+\t\tif (!vma_is_anonymous(vma))\n+\t\t\treturn (unsigned long) -EINVAL;\n+\n+\t\t/* vmi_shrink_vma() needs from/to pointers to be removed,\n+\t\t * (mainly used in munmap) so, specify them.\n+\t\t */\n+\t\tret = vmi_shrink_vma(\u0026vmi, vma, addr + new_len, addr + old_len);\n+\t\tif (ret \u003c 0)\n+\t\t\treturn (unsigned long) ret;\n+\t} else {\n+\t\t/* growth path: grow up to vm_top should be handled here. */\n+\t\tunsigned long old_end = vma-\u003evm_end;\n+\t\tunsigned long end = vma-\u003evm_start + new_len;\n+\t\tunsigned long grow_len = end - old_end;\n+\n+\t\t/*\n+\t\t * Initialize the newly exposed portion before making it visible\n+\t\t * through the VMA or i_mmap.\n+\t\t */\n+\n+\t\t/* read contents of extended map from file, or zero-filled if !vm_file */\n+\t\tif (vma-\u003evm_file) {\n+\t\t\tloff_t fpos;\n+\n+\t\t\tfpos = (loff_t)vma-\u003evm_pgoff \u003c\u003c PAGE_SHIFT;\n+\t\t\tfpos += old_end - vma-\u003evm_start;\n+\n+\t\t\tret = nommu_read_iter(vma-\u003evm_file, (void *)old_end,\n+\t\t\t\t\t grow_len, \u0026fpos);\n+\t\t\tif (ret \u003c 0)\n+\t\t\t\treturn (unsigned long)ret;\n+\n+\t\t\tif (ret \u003c grow_len)\n+\t\t\t\tmemset((char *)old_end + ret, 0, grow_len - ret);\n+\t\t} else {\n+\t\t\tmemset((void *)old_end, 0, grow_len);\n+\t\t}\n+\n+\t\t/* The backing contents are ready. Now update the VMA bookkeeping. */\n+\t\tremove_vma_from_mapping(vma);\n+\n+\t\tvma-\u003evm_end = end;\n+\t\tret = vma_iter_store_gfp(\u0026vmi, vma, GFP_KERNEL);\n+\t\tif (ret) {\n+\t\t\tvma-\u003evm_end = old_end;\n+\t\t\tadd_vma_to_mapping(vma);\n+\t\t\treturn (unsigned long)ret;\n+\t\t}\n+\n+\t\t/* vm_top remains unchanged; only the logical end grows. */\n+\t\tdown_write(\u0026nommu_region_sem);\n+\t\tvma-\u003evm_region-\u003evm_end = end;\n+\t\tup_write(\u0026nommu_region_sem);\n+\n+\t\tadd_vma_to_mapping(vma);\n+\t}\n \treturn vma-\u003evm_start;\n }\n \ndiff --git a/tools/testing/selftests/kselftest.h b/tools/testing/selftests/kselftest.h\nindex ae18c491ae533..ac21bf3d802e5 100644\n--- a/tools/testing/selftests/kselftest.h\n+++ b/tools/testing/selftests/kselftest.h\n@@ -58,6 +58,7 @@\n #include \u003cstring.h\u003e\n #include \u003cstdio.h\u003e\n #include \u003csys/utsname.h\u003e\n+#include \u003cstdint.h\u003e\n #endif\n \n #ifndef ARRAY_SIZE\n@@ -81,6 +82,48 @@\n #endif\n #endif /* end arch */\n \n+#if !defined(NOLIBC) \u0026\u0026 !defined(__GLIBC__)\n+#ifdef __LP64__\n+typedef int64_t __fsword_t;\n+#else\n+typedef int32_t __fsword_t;\n+#endif\n+\n+/*\n+ * for a workaround to avoid struct conflict under\n+ * musl-libc (\u003csys/prctl.h\u003e v.s. \u003clinux/prctl.h\u003e)\n+ */\n+#include \u003csys/prctl.h\u003e\n+#ifndef _LINUX_PRCTL_H\n+#define _LINUX_PRCTL_H\n+#endif\n+\n+#ifndef PR_SET_MDWE\n+#define PR_SET_MDWE 65\n+#endif\n+\n+#ifndef PR_MDWE_REFUSE_EXEC_GAIN\n+#define PR_MDWE_REFUSE_EXEC_GAIN (1UL \u003c\u003c 0)\n+#endif\n+\n+#ifndef PR_MDWE_NO_INHERIT\n+#define PR_MDWE_NO_INHERIT (1UL \u003c\u003c 1)\n+#endif\n+\n+#ifndef PR_GET_MDWE\n+#define PR_GET_MDWE 66\n+#endif\n+\n+#ifndef PR_SET_MEMORY_MERGE\n+#define PR_SET_MEMORY_MERGE 67\n+#endif\n+\n+#ifndef PR_GET_MEMORY_MERGE\n+#define PR_GET_MEMORY_MERGE 68\n+#endif\n+\n+#endif\n+\n /* define kselftest exit codes */\n #define KSFT_PASS 0\n #define KSFT_FAIL 1\ndiff --git a/tools/testing/selftests/kselftest/runner.sh b/tools/testing/selftests/kselftest/runner.sh\nindex 311811dc55a07..7287d8290b6c9 100644\n--- a/tools/testing/selftests/kselftest/runner.sh\n+++ b/tools/testing/selftests/kselftest/runner.sh\n@@ -38,8 +38,12 @@ tap_prefix()\n \n tap_timeout()\n {\n+\t# nommu doesn't support timeout command (missing fork(2))\n+\tif [ \"$NOMMU\" = \"1\" ] ; then\n+\t\techo \"timeout isn't supported for nommu\"\n+\t\t$1\n \t# Make sure tests will time out if utility is available.\n-\tif [ -x /usr/bin/timeout ] ; then\n+\telif [ -x /usr/bin/timeout ] ; then\n \t\t/usr/bin/timeout --foreground \"$kselftest_timeout\" \\\n \t\t\t/usr/bin/timeout \"$kselftest_timeout\" $1\n \telse\n@@ -130,6 +134,7 @@ run_one()\n \t\t\t\treturn $KSFT_FAIL\n \t\t\tfi\n \t\tfi\n+\t\tOLDDIR=$(pwd)\n \t\tcd `dirname $TEST` \u003e /dev/null\n \t\t(((( tap_timeout \"$cmd\" 2\u003e\u00261; echo $? \u003e\u00263) |\n \t\t\ttap_prefix \u003e\u00264) 3\u003e\u00261) |\n@@ -147,7 +152,7 @@ run_one()\n \t\t*)\n \t\t\tktap_test_fail \"$TEST_HDR_MSG # exit=$rc\";;\n \t\tesac\n-\t\tcd - \u003e/dev/null\n+\t\tcd \"$OLDDIR\" \u003e/dev/null\n \tfi\n \n \treturn $rc\ndiff --git a/tools/testing/selftests/kselftest_harness.h b/tools/testing/selftests/kselftest_harness.h\nindex 261e4df94d9d5..8eee7b14f8248 100644\n--- a/tools/testing/selftests/kselftest_harness.h\n+++ b/tools/testing/selftests/kselftest_harness.h\n@@ -1274,6 +1274,10 @@ static int test_harness_run(int argc, char **argv)\n \tunsigned int count = 0;\n \tunsigned int pass_count = 0;\n \n+#ifdef CONFIG_NOMMU\n+\tksft_print_msg(\"harness test doesn't support on NOMMU architecture (no fork(2)).\\n\");\n+\treturn KSFT_SKIP;\n+#endif /* CONFIG_NOMMU */\n \tret = test_harness_argv_check(argc, argv);\n \tif (ret != KSFT_PASS)\n \t\treturn ret;\ndiff --git a/tools/testing/selftests/lib.mk b/tools/testing/selftests/lib.mk\nindex f02cc8a2e4ae3..4734b5ce613f0 100644\n--- a/tools/testing/selftests/lib.mk\n+++ b/tools/testing/selftests/lib.mk\n@@ -97,6 +97,14 @@ TEST_GEN_PROGS := $(patsubst %,$(OUTPUT)/%,$(TEST_GEN_PROGS))\n TEST_GEN_PROGS_EXTENDED := $(patsubst %,$(OUTPUT)/%,$(TEST_GEN_PROGS_EXTENDED))\n TEST_GEN_FILES := $(patsubst %,$(OUTPUT)/%,$(TEST_GEN_FILES))\n \n+# detect if users request NOMMU build or not\n+# User can set NOMMU to 1 to build/test for NOMMU platforms\n+NOMMU ?= 0\n+ifeq ($(NOMMU),1)\n+CFLAGS += -DCONFIG_NOMMU\n+export NOMMU\n+endif\n+\n all: $(TEST_GEN_PROGS) $(TEST_GEN_PROGS_EXTENDED) $(TEST_GEN_FILES) \\\n \t$(if $(TEST_GEN_MODS_DIR),gen_mods_dir)\n \n@@ -197,7 +205,7 @@ clean: $(if $(TEST_GEN_MODS_DIR),clean_mods_dir)\n \t$(CLEAN)\n \n # Build with _GNU_SOURCE by default\n-CFLAGS += -D_GNU_SOURCE=\n+CFLAGS += -D_GNU_SOURCE= -D_LARGEFILE64_SOURCE\n \n # Additional include paths needed by kselftest.h and local headers\n CFLAGS += -I${top_srcdir}/tools/testing/selftests\ndiff --git a/tools/testing/selftests/mm/Makefile b/tools/testing/selftests/mm/Makefile\nindex e6df968f0971c..3986ae7d75bca 100644\n--- a/tools/testing/selftests/mm/Makefile\n+++ b/tools/testing/selftests/mm/Makefile\n@@ -185,6 +185,9 @@ TEST_FILES += run_vmtests.sh\n # required by charge_reserved_hugetlb.sh\n TEST_FILES += write_hugetlb_memory.sh\n \n+TEST_GEN_PROGS += nommu_mmap_test\n+TEST_GEN_PROGS += nommu_mremap_test\n+\n include ../lib.mk\n \n $(TEST_GEN_PROGS): vm_util.c hugepage_settings.c\ndiff --git a/tools/testing/selftests/mm/hugetlb_dio.c b/tools/testing/selftests/mm/hugetlb_dio.c\nindex fb4600570e131..aee6be530ccbf 100644\n--- a/tools/testing/selftests/mm/hugetlb_dio.c\n+++ b/tools/testing/selftests/mm/hugetlb_dio.c\n@@ -22,12 +22,9 @@\n #include \"kselftest.h\"\n #include \"hugepage_settings.h\"\n \n-#ifndef STATX_DIOALIGN\n-#define STATX_DIOALIGN\t\t0x00002000U\n-#endif\n-\n static int get_dio_alignment(int fd)\n {\n+#ifdef STATX_DIOALIGN\n \tstruct statx stx;\n \tint ret;\n \n@@ -43,6 +40,9 @@ static int get_dio_alignment(int fd)\n \t\treturn 1;\n \n \treturn stx.stx_dio_offset_align;\n+#else\n+\treturn -1;\n+#endif\n }\n \n static bool check_dio_alignment(unsigned int start_off,\ndiff --git a/tools/testing/selftests/mm/mdwe_test.c b/tools/testing/selftests/mm/mdwe_test.c\nindex 647779653da09..031c79ed067ea 100644\n--- a/tools/testing/selftests/mm/mdwe_test.c\n+++ b/tools/testing/selftests/mm/mdwe_test.c\n@@ -1,5 +1,7 @@\n // SPDX-License-Identifier: GPL-2.0\n \n+#include \"kselftest_harness.h\"\n+\n #ifdef __aarch64__\n #include \u003casm/hwcap.h\u003e\n #endif\n@@ -14,8 +16,6 @@\n #include \u003csys/wait.h\u003e\n #include \u003cunistd.h\u003e\n \n-#include \"kselftest_harness.h\"\n-\n #ifndef __aarch64__\n # define PROT_BTI\t0\n #endif\ndiff --git a/tools/testing/selftests/mm/nommu_mmap_test.c b/tools/testing/selftests/mm/nommu_mmap_test.c\nnew file mode 100644\nindex 0000000000000..50ce385967f4c\n--- /dev/null\n+++ b/tools/testing/selftests/mm/nommu_mmap_test.c\n@@ -0,0 +1,294 @@\n+// SPDX-License-Identifier: GPL-2.0\n+#define _GNU_SOURCE\n+#include \u003cstdio.h\u003e\n+#include \u003cstdlib.h\u003e\n+#include \u003csys/mman.h\u003e\n+#include \u003cunistd.h\u003e\n+#include \u003cfcntl.h\u003e\n+#include \u003cerrno.h\u003e\n+#include \u003cstring.h\u003e\n+#include \u003climits.h\u003e\n+#include \"../kselftest.h\"\n+\n+#include \u003csys/vfs.h\u003e\n+#ifndef RAMFS_MAGIC\n+#define RAMFS_MAGIC 0x858458f6\n+#endif\n+\n+#ifndef MAP_UNINITIALIZED\n+#define MAP_UNINITIALIZED 0x4000000\n+#endif\n+\n+static size_t ps;\n+\n+struct test_case_t {\n+\tconst char *name;\n+\tconst char *pathname;\n+\tint open_flags;\n+\tint mmap_prot;\n+\tint mmap_flags;\n+\tint exp_err;\n+\tint (*resolve_exp_err)(const char *path);\n+};\n+\n+static int get_shm_expected_error(const char *path)\n+{\n+\tstruct statfs fs;\n+\n+\tif (statfs(path, \u0026fs) == 0) {\n+\t\tif (fs.f_type == RAMFS_MAGIC)\n+\t\t\treturn 0; /* ramfs succeed with contiguous memory */\n+\t}\n+\t /* hostfs, etc returns ENODEV due to lack of contiguous allocation */\n+\treturn ENODEV;\n+}\n+\n+static struct test_case_t test_cases[] = {\n+\t{\n+\t\t\"Anonymous private allocation\",\n+\t\tNULL,\n+\t\tO_CREAT | O_RDWR | O_EXCL,\n+\t\tPROT_READ | PROT_WRITE,\n+\t\tMAP_ANONYMOUS | MAP_PRIVATE,\n+\t\t0,\n+\t\tNULL,\n+\t},\n+\t{\n+\t\t\"Non-anonymous private file mapping (rw-)\",\n+\t\t\"/tmp/ksft.nommu-reg-XXXXXX\",\n+\t\tO_CREAT | O_RDWR | O_EXCL,\n+\t\tPROT_READ | PROT_WRITE,\n+\t\tMAP_PRIVATE,\n+\t\t0,\n+\t\t0,\n+\t},\n+\t{\n+\t\t\"Non-anonymous private file mapping (r--)\",\n+\t\t\"/tmp/ksft.nommu-reg-XXXXXX\",\n+\t\tO_CREAT | O_RDWR | O_EXCL,\n+\t\tPROT_READ,\n+\t\tMAP_PRIVATE,\n+\t\t0,\n+\t\t0,\n+\t},\n+\t{\n+\t\t\"Non-anonymous shared file mapping (rw-)\",\n+\t\t\"/tmp/ksft.nommu-shm-XXXXXX\",\n+\t\tO_CREAT | O_RDWR | O_EXCL,\n+\t\tPROT_READ | PROT_WRITE,\n+\t\tMAP_SHARED,\n+\t\t0,\n+#ifdef CONFIG_NOMMU\n+\t\tget_shm_expected_error,\n+#else\n+\t\t0,\n+#endif\n+\t},\n+\t{\n+\t\t\"Non-anonymous shared file mapping (r--)\",\n+\t\t\"/tmp/ksft.nommu-shm-XXXXXX\",\n+\t\tO_CREAT | O_RDWR | O_EXCL,\n+\t\tPROT_READ,\n+\t\tMAP_SHARED,\n+\t\t0,\n+#ifdef CONFIG_NOMMU\n+\t\tget_shm_expected_error,\n+#else\n+\t\t0,\n+#endif\n+\t},\n+\t{\n+\t\t\"Memory-backed private storage via /dev/zero\",\n+\t\t\"/dev/zero\",\n+\t\tO_RDONLY,\n+\t\tPROT_READ,\n+\t\tMAP_PRIVATE,\n+\t\t0,\n+\t\tNULL,\n+\t},\n+\t{\n+\t\t\"Memory-backed storage via /dev/zero (MAP_SHARED)\",\n+\t\t\"/dev/zero\",\n+\t\tO_RDONLY,\n+\t\tPROT_READ,\n+\t\tMAP_SHARED,\n+#ifdef CONFIG_NOMMU\n+\t\tENODEV,\n+#else\n+\t\t0,\n+#endif\n+\t\tNULL,\n+\t},\n+\t{\n+\t\t\"Block device volatile node mapping\",\n+\t\t\"/dev/loop0\",\n+\t\tO_RDWR,\n+\t\tPROT_READ | PROT_WRITE,\n+\t\tMAP_PRIVATE,\n+\t\t0,\n+\t\tNULL,\n+\t}\n+};\n+\n+static int run_mapping_matrix_test(struct test_case_t *tcase)\n+{\n+\tint fd;\n+\tvoid *ptr;\n+\tchar path_buf[PATH_MAX];\n+\tint rc = KSFT_PASS;\n+\tint expected_error;\n+\n+\tksft_print_msg(\"[RUN] Testing: %s\\n\", tcase-\u003ename);\n+\n+\tif (tcase-\u003epathname == NULL) {\n+\t\tfd = -1;\n+\t} else if (strstr(tcase-\u003epathname, \"XXXXXX\")) {\n+\t\tstrncpy(path_buf, tcase-\u003epathname, sizeof(path_buf) - 1);\n+\t\tpath_buf[sizeof(path_buf) - 1] = '\\0';\n+\t\tfd = mkstemp(path_buf);\n+\t\tif (fd \u003c 0) {\n+\t\t\tksft_test_result_skip(\"Failed to setup temp node: %s\\n\",\n+\t\t\t\t\t tcase-\u003epathname);\n+\t\t\treturn KSFT_SKIP;\n+\t\t}\n+\t\tif (ftruncate(fd, ps) != 0) {\n+\t\t\tksft_test_result_fail(\"ftruncate failed for: %s\\n\",\n+\t\t\t\t\t tcase-\u003epathname);\n+\t\t\tclose(fd);\n+\t\t\tunlink(path_buf);\n+\t\t\treturn KSFT_FAIL;\n+\t\t}\n+\t} else {\n+\t\tfd = open(tcase-\u003epathname, tcase-\u003eopen_flags, 0600);\n+\t\tif (fd \u003c 0) {\n+\t\t\tksft_test_result_skip(\"Device node not accessible: %s\\n\",\n+\t\t\t\t tcase-\u003epathname);\n+\t\t\treturn KSFT_SKIP;\n+\t\t}\n+\t}\n+\n+\texpected_error = tcase-\u003eexp_err;\n+\tif (tcase-\u003eresolve_exp_err \u0026\u0026 fd \u003e= 0)\n+\t\texpected_error = tcase-\u003eresolve_exp_err(path_buf);\n+\n+\tptr = mmap(NULL, ps, tcase-\u003emmap_prot, tcase-\u003emmap_flags, fd, 0);\n+\n+\tif (expected_error != 0) {\n+\t\tif (ptr != MAP_FAILED) {\n+\t\t\tksft_test_result_fail(\"%s: mmap unexpectedly succeeded (exp error %d)\\n\",\n+\t\t\t\t\t tcase-\u003ename, expected_error);\n+\t\t\tmunmap(ptr, ps);\n+\t\t\trc = KSFT_FAIL;\n+\t\t\tgoto cleanup;\n+\t\t}\n+\t\tif (errno != expected_error) {\n+\t\t\tksft_test_result_fail(\"%s: mmap failed with %d (%s), but expected %d\\n\",\n+\t\t\t\t\t tcase-\u003ename, errno, strerror(errno), expected_error);\n+\t\t\trc = KSFT_FAIL;\n+\t\t\tgoto cleanup;\n+\t\t}\n+\t\tksft_test_result_pass(\"%s: Correctly rejected with expected error %s(%d)\\n\",\n+\t\t\t\t tcase-\u003ename, strerror(expected_error), expected_error);\n+\t\trc = KSFT_PASS;\n+\t\tgoto cleanup;\n+\t}\n+\n+\tif (ptr == MAP_FAILED) {\n+\t\tksft_test_result_fail(\"%s: mmap failed unexpectedly: %s\\n\",\n+\t\t\t\t tcase-\u003ename, strerror(errno));\n+\t\trc = KSFT_FAIL;\n+\t\tgoto cleanup;\n+\t}\n+\n+\tksft_test_result_pass(\"%s: mmap validation successfully passed\\n\", tcase-\u003ename);\n+\tmunmap(ptr, ps);\n+\n+cleanup:\n+\tif (fd \u003e= 0) {\n+\t\tclose(fd);\n+\t\tif (tcase-\u003epathname \u0026\u0026 strstr(tcase-\u003epathname, \"XXXXXX\"))\n+\t\t\tunlink(path_buf);\n+\t}\n+\treturn rc;\n+}\n+\n+static int test_map_fixed(void)\n+{\n+\tvoid *ptr = mmap((void *)(ps * 100), ps, PROT_READ | PROT_WRITE,\n+\t\t\t MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);\n+\n+\tksft_print_msg(\"[RUN] Testing MAP_FIXED behavior\\n\");\n+\n+#ifdef CONFIG_NOMMU\n+\tif (ptr == MAP_FAILED \u0026\u0026 (errno == ENODEV || errno == EINVAL)) {\n+\t\tksft_test_result_pass(\"MAP_FIXED correctly rejected under nommu\\n\");\n+\t\treturn KSFT_PASS;\n+\t}\n+\tif (ptr != MAP_FAILED) {\n+\t\tksft_test_result_fail(\"MAP_FIXED unexpectedly allowed under nommu\\n\");\n+\t\tmunmap(ptr, ps);\n+\t\treturn KSFT_FAIL;\n+\t}\n+\tksft_test_result_fail(\"MAP_FIXED failed under NOMMU: %s\\n\",\n+\t\t\t strerror(errno));\n+\treturn KSFT_FAIL;\n+#else\n+\tif (ptr != MAP_FAILED) {\n+\t\tksft_test_result_pass(\"MAP_FIXED successfully allocated under MMU\\n\");\n+\t\tmunmap(ptr, ps);\n+\t\treturn KSFT_PASS;\n+\t}\n+\tksft_test_result_fail(\"MAP_FIXED failed allocation under MMU\\n\");\n+\treturn KSFT_FAIL;\n+#endif\n+}\n+\n+static int test_uninit(void)\n+{\n+\tvoid *ptr = mmap(NULL, ps, PROT_READ | PROT_WRITE,\n+\t\t\t MAP_PRIVATE | MAP_ANONYMOUS | MAP_UNINITIALIZED, -1, 0);\n+\n+\tksft_print_msg(\"[RUN] Testing MAP_UNINITIALIZED behavior\\n\");\n+\n+\tif (ptr == MAP_FAILED) {\n+\t\tksft_test_result_skip(\"MAP_UNINITIALIZED not supported by kernel config\\n\");\n+\t\treturn KSFT_SKIP;\n+\t}\n+\n+\tksft_test_result_pass(\"MAP_UNINITIALIZED allocation successful\\n\");\n+\tmunmap(ptr, ps);\n+\treturn KSFT_PASS;\n+}\n+\n+int main(int argc, char **argv)\n+{\n+\tint result = KSFT_PASS;\n+\tint i, rc;\n+\n+\tps = sysconf(_SC_PAGESIZE);\n+\tksft_print_header();\n+\tksft_set_plan(ARRAY_SIZE(test_cases) + 2);\n+\n+#ifdef CONFIG_NOMMU\n+\tksft_print_msg(\"Running strict MMAP test criteria under nommu architecture\\n\");\n+#else\n+\tksft_print_msg(\"Running MMAP test criteria under MMU architecture\\n\");\n+#endif\n+\n+\tif (test_map_fixed() == KSFT_FAIL)\n+\t\tresult = KSFT_FAIL;\n+\n+\tif (test_uninit() == KSFT_FAIL)\n+\t\tresult = KSFT_FAIL;\n+\n+\tfor (i = 0; i \u003c (int)ARRAY_SIZE(test_cases); i++) {\n+\t\trc = run_mapping_matrix_test(\u0026test_cases[i]);\n+\t\tif (rc == KSFT_FAIL)\n+\t\t\tresult = KSFT_FAIL;\n+\t}\n+\n+\tif (result == KSFT_PASS)\n+\t\tksft_finished();\n+\n+\tksft_exit_fail();\n+}\ndiff --git a/tools/testing/selftests/mm/nommu_mremap_test.c b/tools/testing/selftests/mm/nommu_mremap_test.c\nnew file mode 100644\nindex 0000000000000..b1b8a4e64b555\n--- /dev/null\n+++ b/tools/testing/selftests/mm/nommu_mremap_test.c\n@@ -0,0 +1,583 @@\n+// SPDX-License-Identifier: GPL-2.0\n+#define _GNU_SOURCE\n+#include \u003cstdio.h\u003e\n+#include \u003cstdlib.h\u003e\n+#include \u003csys/mman.h\u003e\n+#include \u003cunistd.h\u003e\n+#include \u003cfcntl.h\u003e\n+#include \u003cerrno.h\u003e\n+#include \u003cstring.h\u003e\n+#include \u003climits.h\u003e\n+#include \"../kselftest.h\"\n+\n+#include \u003csys/vfs.h\u003e\n+#ifndef RAMFS_MAGIC\n+#define RAMFS_MAGIC 0x858458f6\n+#endif\n+\n+static size_t ps;\n+\n+static long get_fs_type(const char *path)\n+{\n+\tstruct statfs fs;\n+\n+\tif (statfs(path, \u0026fs) == 0)\n+\t\treturn fs.f_type;\n+\n+\treturn 0;\n+}\n+\n+/* return original value if succeed */\n+static int set_nr_trim_pages(const char *value)\n+{\n+\tint fd, orig_value;\n+\tssize_t len, written, read_len;\n+\tchar orig_buf[32];\n+\n+\tfd = open(\"/proc/sys/vm/nr_trim_pages\", O_RDWR);\n+\tif (fd \u003c 0)\n+\t\treturn -errno;\n+\n+\tread_len = read(fd, orig_buf, sizeof(orig_buf) - 1);\n+\tif (read_len \u003c 0) {\n+\t\tclose(fd);\n+\t\treturn -errno;\n+\t}\n+\tif (read_len == 0) {\n+\t\tclose(fd);\n+\t\treturn -EIO;\n+\t}\n+\n+\torig_buf[read_len] = '\\0';\n+\torig_value = atoi(orig_buf);\n+\n+\tif (lseek(fd, 0, SEEK_SET) \u003c 0) {\n+\t\tclose(fd);\n+\t\treturn -errno;\n+\t}\n+\n+\tlen = strlen(value);\n+\twritten = write(fd, value, len);\n+\tclose(fd);\n+\n+\tif (written != len)\n+\t\treturn written \u003c 0 ? -errno : -EIO;\n+\n+\treturn orig_value \u003e= 0 ? orig_value : -EINVAL;\n+}\n+\n+static void munmap_shrink_test(void)\n+{\n+\tvoid *addr;\n+\n+\t/* munmap shrink test */\n+\tfor (int i = 0; i \u003c 4; i++) {\n+\t\taddr = mmap(NULL, ps * 4, PROT_READ | PROT_WRITE,\n+\t\t\t MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);\n+\t\tif (addr == MAP_FAILED) {\n+\t\t\tksft_test_result_fail(\"mmap failed: %s(%d)\\n\", strerror(errno), errno);\n+\t\t\treturn;\n+\t\t}\n+\t\tif (munmap(addr + ps * i, ps) != 0) {\n+\t\t\tksft_test_result_fail(\"memory %p isn't unmapped at %p\\n\",\n+\t\t\t\t\t addr, addr + ps * i);\n+\t\t\tfor (int j = 0; j \u003c 4; j++)\n+\t\t\t\tmunmap((char *)addr + j * ps, ps);\n+\t\t\treturn;\n+\t\t}\n+\n+\t\tif (i == 0) {\n+\t\t\tif (munmap(addr + ps, ps * 3))\n+\t\t\t\tgoto error;\n+\t\t} else if (i == 1) {\n+\t\t\tif (munmap(addr, ps) || munmap(addr + (ps * 2), ps * 2))\n+\t\t\t\tgoto error;\n+\t\t} else if (i == 2) {\n+\t\t\tif (munmap(addr, ps * 2) || munmap(addr + (ps * 3), ps))\n+\t\t\t\tgoto error;\n+\t\t} else if (i == 3) {\n+\t\t\tif (munmap(addr, ps * 3))\n+\t\t\t\tgoto error;\n+\t\t}\n+\t}\n+\n+\tksft_test_result_pass(\"%s success\\n\", __func__);\n+\treturn;\n+error:\n+\tfor (int j = 0; j \u003c 4; j++)\n+\t\tmunmap((char *)addr + j * ps, ps);\n+\tksft_test_result_fail(\"%s clean up failures\\n\", __func__);\n+}\n+\n+static size_t page_align(size_t len)\n+{\n+\treturn (len + ps - 1) / ps * ps;\n+}\n+\n+static void mremap_shrink_test(void)\n+{\n+\tvoid *addr, *addr2;\n+\tsize_t current_len;\n+\tsize_t old_len, new_len;\n+\tstruct param {\n+\t\tsize_t old;\n+\t\tsize_t new;\n+\t} params[] = {\n+\t\t/* should not happen any shrink */\n+\t\t{ .old = ps * 4 - 1, .new = ps * 4 - 2 },\n+\t\t/* should not happen any shrink */\n+\t\t{ .old = ps * 4 - 1, .new = ps * 4 },\n+\t\t{ .old = ps * 4, .new = ps * 2 },\n+\t\t/* should not happen any shrink */\n+\t\t{ .old = ps * 2, .new = ps * 2 - 2 },\n+\t\t{ .old = ps * 2 - 2, .new = ps * 1 },\n+\t};\n+\n+\t/* mremap shrink test */\n+\tcurrent_len = page_align(ps * 4 - 1);\n+\taddr = mmap(NULL, ps * 4 - 1, PROT_READ | PROT_WRITE,\n+\t\t MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);\n+\tif (addr == MAP_FAILED) {\n+\t\tksft_test_result_fail(\"mmap failed: %s(%d)\\n\", strerror(errno), errno);\n+\t\treturn;\n+\t}\n+\n+\tfor (int i = 0; i \u003c ARRAY_SIZE(params); i++) {\n+\t\told_len = page_align(params[i].old);\n+\t\tnew_len = page_align(params[i].new);\n+\t\taddr2 = mremap(addr, old_len, new_len, MREMAP_MAYMOVE);\n+\t\tif (addr2 == MAP_FAILED) {\n+\t\t\tksft_test_result_fail(\"memory %p isn't remapped at %p\\n\", addr, addr2);\n+\t\t\tmunmap(addr, old_len);\n+\t\t\treturn;\n+\t\t}\n+\n+\t\taddr = addr2;\n+\t\tcurrent_len = new_len;\n+\t}\n+\n+\tif (munmap(addr, current_len)) {\n+\t\tksft_test_result_fail(\"%s cleanup failed: %s\\n\",\n+\t\t\t\t __func__, strerror(errno));\n+\t\treturn;\n+\t}\n+\tksft_test_result_pass(\"%s success\\n\", __func__);\n+}\n+\n+static void zero_middle_munmap_test(void)\n+{\n+\tint fd = -1;\n+\tvoid *addr = MAP_FAILED;\n+\tvoid *addr2 = MAP_FAILED;\n+\tint left_mapped = 0;\n+\tint right_mapped = 0;\n+\tint ret;\n+\tbool addr2_middle_unmapped = false;\n+\n+\tksft_print_msg(\"[RUN] Testing middle munmap of private /dev/zero mappings\\n\");\n+\n+\tfd = open(\"/dev/zero\", O_RDONLY);\n+\tif (fd \u003c 0) {\n+\t\tksft_test_result_skip(\"Unable to open /dev/zero: %s\\n\",\n+\t\t\t\t strerror(errno));\n+\t\treturn;\n+\t}\n+\n+\taddr = mmap(NULL, ps * 3, PROT_READ | PROT_WRITE,\n+\t\t MAP_PRIVATE, fd, 0);\n+\tclose(fd);\n+\tfd = -1;\n+\tif (addr == MAP_FAILED) {\n+\t\tksft_test_result_fail(\"Initial /dev/zero mmap failed: %s\\n\",\n+\t\t\t\t strerror(errno));\n+\t\treturn;\n+\t}\n+\n+\tleft_mapped = 1;\n+\tright_mapped = 1;\n+\n+\t/*\n+\t * Split one private /dev/zero VMA in the middle. On nommu, the\n+\t * VMA still has vm_file set even though it is semantically\n+\t * anonymous.\n+\t */\n+\tret = munmap((char *)addr + ps, ps);\n+\tif (ret) {\n+\t\tksft_test_result_fail(\"Middle munmap failed: %s\\n\",\n+\t\t\t\t strerror(errno));\n+\t\tmunmap(addr, ps * 3);\n+\t\tleft_mapped = 0;\n+\t\tright_mapped = 0;\n+\t\tgoto out;\n+\t}\n+\n+\t/*\n+\t * Access both remaining VMAs so a stale VMA interval cannot remain\n+\t * hidden behind the unmapped hole.\n+\t */\n+\t((volatile unsigned char *)addr)[0] = 0x5a;\n+\t((volatile unsigned char *)addr + 2 * ps)[0] = 0xa5;\n+\n+\t/*\n+\t * Repeat the operation on another mapping of the same inode. This\n+\t * exercises insertion and removal in /dev/zero's i_mmap interval\n+\t * tree after the first split.\n+\t */\n+\tfd = open(\"/dev/zero\", O_RDONLY);\n+\tif (fd \u003c 0) {\n+\t\tksft_test_result_fail(\"Reopening /dev/zero failed: %s\\n\",\n+\t\t\t\t strerror(errno));\n+\t\tgoto out;\n+\t}\n+\n+\taddr2 = mmap(NULL, ps * 3, PROT_READ | PROT_WRITE,\n+\t\t MAP_PRIVATE, fd, 0);\n+\tclose(fd);\n+\tfd = -1;\n+\tif (addr2 == MAP_FAILED) {\n+\t\tksft_test_result_fail(\"Second /dev/zero mmap failed: %s\\n\",\n+\t\t\t\t strerror(errno));\n+\t\tgoto out;\n+\t}\n+\n+\tret = munmap((char *)addr2 + ps, ps);\n+\tif (ret) {\n+\t\tksft_test_result_fail(\"Second middle munmap failed: %s\\n\",\n+\t\t\t\t strerror(errno));\n+\t\tgoto out;\n+\t}\n+\taddr2_middle_unmapped = true;\n+\n+\tif (munmap(addr, ps)) {\n+\t\tksft_test_result_fail(\"Left VMA munmap failed: %s\\n\",\n+\t\t\t\t strerror(errno));\n+\t\tgoto out;\n+\t}\n+\tleft_mapped = 0;\n+\n+\tif (munmap((char *)addr + 2 * ps, ps)) {\n+\t\tksft_test_result_fail(\"Right VMA munmap failed: %s\\n\",\n+\t\t\t\t strerror(errno));\n+\t\tgoto out;\n+\t}\n+\tright_mapped = 0;\n+\n+\tif (munmap(addr2, ps)) {\n+\t\tksft_test_result_fail(\"Second left VMA munmap failed: %s\\n\",\n+\t\t\t\t strerror(errno));\n+\t\tgoto out;\n+\t}\n+\n+\tif (munmap((char *)addr2 + 2 * ps, ps)) {\n+\t\tksft_test_result_fail(\"Second right VMA munmap failed: %s\\n\",\n+\t\t\t\t strerror(errno));\n+\t\tgoto out;\n+\t}\n+\taddr2 = MAP_FAILED;\n+\n+\tksft_test_result_pass(\"%s success\\n\", __func__);\n+\n+out:\n+\tif (fd \u003e= 0)\n+\t\tclose(fd);\n+\tif (left_mapped)\n+\t\tmunmap(addr, ps);\n+\tif (right_mapped)\n+\t\tmunmap((char *)addr + 2 * ps, ps);\n+\tif (addr2 != MAP_FAILED) {\n+\t\tif (addr2_middle_unmapped) {\n+\t\t\tmunmap(addr2, ps);\n+\t\t\tmunmap((char *)addr2 + 2 * ps, ps);\n+\t\t} else {\n+\t\t\t/* The middle munmap failed, so all three pages remain mapped. */\n+\t\t\tmunmap(addr2, ps * 3);\n+\t\t}\n+\t}\n+}\n+\n+static int get_shared_writable_file_expected_error(const char *path)\n+{\n+\tif (get_fs_type(path) == RAMFS_MAGIC)\n+\t\treturn EPERM; /* ramfs failed */\n+\n+\treturn 0;\n+}\n+\n+static int pre_conf_trim_page(void)\n+{\n+\treturn set_nr_trim_pages(\"0\\n\");\n+}\n+\n+static int post_conf_trim_page(int value)\n+{\n+\tchar buf[32];\n+\n+\tif (snprintf(buf, sizeof(buf), \"%d\\n\", value) \u003c 0)\n+\t\treturn -EIO;\n+\n+\treturn set_nr_trim_pages(buf);\n+}\n+\n+struct mremap_case_t {\n+\tconst char *name;\n+\tconst char *pathname;\n+\tint open_flags;\n+\tint mmap_prot;\n+\tint mmap_flags;\n+\tint exp_err;\n+\tint (*resolve_exp_err)(const char *path);\n+\tunsigned int old_pages;\n+\tunsigned int new_pages;\n+\tint (*pre_hook)(void);\n+\tint (*post_hook)(int value);\n+};\n+\n+static struct mremap_case_t mremap_cases[] = {\n+\t{\n+\t\t.name = \"anonymous shrink (r--)\",\n+\t\t.pathname = NULL,\n+\t\t.open_flags = O_CREAT | O_RDWR | O_EXCL,\n+\t\t.mmap_prot = PROT_READ,\n+\t\t.mmap_flags = MAP_ANONYMOUS | MAP_PRIVATE,\n+\t\t.exp_err = 0,\n+\t\t.resolve_exp_err = 0,\n+\t},\n+\t{\n+\t\t.name = \"shared file shrink (r--)\",\n+\t\t.pathname = \"/tmp/ksft.nommu-remap-XXXXXX\",\n+\t\t.open_flags = O_CREAT | O_RDWR | O_EXCL,\n+\t\t.mmap_prot = PROT_READ,\n+\t\t.mmap_flags = MAP_SHARED,\n+\t\t.exp_err = 0,\n+#ifdef CONFIG_NOMMU\n+\t\t.resolve_exp_err = get_shared_writable_file_expected_error,\n+#else\n+\t\t.resolve_exp_err = 0,\n+#endif\n+\t},\n+\t{\n+\t\t.name = \"zero device shrink (r--)\",\n+\t\t.pathname = \"/dev/zero\",\n+\t\t.open_flags = O_RDONLY,\n+\t\t.mmap_prot = PROT_READ,\n+\t\t.mmap_flags = MAP_PRIVATE,\n+\t\t.exp_err = 0,\n+\t\t.resolve_exp_err = 0,\n+\t},\n+\t{\n+\t\t.name = \"private file unchanged length (r-)\",\n+\t\t.pathname = \"/tmp/ksft.nommu-remap-XXXXXX\",\n+\t\t.open_flags = O_CREAT | O_RDWR | O_EXCL,\n+\t\t.mmap_prot = PROT_READ,\n+\t\t.mmap_flags = MAP_PRIVATE,\n+#ifdef CONFIG_NOMMU\n+\t\t.exp_err = EPERM,\n+#else\n+\t\t.exp_err = 0,\n+#endif\n+\t\t.resolve_exp_err = 0,\n+\t\t.old_pages = 4,\n+\t\t.new_pages = 4,\n+\t},\n+\t{\n+\t\t.name = \"private file unchanged length (rw-)\",\n+\t\t.pathname = \"/tmp/ksft.nommu-remap-XXXXXX\",\n+\t\t.open_flags = O_CREAT | O_RDWR | O_EXCL,\n+\t\t.mmap_prot = PROT_READ | PROT_WRITE,\n+\t\t.mmap_flags = MAP_PRIVATE,\n+\t\t.exp_err = 0,\n+\t\t.resolve_exp_err = 0,\n+\t\t.old_pages = 4,\n+\t\t.new_pages = 4,\n+\t},\n+\t{\n+\t\t.name = \"private file growth (r-)\",\n+\t\t.pathname = \"/tmp/ksft.nommu-remap-XXXXXX\",\n+\t\t.open_flags = O_CREAT | O_RDWR | O_EXCL,\n+\t\t.mmap_prot = PROT_READ,\n+\t\t.mmap_flags = MAP_PRIVATE,\n+#ifdef CONFIG_NOMMU\n+\t\t.exp_err = EPERM,\n+#else\n+\t\t.exp_err = 0,\n+#endif\n+\t\t.resolve_exp_err = 0,\n+\t\t.old_pages = 4,\n+\t\t.new_pages = 8,\n+\t},\n+\t{\n+\t\t.name = \"private file growth with reserved capacity (rw-)\",\n+\t\t.pathname = \"/tmp/ksft.nommu-remap-XXXXXX\",\n+\t\t.open_flags = O_CREAT | O_RDWR | O_EXCL,\n+\t\t.mmap_prot = PROT_READ | PROT_WRITE,\n+\t\t.mmap_flags = MAP_PRIVATE,\n+\t\t.exp_err = 0,\n+\t\t.resolve_exp_err = 0,\n+\t\t.old_pages = 3,\n+\t\t.new_pages = 4,\n+#ifdef CONFIG_NOMMU\n+\t\t.pre_hook = pre_conf_trim_page,\n+\t\t.post_hook = post_conf_trim_page,\n+#endif\n+\t},\n+\t{\n+\t\t.name = \"private file growth (rw-)\",\n+\t\t.pathname = \"/tmp/ksft.nommu-remap-XXXXXX\",\n+\t\t.open_flags = O_CREAT | O_RDWR | O_EXCL,\n+\t\t.mmap_prot = PROT_READ | PROT_WRITE,\n+\t\t.mmap_flags = MAP_PRIVATE,\n+#ifdef CONFIG_NOMMU\n+\t\t.exp_err = ENOMEM,\n+#else\n+\t\t.exp_err = 0,\n+#endif\n+\t\t.resolve_exp_err = 0,\n+\t\t.old_pages = 4,\n+\t\t.new_pages = 8,\n+\t},\n+};\n+\n+static int run_mremap_test(struct mremap_case_t *tcase)\n+{\n+\tint fd = -1;\n+\tvoid *addr, *addr2;\n+\tchar pb[PATH_MAX];\n+\tint rc = KSFT_PASS;\n+\tint expected_error;\n+\tunsigned int old_pages = tcase-\u003eold_pages ?: 4;\n+\tunsigned int new_pages = tcase-\u003enew_pages ?: 2;\n+\tunsigned int file_pages = old_pages \u003e new_pages ?\n+\t\t\t\t old_pages : new_pages;\n+\tint orig_nr_trip_pages = -1;\n+\n+\tksft_print_msg(\"[RUN] Testing mremap: %s\\n\", tcase-\u003ename);\n+\n+\tif (tcase-\u003epathname \u0026\u0026 strstr(tcase-\u003epathname, \"XXXXXX\")) {\n+\t\tstrncpy(pb, tcase-\u003epathname, sizeof(pb) - 1);\n+\t\tpb[sizeof(pb) - 1] = '\\0';\n+\t\tfd = mkstemp(pb);\n+\t\tif (fd \u003c 0) {\n+\t\t\tksft_test_result_skip(\"Failed to setup file backing\\n\");\n+\t\t\treturn KSFT_SKIP;\n+\t\t}\n+\t\tif (ftruncate(fd, ps * file_pages) != 0) {\n+\t\t\tksft_test_result_fail(\"Failed to setup file backing\\n\");\n+\t\t\tclose(fd);\n+\t\t\tunlink(pb);\n+\t\t\treturn KSFT_FAIL;\n+\t\t}\n+\n+\t\tif ((tcase-\u003emmap_flags \u0026 MAP_SHARED) \u0026\u0026 get_fs_type(pb) != RAMFS_MAGIC) {\n+\t\t\tksft_test_result_skip(\"Skip the test under non-ramfs filesystem (%s)\\n\",\n+\t\t\t\t\t pb);\n+\t\t\tclose(fd);\n+\t\t\tunlink(pb);\n+\t\t\treturn KSFT_SKIP;\n+\t\t}\n+\t} else if (tcase-\u003epathname) {\n+\t\tfd = open(tcase-\u003epathname, tcase-\u003eopen_flags, 0600);\n+\t\tif (fd \u003c 0) {\n+\t\t\tksft_test_result_skip(\"Backing node not accessible\\n\");\n+\t\t\treturn KSFT_SKIP;\n+\t\t}\n+\n+\t\tif ((tcase-\u003emmap_flags \u0026 MAP_SHARED) \u0026\u0026\n+\t\t get_fs_type(tcase-\u003epathname) != RAMFS_MAGIC) {\n+\t\t\tksft_test_result_skip(\"Skip the test under non-ramfs filesystem (%s)\\n\",\n+\t\t\t\t\t tcase-\u003epathname);\n+\t\t\tclose(fd);\n+\t\t\treturn KSFT_SKIP;\n+\t\t}\n+\t}\n+\n+\tif (tcase-\u003epre_hook) {\n+\t\torig_nr_trip_pages = tcase-\u003epre_hook();\n+\t\tif (orig_nr_trip_pages \u003c 0) {\n+\t\t\tksft_print_msg(\"pre-hook failed %s(%d)\\n\",\n+\t\t\t\t\t strerror(errno), errno);\n+\t\t\trc = KSFT_FAIL;\n+\t\t\tgoto out;\n+\t\t}\n+\t}\n+\n+\taddr = mmap(NULL, ps * old_pages, tcase-\u003emmap_prot,\n+\t\t tcase-\u003emmap_flags, fd, 0);\n+\tif (addr == MAP_FAILED) {\n+\t\tksft_print_msg(\"mmap mapping failed %s(%d)\\n\", strerror(errno), errno);\n+\t\trc = KSFT_FAIL;\n+\t\tgoto out;\n+\t}\n+\n+\texpected_error = tcase-\u003eexp_err;\n+\tif (tcase-\u003eresolve_exp_err \u0026\u0026 fd \u003e= 0)\n+\t\texpected_error = tcase-\u003eresolve_exp_err(pb);\n+\n+\taddr2 = mremap(addr, ps * old_pages, ps * new_pages,\n+\t\t MREMAP_MAYMOVE);\n+\n+\tif (expected_error != 0) {\n+\t\tif (addr2 != MAP_FAILED || errno != expected_error) {\n+\t\t\tksft_print_msg(\"Expected error %d, got %s(%d)\\n\",\n+\t\t\t\t expected_error, strerror(errno), errno);\n+\t\t\trc = KSFT_FAIL;\n+\t\t} else {\n+\t\t\tksft_print_msg(\"%s/%s: Handled expected error path (errno=%d)\\n\",\n+\t\t\t\t __func__, tcase-\u003ename, expected_error);\n+\t\t}\n+\t} else if (addr2 == MAP_FAILED) {\n+\t\tksft_print_msg(\"mremap shrink failed unexpectedly: %s\\n\",\n+\t\t\t strerror(errno));\n+\t\trc = KSFT_FAIL;\n+\t} else {\n+\t\tksft_print_msg(\"%s/%s step successful\\n\", __func__, tcase-\u003ename);\n+\t}\n+\n+\t/* clean up */\n+\tif (munmap(addr2 == MAP_FAILED ? addr : addr2,\n+\t\t addr2 == MAP_FAILED ? ps * old_pages : ps * new_pages)) {\n+\t\tksft_print_msg(\"munmap failed: %s\\n\", strerror(errno));\n+\t\trc = KSFT_FAIL;\n+\t}\n+\n+out:\n+\tif (fd \u003e= 0) {\n+\t\tclose(fd);\n+\t\tif (tcase-\u003epathname \u0026\u0026 strstr(tcase-\u003epathname, \"XXXXXX\"))\n+\t\t\tunlink(pb);\n+\t}\n+\n+\tif (tcase-\u003epost_hook) {\n+\t\tif (tcase-\u003epost_hook(orig_nr_trip_pages) \u003c 0) {\n+\t\t\tksft_print_msg(\"post-hook failed %s(%d)\\n\",\n+\t\t\t\t strerror(errno), errno);\n+\t\t\trc = KSFT_FAIL;\n+\t\t}\n+\t}\n+\n+\tksft_test_result_report(rc, \"%s:%s\\n\", __func__, tcase-\u003ename);\n+\treturn rc;\n+}\n+\n+int main(int argc, char **argv)\n+{\n+\tint res = KSFT_PASS;\n+\tint i;\n+\n+\tps = sysconf(_SC_PAGESIZE);\n+\tksft_print_header();\n+\tksft_set_plan(ARRAY_SIZE(mremap_cases) + 3);\n+\n+\tmunmap_shrink_test();\n+\tmremap_shrink_test();\n+\tzero_middle_munmap_test();\n+\n+\tfor (i = 0; i \u003c (int)ARRAY_SIZE(mremap_cases); i++) {\n+\t\tif (run_mremap_test(\u0026mremap_cases[i]) == KSFT_FAIL)\n+\t\t\tres = KSFT_FAIL;\n+\t}\n+\n+\tif (res == KSFT_PASS)\n+\t\tksft_finished();\n+\n+\tksft_exit_fail();\n+}\n"
} |
| 2/1 | 2026/08/13 06:57 | agent | patch-evaluator |
2mModel:best-expensive Args: null Results: {
"EnableConfigs": [
"MMAP_ALLOW_UNINITIALIZED"
],
"FocusSymbols": [
"generic_file_readonly_mmap_prepare",
"generic_file_mmap_prepare"
],
"Reasoning": "The patch modifies core memory mapping logic (mmap, munmap, mremap) and file mapping preparation for nommu architectures. These changes are functional and reachable in nommu kernels emulated by QEMU.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). - Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed, ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU. - Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove) that are executed only during PCI hot-unplug or sysfs driver unbind operations. If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. Prefer non-static, non-inlined API entrypoint functions over internal static helper functions (which are inlined by the compiler and do not have distinct symbol addresses). 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs output array, and DO NOT add a '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: For your convenience, here is the diff of the changes:
commit 6216c638fbd4b11f0b5b5c76b1b4d376b68eb3e2
Author: syz-cluster <triage@syzkaller.com>
Date: Thu Aug 13 06:57:37 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/dev-tools/kselftest.rst b/Documentation/dev-tools/kselftest.rst
index 64c0ec7428a27..800b2b688aff0 100644
--- a/Documentation/dev-tools/kselftest.rst
+++ b/Documentation/dev-tools/kselftest.rst
@@ -230,6 +230,18 @@ section::
.. _tar's auto-compress: https://www.gnu.org/software/tar/manual/html_node/gzip.html#auto_002dcompress
+Build and test on nommu target
+==============================
+
+If you (cross-)build kselftests for nommu targets, or run tests on nommu targets, use
+``NOMMU=1`` as a make variable/environment setting to tell build system to do the additional
+checks. These nommu targets may differ in several ways, such as not supporting fork(2) or
+using musl or another libc. Set this variable to apply the necessary build and test adjustments.
+
+ $ make ARCH=um NOMMU=1 O=build kselftest
+ $ make ARCH=um NOMMU=1 -C tools/testing/selftests/mm run_tests
+ $ NOMMU=1 /tmp/kselftest_install/run_kselftest.sh -s -c mm
+
Contributing new tests
======================
diff --git a/drivers/char/mem.c b/drivers/char/mem.c
index 63253d1de5d70..dba24d0a7b33d 100644
--- a/drivers/char/mem.c
+++ b/drivers/char/mem.c
@@ -500,11 +500,10 @@ static ssize_t read_zero(struct file *file, char __user *buf,
static int mmap_zero_prepare(struct vm_area_desc *desc)
{
-#ifndef CONFIG_MMU
- return -ENOSYS;
-#endif
+#ifdef CONFIG_MMU
if (vma_desc_test(desc, VMA_SHARED_BIT))
return shmem_zero_setup_desc(desc);
+#endif
/*
* This is a highly unique situation where we mark a MAP_PRIVATE mapping
diff --git a/mm/filemap.c b/mm/filemap.c
index d721986d5f465..cf02faad86aa6 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -4077,7 +4077,7 @@ int generic_file_mmap(struct file *file, struct vm_area_struct *vma)
}
int generic_file_mmap_prepare(struct vm_area_desc *desc)
{
- return -ENOSYS;
+ return 0;
}
int generic_file_readonly_mmap(struct file *file, struct vm_area_struct *vma)
{
@@ -4085,7 +4085,9 @@ int generic_file_readonly_mmap(struct file *file, struct vm_area_struct *vma)
}
int generic_file_readonly_mmap_prepare(struct vm_area_desc *desc)
{
- return -ENOSYS;
+ if (is_shared_maywrite(&desc->vma_flags))
+ return -EINVAL;
+ return generic_file_mmap_prepare(desc);
}
#endif /* CONFIG_MMU */
diff --git a/mm/nommu.c b/mm/nommu.c
index ed3934bc2de48..a29a53c1c80a6 100644
--- a/mm/nommu.c
+++ b/mm/nommu.c
@@ -37,6 +37,7 @@
#include <linux/uaccess.h>
#include <linux/uio.h>
+#include <linux/major.h>
#include <asm/tlb.h>
#include <asm/tlbflush.h>
#include <asm/mmu_context.h>
@@ -559,36 +560,51 @@ static void put_nommu_region(struct vm_region *region)
__put_nommu_region(region);
}
+static void add_vma_to_mapping(struct vm_area_struct *vma)
+{
+ struct address_space *mapping;
+
+ if (!vma->vm_file)
+ return;
+
+ mapping = vma->vm_file->f_mapping;
+ i_mmap_lock_write(mapping);
+ flush_dcache_mmap_lock(mapping);
+ vma_interval_tree_insert(vma, &mapping->i_mmap);
+ flush_dcache_mmap_unlock(mapping);
+ i_mmap_unlock_write(mapping);
+}
+
+static void remove_vma_from_mapping(struct vm_area_struct *vma)
+{
+ struct address_space *mapping;
+
+ if (!vma->vm_file)
+ return;
+
+ mapping = vma->vm_file->f_mapping;
+ i_mmap_lock_write(mapping);
+ flush_dcache_mmap_lock(mapping);
+ vma_interval_tree_remove(vma, &mapping->i_mmap);
+ flush_dcache_mmap_unlock(mapping);
+ i_mmap_unlock_write(mapping);
+}
+
static void setup_vma_to_mm(struct vm_area_struct *vma, struct mm_struct *mm)
{
vma->vm_mm = mm;
/* add the VMA to the mapping */
- if (vma->vm_file) {
- struct address_space *mapping = vma->vm_file->f_mapping;
-
- i_mmap_lock_write(mapping);
- flush_dcache_mmap_lock(mapping);
- vma_interval_tree_insert(vma, &mapping->i_mmap);
- flush_dcache_mmap_unlock(mapping);
- i_mmap_unlock_write(mapping);
- }
+ if (vma->vm_file)
+ add_vma_to_mapping(vma);
}
static void cleanup_vma_from_mm(struct vm_area_struct *vma)
{
vma->vm_mm->map_count--;
/* remove the VMA from the mapping */
- if (vma->vm_file) {
- struct address_space *mapping;
- mapping = vma->vm_file->f_mapping;
-
- i_mmap_lock_write(mapping);
- flush_dcache_mmap_lock(mapping);
- vma_interval_tree_remove(vma, &mapping->i_mmap);
- flush_dcache_mmap_unlock(mapping);
- i_mmap_unlock_write(mapping);
- }
+ if (vma->vm_file)
+ remove_vma_from_mapping(vma);
}
/*
@@ -841,6 +857,22 @@ static int validate_mmap_request(struct file *file,
return 0;
}
+static int is_file_anonymous(struct file *file)
+{
+ if (!file)
+ return 1;
+
+ if (file->f_path.dentry && file->f_path.dentry->d_inode) {
+ struct inode *inode = file->f_path.dentry->d_inode;
+ /* if the device is /dev/zero */
+ if (S_ISCHR(inode->i_mode) &&
+ imajor(inode) == MEM_MAJOR && iminor(inode) == 5)
+ return 1;
+ }
+
+ return 0;
+}
+
/*
* we've determined that we can make the mapping, now translate what we
* now know into VMA flags
@@ -854,7 +886,11 @@ static vm_flags_t determine_vm_flags(struct file *file,
vm_flags = calc_vm_prot_bits(prot, 0) | calc_vm_flag_bits(file, flags);
- if (!file) {
+ /* private and file mapping will be marked anonymous later (do_mmap_private()).
+ * and /dev/zero is marked by them at .mmap_prepare,
+ * which should be _before_ this point.
+ */
+ if (is_file_anonymous(file)) {
/*
* MAP_ANONYMOUS. MAP_SHARED is mapped to MAP_PRIVATE, because
* there is no fork().
@@ -908,6 +944,29 @@ static int do_mmap_shared_file(struct vm_area_struct *vma)
return -ENODEV;
}
+static ssize_t nommu_read_iter(struct file *file, void *buf,
+ size_t count, loff_t *pos)
+{
+ struct iov_iter iter;
+ ssize_t ret;
+ size_t done = 0;
+
+ while (done < count) {
+ struct kvec iov = {
+ .iov_base = buf + done,
+ .iov_len = min_t(size_t, count - done, MAX_RW_COUNT),
+ };
+
+ iov_iter_kvec(&iter, ITER_DEST, &iov, 1, iov.iov_len);
+ ret = vfs_iter_read(file, &iter, pos, 0);
+ if (ret <= 0)
+ return done ? done : ret;
+ done += ret;
+ }
+
+ return done;
+}
+
/*
* set up a private mapping or an anonymous shared mapping
*/
@@ -978,7 +1037,7 @@ static int do_mmap_private(struct vm_area_struct *vma,
fpos = vma->vm_pgoff;
fpos <<= PAGE_SHIFT;
- ret = kernel_read(vma->vm_file, base, len, &fpos);
+ ret = nommu_read_iter(vma->vm_file, base, len, &fpos);
if (ret < 0)
goto error_free;
@@ -1065,6 +1124,28 @@ unsigned long do_mmap(struct file *file,
vma->vm_file = get_file(file);
}
+ /* call mmap_prepare function if any */
+ if (!(flags & MAP_SHARED) && !(capabilities & NOMMU_MAP_DIRECT) &&
+ (vma->vm_file && vma->vm_file->f_op->mmap_prepare)) {
+ struct vm_area_desc desc;
+
+ vma->vm_start = addr;
+ vma->vm_end = addr + len;
+
+ compat_set_desc_from_vma(&desc, vma->vm_file, vma);
+ ret = vma->vm_file->f_op->mmap_prepare(&desc);
+ /* private ramfs/romfs mappings fails with -ENOSYS so,
+ * fall back to copied mapping.
+ */
+ if (ret && ret != -ENOSYS)
+ goto error_mmap_prepare;
+
+ ret = __compat_vma_mmap(&desc, vma);
+ if (ret)
+ goto error_mmap_prepare;
+ }
+
+
down_write(&nommu_region_sem);
/* if we want to share, we need to check for regions created by other
@@ -1181,7 +1262,7 @@ unsigned long do_mmap(struct file *file,
add_nommu_region(region);
/* clear anonymous mappings that don't ask for uninitialized data */
- if (!vma->vm_file &&
+ if (is_file_anonymous(vma->vm_file) &&
(!IS_ENABLED(CONFIG_MMAP_ALLOW_UNINITIALIZED) ||
!(flags & MAP_UNINITIALIZED)))
memset((void *)region->vm_start, 0,
@@ -1232,6 +1313,18 @@ unsigned long do_mmap(struct file *file,
ret = -EINVAL;
goto error;
+error_mmap_prepare:
+ if (region->vm_file)
+ fput(region->vm_file);
+ kmem_cache_free(vm_region_jar, region);
+ if (vma->vm_file)
+ fput(vma->vm_file);
+ vm_area_free(vma);
+
+ pr_warn("mmap_prepare failed for %lu byte allocation from process %d\n",
+ len, current->pid);
+ return ret;
+
error_getting_vma:
kmem_cache_free(vm_region_jar, region);
pr_warn("Allocation of vma for %lu byte allocation from process %d failed\n",
@@ -1313,7 +1406,7 @@ static int split_vma(struct vma_iterator *vmi, struct vm_area_struct *vma,
/* we're only permitted to split anonymous regions (these should have
* only a single usage on the region) */
- if (vma->vm_file)
+ if (!vma_is_anonymous(vma))
return -ENOMEM;
mm = vma->vm_mm;
@@ -1351,6 +1444,8 @@ static int split_vma(struct vma_iterator *vmi, struct vm_area_struct *vma,
if (new->vm_ops && new->vm_ops->open)
new->vm_ops->open(new);
+ remove_vma_from_mapping(vma);
+
down_write(&nommu_region_sem);
delete_nommu_region(vma->vm_region);
if (new_below) {
@@ -1364,6 +1459,11 @@ static int split_vma(struct vma_iterator *vmi, struct vm_area_struct *vma,
add_nommu_region(new->vm_region);
up_write(&nommu_region_sem);
+ if (new->vm_file) {
+ vma->vm_file = get_file(vma->vm_file);
+ new->vm_file = get_file(new->vm_file);
+ }
+
setup_vma_to_mm(vma, mm);
setup_vma_to_mm(new, mm);
vma_iter_store_new(vmi, new);
@@ -1386,16 +1486,20 @@ static int vmi_shrink_vma(struct vma_iterator *vmi,
unsigned long from, unsigned long to)
{
struct vm_region *region;
+ bool has_mapping = !!vma->vm_file;
+
+ if (has_mapping)
+ remove_vma_from_mapping(vma);
/* adjust the VMA's pointers, which may reposition it in the MM's tree
* and list */
if (from > vma->vm_start) {
if (vma_iter_clear_gfp(vmi, from, vma->vm_end, GFP_KERNEL))
- return -ENOMEM;
+ goto restore_mapping;
vma->vm_end = from;
} else {
if (vma_iter_clear_gfp(vmi, vma->vm_start, to, GFP_KERNEL))
- return -ENOMEM;
+ goto restore_mapping;
vma->vm_start = to;
}
@@ -1415,7 +1519,15 @@ static int vmi_shrink_vma(struct vma_iterator *vmi,
up_write(&nommu_region_sem);
free_page_series(from, to);
+ if (has_mapping)
+ add_vma_to_mapping(vma);
+
return 0;
+
+restore_mapping:
+ if (has_mapping)
+ add_vma_to_mapping(vma);
+ return -ENOMEM;
}
/*
@@ -1450,7 +1562,7 @@ int do_munmap(struct mm_struct *mm, unsigned long start, size_t len, struct list
}
/* we're allowed to split an anonymous VMA but not a file-backed one */
- if (vma->vm_file) {
+ if (!vma_is_anonymous(vma)) {
do {
if (start > vma->vm_start)
return -EINVAL;
@@ -1544,6 +1656,9 @@ static unsigned long do_mremap(unsigned long addr,
unsigned long flags, unsigned long new_addr)
{
struct vm_area_struct *vma;
+ int ret;
+
+ VMA_ITERATOR(vmi, current->mm, addr);
/* insanity checks first */
old_len = PAGE_ALIGN(old_len);
@@ -1567,11 +1682,75 @@ static unsigned long do_mremap(unsigned long addr,
if (is_nommu_shared_mapping(vma->vm_flags))
return (unsigned long) -EPERM;
- if (new_len > vma->vm_region->vm_end - vma->vm_region->vm_start)
+ /* vm_region->vm_top != vm_region->vm_end when sysctl_nr_trim_pages is 0 (default: 1) */
+ if (new_len > vma->vm_region->vm_top - vma->vm_region->vm_start)
return (unsigned long) -ENOMEM;
/* all checks complete - do it */
- vma->vm_end = vma->vm_start + new_len;
+ if (new_len == old_len)
+ return vma->vm_start;
+
+ /* shrink only happens addr + new_len and old_len are in different pages */
+ if (new_len < old_len) {
+ /* like do_munmap(), we're allowed to shrink an anonymous VMA but not
+ * a file-backed one
+ */
+ if (!vma_is_anonymous(vma))
+ return (unsigned long) -EINVAL;
+
+ /* vmi_shrink_vma() needs from/to pointers to be removed,
+ * (mainly used in munmap) so, specify them.
+ */
+ ret = vmi_shrink_vma(&vmi, vma, addr + new_len, addr + old_len);
+ if (ret < 0)
+ return (unsigned long) ret;
+ } else {
+ /* growth path: grow up to vm_top should be handled here. */
+ unsigned long old_end = vma->vm_end;
+ unsigned long end = vma->vm_start + new_len;
+ unsigned long grow_len = end - old_end;
+
+ /*
+ * Initialize the newly exposed portion before making it visible
+ * through the VMA or i_mmap.
+ */
+
+ /* read contents of extended map from file, or zero-filled if !vm_file */
+ if (vma->vm_file) {
+ loff_t fpos;
+
+ fpos = (loff_t)vma->vm_pgoff << PAGE_SHIFT;
+ fpos += old_end - vma->vm_start;
+
+ ret = nommu_read_iter(vma->vm_file, (void *)old_end,
+ grow_len, &fpos);
+ if (ret < 0)
+ return (unsigned long)ret;
+
+ if (ret < grow_len)
+ memset((char *)old_end + ret, 0, grow_len - ret);
+ } else {
+ memset((void *)old_end, 0, grow_len);
+ }
+
+ /* The backing contents are ready. Now update the VMA bookkeeping. */
+ remove_vma_from_mapping(vma);
+
+ vma->vm_end = end;
+ ret = vma_iter_store_gfp(&vmi, vma, GFP_KERNEL);
+ if (ret) {
+ vma->vm_end = old_end;
+ add_vma_to_mapping(vma);
+ return (unsigned long)ret;
+ }
+
+ /* vm_top remains unchanged; only the logical end grows. */
+ down_write(&nommu_region_sem);
+ vma->vm_region->vm_end = end;
+ up_write(&nommu_region_sem);
+
+ add_vma_to_mapping(vma);
+ }
return vma->vm_start;
}
diff --git a/tools/testing/selftests/kselftest.h b/tools/testing/selftests/kselftest.h
index ae18c491ae533..ac21bf3d802e5 100644
--- a/tools/testing/selftests/kselftest.h
+++ b/tools/testing/selftests/kselftest.h
@@ -58,6 +58,7 @@
#include <string.h>
#include <stdio.h>
#include <sys/utsname.h>
+#include <stdint.h>
#endif
#ifndef ARRAY_SIZE
@@ -81,6 +82,48 @@
#endif
#endif /* end arch */
+#if !defined(NOLIBC) && !defined(__GLIBC__)
+#ifdef __LP64__
+typedef int64_t __fsword_t;
+#else
+typedef int32_t __fsword_t;
+#endif
+
+/*
+ * for a workaround to avoid struct conflict under
+ * musl-libc (<sys/prctl.h> v.s. <linux/prctl.h>)
+ */
+#include <sys/prctl.h>
+#ifndef _LINUX_PRCTL_H
+#define _LINUX_PRCTL_H
+#endif
+
+#ifndef PR_SET_MDWE
+#define PR_SET_MDWE 65
+#endif
+
+#ifndef PR_MDWE_REFUSE_EXEC_GAIN
+#define PR_MDWE_REFUSE_EXEC_GAIN (1UL << 0)
+#endif
+
+#ifndef PR_MDWE_NO_INHERIT
+#define PR_MDWE_NO_INHERIT (1UL << 1)
+#endif
+
+#ifndef PR_GET_MDWE
+#define PR_GET_MDWE 66
+#endif
+
+#ifndef PR_SET_MEMORY_MERGE
+#define PR_SET_MEMORY_MERGE 67
+#endif
+
+#ifndef PR_GET_MEMORY_MERGE
+#define PR_GET_MEMORY_MERGE 68
+#endif
+
+#endif
+
/* define kselftest exit codes */
#define KSFT_PASS 0
#define KSFT_FAIL 1
diff --git a/tools/testing/selftests/kselftest/runner.sh b/tools/testing/selftests/kselftest/runner.sh
index 311811dc55a07..7287d8290b6c9 100644
--- a/tools/testing/selftests/kselftest/runner.sh
+++ b/tools/testing/selftests/kselftest/runner.sh
@@ -38,8 +38,12 @@ tap_prefix()
tap_timeout()
{
+ # nommu doesn't support timeout command (missing fork(2))
+ if [ "$NOMMU" = "1" ] ; then
+ echo "timeout isn't supported for nommu"
+ $1
# Make sure tests will time out if utility is available.
- if [ -x /usr/bin/timeout ] ; then
+ elif [ -x /usr/bin/timeout ] ; then
/usr/bin/timeout --foreground "$kselftest_timeout" \
/usr/bin/timeout "$kselftest_timeout" $1
else
@@ -130,6 +134,7 @@ run_one()
return $KSFT_FAIL
fi
fi
+ OLDDIR=$(pwd)
cd `dirname $TEST` > /dev/null
(((( tap_timeout "$cmd" 2>&1; echo $? >&3) |
tap_prefix >&4) 3>&1) |
@@ -147,7 +152,7 @@ run_one()
*)
ktap_test_fail "$TEST_HDR_MSG # exit=$rc";;
esac
- cd - >/dev/null
+ cd "$OLDDIR" >/dev/null
fi
return $rc
diff --git a/tools/testing/selftests/kselftest_harness.h b/tools/testing/selftests/kselftest_harness.h
index 261e4df94d9d5..8eee7b14f8248 100644
--- a/tools/testing/selftests/kselftest_harness.h
+++ b/tools/testing/selftests/kselftest_harness.h
@@ -1274,6 +1274,10 @@ static int test_harness_run(int argc, char **argv)
unsigned int count = 0;
unsigned int pass_count = 0;
+#ifdef CONFIG_NOMMU
+ ksft_print_msg("harness test doesn't support on NOMMU architecture (no fork(2)).\n");
+ return KSFT_SKIP;
+#endif /* CONFIG_NOMMU */
ret = test_harness_argv_check(argc, argv);
if (ret != KSFT_PASS)
return ret;
diff --git a/tools/testing/selftests/lib.mk b/tools/testing/selftests/lib.mk
index f02cc8a2e4ae3..4734b5ce613f0 100644
--- a/tools/testing/selftests/lib.mk
+++ b/tools/testing/selftests/lib.mk
@@ -97,6 +97,14 @@ TEST_GEN_PROGS := $(patsubst %,$(OUTPUT)/%,$(TEST_GEN_PROGS))
TEST_GEN_PROGS_EXTENDED := $(patsubst %,$(OUTPUT)/%,$(TEST_GEN_PROGS_EXTENDED))
TEST_GEN_FILES := $(patsubst %,$(OUTPUT)/%,$(TEST_GEN_FILES))
+# detect if users request NOMMU build or not
+# User can set NOMMU to 1 to build/test for NOMMU platforms
+NOMMU ?= 0
+ifeq ($(NOMMU),1)
+CFLAGS += -DCONFIG_NOMMU
+export NOMMU
+endif
+
all: $(TEST_GEN_PROGS) $(TEST_GEN_PROGS_EXTENDED) $(TEST_GEN_FILES) \
$(if $(TEST_GEN_MODS_DIR),gen_mods_dir)
@@ -197,7 +205,7 @@ clean: $(if $(TEST_GEN_MODS_DIR),clean_mods_dir)
$(CLEAN)
# Build with _GNU_SOURCE by default
-CFLAGS += -D_GNU_SOURCE=
+CFLAGS += -D_GNU_SOURCE= -D_LARGEFILE64_SOURCE
# Additional include paths needed by kselftest.h and local headers
CFLAGS += -I${top_srcdir}/tools/testing/selftests
diff --git a/tools/testing/selftests/mm/Makefile b/tools/testing/selftests/mm/Makefile
index e6df968f0971c..3986ae7d75bca 100644
--- a/tools/testing/selftests/mm/Makefile
+++ b/tools/testing/selftests/mm/Makefile
@@ -185,6 +185,9 @@ TEST_FILES += run_vmtests.sh
# required by charge_reserved_hugetlb.sh
TEST_FILES += write_hugetlb_memory.sh
+TEST_GEN_PROGS += nommu_mmap_test
+TEST_GEN_PROGS += nommu_mremap_test
+
include ../lib.mk
$(TEST_GEN_PROGS): vm_util.c hugepage_settings.c
diff --git a/tools/testing/selftests/mm/hugetlb_dio.c b/tools/testing/selftests/mm/hugetlb_dio.c
index fb4600570e131..aee6be530ccbf 100644
--- a/tools/testing/selftests/mm/hugetlb_dio.c
+++ b/tools/testing/selftests/mm/hugetlb_dio.c
@@ -22,12 +22,9 @@
#include "kselftest.h"
#include "hugepage_settings.h"
-#ifndef STATX_DIOALIGN
-#define STATX_DIOALIGN 0x00002000U
-#endif
-
static int get_dio_alignment(int fd)
{
+#ifdef STATX_DIOALIGN
struct statx stx;
int ret;
@@ -43,6 +40,9 @@ static int get_dio_alignment(int fd)
return 1;
return stx.stx_dio_offset_align;
+#else
+ return -1;
+#endif
}
static bool check_dio_alignment(unsigned int start_off,
diff --git a/tools/testing/selftests/mm/mdwe_test.c b/tools/testing/selftests/mm/mdwe_test.c
index 647779653da09..031c79ed067ea 100644
--- a/tools/testing/selftests/mm/mdwe_test.c
+++ b/tools/testing/selftests/mm/mdwe_test.c
@@ -1,5 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
+#include "kselftest_harness.h"
+
#ifdef __aarch64__
#include <asm/hwcap.h>
#endif
@@ -14,8 +16,6 @@
#include <sys/wait.h>
#include <unistd.h>
-#include "kselftest_harness.h"
-
#ifndef __aarch64__
# define PROT_BTI 0
#endif
diff --git a/tools/testing/selftests/mm/nommu_mmap_test.c b/tools/testing/selftests/mm/nommu_mmap_test.c
new file mode 100644
index 0000000000000..50ce385967f4c
--- /dev/null
+++ b/tools/testing/selftests/mm/nommu_mmap_test.c
@@ -0,0 +1,294 @@
+// SPDX-License-Identifier: GPL-2.0
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <limits.h>
+#include "../kselftest.h"
+
+#include <sys/vfs.h>
+#ifndef RAMFS_MAGIC
+#define RAMFS_MAGIC 0x858458f6
+#endif
+
+#ifndef MAP_UNINITIALIZED
+#define MAP_UNINITIALIZED 0x4000000
+#endif
+
+static size_t ps;
+
+struct test_case_t {
+ const char *name;
+ const char *pathname;
+ int open_flags;
+ int mmap_prot;
+ int mmap_flags;
+ int exp_err;
+ int (*resolve_exp_err)(const char *path);
+};
+
+static int get_shm_expected_error(const char *path)
+{
+ struct statfs fs;
+
+ if (statfs(path, &fs) == 0) {
+ if (fs.f_type == RAMFS_MAGIC)
+ return 0; /* ramfs succeed with contiguous memory */
+ }
+ /* hostfs, etc returns ENODEV due to lack of contiguous allocation */
+ return ENODEV;
+}
+
+static struct test_case_t test_cases[] = {
+ {
+ "Anonymous private allocation",
+ NULL,
+ O_CREAT | O_RDWR | O_EXCL,
+ PROT_READ | PROT_WRITE,
+ MAP_ANONYMOUS | MAP_PRIVATE,
+ 0,
+ NULL,
+ },
+ {
+ "Non-anonymous private file mapping (rw-)",
+ "/tmp/ksft.nommu-reg-XXXXXX",
+ O_CREAT | O_RDWR | O_EXCL,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE,
+ 0,
+ 0,
+ },
+ {
+ "Non-anonymous private file mapping (r--)",
+ "/tmp/ksft.nommu-reg-XXXXXX",
+ O_CREAT | O_RDWR | O_EXCL,
+ PROT_READ,
+ MAP_PRIVATE,
+ 0,
+ 0,
+ },
+ {
+ "Non-anonymous shared file mapping (rw-)",
+ "/tmp/ksft.nommu-shm-XXXXXX",
+ O_CREAT | O_RDWR | O_EXCL,
+ PROT_READ | PROT_WRITE,
+ MAP_SHARED,
+ 0,
+#ifdef CONFIG_NOMMU
+ get_shm_expected_error,
+#else
+ 0,
+#endif
+ },
+ {
+ "Non-anonymous shared file mapping (r--)",
+ "/tmp/ksft.nommu-shm-XXXXXX",
+ O_CREAT | O_RDWR | O_EXCL,
+ PROT_READ,
+ MAP_SHARED,
+ 0,
+#ifdef CONFIG_NOMMU
+ get_shm_expected_error,
+#else
+ 0,
+#endif
+ },
+ {
+ "Memory-backed private storage via /dev/zero",
+ "/dev/zero",
+ O_RDONLY,
+ PROT_READ,
+ MAP_PRIVATE,
+ 0,
+ NULL,
+ },
+ {
+ "Memory-backed storage via /dev/zero (MAP_SHARED)",
+ "/dev/zero",
+ O_RDONLY,
+ PROT_READ,
+ MAP_SHARED,
+#ifdef CONFIG_NOMMU
+ ENODEV,
+#else
+ 0,
+#endif
+ NULL,
+ },
+ {
+ "Block device volatile node mapping",
+ "/dev/loop0",
+ O_RDWR,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE,
+ 0,
+ NULL,
+ }
+};
+
+static int run_mapping_matrix_test(struct test_case_t *tcase)
+{
+ int fd;
+ void *ptr;
+ char path_buf[PATH_MAX];
+ int rc = KSFT_PASS;
+ int expected_error;
+
+ ksft_print_msg("[RUN] Testing: %s\n", tcase->name);
+
+ if (tcase->pathname == NULL) {
+ fd = -1;
+ } else if (strstr(tcase->pathname, "XXXXXX")) {
+ strncpy(path_buf, tcase->pathname, sizeof(path_buf) - 1);
+ path_buf[sizeof(path_buf) - 1] = '\0';
+ fd = mkstemp(path_buf);
+ if (fd < 0) {
+ ksft_test_result_skip("Failed to setup temp node: %s\n",
+ tcase->pathname);
+ return KSFT_SKIP;
+ }
+ if (ftruncate(fd, ps) != 0) {
+ ksft_test_result_fail("ftruncate failed for: %s\n",
+ tcase->pathname);
+ close(fd);
+ unlink(path_buf);
+ return KSFT_FAIL;
+ }
+ } else {
+ fd = open(tcase->pathname, tcase->open_flags, 0600);
+ if (fd < 0) {
+ ksft_test_result_skip("Device node not accessible: %s\n",
+ tcase->pathname);
+ return KSFT_SKIP;
+ }
+ }
+
+ expected_error = tcase->exp_err;
+ if (tcase->resolve_exp_err && fd >= 0)
+ expected_error = tcase->resolve_exp_err(path_buf);
+
+ ptr = mmap(NULL, ps, tcase->mmap_prot, tcase->mmap_flags, fd, 0);
+
+ if (expected_error != 0) {
+ if (ptr != MAP_FAILED) {
+ ksft_test_result_fail("%s: mmap unexpectedly succeeded (exp error %d)\n",
+ tcase->name, expected_error);
+ munmap(ptr, ps);
+ rc = KSFT_FAIL;
+ goto cleanup;
+ }
+ if (errno != expected_error) {
+ ksft_test_result_fail("%s: mmap failed with %d (%s), but expected %d\n",
+ tcase->name, errno, strerror(errno), expected_error);
+ rc = KSFT_FAIL;
+ goto cleanup;
+ }
+ ksft_test_result_pass("%s: Correctly rejected with expected error %s(%d)\n",
+ tcase->name, strerror(expected_error), expected_error);
+ rc = KSFT_PASS;
+ goto cleanup;
+ }
+
+ if (ptr == MAP_FAILED) {
+ ksft_test_result_fail("%s: mmap failed unexpectedly: %s\n",
+ tcase->name, strerror(errno));
+ rc = KSFT_FAIL;
+ goto cleanup;
+ }
+
+ ksft_test_result_pass("%s: mmap validation successfully passed\n", tcase->name);
+ munmap(ptr, ps);
+
+cleanup:
+ if (fd >= 0) {
+ close(fd);
+ if (tcase->pathname && strstr(tcase->pathname, "XXXXXX"))
+ unlink(path_buf);
+ }
+ return rc;
+}
+
+static int test_map_fixed(void)
+{
+ void *ptr = mmap((void *)(ps * 100), ps, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
+
+ ksft_print_msg("[RUN] Testing MAP_FIXED behavior\n");
+
+#ifdef CONFIG_NOMMU
+ if (ptr == MAP_FAILED && (errno == ENODEV || errno == EINVAL)) {
+ ksft_test_result_pass("MAP_FIXED correctly rejected under nommu\n");
+ return KSFT_PASS;
+ }
+ if (ptr != MAP_FAILED) {
+ ksft_test_result_fail("MAP_FIXED unexpectedly allowed under nommu\n");
+ munmap(ptr, ps);
+ return KSFT_FAIL;
+ }
+ ksft_test_result_fail("MAP_FIXED failed under NOMMU: %s\n",
+ strerror(errno));
+ return KSFT_FAIL;
+#else
+ if (ptr != MAP_FAILED) {
+ ksft_test_result_pass("MAP_FIXED successfully allocated under MMU\n");
+ munmap(ptr, ps);
+ return KSFT_PASS;
+ }
+ ksft_test_result_fail("MAP_FIXED failed allocation under MMU\n");
+ return KSFT_FAIL;
+#endif
+}
+
+static int test_uninit(void)
+{
+ void *ptr = mmap(NULL, ps, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_UNINITIALIZED, -1, 0);
+
+ ksft_print_msg("[RUN] Testing MAP_UNINITIALIZED behavior\n");
+
+ if (ptr == MAP_FAILED) {
+ ksft_test_result_skip("MAP_UNINITIALIZED not supported by kernel config\n");
+ return KSFT_SKIP;
+ }
+
+ ksft_test_result_pass("MAP_UNINITIALIZED allocation successful\n");
+ munmap(ptr, ps);
+ return KSFT_PASS;
+}
+
+int main(int argc, char **argv)
+{
+ int result = KSFT_PASS;
+ int i, rc;
+
+ ps = sysconf(_SC_PAGESIZE);
+ ksft_print_header();
+ ksft_set_plan(ARRAY_SIZE(test_cases) + 2);
+
+#ifdef CONFIG_NOMMU
+ ksft_print_msg("Running strict MMAP test criteria under nommu architecture\n");
+#else
+ ksft_print_msg("Running MMAP test criteria under MMU architecture\n");
+#endif
+
+ if (test_map_fixed() == KSFT_FAIL)
+ result = KSFT_FAIL;
+
+ if (test_uninit() == KSFT_FAIL)
+ result = KSFT_FAIL;
+
+ for (i = 0; i < (int)ARRAY_SIZE(test_cases); i++) {
+ rc = run_mapping_matrix_test(&test_cases[i]);
+ if (rc == KSFT_FAIL)
+ result = KSFT_FAIL;
+ }
+
+ if (result == KSFT_PASS)
+ ksft_finished();
+
+ ksft_exit_fail();
+}
diff --git a/tools/testing/selftests/mm/nommu_mremap_test.c b/tools/testing/selftests/mm/nommu_mremap_test.c
new file mode 100644
index 0000000000000..b1b8a4e64b555
--- /dev/null
+++ b/tools/testing/selftests/mm/nommu_mremap_test.c
@@ -0,0 +1,583 @@
+// SPDX-License-Identifier: GPL-2.0
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <limits.h>
+#include "../kselftest.h"
+
+#include <sys/vfs.h>
+#ifndef RAMFS_MAGIC
+#define RAMFS_MAGIC 0x858458f6
+#endif
+
+static size_t ps;
+
+static long get_fs_type(const char *path)
+{
+ struct statfs fs;
+
+ if (statfs(path, &fs) == 0)
+ return fs.f_type;
+
+ return 0;
+}
+
+/* return original value if succeed */
+static int set_nr_trim_pages(const char *value)
+{
+ int fd, orig_value;
+ ssize_t len, written, read_len;
+ char orig_buf[32];
+
+ fd = open("/proc/sys/vm/nr_trim_pages", O_RDWR);
+ if (fd < 0)
+ return -errno;
+
+ read_len = read(fd, orig_buf, sizeof(orig_buf) - 1);
+ if (read_len < 0) {
+ close(fd);
+ return -errno;
+ }
+ if (read_len == 0) {
+ close(fd);
+ return -EIO;
+ }
+
+ orig_buf[read_len] = '\0';
+ orig_value = atoi(orig_buf);
+
+ if (lseek(fd, 0, SEEK_SET) < 0) {
+ close(fd);
+ return -errno;
+ }
+
+ len = strlen(value);
+ written = write(fd, value, len);
+ close(fd);
+
+ if (written != len)
+ return written < 0 ? -errno : -EIO;
+
+ return orig_value >= 0 ? orig_value : -EINVAL;
+}
+
+static void munmap_shrink_test(void)
+{
+ void *addr;
+
+ /* munmap shrink test */
+ for (int i = 0; i < 4; i++) {
+ addr = mmap(NULL, ps * 4, PROT_READ | PROT_WRITE,
+ MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+ if (addr == MAP_FAILED) {
+ ksft_test_result_fail("mmap failed: %s(%d)\n", strerror(errno), errno);
+ return;
+ }
+ if (munmap(addr + ps * i, ps) != 0) {
+ ksft_test_result_fail("memory %p isn't unmapped at %p\n",
+ addr, addr + ps * i);
+ for (int j = 0; j < 4; j++)
+ munmap((char *)addr + j * ps, ps);
+ return;
+ }
+
+ if (i == 0) {
+ if (munmap(addr + ps, ps * 3))
+ goto error;
+ } else if (i == 1) {
+ if (munmap(addr, ps) || munmap(addr + (ps * 2), ps * 2))
+ goto error;
+ } else if (i == 2) {
+ if (munmap(addr, ps * 2) || munmap(addr + (ps * 3), ps))
+ goto error;
+ } else if (i == 3) {
+ if (munmap(addr, ps * 3))
+ goto error;
+ }
+ }
+
+ ksft_test_result_pass("%s success\n", __func__);
+ return;
+error:
+ for (int j = 0; j < 4; j++)
+ munmap((char *)addr + j * ps, ps);
+ ksft_test_result_fail("%s clean up failures\n", __func__);
+}
+
+static size_t page_align(size_t len)
+{
+ return (len + ps - 1) / ps * ps;
+}
+
+static void mremap_shrink_test(void)
+{
+ void *addr, *addr2;
+ size_t current_len;
+ size_t old_len, new_len;
+ struct param {
+ size_t old;
+ size_t new;
+ } params[] = {
+ /* should not happen any shrink */
+ { .old = ps * 4 - 1, .new = ps * 4 - 2 },
+ /* should not happen any shrink */
+ { .old = ps * 4 - 1, .new = ps * 4 },
+ { .old = ps * 4, .new = ps * 2 },
+ /* should not happen any shrink */
+ { .old = ps * 2, .new = ps * 2 - 2 },
+ { .old = ps * 2 - 2, .new = ps * 1 },
+ };
+
+ /* mremap shrink test */
+ current_len = page_align(ps * 4 - 1);
+ addr = mmap(NULL, ps * 4 - 1, PROT_READ | PROT_WRITE,
+ MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+ if (addr == MAP_FAILED) {
+ ksft_test_result_fail("mmap failed: %s(%d)\n", strerror(errno), errno);
+ return;
+ }
+
+ for (int i = 0; i < ARRAY_SIZE(params); i++) {
+ old_len = page_align(params[i].old);
+ new_len = page_align(params[i].new);
+ addr2 = mremap(addr, old_len, new_len, MREMAP_MAYMOVE);
+ if (addr2 == MAP_FAILED) {
+ ksft_test_result_fail("memory %p isn't remapped at %p\n", addr, addr2);
+ munmap(addr, old_len);
+ return;
+ }
+
+ addr = addr2;
+ current_len = new_len;
+ }
+
+ if (munmap(addr, current_len)) {
+ ksft_test_result_fail("%s cleanup failed: %s\n",
+ __func__, strerror(errno));
+ return;
+ }
+ ksft_test_result_pass("%s success\n", __func__);
+}
+
+static void zero_middle_munmap_test(void)
+{
+ int fd = -1;
+ void *addr = MAP_FAILED;
+ void *addr2 = MAP_FAILED;
+ int left_mapped = 0;
+ int right_mapped = 0;
+ int ret;
+ bool addr2_middle_unmapped = false;
+
+ ksft_print_msg("[RUN] Testing middle munmap of private /dev/zero mappings\n");
+
+ fd = open("/dev/zero", O_RDONLY);
+ if (fd < 0) {
+ ksft_test_result_skip("Unable to open /dev/zero: %s\n",
+ strerror(errno));
+ return;
+ }
+
+ addr = mmap(NULL, ps * 3, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE, fd, 0);
+ close(fd);
+ fd = -1;
+ if (addr == MAP_FAILED) {
+ ksft_test_result_fail("Initial /dev/zero mmap failed: %s\n",
+ strerror(errno));
+ return;
+ }
+
+ left_mapped = 1;
+ right_mapped = 1;
+
+ /*
+ * Split one private /dev/zero VMA in the middle. On nommu, the
+ * VMA still has vm_file set even though it is semantically
+ * anonymous.
+ */
+ ret = munmap((char *)addr + ps, ps);
+ if (ret) {
+ ksft_test_result_fail("Middle munmap failed: %s\n",
+ strerror(errno));
+ munmap(addr, ps * 3);
+ left_mapped = 0;
+ right_mapped = 0;
+ goto out;
+ }
+
+ /*
+ * Access both remaining VMAs so a stale VMA interval cannot remain
+ * hidden behind the unmapped hole.
+ */
+ ((volatile unsigned char *)addr)[0] = 0x5a;
+ ((volatile unsigned char *)addr + 2 * ps)[0] = 0xa5;
+
+ /*
+ * Repeat the operation on another mapping of the same inode. This
+ * exercises insertion and removal in /dev/zero's i_mmap interval
+ * tree after the first split.
+ */
+ fd = open("/dev/zero", O_RDONLY);
+ if (fd < 0) {
+ ksft_test_result_fail("Reopening /dev/zero failed: %s\n",
+ strerror(errno));
+ goto out;
+ }
+
+ addr2 = mmap(NULL, ps * 3, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE, fd, 0);
+ close(fd);
+ fd = -1;
+ if (addr2 == MAP_FAILED) {
+ ksft_test_result_fail("Second /dev/zero mmap failed: %s\n",
+ strerror(errno));
+ goto out;
+ }
+
+ ret = munmap((char *)addr2 + ps, ps);
+ if (ret) {
+ ksft_test_result_fail("Second middle munmap failed: %s\n",
+ strerror(errno));
+ goto out;
+ }
+ addr2_middle_unmapped = true;
+
+ if (munmap(addr, ps)) {
+ ksft_test_result_fail("Left VMA munmap failed: %s\n",
+ strerror(errno));
+ goto out;
+ }
+ left_mapped = 0;
+
+ if (munmap((char *)addr + 2 * ps, ps)) {
+ ksft_test_result_fail("Right VMA munmap failed: %s\n",
+ strerror(errno));
+ goto out;
+ }
+ right_mapped = 0;
+
+ if (munmap(addr2, ps)) {
+ ksft_test_result_fail("Second left VMA munmap failed: %s\n",
+ strerror(errno));
+ goto out;
+ }
+
+ if (munmap((char *)addr2 + 2 * ps, ps)) {
+ ksft_test_result_fail("Second right VMA munmap failed: %s\n",
+ strerror(errno));
+ goto out;
+ }
+ addr2 = MAP_FAILED;
+
+ ksft_test_result_pass("%s success\n", __func__);
+
+out:
+ if (fd >= 0)
+ close(fd);
+ if (left_mapped)
+ munmap(addr, ps);
+ if (right_mapped)
+ munmap((char *)addr + 2 * ps, ps);
+ if (addr2 != MAP_FAILED) {
+ if (addr2_middle_unmapped) {
+ munmap(addr2, ps);
+ munmap((char *)addr2 + 2 * ps, ps);
+ } else {
+ /* The middle munmap failed, so all three pages remain mapped. */
+ munmap(addr2, ps * 3);
+ }
+ }
+}
+
+static int get_shared_writable_file_expected_error(const char *path)
+{
+ if (get_fs_type(path) == RAMFS_MAGIC)
+ return EPERM; /* ramfs failed */
+
+ return 0;
+}
+
+static int pre_conf_trim_page(void)
+{
+ return set_nr_trim_pages("0\n");
+}
+
+static int post_conf_trim_page(int value)
+{
+ char buf[32];
+
+ if (snprintf(buf, sizeof(buf), "%d\n", value) < 0)
+ return -EIO;
+
+ return set_nr_trim_pages(buf);
+}
+
+struct mremap_case_t {
+ const char *name;
+ const char *pathname;
+ int open_flags;
+ int mmap_prot;
+ int mmap_flags;
+ int exp_err;
+ int (*resolve_exp_err)(const char *path);
+ unsigned int old_pages;
+ unsigned int new_pages;
+ int (*pre_hook)(void);
+ int (*post_hook)(int value);
+};
+
+static struct mremap_case_t mremap_cases[] = {
+ {
+ .name = "anonymous shrink (r--)",
+ .pathname = NULL,
+ .open_flags = O_CREAT | O_RDWR | O_EXCL,
+ .mmap_prot = PROT_READ,
+ .mmap_flags = MAP_ANONYMOUS | MAP_PRIVATE,
+ .exp_err = 0,
+ .resolve_exp_err = 0,
+ },
+ {
+ .name = "shared file shrink (r--)",
+ .pathname = "/tmp/ksft.nommu-remap-XXXXXX",
+ .open_flags = O_CREAT | O_RDWR | O_EXCL,
+ .mmap_prot = PROT_READ,
+ .mmap_flags = MAP_SHARED,
+ .exp_err = 0,
+#ifdef CONFIG_NOMMU
+ .resolve_exp_err = get_shared_writable_file_expected_error,
+#else
+ .resolve_exp_err = 0,
+#endif
+ },
+ {
+ .name = "zero device shrink (r--)",
+ .pathname = "/dev/zero",
+ .open_flags = O_RDONLY,
+ .mmap_prot = PROT_READ,
+ .mmap_flags = MAP_PRIVATE,
+ .exp_err = 0,
+ .resolve_exp_err = 0,
+ },
+ {
+ .name = "private file unchanged length (r-)",
+ .pathname = "/tmp/ksft.nommu-remap-XXXXXX",
+ .open_flags = O_CREAT | O_RDWR | O_EXCL,
+ .mmap_prot = PROT_READ,
+ .mmap_flags = MAP_PRIVATE,
+#ifdef CONFIG_NOMMU
+ .exp_err = EPERM,
+#else
+ .exp_err = 0,
+#endif
+ .resolve_exp_err = 0,
+ .old_pages = 4,
+ .new_pages = 4,
+ },
+ {
+ .name = "private file unchanged length (rw-)",
+ .pathname = "/tmp/ksft.nommu-remap-XXXXXX",
+ .open_flags = O_CREAT | O_RDWR | O_EXCL,
+ .mmap_prot = PROT_READ | PROT_WRITE,
+ .mmap_flags = MAP_PRIVATE,
+ .exp_err = 0,
+ .resolve_exp_err = 0,
+ .old_pages = 4,
+ .new_pages = 4,
+ },
+ {
+ .name = "private file growth (r-)",
+ .pathname = "/tmp/ksft.nommu-remap-XXXXXX",
+ .open_flags = O_CREAT | O_RDWR | O_EXCL,
+ .mmap_prot = PROT_READ,
+ .mmap_flags = MAP_PRIVATE,
+#ifdef CONFIG_NOMMU
+ .exp_err = EPERM,
+#else
+ .exp_err = 0,
+#endif
+ .resolve_exp_err = 0,
+ .old_pages = 4,
+ .new_pages = 8,
+ },
+ {
+ .name = "private file growth with reserved capacity (rw-)",
+ .pathname = "/tmp/ksft.nommu-remap-XXXXXX",
+ .open_flags = O_CREAT | O_RDWR | O_EXCL,
+ .mmap_prot = PROT_READ | PROT_WRITE,
+ .mmap_flags = MAP_PRIVATE,
+ .exp_err = 0,
+ .resolve_exp_err = 0,
+ .old_pages = 3,
+ .new_pages = 4,
+#ifdef CONFIG_NOMMU
+ .pre_hook = pre_conf_trim_page,
+ .post_hook = post_conf_trim_page,
+#endif
+ },
+ {
+ .name = "private file growth (rw-)",
+ .pathname = "/tmp/ksft.nommu-remap-XXXXXX",
+ .open_flags = O_CREAT | O_RDWR | O_EXCL,
+ .mmap_prot = PROT_READ | PROT_WRITE,
+ .mmap_flags = MAP_PRIVATE,
+#ifdef CONFIG_NOMMU
+ .exp_err = ENOMEM,
+#else
+ .exp_err = 0,
+#endif
+ .resolve_exp_err = 0,
+ .old_pages = 4,
+ .new_pages = 8,
+ },
+};
+
+static int run_mremap_test(struct mremap_case_t *tcase)
+{
+ int fd = -1;
+ void *addr, *addr2;
+ char pb[PATH_MAX];
+ int rc = KSFT_PASS;
+ int expected_error;
+ unsigned int old_pages = tcase->old_pages ?: 4;
+ unsigned int new_pages = tcase->new_pages ?: 2;
+ unsigned int file_pages = old_pages > new_pages ?
+ old_pages : new_pages;
+ int orig_nr_trip_pages = -1;
+
+ ksft_print_msg("[RUN] Testing mremap: %s\n", tcase->name);
+
+ if (tcase->pathname && strstr(tcase->pathname, "XXXXXX")) {
+ strncpy(pb, tcase->pathname, sizeof(pb) - 1);
+ pb[sizeof(pb) - 1] = '\0';
+ fd = mkstemp(pb);
+ if (fd < 0) {
+ ksft_test_result_skip("Failed to setup file backing\n");
+ return KSFT_SKIP;
+ }
+ if (ftruncate(fd, ps * file_pages) != 0) {
+ ksft_test_result_fail("Failed to setup file backing\n");
+ close(fd);
+ unlink(pb);
+ return KSFT_FAIL;
+ }
+
+ if ((tcase->mmap_flags & MAP_SHARED) && get_fs_type(pb) != RAMFS_MAGIC) {
+ ksft_test_result_skip("Skip the test under non-ramfs filesystem (%s)\n",
+ pb);
+ close(fd);
+ unlink(pb);
+ return KSFT_SKIP;
+ }
+ } else if (tcase->pathname) {
+ fd = open(tcase->pathname, tcase->open_flags, 0600);
+ if (fd < 0) {
+ ksft_test_result_skip("Backing node not accessible\n");
+ return KSFT_SKIP;
+ }
+
+ if ((tcase->mmap_flags & MAP_SHARED) &&
+ get_fs_type(tcase->pathname) != RAMFS_MAGIC) {
+ ksft_test_result_skip("Skip the test under non-ramfs filesystem (%s)\n",
+ tcase->pathname);
+ close(fd);
+ return KSFT_SKIP;
+ }
+ }
+
+ if (tcase->pre_hook) {
+ orig_nr_trip_pages = tcase->pre_hook();
+ if (orig_nr_trip_pages < 0) {
+ ksft_print_msg("pre-hook failed %s(%d)\n",
+ strerror(errno), errno);
+ rc = KSFT_FAIL;
+ goto out;
+ }
+ }
+
+ addr = mmap(NULL, ps * old_pages, tcase->mmap_prot,
+ tcase->mmap_flags, fd, 0);
+ if (addr == MAP_FAILED) {
+ ksft_print_msg("mmap mapping failed %s(%d)\n", strerror(errno), errno);
+ rc = KSFT_FAIL;
+ goto out;
+ }
+
+ expected_error = tcase->exp_err;
+ if (tcase->resolve_exp_err && fd >= 0)
+ expected_error = tcase->resolve_exp_err(pb);
+
+ addr2 = mremap(addr, ps * old_pages, ps * new_pages,
+ MREMAP_MAYMOVE);
+
+ if (expected_error != 0) {
+ if (addr2 != MAP_FAILED || errno != expected_error) {
+ ksft_print_msg("Expected error %d, got %s(%d)\n",
+ expected_error, strerror(errno), errno);
+ rc = KSFT_FAIL;
+ } else {
+ ksft_print_msg("%s/%s: Handled expected error path (errno=%d)\n",
+ __func__, tcase->name, expected_error);
+ }
+ } else if (addr2 == MAP_FAILED) {
+ ksft_print_msg("mremap shrink failed unexpectedly: %s\n",
+ strerror(errno));
+ rc = KSFT_FAIL;
+ } else {
+ ksft_print_msg("%s/%s step successful\n", __func__, tcase->name);
+ }
+
+ /* clean up */
+ if (munmap(addr2 == MAP_FAILED ? addr : addr2,
+ addr2 == MAP_FAILED ? ps * old_pages : ps * new_pages)) {
+ ksft_print_msg("munmap failed: %s\n", strerror(errno));
+ rc = KSFT_FAIL;
+ }
+
+out:
+ if (fd >= 0) {
+ close(fd);
+ if (tcase->pathname && strstr(tcase->pathname, "XXXXXX"))
+ unlink(pb);
+ }
+
+ if (tcase->post_hook) {
+ if (tcase->post_hook(orig_nr_trip_pages) < 0) {
+ ksft_print_msg("post-hook failed %s(%d)\n",
+ strerror(errno), errno);
+ rc = KSFT_FAIL;
+ }
+ }
+
+ ksft_test_result_report(rc, "%s:%s\n", __func__, tcase->name);
+ return rc;
+}
+
+int main(int argc, char **argv)
+{
+ int res = KSFT_PASS;
+ int i;
+
+ ps = sysconf(_SC_PAGESIZE);
+ ksft_print_header();
+ ksft_set_plan(ARRAY_SIZE(mremap_cases) + 3);
+
+ munmap_shrink_test();
+ mremap_shrink_test();
+ zero_middle_munmap_test();
+
+ for (i = 0; i < (int)ARRAY_SIZE(mremap_cases); i++) {
+ if (run_mremap_test(&mremap_cases[i]) == KSFT_FAIL)
+ res = KSFT_FAIL;
+ }
+
+ if (res == KSFT_PASS)
+ ksft_finished();
+
+ ksft_exit_fail();
+}
|
| 3/2 | 2026/08/13 06:57 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 19153 output: 27 thoughts: 503 Thoughts: **Analyzing a Patch for Nommu Memory Mapping** Okay, so I'm looking at a patch here, and it's all about modifying memory mapping logic, specifically for `nommu` architectures. The changes are spread across a few key files: `mm/nommu.c`, `mm/filemap.c`, and `drivers/char/mem.c`. There are also some added tests in `tools/testing/selftests/mm/nommu_mmap_test.c` and `nommu_mremap_test.c`. Let's dive into the core changes in `mm/nommu.c`. We've got `add_vma_to_mapping` and `remove_vma_from_mapping` being introduced, as well as a helper function `is_file_anonymous` that seems to be checking if a file is `/dev/zero`. The `do_mmap` function now uses a `mmap_prepare` hook if available. The existing `split_vma` and `do_munmap` routines have been updated to correctly handle both anonymous mappings and file mappings. Finally, `do_mremap` logic is updated to manage VMA shrinking and growing as well. The prompt, however, wants me to determine if the patch is worth fuzzing. I need to return `WorthFuzzing=false` if the changes are unreachable in virtualized environments like GCE or QEMU. This is where it gets interesting, since the patch is specifically for `nommu` architectures. The key question is whether or not `nommu` is accessible in standard virtualized environments. I *know* QEMU can emulate `nommu` architectures. I've worked with QEMU emulating ARM Cortex-M, RISC-V nommu, and m68k. But, I know Syzkaller primarily targets MMU-enabled architectures like `x86_64`, `arm64`, `riscv64`, and `s390x`. While Syzkaller *can* be configured to fuzz `nommu` kernels, it isn't typical. Hold on a minute. Is `nommu` even *possible* in a standard kernel build, like x86_64? No, because `CONFIG_MMU` is enabled on x86_64 and `mm/nommu.c` is only compiled when `CONFIG_MMU` is NOT set. I should probably double-check the `mm/Makefile` to confirm that. So, while `nommu` *is* reachable via QEMU, the typical target architectures are all MMU based, and `nommu` isn't accessible in an ordinary configuration. |
| 4/2 | 2026/08/13 06:57 | tool | grepper |
0mArgs:{
"Expression": "nommu\\.o",
"PathPrefix": "mm/Makefile"
}Results: {
"Output": "mm/Makefile=34=CFLAGS_init-mm.o += -Wno-override-init\nmm/Makefile-35-\nmm/Makefile:36:mmu-y\t\t\t:= nommu.o\nmm/Makefile-37-mmu-$(CONFIG_MMU)\t:= highmem.o memory.o mincore.o \\\n"
} |
| 5/2 | 2026/08/13 06:57 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 20361 output: 25 thoughts: 68 Thoughts: **Examining the Memory Management Module Build Process** Okay, so I'm looking at how the kernel builds its memory management modules. The first thing that jumps out is this assignment: `mmu-y := nommu.o`. That's the default, but only when `CONFIG_MMU` isn't set. Makes sense, a No-MMU system needs a specific module. Now, if `CONFIG_MMU` *is* set, it looks like `mmu-$(CONFIG_MMU)` expands to include modules like `highmem.o` and `mmap.o`, which are the foundation of any paging system. My instinct, of course, is to dive straight into `mm/Makefile`. I'm specifically looking for where `nommu.o` is defined and how it gets included in the build process. I want to see the conditional logic and dependencies that are at play. This kind of interplay is crucial for a clean and efficient build. |
| 6/2 | 2026/08/13 06:58 | tool | grepper |
0mArgs:{
"Expression": "nommu",
"PathPrefix": "mm/Makefile"
}Results: {
"Output": "mm/Makefile=34=CFLAGS_init-mm.o += -Wno-override-init\nmm/Makefile-35-\nmm/Makefile:36:mmu-y\t\t\t:= nommu.o\nmm/Makefile-37-mmu-$(CONFIG_MMU)\t:= highmem.o memory.o mincore.o \\\n"
} |
| 7/2 | 2026/08/13 06:58 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 20752 output: 30 thoughts: 15 Thoughts: Here's my summary, as though I'm thinking through this process: **Diving into the Makefile for `mm`** Alright, time to get my hands dirty. I'm going to take a look at the `mm/Makefile` file. Specifically, I'm zeroing in around line 36. That's usually where things start to get interesting—the meat of a build process or a key configuration setting often resides around that area. I need to understand what's happening in this particular context, so I'll be meticulously examining those lines to glean the relevant information. I'm expecting to find some crucial build rules, dependencies, or maybe even some conditional logic that's influencing the `mm` component's build. Let's see what secrets this section of the Makefile holds. |
| 8/2 | 2026/08/13 06:58 | tool | read-file |
0mArgs:{
"File": "mm/Makefile",
"FirstLine": 30,
"LineCount": 20
}Results: {
"Contents": " 30:\tKCOV_INSTRUMENT_mmzone.o := n\n 31:\tKCOV_INSTRUMENT_vmstat.o := n\n 32:\tKCOV_INSTRUMENT_failslab.o := n\n 33:\t\n 34:\tCFLAGS_init-mm.o += -Wno-override-init\n 35:\t\n 36:\tmmu-y\t\t\t:= nommu.o\n 37:\tmmu-$(CONFIG_MMU)\t:= highmem.o memory.o mincore.o \\\n 38:\t\t\t\t mlock.o mmap.o mmu_gather.o mprotect.o mremap.o \\\n 39:\t\t\t\t msync.o page_vma_mapped.o pagewalk.o \\\n 40:\t\t\t\t pgtable-generic.o rmap.o vmalloc.o vma.o vma_exec.o\n 41:\t\n 42:\t\n 43:\tifdef CONFIG_CROSS_MEMORY_ATTACH\n 44:\tmmu-$(CONFIG_MMU)\t+= process_vm_access.o\n 45:\tendif\n 46:\t\n 47:\tifdef CONFIG_64BIT\n 48:\tmmu-$(CONFIG_MMU)\t+= mseal.o\n 49:\tendif\n"
} |
| 9/2 | 2026/08/13 06:58 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 21306 output: 31 thoughts: 214 Thoughts: **My Thought Process on `mmu-y` in the Kernel Build System** Okay, let me break this down. It looks like `mmu-y` starts as `nommu.o` when `CONFIG_MMU` isn't defined. Seems straightforward. But wait, is that *always* the case? Let me double-check. Yes, that's what it looks like, `nommu.o` if `CONFIG_MMU` is not set. Right, now, if `CONFIG_MMU` *is* set, then things get interesting. The line `mmu-$(CONFIG_MMU) := ...` means that `mmu-y` gets assigned a different set of objects, specifically, `highmem.o memory.o ...`. Hang on, does that *override* the initial assignment? Okay, let me clarify: `mmu-y` is *first* initialized to `nommu.o`. Then, the `mmu-$(CONFIG_MMU) := ...` line comes into play. Does this actually overwrite the previous value, or is this conditional assignment somehow different? Wait, using `:=` seems to only be a regular assignment, like setting `mmu-y` to `highmem.o ...` if `CONFIG_MMU=y`. So, yeah, if `CONFIG_MMU=y`, `mmu-y` is assigned `highmem.o`, `memory.o`, etc. That should *completely* overwrite the initial `nommu.o`. Makes sense. Alright, a quick sanity check: It seems clear that the objects are linked based on the config. So I need to make sure that `nommu.c` is not compiled when `CONFIG_MMU` is set. That should be handled by the build system. |
| 10/2 | 2026/08/13 06:58 | tool | grepper |
0mError:no matches Args: {
"Expression": "obj-y.*mmu-y",
"PathPrefix": "mm/Makefile"
}Results: {
"error": "no matches"
} |
| 11/2 | 2026/08/13 06:58 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 21954 output: 30 thoughts: 11 Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience: **Diving Deeper into the Makefile** Right, let's get down to brass tacks. I've got a good handle on the overall project structure, but I need to understand the nuances of the build process for this particular module, `mm`. The `mm/Makefile` is where the rubber meets the road, so to speak. I need to dissect this. I want to see exactly how dependencies are handled, how the compilation and linking are orchestrated, and, critically, how any module-specific flags or optimizations are incorporated. What preprocessor definitions are in play? Are there any clever tricks or custom targets I should be aware of? This could be key to understanding performance bottlenecks or potential integration issues down the line. It's time to become intimate with this `Makefile`. Let's get reading. |
| 12/2 | 2026/08/13 06:58 | tool | read-file |
0mArgs:{
"File": "mm/Makefile",
"FirstLine": 50,
"LineCount": 20
}Results: {
"Contents": " 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 swap.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"
} |
| 13/2 | 2026/08/13 06:58 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 22577 output: 31 thoughts: 197 Thoughts: **Examining the MMU Configuration in the Kernel Build System** Okay, so I'm looking at this build rule: `obj-y += ... $(mmu-y)`. It's pretty straightforward. The key here is how `mmu-y` expands based on the `CONFIG_MMU` setting. If `CONFIG_MMU` is enabled, then `mmu-y` includes the MMU-specific object files like `highmem.o` and `memory.o`, and `nommu.o` is *excluded*. But if `CONFIG_MMU` isn't set, then `mmu-y` becomes just `nommu.o`. That means `nommu.c` is only built when the MMU isn't being used. Now, a question pops into my head: does Syzkaller *always* fuzz architectures with the MMU enabled? I know Syzkaller can handle `arm` or `riscv` without an MMU, but in my experience, it's usually fuzzing configurations *with* an MMU. But wait... can you even *disable* `CONFIG_MMU` on an `x86_64` system? I'm pretty sure `x86` is *always* MMU-required. I should double check that though. Let's go look at `arch/x86/Kconfig` to be sure. |
| 14/2 | 2026/08/13 06:58 | tool | grepper |
0mArgs:{
"Expression": "config MMU",
"PathPrefix": "arch/x86/Kconfig"
}Results: {
"Output": "arch/x86/Kconfig=352=config STACKTRACE_SUPPORT\n--\narch/x86/Kconfig-354-\narch/x86/Kconfig:355:config MMU\narch/x86/Kconfig-356-\tdef_bool y\n"
} |
| 15/2 | 2026/08/13 06:58 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 23194 output: 33 thoughts: 251 Thoughts: **My Assessment of the Patch's Fuzzing Worth** Okay, so the patch touches `mm/nommu.c`, which, as I know, concerns memory management on architectures without a Memory Management Unit. On x86, that's essentially a `def_bool y` for `MMU`. The prompt's got me thinking: can we even fuzz `nommu` effectively? Syzkaller *does* have some support, I recall seeing it for ARM and RISC-V `nommu` configurations, but it’s pretty uncommon to see. Now, wait a minute, the specific instructions from the prompt say "Return WorthFuzzing=false if the patch only contains code paths impossible to reach in virtualized environments like GCE or QEMU, even with software-emulated hardware". Is `nommu` *truly* impossible to reach in QEMU? No, that's wrong - QEMU *can* emulate those `nommu` architectures. But is it *worth* fuzzing? I need to dig deeper. The patch hits `mm/nommu.c`, which is the very heart of memory management for `nommu` systems, that seems relevant. It *also* modifies `drivers/char/mem.c` and `mm/filemap.c`, but only under `#ifdef CONFIG_MMU` or specifically for `nommu`. Okay, let's start with `drivers/char/mem.c` and see what's actually changed. |
| 16/2 | 2026/08/13 06:58 | tool | grepper |
0mArgs:{
"Expression": "mmap_zero_prepare",
"PathPrefix": "drivers/char/mem.c"
}Results: {
"Output": "drivers/char/mem.c=474=static ssize_t read_zero(struct file *file, char __user *buf,\n--\ndrivers/char/mem.c-500-\ndrivers/char/mem.c:501:static int mmap_zero_prepare(struct vm_area_desc *desc)\ndrivers/char/mem.c-502-{\n--\ndrivers/char/mem.c=524=static unsigned long get_unmapped_area_zero(struct file *file,\n--\ndrivers/char/mem.c-529-\t\t/*\ndrivers/char/mem.c:530:\t\t * mmap_zero_prepare() will call shmem_zero_setup() to create a\ndrivers/char/mem.c-531-\t\t * file, so use shmem's get_unmapped_area in case it can be\n--\ndrivers/char/mem.c=665=static const struct file_operations zero_fops = {\n--\ndrivers/char/mem.c-672-\t.splice_write\t= splice_write_zero,\ndrivers/char/mem.c:673:\t.mmap_prepare\t= mmap_zero_prepare,\ndrivers/char/mem.c-674-\t.get_unmapped_area = get_unmapped_area_zero,\n"
} |
| 17/2 | 2026/08/13 06:58 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 24133 output: 34 thoughts: 323 Thoughts: **Analyzing Memory Mapping Preparations** Okay, so I'm looking at this patch for memory mapping, specifically how `/dev/zero` is handled, and my initial thought is, "Huh, `mmap_zero_prepare` – that's for `/dev/zero`, right?". It's a quick mental association, a common pattern. Then the code itself. The patch seems to be changing how `mmap_zero_prepare` behaves when compiled without an MMU. Originally, there was a block that returned `-ENOSYS` if `CONFIG_MMU` wasn't defined. Now, that return is gone, and the call to `shmem_zero_setup_desc` is *conditionally* compiled using `#ifdef CONFIG_MMU`. That means `mmap_zero_prepare` is now doing *something* even when there's no MMU. Interesting. I need to keep that in mind. Then the code calls `generic_file_mmap_prepare`. Now, I need to know where this is conditionally defined. Let's see: I'll check `mm/filemap.c`. That will tell me which condition this is. |
| 18/2 | 2026/08/13 06:58 | tool | grepper |
0mArgs:{
"Expression": "generic_file_mmap_prepare",
"PathPrefix": "mm/filemap.c"
}Results: {
"Output": "mm/filemap.c=4030=int generic_file_mmap(struct file *file, struct vm_area_struct *vma)\n--\nmm/filemap.c-4040-\nmm/filemap.c:4041:int generic_file_mmap_prepare(struct vm_area_desc *desc)\nmm/filemap.c-4042-{\n--\nmm/filemap.c=4063=int generic_file_readonly_mmap_prepare(struct vm_area_desc *desc)\n--\nmm/filemap.c-4066-\t\treturn -EINVAL;\nmm/filemap.c:4067:\treturn generic_file_mmap_prepare(desc);\nmm/filemap.c-4068-}\n--\nmm/filemap.c=4074=int generic_file_mmap(struct file *file, struct vm_area_struct *vma)\n--\nmm/filemap.c-4077-}\nmm/filemap.c:4078:int generic_file_mmap_prepare(struct vm_area_desc *desc)\nmm/filemap.c-4079-{\n--\nmm/filemap.c=4086=int generic_file_readonly_mmap_prepare(struct vm_area_desc *desc)\n--\nmm/filemap.c-4089-\t\treturn -EINVAL;\nmm/filemap.c:4090:\treturn generic_file_mmap_prepare(desc);\nmm/filemap.c-4091-}\n--\nmm/filemap.c=4095=EXPORT_SYMBOL(generic_file_mmap);\nmm/filemap.c:4096:EXPORT_SYMBOL(generic_file_mmap_prepare);\nmm/filemap.c-4097-EXPORT_SYMBOL(generic_file_readonly_mmap);\n"
} |
| 19/2 | 2026/08/13 06:58 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 25221 output: 35 thoughts: 26 Thoughts: Here's a summary of my thought process, as if I were analyzing that code snippet: **Diving into `generic_file_mmap_prepare`** Okay, so I'm looking at `generic_file_mmap_prepare` in `mm/filemap.c`. Right, that means we're dealing with memory mapping of files, and I need to understand how this function fits into that picture. Since it's a "prepare" function, I'm expecting it to handle some initial setup before the actual mapping happens. I imagine it's crucial for things like checking permissions, validating the file size against the requested mapping, and potentially doing some preliminary page allocation or setup. I'll need to see if it manages some specific flags as well. Is it checking for copy on write? Or maybe how it's dealing with huge pages? I bet this is where a lot of the security checks happen too. Time to dig in and see exactly how it's orchestrating all this before the mapping is finally established. I'm keen to see the specific calls and how this function integrates with the broader mmap workflow. Let's see how it makes sure everything is safe. |
| 20/2 | 2026/08/13 06:58 | tool | read-file |
0mArgs:{
"File": "mm/filemap.c",
"FirstLine": 4070,
"LineCount": 30
}Results: {
"Contents": "4070:\tvm_fault_t filemap_page_mkwrite(struct vm_fault *vmf)\n4071:\t{\n4072:\t\treturn VM_FAULT_SIGBUS;\n4073:\t}\n4074:\tint generic_file_mmap(struct file *file, struct vm_area_struct *vma)\n4075:\t{\n4076:\t\treturn -ENOSYS;\n4077:\t}\n4078:\tint generic_file_mmap_prepare(struct vm_area_desc *desc)\n4079:\t{\n4080:\t\treturn 0;\n4081:\t}\n4082:\tint generic_file_readonly_mmap(struct file *file, struct vm_area_struct *vma)\n4083:\t{\n4084:\t\treturn -ENOSYS;\n4085:\t}\n4086:\tint generic_file_readonly_mmap_prepare(struct vm_area_desc *desc)\n4087:\t{\n4088:\t\tif (is_shared_maywrite(\u0026desc-\u003evma_flags))\n4089:\t\t\treturn -EINVAL;\n4090:\t\treturn generic_file_mmap_prepare(desc);\n4091:\t}\n4092:\t#endif /* CONFIG_MMU */\n4093:\t\n4094:\tEXPORT_SYMBOL(filemap_page_mkwrite);\n4095:\tEXPORT_SYMBOL(generic_file_mmap);\n4096:\tEXPORT_SYMBOL(generic_file_mmap_prepare);\n4097:\tEXPORT_SYMBOL(generic_file_readonly_mmap);\n4098:\tEXPORT_SYMBOL(generic_file_readonly_mmap_prepare);\n4099:\t\n"
} |
| 21/2 | 2026/08/13 06:58 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 26004 output: 28 thoughts: 1223 Thoughts: **Analysis of the Patch's Fuzzing Worthiness** Okay, so this patch is clearly targeting the `#ifndef CONFIG_MMU` block. That immediately flags it as a `nommu` architecture change, as `CONFIG_MMU` is disabled for `nommu`. The question is, is this `nommu` code path worth fuzzing? The prompt explicitly states to return `WorthFuzzing=false` if the changes are unreachable in virtualized environments like QEMU. However, `nommu` is indeed reachable within QEMU, and syzkaller *can* fuzz `nommu` kernels, specifically for `riscv64` and `arm` architectures. The patch seems to primarily modify `mm/nommu.c`, which is the core memory management file for the `nommu` architecture. This clearly indicates a functional change that is reachable and therefore, worth fuzzing. The patch also touches selftests, specifically: `tools/testing/selftests/kselftest.h`, `tools/testing/selftests/kselftest/runner.sh`, `tools/testing/selftests/kselftest_harness.h`, `tools/testing/selftests/lib.mk`, `tools/testing/selftests/mm/Makefile`, `tools/testing/selftests/mm/hugetlb_dio.c`, `tools/testing/selftests/mm/mdwe_test.c`, `tools/testing/selftests/mm/nommu_mmap_test.c`, `tools/testing/selftests/mm/nommu_mremap_test.c`. Those are also relevant, in a testing context. Let me double check, though, because it seems too straightforward. In `drivers/char/mem.c`, there's a modification to `mmap_zero_prepare`. However, before the patch, the code inside the `#ifndef CONFIG_MMU` block returned `-ENOSYS`. After the patch, that code block has been removed, but the code related to `CONFIG_MMU` kernels is *unchanged*. This is because the code inside the `ifdef` statement is the same pre and post-patch. This modification does not affect kernels that have `CONFIG_MMU` enabled, which is the norm for common architectures like x86 and arm64. The change is restricted to `!CONFIG_MMU` (nommu) kernels, where the function now sets up `/dev/zero` mapping. So, in essence, this patch is *solely* for `nommu` kernels. Since `nommu` kernels are fuzzable by syzkaller, this is worth fuzzing. The critical configuration here is to *disable* `CONFIG_MMU`. But how do we test this within the current infrastructure? We cannot *disable* a config directly in `EnableConfigs`; we can only *enable* them. And since `CONFIG_MMU` is typically `def_bool y` on most architectures, we will need a `nommu` architecture to test this, such as `m68k`, `arm`, `riscv`, `sh`, `microblaze`, `csky`, or `xtensa`. Given that, we cannot provide an `EnableConfigs` to the testing framework that properly handles that edge case. We will rely on syzkaller automatically selecting an instance. The testing infrastructure needs to be able to disable `CONFIG_MMU`, or we should consider adding a `DisableConfigs` section. |
| 22/2 | 2026/08/13 06:59 | tool | grepper |
0mArgs:{
"Expression": "CONFIG_",
"PathPrefix": "mm/nommu.c"
}Results: {
"Output": "mm/nommu.c=381=SYSCALL_DEFINE1(brk, unsigned long, brk)\n--\nmm/nommu.c-405-\nmm/nommu.c:406:static int sysctl_nr_trim_pages = CONFIG_NOMMU_INITIAL_TRIM_EXCESS;\nmm/nommu.c-407-\n--\nmm/nommu.c=423=void __init mmap_init(void)\n--\nmm/nommu.c-437- */\nmm/nommu.c:438:#ifdef CONFIG_DEBUG_NOMMU_REGIONS\nmm/nommu.c-439-static noinline void validate_nommu_regions(void)\n--\nmm/nommu.c=1071=unsigned long do_mmap(struct file *file,\n--\nmm/nommu.c-1265-\tif (is_file_anonymous(vma-\u003evm_file) \u0026\u0026\nmm/nommu.c:1266:\t (!IS_ENABLED(CONFIG_MMAP_ALLOW_UNINITIALIZED) ||\nmm/nommu.c-1267-\t !(flags \u0026 MAP_UNINITIALIZED)))\n--\nmm/nommu.c=1891=EXPORT_SYMBOL_GPL(access_process_vm);\nmm/nommu.c-1892-\nmm/nommu.c:1893:#ifdef CONFIG_BPF_SYSCALL\nmm/nommu.c-1894-/*\n--\nmm/nommu.c=1969=EXPORT_SYMBOL_GPL(copy_remote_vm_str);\nmm/nommu.c:1970:#endif /* CONFIG_BPF_SYSCALL */\nmm/nommu.c-1971-\n"
} |
| 23/2 | 2026/08/13 06:59 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 28458 output: 30 thoughts: 398 Thoughts: **My Analysis of the Patch and Focus Symbol Selection** Okay, so I'm looking at this patch and trying to figure out the best way to approach testing it, specifically considering what to flag as "FocusSymbols." The patch seems to be touching on the interaction between `NOMMU_INITIAL_TRIM_EXCESS` and `MMAP_ALLOW_UNINITIALIZED`. I see that `MMAP_ALLOW_UNINITIALIZED` is already being used in a conditional check within the code, specifically checking for uninitialized data in anonymous mappings using `is_file_anonymous`. It looks like the patch is making a modification to an existing configuration check. Now, let's get down to the core of the matter: finding the right functions to highlight. I need to figure out which functions are being directly modified. I see `do_mmap`, `do_munmap`, `do_mremap`, `split_vma`, and `vmi_shrink_vma` are all listed as modified within `mm/nommu.c`. These are fundamental memory management functions in the `nommu` context. My initial instinct is to list `do_mmap`, `do_munmap`, and `do_mremap` as FocusSymbols since they are the ones directly being touched. However, the prompt is telling me to be cautious about listing overly generic "hot-path" functions. Hmm, `do_mmap`... isn't that a hot path? Oh right, in `nommu` it's the equivalent of `mmap`, and it's heavily involved in process creation and memory allocation. It's the entry point for the modified logic, so focusing on it might be okay for fuzzing, despite being a hot path. But wait, the prompt specifically says to avoid generic hot-path functions, so I need to check whether the modified functions are static or not. Are `do_mmap`, `do_munmap`, and `do_mremap` declared as static? That would determine whether I should list them as FocusSymbols. Since these are the *exact* functions modified, it seems difficult to avoid them. So, I need to confirm whether or not they are static functions. |
| 24/2 | 2026/08/13 06:59 | tool | grepper |
0mArgs:{
"Expression": "do_mmap",
"PathPrefix": "mm/nommu.c"
}Results: {
"Output": "mm/nommu.c=880=static vm_flags_t determine_vm_flags(struct file *file,\n--\nmm/nommu.c-888-\nmm/nommu.c:889:\t/* private and file mapping will be marked anonymous later (do_mmap_private()).\nmm/nommu.c-890-\t * and /dev/zero is marked by them at .mmap_prepare,\n--\nmm/nommu.c-928- */\nmm/nommu.c:929:static int do_mmap_shared_file(struct vm_area_struct *vma)\nmm/nommu.c-930-{\n--\nmm/nommu.c=947=static ssize_t nommu_read_iter(struct file *file, void *buf,\n--\nmm/nommu.c-972- */\nmm/nommu.c:973:static int do_mmap_private(struct vm_area_struct *vma,\nmm/nommu.c-974-\t\t\t struct vm_region *region,\n--\nmm/nommu.c-1070- */\nmm/nommu.c:1071:unsigned long do_mmap(struct file *file,\nmm/nommu.c-1072-\t\t\tunsigned long addr,\n--\nmm/nommu.c-1206-\t\t\telse {\nmm/nommu.c:1207:\t\t\t\tret = do_mmap_shared_file(vma);\nmm/nommu.c-1208-\t\t\t\tif (ret \u003c 0) {\n--\nmm/nommu.c-1256-\tif (file \u0026\u0026 vma-\u003evm_flags \u0026 VM_SHARED)\nmm/nommu.c:1257:\t\tret = do_mmap_shared_file(vma);\nmm/nommu.c-1258-\telse\nmm/nommu.c:1259:\t\tret = do_mmap_private(vma, region, len, capabilities);\nmm/nommu.c-1260-\tif (ret \u003c 0)\n--\nmm/nommu.c=1620=void exit_mmap(struct mm_struct *mm)\n--\nmm/nommu.c-1648- * under NOMMU conditions, we only permit changing a mapping's size, and only\nmm/nommu.c:1649: * as long as it stays within the region allocated by do_mmap_private() and the\nmm/nommu.c-1650- * block is not shareable\n--\nmm/nommu.c=1969=EXPORT_SYMBOL_GPL(copy_remote_vm_str);\n--\nmm/nommu.c-1979- * make sure that any outstanding VMAs aren't broken and then shrink the\nmm/nommu.c:1980: * vm_regions that extend beyond so that do_mmap() doesn't\nmm/nommu.c-1981- * automatically grant mappings that are too large.\n"
} |
| 25/2 | 2026/08/13 06:59 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 30113 output: 30 thoughts: 21 Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue: **Investigating Memory Mapping Function Design** Hmm, interesting. I'm noting that the `do_mmap` function is declared as non-static. That likely implies it's intended to be called from various places within the system, not just a restricted scope within a single source file. It’s probably a core, publicly accessible function dealing with memory mapping. This makes sense from a design perspective, given the fundamental nature of mapping memory regions. Now, my next question arises naturally from that observation: what about `do_munmap`? If `do_mmap` is flexible in its scope, is `do_munmap` similarly accessible? Are the design patterns consistent? This is an area worth exploring. If they're both meant to be fairly broad-reaching calls, that points to an important architectural consideration. Are there design decisions that can be reused? |
| 26/2 | 2026/08/13 06:59 | tool | grepper |
0mArgs:{
"Expression": "do_munmap",
"PathPrefix": "mm/nommu.c"
}Results: {
"Output": "mm/nommu.c=1484=static int vmi_shrink_vma(struct vma_iterator *vmi,\n--\nmm/nommu.c-1537- */\nmm/nommu.c:1538:int do_munmap(struct mm_struct *mm, unsigned long start, size_t len, struct list_head *uf)\nmm/nommu.c-1539-{\n--\nmm/nommu.c=1600=int vm_munmap(unsigned long addr, size_t len)\n--\nmm/nommu.c-1605-\tmmap_write_lock(mm);\nmm/nommu.c:1606:\tret = do_munmap(mm, addr, len, NULL);\nmm/nommu.c-1607-\tmmap_write_unlock(mm);\n--\nmm/nommu.c=1654=static unsigned long do_mremap(unsigned long addr,\n--\nmm/nommu.c-1694-\tif (new_len \u003c old_len) {\nmm/nommu.c:1695:\t\t/* like do_munmap(), we're allowed to shrink an anonymous VMA but not\nmm/nommu.c-1696-\t\t * a file-backed one\n"
} |
| 27/2 | 2026/08/13 06:59 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 30697 output: 31 thoughts: 22 Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue: **Investigating Memory Mapping Function Attributes** Alright, so I'm thinking about memory mapping functions. Specifically, I see that `do_munmap` isn't declared as `static`. Interesting. That immediately makes me wonder about its sibling function, `do_mremap`. Is *it* declared `static`? The implications are pretty clear. The non-`static` nature of `do_munmap` suggests it's designed to be potentially called from outside the module, maybe by other parts of the kernel, or perhaps even by userspace through specific system calls that are using this function. I need to quickly determine whether the design of `do_mremap` also grants wider access or is it specifically intended for internal use only within the memory management subsystem. This will immediately help clarify if we have a consistent access philosophy across these two key memory manipulation routines or if there's a difference. It's a fundamental question that can reveal a lot about the design intentions and architectural choices made during the development of the kernel. Understanding these decisions is important for overall system security and maintenance. |
| 28/2 | 2026/08/13 06:59 | tool | grepper |
0mArgs:{
"Expression": "do_mremap",
"PathPrefix": "mm/nommu.c"
}Results: {
"Output": "mm/nommu.c=1620=void exit_mmap(struct mm_struct *mm)\n--\nmm/nommu.c-1653- */\nmm/nommu.c:1654:static unsigned long do_mremap(unsigned long addr,\nmm/nommu.c-1655-\t\t\tunsigned long old_len, unsigned long new_len,\n--\nmm/nommu.c=1757=SYSCALL_DEFINE5(mremap, unsigned long, addr, unsigned long, old_len,\n--\nmm/nommu.c-1763-\tmmap_write_lock(current-\u003emm);\nmm/nommu.c:1764:\tret = do_mremap(addr, old_len, new_len, flags, new_addr);\nmm/nommu.c-1765-\tmmap_write_unlock(current-\u003emm);\n"
} |
| 29/2 | 2026/08/13 06:59 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 31232 output: 29 thoughts: 609 Thoughts: **My Analysis of the `nommu.c` Code Path for Fuzzing** Okay, let's break this down. My initial thought process focused on identifying the key entry points for memory management within the `nommu.c` context. I started by noticing `do_mremap` is static, which initially seemed like a dead end. Then, I remembered that `sys_mremap` calls it. So, `sys_mremap`, or its architecture-specific variations like `__x64_sys_mremap`, is the entry point. But, since this is the `nommu.c` file, the important functions are `sys_mmap_pgoff`, `sys_munmap`, and `sys_mremap`. I checked and made sure that `do_mmap` and `do_munmap` are exported or non-static. Those, along with `sys_mremap`, initially seemed like good targets, so I made sure I was tracking `do_mmap`, `do_munmap`, and `sys_mremap` as focus symbols. I then realized `sys_mremap` is defined using `SYSCALL_DEFINE5` in `mm/nommu.c`. I checked that `sys_mremap` is the actual symbol in the kernel by making sure it becomes something like `__se_sys_mremap` or `__arm64_sys_mremap`. Since `do_mmap` and `do_munmap` are the important symbols, I can just focus on those. Now, the important question is: can this code path be fuzzed given the prompt's constraints? The prompt highlights that we should verify that any changes are made to reachable core kernel logic, drivers or architectures. So I asked myself, "Is `nommu` reachable in standard virtualized environments?" The answer is yes, QEMU can emulate `nommu` architectures. However, Syzkaller typically runs on `x86_64`, which has an MMU. So, if we want to fuzz this code, we'd need to run syzkaller on a `nommu` architecture. I then needed to make sure that it was "worth fuzzing". This is determined if changes are made to the core of the kernel logic. Since `nommu` is a feature of the architecture, this is the case. The prompt requires that I set `WorthFuzzing=true`. Next I considered compilation. Can we compile `nommu` on `x86_64`? No. However, we can compile `nommu` on `arm` or `riscv`. Therefore, we don't need to specify `EnableConfigs` for `x86_64`, but we should use `MMAP_ALLOW_UNINITIALIZED`. I looked into whether we should specify the config, since it is used in the patch. The patch uses `IS_ENABLED(CONFIG_MMAP_ALLOW_UNINITIALIZED)`. I should make sure this config is valid. |
| 30/2 | 2026/08/13 06:59 | tool | grepper |
0mArgs:{
"Expression": "config MMAP_ALLOW_UNINITIALIZED",
"PathPrefix": ""
}Results: {
"Output": "mm/Kconfig=346=config COMPAT_BRK\n--\nmm/Kconfig-357-\nmm/Kconfig:358:config MMAP_ALLOW_UNINITIALIZED\nmm/Kconfig-359-\tbool \"Allow mmapped anonymous memory to be uninitialized\"\n"
} |
| 31/2 | 2026/08/13 06:59 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 32609 output: 36 thoughts: 518 Thoughts: **Analyzing the Scope of Changes for Syzkaller Focus** Okay, so I'm looking at this patch and figuring out what to list as `FocusSymbols` for Syzkaller. It looks like the patch is targeting the NOMMU configuration, which is interesting. I see tests added in `tools/testing/selftests/mm/nommu_mmap_test.c` and `nommu_mremap_test.c`, but Syzkaller doesn't directly run those. It's about kernel fuzzing. The core kernel modifications are in `mm/nommu.c`, `mm/filemap.c`, and `drivers/char/mem.c`. Let's break down `mm/nommu.c`. It has a number of modified functions, some static, some non-static. The non-static ones are the ones that are relevant because they're entry points. I initially considered `do_mmap` and `do_munmap`. But the prompt advises against listing generic, hot-path functions to avoid test distribution skew. The problem is, in the NOMMU context, `do_mmap` is *the* memory mapping function, so I'm conflicted. It's a hot path, absolutely. But it's also explicitly modified by the patch, and is the only non-static function changed in the code. I am leaning toward listing it in the focus symbols. The prompt says to prefer non-static, non-inlined API entrypoints functions. I can't list any other non-static functions except `do_mmap` and `do_munmap` from the `mm/nommu.c` file. So it looks like `mm/filemap.c` is modified as well. I think I'll include `generic_file_readonly_mmap_prepare`. |
| 32/2 | 2026/08/13 07:00 | tool | grepper |
0mArgs:{
"Expression": "generic_file_readonly_mmap_prepare",
"PathPrefix": "mm/filemap.c"
}Results: {
"Output": "mm/filemap.c=4056=int generic_file_readonly_mmap(struct file *file, struct vm_area_struct *vma)\n--\nmm/filemap.c-4062-\nmm/filemap.c:4063:int generic_file_readonly_mmap_prepare(struct vm_area_desc *desc)\nmm/filemap.c-4064-{\n--\nmm/filemap.c=4082=int generic_file_readonly_mmap(struct file *file, struct vm_area_struct *vma)\n--\nmm/filemap.c-4085-}\nmm/filemap.c:4086:int generic_file_readonly_mmap_prepare(struct vm_area_desc *desc)\nmm/filemap.c-4087-{\n--\nmm/filemap.c=4097=EXPORT_SYMBOL(generic_file_readonly_mmap);\nmm/filemap.c:4098:EXPORT_SYMBOL(generic_file_readonly_mmap_prepare);\nmm/filemap.c-4099-\n"
} |
| 33/2 | 2026/08/13 07:00 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 33840 output: 34 thoughts: 41 Thoughts: Okay, here's my take on those thoughts, framed as my own mental processing: **Examining Memory Mapping Preparations** Alright, let's see... `generic_file_readonly_mmap_prepare`... Yup, that's definitely exported, and it's not declared static. We can readily list it and confirm its visibility. Now, the next question that springs to mind is, what about its counterpart, `generic_file_mmap_prepare`? Is that available and visible too? I need to check its status and understand if there's any distinction or relationship between the two. |
| 34/2 | 2026/08/13 07:00 | tool | grepper |
0mArgs:{
"Expression": "generic_file_mmap_prepare",
"PathPrefix": "mm/filemap.c"
}Results: {
"Output": "mm/filemap.c=4030=int generic_file_mmap(struct file *file, struct vm_area_struct *vma)\n--\nmm/filemap.c-4040-\nmm/filemap.c:4041:int generic_file_mmap_prepare(struct vm_area_desc *desc)\nmm/filemap.c-4042-{\n--\nmm/filemap.c=4063=int generic_file_readonly_mmap_prepare(struct vm_area_desc *desc)\n--\nmm/filemap.c-4066-\t\treturn -EINVAL;\nmm/filemap.c:4067:\treturn generic_file_mmap_prepare(desc);\nmm/filemap.c-4068-}\n--\nmm/filemap.c=4074=int generic_file_mmap(struct file *file, struct vm_area_struct *vma)\n--\nmm/filemap.c-4077-}\nmm/filemap.c:4078:int generic_file_mmap_prepare(struct vm_area_desc *desc)\nmm/filemap.c-4079-{\n--\nmm/filemap.c=4086=int generic_file_readonly_mmap_prepare(struct vm_area_desc *desc)\n--\nmm/filemap.c-4089-\t\treturn -EINVAL;\nmm/filemap.c:4090:\treturn generic_file_mmap_prepare(desc);\nmm/filemap.c-4091-}\n--\nmm/filemap.c=4095=EXPORT_SYMBOL(generic_file_mmap);\nmm/filemap.c:4096:EXPORT_SYMBOL(generic_file_mmap_prepare);\nmm/filemap.c-4097-EXPORT_SYMBOL(generic_file_readonly_mmap);\n"
} |
| 35/2 | 2026/08/13 07:00 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 34520 output: 108 thoughts: 1138 Thoughts: **My Analysis of the Patch for Syzkaller Focus** Okay, so I'm looking at this patch and figuring out how to guide Syzkaller's fuzzing efforts. First, I confirmed that `generic_file_mmap_prepare` is exported. However, `mmap_zero_prepare` is static, so that's out. I can list `generic_file_readonly_mmap_prepare` and `generic_file_mmap_prepare`, but these are only compiled when `!CONFIG_MMU`. Syzkaller will definitely be interested in those if it runs on a `nommu` kernel. I need to confirm if Syzkaller can actually *use* the `nommu` configuration. The prompt says to return `WorthFuzzing=false` if the code paths aren't reachable in virtualized environments. Luckily, `nommu` kernels are perfectly reachable via QEMU (e.g., `qemu-system-arm` or `qemu-system-riscv64` with a `nommu` kernel), so this patch is definitely fuzzable. Thus, `WorthFuzzing=true`. Now, for the *FocusSymbols*. I initially thought of listing `do_mmap` and `do_munmap`, but the prompt specifically tells me to avoid generic hot-path functions to prevent skewed test distributions. `do_mmap` is called all the time in `nommu`, so that's definitely a hot-path. `generic_file_mmap_prepare` just returns 0, so that's trivial. I have to think if there's any important, non-hot-path function here. I see that `do_mmap` in `nommu.c` has a lot of complex logic modified by the patch (e.g. calling `mmap_prepare`, handling anonymous files, etc). If I don't list it, Syzkaller might not focus on the new logic, but it *is* a hot-path. I have to think about this carefully. `generic_file_readonly_mmap_prepare` has some small logic. After reviewing the changes again, all the other functions I see are static. So I will focus on `generic_file_readonly_mmap_prepare`. Let's check `EnableConfigs`. The patch uses `MMAP_ALLOW_UNINITIALIZED`, so I'll include it. **Final Summary:** * **WorthFuzzing: true** * **FocusSymbols:** `generic_file_readonly_mmap_prepare` * **EnableConfigs:** `MMAP_ALLOW_UNINITIALIZED` * **Reasoning:** The patch modifies core memory mapping logic and file mapping preparation specifically for `nommu` architectures. These changes are functional and reachable in `nommu` kernels emulated by QEMU. While `do_mmap` and `do_munmap` are modified by this patch, they are too generic to focus syzkaller on. |
| 36/2 | 2026/08/13 07:00 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"MMAP_ALLOW_UNINITIALIZED"
],
"FocusSymbols": [
"generic_file_readonly_mmap_prepare",
"generic_file_mmap_prepare"
],
"Reasoning": "The patch modifies core memory mapping logic (mmap, munmap, mremap) and file mapping preparation for nommu architectures. These changes are functional and reachable in nommu kernels emulated by QEMU.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"MMAP_ALLOW_UNINITIALIZED"
],
"FocusSymbols": [
"generic_file_readonly_mmap_prepare",
"generic_file_mmap_prepare"
],
"Reasoning": "The patch modifies core memory mapping logic (mmap, munmap, mremap) and file mapping preparation for nommu architectures. These changes are functional and reachable in nommu kernels emulated by QEMU.",
"WorthFuzzing": true
} |
| 37/1 | 2026/08/13 07:00 | action | If |
3mArgs:{
"WorthFuzzing": true
}Results: null |
| 38/2 | 2026/08/13 07:00 | agent | kmsan-evaluator |
3mModel:best-expensive Args: null Results: {
"KMSANReasoning": "The patch series introduces `mmap_prepare` and `mremap` shrinking/growing support specifically for NOMMU architectures (`mm/nommu.c`, `mm/filemap.c` under `#ifndef CONFIG_MMU`, and `drivers/char/mem.c` for NOMMU). The only change to code compiled with `CONFIG_MMU` is in `drivers/char/mem.c`, where an `#ifndef CONFIG_MMU` block was replaced with an `#ifdef CONFIG_MMU` block, resulting in identical behavior for MMU builds.\n\nKMSAN is a dynamic analysis tool that requires compiler instrumentation and is currently only supported on architectures with an MMU (x86_64 and s390). It cannot be enabled on NOMMU builds. Because the functional changes in this patch series are exclusively for NOMMU configurations, they will never be compiled or executed in a KMSAN-enabled kernel.\n\nFurthermore, even within the NOMMU code, the patch carefully initializes newly exposed memory during `mremap` (either by reading from the backing file via `nommu_read_iter` or zero-filling) and correctly handles anonymous mappings, preventing uninitialized memory leaks. Therefore, a dedicated KMSAN fuzzing session is not justified.",
"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: For your convenience, here is the diff of the changes:
commit 6216c638fbd4b11f0b5b5c76b1b4d376b68eb3e2
Author: syz-cluster <triage@syzkaller.com>
Date: Thu Aug 13 06:57:37 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/dev-tools/kselftest.rst b/Documentation/dev-tools/kselftest.rst
index 64c0ec7428a27..800b2b688aff0 100644
--- a/Documentation/dev-tools/kselftest.rst
+++ b/Documentation/dev-tools/kselftest.rst
@@ -230,6 +230,18 @@ section::
.. _tar's auto-compress: https://www.gnu.org/software/tar/manual/html_node/gzip.html#auto_002dcompress
+Build and test on nommu target
+==============================
+
+If you (cross-)build kselftests for nommu targets, or run tests on nommu targets, use
+``NOMMU=1`` as a make variable/environment setting to tell build system to do the additional
+checks. These nommu targets may differ in several ways, such as not supporting fork(2) or
+using musl or another libc. Set this variable to apply the necessary build and test adjustments.
+
+ $ make ARCH=um NOMMU=1 O=build kselftest
+ $ make ARCH=um NOMMU=1 -C tools/testing/selftests/mm run_tests
+ $ NOMMU=1 /tmp/kselftest_install/run_kselftest.sh -s -c mm
+
Contributing new tests
======================
diff --git a/drivers/char/mem.c b/drivers/char/mem.c
index 63253d1de5d70..dba24d0a7b33d 100644
--- a/drivers/char/mem.c
+++ b/drivers/char/mem.c
@@ -500,11 +500,10 @@ static ssize_t read_zero(struct file *file, char __user *buf,
static int mmap_zero_prepare(struct vm_area_desc *desc)
{
-#ifndef CONFIG_MMU
- return -ENOSYS;
-#endif
+#ifdef CONFIG_MMU
if (vma_desc_test(desc, VMA_SHARED_BIT))
return shmem_zero_setup_desc(desc);
+#endif
/*
* This is a highly unique situation where we mark a MAP_PRIVATE mapping
diff --git a/mm/filemap.c b/mm/filemap.c
index d721986d5f465..cf02faad86aa6 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -4077,7 +4077,7 @@ int generic_file_mmap(struct file *file, struct vm_area_struct *vma)
}
int generic_file_mmap_prepare(struct vm_area_desc *desc)
{
- return -ENOSYS;
+ return 0;
}
int generic_file_readonly_mmap(struct file *file, struct vm_area_struct *vma)
{
@@ -4085,7 +4085,9 @@ int generic_file_readonly_mmap(struct file *file, struct vm_area_struct *vma)
}
int generic_file_readonly_mmap_prepare(struct vm_area_desc *desc)
{
- return -ENOSYS;
+ if (is_shared_maywrite(&desc->vma_flags))
+ return -EINVAL;
+ return generic_file_mmap_prepare(desc);
}
#endif /* CONFIG_MMU */
diff --git a/mm/nommu.c b/mm/nommu.c
index ed3934bc2de48..a29a53c1c80a6 100644
--- a/mm/nommu.c
+++ b/mm/nommu.c
@@ -37,6 +37,7 @@
#include <linux/uaccess.h>
#include <linux/uio.h>
+#include <linux/major.h>
#include <asm/tlb.h>
#include <asm/tlbflush.h>
#include <asm/mmu_context.h>
@@ -559,36 +560,51 @@ static void put_nommu_region(struct vm_region *region)
__put_nommu_region(region);
}
+static void add_vma_to_mapping(struct vm_area_struct *vma)
+{
+ struct address_space *mapping;
+
+ if (!vma->vm_file)
+ return;
+
+ mapping = vma->vm_file->f_mapping;
+ i_mmap_lock_write(mapping);
+ flush_dcache_mmap_lock(mapping);
+ vma_interval_tree_insert(vma, &mapping->i_mmap);
+ flush_dcache_mmap_unlock(mapping);
+ i_mmap_unlock_write(mapping);
+}
+
+static void remove_vma_from_mapping(struct vm_area_struct *vma)
+{
+ struct address_space *mapping;
+
+ if (!vma->vm_file)
+ return;
+
+ mapping = vma->vm_file->f_mapping;
+ i_mmap_lock_write(mapping);
+ flush_dcache_mmap_lock(mapping);
+ vma_interval_tree_remove(vma, &mapping->i_mmap);
+ flush_dcache_mmap_unlock(mapping);
+ i_mmap_unlock_write(mapping);
+}
+
static void setup_vma_to_mm(struct vm_area_struct *vma, struct mm_struct *mm)
{
vma->vm_mm = mm;
/* add the VMA to the mapping */
- if (vma->vm_file) {
- struct address_space *mapping = vma->vm_file->f_mapping;
-
- i_mmap_lock_write(mapping);
- flush_dcache_mmap_lock(mapping);
- vma_interval_tree_insert(vma, &mapping->i_mmap);
- flush_dcache_mmap_unlock(mapping);
- i_mmap_unlock_write(mapping);
- }
+ if (vma->vm_file)
+ add_vma_to_mapping(vma);
}
static void cleanup_vma_from_mm(struct vm_area_struct *vma)
{
vma->vm_mm->map_count--;
/* remove the VMA from the mapping */
- if (vma->vm_file) {
- struct address_space *mapping;
- mapping = vma->vm_file->f_mapping;
-
- i_mmap_lock_write(mapping);
- flush_dcache_mmap_lock(mapping);
- vma_interval_tree_remove(vma, &mapping->i_mmap);
- flush_dcache_mmap_unlock(mapping);
- i_mmap_unlock_write(mapping);
- }
+ if (vma->vm_file)
+ remove_vma_from_mapping(vma);
}
/*
@@ -841,6 +857,22 @@ static int validate_mmap_request(struct file *file,
return 0;
}
+static int is_file_anonymous(struct file *file)
+{
+ if (!file)
+ return 1;
+
+ if (file->f_path.dentry && file->f_path.dentry->d_inode) {
+ struct inode *inode = file->f_path.dentry->d_inode;
+ /* if the device is /dev/zero */
+ if (S_ISCHR(inode->i_mode) &&
+ imajor(inode) == MEM_MAJOR && iminor(inode) == 5)
+ return 1;
+ }
+
+ return 0;
+}
+
/*
* we've determined that we can make the mapping, now translate what we
* now know into VMA flags
@@ -854,7 +886,11 @@ static vm_flags_t determine_vm_flags(struct file *file,
vm_flags = calc_vm_prot_bits(prot, 0) | calc_vm_flag_bits(file, flags);
- if (!file) {
+ /* private and file mapping will be marked anonymous later (do_mmap_private()).
+ * and /dev/zero is marked by them at .mmap_prepare,
+ * which should be _before_ this point.
+ */
+ if (is_file_anonymous(file)) {
/*
* MAP_ANONYMOUS. MAP_SHARED is mapped to MAP_PRIVATE, because
* there is no fork().
@@ -908,6 +944,29 @@ static int do_mmap_shared_file(struct vm_area_struct *vma)
return -ENODEV;
}
+static ssize_t nommu_read_iter(struct file *file, void *buf,
+ size_t count, loff_t *pos)
+{
+ struct iov_iter iter;
+ ssize_t ret;
+ size_t done = 0;
+
+ while (done < count) {
+ struct kvec iov = {
+ .iov_base = buf + done,
+ .iov_len = min_t(size_t, count - done, MAX_RW_COUNT),
+ };
+
+ iov_iter_kvec(&iter, ITER_DEST, &iov, 1, iov.iov_len);
+ ret = vfs_iter_read(file, &iter, pos, 0);
+ if (ret <= 0)
+ return done ? done : ret;
+ done += ret;
+ }
+
+ return done;
+}
+
/*
* set up a private mapping or an anonymous shared mapping
*/
@@ -978,7 +1037,7 @@ static int do_mmap_private(struct vm_area_struct *vma,
fpos = vma->vm_pgoff;
fpos <<= PAGE_SHIFT;
- ret = kernel_read(vma->vm_file, base, len, &fpos);
+ ret = nommu_read_iter(vma->vm_file, base, len, &fpos);
if (ret < 0)
goto error_free;
@@ -1065,6 +1124,28 @@ unsigned long do_mmap(struct file *file,
vma->vm_file = get_file(file);
}
+ /* call mmap_prepare function if any */
+ if (!(flags & MAP_SHARED) && !(capabilities & NOMMU_MAP_DIRECT) &&
+ (vma->vm_file && vma->vm_file->f_op->mmap_prepare)) {
+ struct vm_area_desc desc;
+
+ vma->vm_start = addr;
+ vma->vm_end = addr + len;
+
+ compat_set_desc_from_vma(&desc, vma->vm_file, vma);
+ ret = vma->vm_file->f_op->mmap_prepare(&desc);
+ /* private ramfs/romfs mappings fails with -ENOSYS so,
+ * fall back to copied mapping.
+ */
+ if (ret && ret != -ENOSYS)
+ goto error_mmap_prepare;
+
+ ret = __compat_vma_mmap(&desc, vma);
+ if (ret)
+ goto error_mmap_prepare;
+ }
+
+
down_write(&nommu_region_sem);
/* if we want to share, we need to check for regions created by other
@@ -1181,7 +1262,7 @@ unsigned long do_mmap(struct file *file,
add_nommu_region(region);
/* clear anonymous mappings that don't ask for uninitialized data */
- if (!vma->vm_file &&
+ if (is_file_anonymous(vma->vm_file) &&
(!IS_ENABLED(CONFIG_MMAP_ALLOW_UNINITIALIZED) ||
!(flags & MAP_UNINITIALIZED)))
memset((void *)region->vm_start, 0,
@@ -1232,6 +1313,18 @@ unsigned long do_mmap(struct file *file,
ret = -EINVAL;
goto error;
+error_mmap_prepare:
+ if (region->vm_file)
+ fput(region->vm_file);
+ kmem_cache_free(vm_region_jar, region);
+ if (vma->vm_file)
+ fput(vma->vm_file);
+ vm_area_free(vma);
+
+ pr_warn("mmap_prepare failed for %lu byte allocation from process %d\n",
+ len, current->pid);
+ return ret;
+
error_getting_vma:
kmem_cache_free(vm_region_jar, region);
pr_warn("Allocation of vma for %lu byte allocation from process %d failed\n",
@@ -1313,7 +1406,7 @@ static int split_vma(struct vma_iterator *vmi, struct vm_area_struct *vma,
/* we're only permitted to split anonymous regions (these should have
* only a single usage on the region) */
- if (vma->vm_file)
+ if (!vma_is_anonymous(vma))
return -ENOMEM;
mm = vma->vm_mm;
@@ -1351,6 +1444,8 @@ static int split_vma(struct vma_iterator *vmi, struct vm_area_struct *vma,
if (new->vm_ops && new->vm_ops->open)
new->vm_ops->open(new);
+ remove_vma_from_mapping(vma);
+
down_write(&nommu_region_sem);
delete_nommu_region(vma->vm_region);
if (new_below) {
@@ -1364,6 +1459,11 @@ static int split_vma(struct vma_iterator *vmi, struct vm_area_struct *vma,
add_nommu_region(new->vm_region);
up_write(&nommu_region_sem);
+ if (new->vm_file) {
+ vma->vm_file = get_file(vma->vm_file);
+ new->vm_file = get_file(new->vm_file);
+ }
+
setup_vma_to_mm(vma, mm);
setup_vma_to_mm(new, mm);
vma_iter_store_new(vmi, new);
@@ -1386,16 +1486,20 @@ static int vmi_shrink_vma(struct vma_iterator *vmi,
unsigned long from, unsigned long to)
{
struct vm_region *region;
+ bool has_mapping = !!vma->vm_file;
+
+ if (has_mapping)
+ remove_vma_from_mapping(vma);
/* adjust the VMA's pointers, which may reposition it in the MM's tree
* and list */
if (from > vma->vm_start) {
if (vma_iter_clear_gfp(vmi, from, vma->vm_end, GFP_KERNEL))
- return -ENOMEM;
+ goto restore_mapping;
vma->vm_end = from;
} else {
if (vma_iter_clear_gfp(vmi, vma->vm_start, to, GFP_KERNEL))
- return -ENOMEM;
+ goto restore_mapping;
vma->vm_start = to;
}
@@ -1415,7 +1519,15 @@ static int vmi_shrink_vma(struct vma_iterator *vmi,
up_write(&nommu_region_sem);
free_page_series(from, to);
+ if (has_mapping)
+ add_vma_to_mapping(vma);
+
return 0;
+
+restore_mapping:
+ if (has_mapping)
+ add_vma_to_mapping(vma);
+ return -ENOMEM;
}
/*
@@ -1450,7 +1562,7 @@ int do_munmap(struct mm_struct *mm, unsigned long start, size_t len, struct list
}
/* we're allowed to split an anonymous VMA but not a file-backed one */
- if (vma->vm_file) {
+ if (!vma_is_anonymous(vma)) {
do {
if (start > vma->vm_start)
return -EINVAL;
@@ -1544,6 +1656,9 @@ static unsigned long do_mremap(unsigned long addr,
unsigned long flags, unsigned long new_addr)
{
struct vm_area_struct *vma;
+ int ret;
+
+ VMA_ITERATOR(vmi, current->mm, addr);
/* insanity checks first */
old_len = PAGE_ALIGN(old_len);
@@ -1567,11 +1682,75 @@ static unsigned long do_mremap(unsigned long addr,
if (is_nommu_shared_mapping(vma->vm_flags))
return (unsigned long) -EPERM;
- if (new_len > vma->vm_region->vm_end - vma->vm_region->vm_start)
+ /* vm_region->vm_top != vm_region->vm_end when sysctl_nr_trim_pages is 0 (default: 1) */
+ if (new_len > vma->vm_region->vm_top - vma->vm_region->vm_start)
return (unsigned long) -ENOMEM;
/* all checks complete - do it */
- vma->vm_end = vma->vm_start + new_len;
+ if (new_len == old_len)
+ return vma->vm_start;
+
+ /* shrink only happens addr + new_len and old_len are in different pages */
+ if (new_len < old_len) {
+ /* like do_munmap(), we're allowed to shrink an anonymous VMA but not
+ * a file-backed one
+ */
+ if (!vma_is_anonymous(vma))
+ return (unsigned long) -EINVAL;
+
+ /* vmi_shrink_vma() needs from/to pointers to be removed,
+ * (mainly used in munmap) so, specify them.
+ */
+ ret = vmi_shrink_vma(&vmi, vma, addr + new_len, addr + old_len);
+ if (ret < 0)
+ return (unsigned long) ret;
+ } else {
+ /* growth path: grow up to vm_top should be handled here. */
+ unsigned long old_end = vma->vm_end;
+ unsigned long end = vma->vm_start + new_len;
+ unsigned long grow_len = end - old_end;
+
+ /*
+ * Initialize the newly exposed portion before making it visible
+ * through the VMA or i_mmap.
+ */
+
+ /* read contents of extended map from file, or zero-filled if !vm_file */
+ if (vma->vm_file) {
+ loff_t fpos;
+
+ fpos = (loff_t)vma->vm_pgoff << PAGE_SHIFT;
+ fpos += old_end - vma->vm_start;
+
+ ret = nommu_read_iter(vma->vm_file, (void *)old_end,
+ grow_len, &fpos);
+ if (ret < 0)
+ return (unsigned long)ret;
+
+ if (ret < grow_len)
+ memset((char *)old_end + ret, 0, grow_len - ret);
+ } else {
+ memset((void *)old_end, 0, grow_len);
+ }
+
+ /* The backing contents are ready. Now update the VMA bookkeeping. */
+ remove_vma_from_mapping(vma);
+
+ vma->vm_end = end;
+ ret = vma_iter_store_gfp(&vmi, vma, GFP_KERNEL);
+ if (ret) {
+ vma->vm_end = old_end;
+ add_vma_to_mapping(vma);
+ return (unsigned long)ret;
+ }
+
+ /* vm_top remains unchanged; only the logical end grows. */
+ down_write(&nommu_region_sem);
+ vma->vm_region->vm_end = end;
+ up_write(&nommu_region_sem);
+
+ add_vma_to_mapping(vma);
+ }
return vma->vm_start;
}
diff --git a/tools/testing/selftests/kselftest.h b/tools/testing/selftests/kselftest.h
index ae18c491ae533..ac21bf3d802e5 100644
--- a/tools/testing/selftests/kselftest.h
+++ b/tools/testing/selftests/kselftest.h
@@ -58,6 +58,7 @@
#include <string.h>
#include <stdio.h>
#include <sys/utsname.h>
+#include <stdint.h>
#endif
#ifndef ARRAY_SIZE
@@ -81,6 +82,48 @@
#endif
#endif /* end arch */
+#if !defined(NOLIBC) && !defined(__GLIBC__)
+#ifdef __LP64__
+typedef int64_t __fsword_t;
+#else
+typedef int32_t __fsword_t;
+#endif
+
+/*
+ * for a workaround to avoid struct conflict under
+ * musl-libc (<sys/prctl.h> v.s. <linux/prctl.h>)
+ */
+#include <sys/prctl.h>
+#ifndef _LINUX_PRCTL_H
+#define _LINUX_PRCTL_H
+#endif
+
+#ifndef PR_SET_MDWE
+#define PR_SET_MDWE 65
+#endif
+
+#ifndef PR_MDWE_REFUSE_EXEC_GAIN
+#define PR_MDWE_REFUSE_EXEC_GAIN (1UL << 0)
+#endif
+
+#ifndef PR_MDWE_NO_INHERIT
+#define PR_MDWE_NO_INHERIT (1UL << 1)
+#endif
+
+#ifndef PR_GET_MDWE
+#define PR_GET_MDWE 66
+#endif
+
+#ifndef PR_SET_MEMORY_MERGE
+#define PR_SET_MEMORY_MERGE 67
+#endif
+
+#ifndef PR_GET_MEMORY_MERGE
+#define PR_GET_MEMORY_MERGE 68
+#endif
+
+#endif
+
/* define kselftest exit codes */
#define KSFT_PASS 0
#define KSFT_FAIL 1
diff --git a/tools/testing/selftests/kselftest/runner.sh b/tools/testing/selftests/kselftest/runner.sh
index 311811dc55a07..7287d8290b6c9 100644
--- a/tools/testing/selftests/kselftest/runner.sh
+++ b/tools/testing/selftests/kselftest/runner.sh
@@ -38,8 +38,12 @@ tap_prefix()
tap_timeout()
{
+ # nommu doesn't support timeout command (missing fork(2))
+ if [ "$NOMMU" = "1" ] ; then
+ echo "timeout isn't supported for nommu"
+ $1
# Make sure tests will time out if utility is available.
- if [ -x /usr/bin/timeout ] ; then
+ elif [ -x /usr/bin/timeout ] ; then
/usr/bin/timeout --foreground "$kselftest_timeout" \
/usr/bin/timeout "$kselftest_timeout" $1
else
@@ -130,6 +134,7 @@ run_one()
return $KSFT_FAIL
fi
fi
+ OLDDIR=$(pwd)
cd `dirname $TEST` > /dev/null
(((( tap_timeout "$cmd" 2>&1; echo $? >&3) |
tap_prefix >&4) 3>&1) |
@@ -147,7 +152,7 @@ run_one()
*)
ktap_test_fail "$TEST_HDR_MSG # exit=$rc";;
esac
- cd - >/dev/null
+ cd "$OLDDIR" >/dev/null
fi
return $rc
diff --git a/tools/testing/selftests/kselftest_harness.h b/tools/testing/selftests/kselftest_harness.h
index 261e4df94d9d5..8eee7b14f8248 100644
--- a/tools/testing/selftests/kselftest_harness.h
+++ b/tools/testing/selftests/kselftest_harness.h
@@ -1274,6 +1274,10 @@ static int test_harness_run(int argc, char **argv)
unsigned int count = 0;
unsigned int pass_count = 0;
+#ifdef CONFIG_NOMMU
+ ksft_print_msg("harness test doesn't support on NOMMU architecture (no fork(2)).\n");
+ return KSFT_SKIP;
+#endif /* CONFIG_NOMMU */
ret = test_harness_argv_check(argc, argv);
if (ret != KSFT_PASS)
return ret;
diff --git a/tools/testing/selftests/lib.mk b/tools/testing/selftests/lib.mk
index f02cc8a2e4ae3..4734b5ce613f0 100644
--- a/tools/testing/selftests/lib.mk
+++ b/tools/testing/selftests/lib.mk
@@ -97,6 +97,14 @@ TEST_GEN_PROGS := $(patsubst %,$(OUTPUT)/%,$(TEST_GEN_PROGS))
TEST_GEN_PROGS_EXTENDED := $(patsubst %,$(OUTPUT)/%,$(TEST_GEN_PROGS_EXTENDED))
TEST_GEN_FILES := $(patsubst %,$(OUTPUT)/%,$(TEST_GEN_FILES))
+# detect if users request NOMMU build or not
+# User can set NOMMU to 1 to build/test for NOMMU platforms
+NOMMU ?= 0
+ifeq ($(NOMMU),1)
+CFLAGS += -DCONFIG_NOMMU
+export NOMMU
+endif
+
all: $(TEST_GEN_PROGS) $(TEST_GEN_PROGS_EXTENDED) $(TEST_GEN_FILES) \
$(if $(TEST_GEN_MODS_DIR),gen_mods_dir)
@@ -197,7 +205,7 @@ clean: $(if $(TEST_GEN_MODS_DIR),clean_mods_dir)
$(CLEAN)
# Build with _GNU_SOURCE by default
-CFLAGS += -D_GNU_SOURCE=
+CFLAGS += -D_GNU_SOURCE= -D_LARGEFILE64_SOURCE
# Additional include paths needed by kselftest.h and local headers
CFLAGS += -I${top_srcdir}/tools/testing/selftests
diff --git a/tools/testing/selftests/mm/Makefile b/tools/testing/selftests/mm/Makefile
index e6df968f0971c..3986ae7d75bca 100644
--- a/tools/testing/selftests/mm/Makefile
+++ b/tools/testing/selftests/mm/Makefile
@@ -185,6 +185,9 @@ TEST_FILES += run_vmtests.sh
# required by charge_reserved_hugetlb.sh
TEST_FILES += write_hugetlb_memory.sh
+TEST_GEN_PROGS += nommu_mmap_test
+TEST_GEN_PROGS += nommu_mremap_test
+
include ../lib.mk
$(TEST_GEN_PROGS): vm_util.c hugepage_settings.c
diff --git a/tools/testing/selftests/mm/hugetlb_dio.c b/tools/testing/selftests/mm/hugetlb_dio.c
index fb4600570e131..aee6be530ccbf 100644
--- a/tools/testing/selftests/mm/hugetlb_dio.c
+++ b/tools/testing/selftests/mm/hugetlb_dio.c
@@ -22,12 +22,9 @@
#include "kselftest.h"
#include "hugepage_settings.h"
-#ifndef STATX_DIOALIGN
-#define STATX_DIOALIGN 0x00002000U
-#endif
-
static int get_dio_alignment(int fd)
{
+#ifdef STATX_DIOALIGN
struct statx stx;
int ret;
@@ -43,6 +40,9 @@ static int get_dio_alignment(int fd)
return 1;
return stx.stx_dio_offset_align;
+#else
+ return -1;
+#endif
}
static bool check_dio_alignment(unsigned int start_off,
diff --git a/tools/testing/selftests/mm/mdwe_test.c b/tools/testing/selftests/mm/mdwe_test.c
index 647779653da09..031c79ed067ea 100644
--- a/tools/testing/selftests/mm/mdwe_test.c
+++ b/tools/testing/selftests/mm/mdwe_test.c
@@ -1,5 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
+#include "kselftest_harness.h"
+
#ifdef __aarch64__
#include <asm/hwcap.h>
#endif
@@ -14,8 +16,6 @@
#include <sys/wait.h>
#include <unistd.h>
-#include "kselftest_harness.h"
-
#ifndef __aarch64__
# define PROT_BTI 0
#endif
diff --git a/tools/testing/selftests/mm/nommu_mmap_test.c b/tools/testing/selftests/mm/nommu_mmap_test.c
new file mode 100644
index 0000000000000..50ce385967f4c
--- /dev/null
+++ b/tools/testing/selftests/mm/nommu_mmap_test.c
@@ -0,0 +1,294 @@
+// SPDX-License-Identifier: GPL-2.0
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <limits.h>
+#include "../kselftest.h"
+
+#include <sys/vfs.h>
+#ifndef RAMFS_MAGIC
+#define RAMFS_MAGIC 0x858458f6
+#endif
+
+#ifndef MAP_UNINITIALIZED
+#define MAP_UNINITIALIZED 0x4000000
+#endif
+
+static size_t ps;
+
+struct test_case_t {
+ const char *name;
+ const char *pathname;
+ int open_flags;
+ int mmap_prot;
+ int mmap_flags;
+ int exp_err;
+ int (*resolve_exp_err)(const char *path);
+};
+
+static int get_shm_expected_error(const char *path)
+{
+ struct statfs fs;
+
+ if (statfs(path, &fs) == 0) {
+ if (fs.f_type == RAMFS_MAGIC)
+ return 0; /* ramfs succeed with contiguous memory */
+ }
+ /* hostfs, etc returns ENODEV due to lack of contiguous allocation */
+ return ENODEV;
+}
+
+static struct test_case_t test_cases[] = {
+ {
+ "Anonymous private allocation",
+ NULL,
+ O_CREAT | O_RDWR | O_EXCL,
+ PROT_READ | PROT_WRITE,
+ MAP_ANONYMOUS | MAP_PRIVATE,
+ 0,
+ NULL,
+ },
+ {
+ "Non-anonymous private file mapping (rw-)",
+ "/tmp/ksft.nommu-reg-XXXXXX",
+ O_CREAT | O_RDWR | O_EXCL,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE,
+ 0,
+ 0,
+ },
+ {
+ "Non-anonymous private file mapping (r--)",
+ "/tmp/ksft.nommu-reg-XXXXXX",
+ O_CREAT | O_RDWR | O_EXCL,
+ PROT_READ,
+ MAP_PRIVATE,
+ 0,
+ 0,
+ },
+ {
+ "Non-anonymous shared file mapping (rw-)",
+ "/tmp/ksft.nommu-shm-XXXXXX",
+ O_CREAT | O_RDWR | O_EXCL,
+ PROT_READ | PROT_WRITE,
+ MAP_SHARED,
+ 0,
+#ifdef CONFIG_NOMMU
+ get_shm_expected_error,
+#else
+ 0,
+#endif
+ },
+ {
+ "Non-anonymous shared file mapping (r--)",
+ "/tmp/ksft.nommu-shm-XXXXXX",
+ O_CREAT | O_RDWR | O_EXCL,
+ PROT_READ,
+ MAP_SHARED,
+ 0,
+#ifdef CONFIG_NOMMU
+ get_shm_expected_error,
+#else
+ 0,
+#endif
+ },
+ {
+ "Memory-backed private storage via /dev/zero",
+ "/dev/zero",
+ O_RDONLY,
+ PROT_READ,
+ MAP_PRIVATE,
+ 0,
+ NULL,
+ },
+ {
+ "Memory-backed storage via /dev/zero (MAP_SHARED)",
+ "/dev/zero",
+ O_RDONLY,
+ PROT_READ,
+ MAP_SHARED,
+#ifdef CONFIG_NOMMU
+ ENODEV,
+#else
+ 0,
+#endif
+ NULL,
+ },
+ {
+ "Block device volatile node mapping",
+ "/dev/loop0",
+ O_RDWR,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE,
+ 0,
+ NULL,
+ }
+};
+
+static int run_mapping_matrix_test(struct test_case_t *tcase)
+{
+ int fd;
+ void *ptr;
+ char path_buf[PATH_MAX];
+ int rc = KSFT_PASS;
+ int expected_error;
+
+ ksft_print_msg("[RUN] Testing: %s\n", tcase->name);
+
+ if (tcase->pathname == NULL) {
+ fd = -1;
+ } else if (strstr(tcase->pathname, "XXXXXX")) {
+ strncpy(path_buf, tcase->pathname, sizeof(path_buf) - 1);
+ path_buf[sizeof(path_buf) - 1] = '\0';
+ fd = mkstemp(path_buf);
+ if (fd < 0) {
+ ksft_test_result_skip("Failed to setup temp node: %s\n",
+ tcase->pathname);
+ return KSFT_SKIP;
+ }
+ if (ftruncate(fd, ps) != 0) {
+ ksft_test_result_fail("ftruncate failed for: %s\n",
+ tcase->pathname);
+ close(fd);
+ unlink(path_buf);
+ return KSFT_FAIL;
+ }
+ } else {
+ fd = open(tcase->pathname, tcase->open_flags, 0600);
+ if (fd < 0) {
+ ksft_test_result_skip("Device node not accessible: %s\n",
+ tcase->pathname);
+ return KSFT_SKIP;
+ }
+ }
+
+ expected_error = tcase->exp_err;
+ if (tcase->resolve_exp_err && fd >= 0)
+ expected_error = tcase->resolve_exp_err(path_buf);
+
+ ptr = mmap(NULL, ps, tcase->mmap_prot, tcase->mmap_flags, fd, 0);
+
+ if (expected_error != 0) {
+ if (ptr != MAP_FAILED) {
+ ksft_test_result_fail("%s: mmap unexpectedly succeeded (exp error %d)\n",
+ tcase->name, expected_error);
+ munmap(ptr, ps);
+ rc = KSFT_FAIL;
+ goto cleanup;
+ }
+ if (errno != expected_error) {
+ ksft_test_result_fail("%s: mmap failed with %d (%s), but expected %d\n",
+ tcase->name, errno, strerror(errno), expected_error);
+ rc = KSFT_FAIL;
+ goto cleanup;
+ }
+ ksft_test_result_pass("%s: Correctly rejected with expected error %s(%d)\n",
+ tcase->name, strerror(expected_error), expected_error);
+ rc = KSFT_PASS;
+ goto cleanup;
+ }
+
+ if (ptr == MAP_FAILED) {
+ ksft_test_result_fail("%s: mmap failed unexpectedly: %s\n",
+ tcase->name, strerror(errno));
+ rc = KSFT_FAIL;
+ goto cleanup;
+ }
+
+ ksft_test_result_pass("%s: mmap validation successfully passed\n", tcase->name);
+ munmap(ptr, ps);
+
+cleanup:
+ if (fd >= 0) {
+ close(fd);
+ if (tcase->pathname && strstr(tcase->pathname, "XXXXXX"))
+ unlink(path_buf);
+ }
+ return rc;
+}
+
+static int test_map_fixed(void)
+{
+ void *ptr = mmap((void *)(ps * 100), ps, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
+
+ ksft_print_msg("[RUN] Testing MAP_FIXED behavior\n");
+
+#ifdef CONFIG_NOMMU
+ if (ptr == MAP_FAILED && (errno == ENODEV || errno == EINVAL)) {
+ ksft_test_result_pass("MAP_FIXED correctly rejected under nommu\n");
+ return KSFT_PASS;
+ }
+ if (ptr != MAP_FAILED) {
+ ksft_test_result_fail("MAP_FIXED unexpectedly allowed under nommu\n");
+ munmap(ptr, ps);
+ return KSFT_FAIL;
+ }
+ ksft_test_result_fail("MAP_FIXED failed under NOMMU: %s\n",
+ strerror(errno));
+ return KSFT_FAIL;
+#else
+ if (ptr != MAP_FAILED) {
+ ksft_test_result_pass("MAP_FIXED successfully allocated under MMU\n");
+ munmap(ptr, ps);
+ return KSFT_PASS;
+ }
+ ksft_test_result_fail("MAP_FIXED failed allocation under MMU\n");
+ return KSFT_FAIL;
+#endif
+}
+
+static int test_uninit(void)
+{
+ void *ptr = mmap(NULL, ps, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_UNINITIALIZED, -1, 0);
+
+ ksft_print_msg("[RUN] Testing MAP_UNINITIALIZED behavior\n");
+
+ if (ptr == MAP_FAILED) {
+ ksft_test_result_skip("MAP_UNINITIALIZED not supported by kernel config\n");
+ return KSFT_SKIP;
+ }
+
+ ksft_test_result_pass("MAP_UNINITIALIZED allocation successful\n");
+ munmap(ptr, ps);
+ return KSFT_PASS;
+}
+
+int main(int argc, char **argv)
+{
+ int result = KSFT_PASS;
+ int i, rc;
+
+ ps = sysconf(_SC_PAGESIZE);
+ ksft_print_header();
+ ksft_set_plan(ARRAY_SIZE(test_cases) + 2);
+
+#ifdef CONFIG_NOMMU
+ ksft_print_msg("Running strict MMAP test criteria under nommu architecture\n");
+#else
+ ksft_print_msg("Running MMAP test criteria under MMU architecture\n");
+#endif
+
+ if (test_map_fixed() == KSFT_FAIL)
+ result = KSFT_FAIL;
+
+ if (test_uninit() == KSFT_FAIL)
+ result = KSFT_FAIL;
+
+ for (i = 0; i < (int)ARRAY_SIZE(test_cases); i++) {
+ rc = run_mapping_matrix_test(&test_cases[i]);
+ if (rc == KSFT_FAIL)
+ result = KSFT_FAIL;
+ }
+
+ if (result == KSFT_PASS)
+ ksft_finished();
+
+ ksft_exit_fail();
+}
diff --git a/tools/testing/selftests/mm/nommu_mremap_test.c b/tools/testing/selftests/mm/nommu_mremap_test.c
new file mode 100644
index 0000000000000..b1b8a4e64b555
--- /dev/null
+++ b/tools/testing/selftests/mm/nommu_mremap_test.c
@@ -0,0 +1,583 @@
+// SPDX-License-Identifier: GPL-2.0
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <limits.h>
+#include "../kselftest.h"
+
+#include <sys/vfs.h>
+#ifndef RAMFS_MAGIC
+#define RAMFS_MAGIC 0x858458f6
+#endif
+
+static size_t ps;
+
+static long get_fs_type(const char *path)
+{
+ struct statfs fs;
+
+ if (statfs(path, &fs) == 0)
+ return fs.f_type;
+
+ return 0;
+}
+
+/* return original value if succeed */
+static int set_nr_trim_pages(const char *value)
+{
+ int fd, orig_value;
+ ssize_t len, written, read_len;
+ char orig_buf[32];
+
+ fd = open("/proc/sys/vm/nr_trim_pages", O_RDWR);
+ if (fd < 0)
+ return -errno;
+
+ read_len = read(fd, orig_buf, sizeof(orig_buf) - 1);
+ if (read_len < 0) {
+ close(fd);
+ return -errno;
+ }
+ if (read_len == 0) {
+ close(fd);
+ return -EIO;
+ }
+
+ orig_buf[read_len] = '\0';
+ orig_value = atoi(orig_buf);
+
+ if (lseek(fd, 0, SEEK_SET) < 0) {
+ close(fd);
+ return -errno;
+ }
+
+ len = strlen(value);
+ written = write(fd, value, len);
+ close(fd);
+
+ if (written != len)
+ return written < 0 ? -errno : -EIO;
+
+ return orig_value >= 0 ? orig_value : -EINVAL;
+}
+
+static void munmap_shrink_test(void)
+{
+ void *addr;
+
+ /* munmap shrink test */
+ for (int i = 0; i < 4; i++) {
+ addr = mmap(NULL, ps * 4, PROT_READ | PROT_WRITE,
+ MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+ if (addr == MAP_FAILED) {
+ ksft_test_result_fail("mmap failed: %s(%d)\n", strerror(errno), errno);
+ return;
+ }
+ if (munmap(addr + ps * i, ps) != 0) {
+ ksft_test_result_fail("memory %p isn't unmapped at %p\n",
+ addr, addr + ps * i);
+ for (int j = 0; j < 4; j++)
+ munmap((char *)addr + j * ps, ps);
+ return;
+ }
+
+ if (i == 0) {
+ if (munmap(addr + ps, ps * 3))
+ goto error;
+ } else if (i == 1) {
+ if (munmap(addr, ps) || munmap(addr + (ps * 2), ps * 2))
+ goto error;
+ } else if (i == 2) {
+ if (munmap(addr, ps * 2) || munmap(addr + (ps * 3), ps))
+ goto error;
+ } else if (i == 3) {
+ if (munmap(addr, ps * 3))
+ goto error;
+ }
+ }
+
+ ksft_test_result_pass("%s success\n", __func__);
+ return;
+error:
+ for (int j = 0; j < 4; j++)
+ munmap((char *)addr + j * ps, ps);
+ ksft_test_result_fail("%s clean up failures\n", __func__);
+}
+
+static size_t page_align(size_t len)
+{
+ return (len + ps - 1) / ps * ps;
+}
+
+static void mremap_shrink_test(void)
+{
+ void *addr, *addr2;
+ size_t current_len;
+ size_t old_len, new_len;
+ struct param {
+ size_t old;
+ size_t new;
+ } params[] = {
+ /* should not happen any shrink */
+ { .old = ps * 4 - 1, .new = ps * 4 - 2 },
+ /* should not happen any shrink */
+ { .old = ps * 4 - 1, .new = ps * 4 },
+ { .old = ps * 4, .new = ps * 2 },
+ /* should not happen any shrink */
+ { .old = ps * 2, .new = ps * 2 - 2 },
+ { .old = ps * 2 - 2, .new = ps * 1 },
+ };
+
+ /* mremap shrink test */
+ current_len = page_align(ps * 4 - 1);
+ addr = mmap(NULL, ps * 4 - 1, PROT_READ | PROT_WRITE,
+ MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+ if (addr == MAP_FAILED) {
+ ksft_test_result_fail("mmap failed: %s(%d)\n", strerror(errno), errno);
+ return;
+ }
+
+ for (int i = 0; i < ARRAY_SIZE(params); i++) {
+ old_len = page_align(params[i].old);
+ new_len = page_align(params[i].new);
+ addr2 = mremap(addr, old_len, new_len, MREMAP_MAYMOVE);
+ if (addr2 == MAP_FAILED) {
+ ksft_test_result_fail("memory %p isn't remapped at %p\n", addr, addr2);
+ munmap(addr, old_len);
+ return;
+ }
+
+ addr = addr2;
+ current_len = new_len;
+ }
+
+ if (munmap(addr, current_len)) {
+ ksft_test_result_fail("%s cleanup failed: %s\n",
+ __func__, strerror(errno));
+ return;
+ }
+ ksft_test_result_pass("%s success\n", __func__);
+}
+
+static void zero_middle_munmap_test(void)
+{
+ int fd = -1;
+ void *addr = MAP_FAILED;
+ void *addr2 = MAP_FAILED;
+ int left_mapped = 0;
+ int right_mapped = 0;
+ int ret;
+ bool addr2_middle_unmapped = false;
+
+ ksft_print_msg("[RUN] Testing middle munmap of private /dev/zero mappings\n");
+
+ fd = open("/dev/zero", O_RDONLY);
+ if (fd < 0) {
+ ksft_test_result_skip("Unable to open /dev/zero: %s\n",
+ strerror(errno));
+ return;
+ }
+
+ addr = mmap(NULL, ps * 3, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE, fd, 0);
+ close(fd);
+ fd = -1;
+ if (addr == MAP_FAILED) {
+ ksft_test_result_fail("Initial /dev/zero mmap failed: %s\n",
+ strerror(errno));
+ return;
+ }
+
+ left_mapped = 1;
+ right_mapped = 1;
+
+ /*
+ * Split one private /dev/zero VMA in the middle. On nommu, the
+ * VMA still has vm_file set even though it is semantically
+ * anonymous.
+ */
+ ret = munmap((char *)addr + ps, ps);
+ if (ret) {
+ ksft_test_result_fail("Middle munmap failed: %s\n",
+ strerror(errno));
+ munmap(addr, ps * 3);
+ left_mapped = 0;
+ right_mapped = 0;
+ goto out;
+ }
+
+ /*
+ * Access both remaining VMAs so a stale VMA interval cannot remain
+ * hidden behind the unmapped hole.
+ */
+ ((volatile unsigned char *)addr)[0] = 0x5a;
+ ((volatile unsigned char *)addr + 2 * ps)[0] = 0xa5;
+
+ /*
+ * Repeat the operation on another mapping of the same inode. This
+ * exercises insertion and removal in /dev/zero's i_mmap interval
+ * tree after the first split.
+ */
+ fd = open("/dev/zero", O_RDONLY);
+ if (fd < 0) {
+ ksft_test_result_fail("Reopening /dev/zero failed: %s\n",
+ strerror(errno));
+ goto out;
+ }
+
+ addr2 = mmap(NULL, ps * 3, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE, fd, 0);
+ close(fd);
+ fd = -1;
+ if (addr2 == MAP_FAILED) {
+ ksft_test_result_fail("Second /dev/zero mmap failed: %s\n",
+ strerror(errno));
+ goto out;
+ }
+
+ ret = munmap((char *)addr2 + ps, ps);
+ if (ret) {
+ ksft_test_result_fail("Second middle munmap failed: %s\n",
+ strerror(errno));
+ goto out;
+ }
+ addr2_middle_unmapped = true;
+
+ if (munmap(addr, ps)) {
+ ksft_test_result_fail("Left VMA munmap failed: %s\n",
+ strerror(errno));
+ goto out;
+ }
+ left_mapped = 0;
+
+ if (munmap((char *)addr + 2 * ps, ps)) {
+ ksft_test_result_fail("Right VMA munmap failed: %s\n",
+ strerror(errno));
+ goto out;
+ }
+ right_mapped = 0;
+
+ if (munmap(addr2, ps)) {
+ ksft_test_result_fail("Second left VMA munmap failed: %s\n",
+ strerror(errno));
+ goto out;
+ }
+
+ if (munmap((char *)addr2 + 2 * ps, ps)) {
+ ksft_test_result_fail("Second right VMA munmap failed: %s\n",
+ strerror(errno));
+ goto out;
+ }
+ addr2 = MAP_FAILED;
+
+ ksft_test_result_pass("%s success\n", __func__);
+
+out:
+ if (fd >= 0)
+ close(fd);
+ if (left_mapped)
+ munmap(addr, ps);
+ if (right_mapped)
+ munmap((char *)addr + 2 * ps, ps);
+ if (addr2 != MAP_FAILED) {
+ if (addr2_middle_unmapped) {
+ munmap(addr2, ps);
+ munmap((char *)addr2 + 2 * ps, ps);
+ } else {
+ /* The middle munmap failed, so all three pages remain mapped. */
+ munmap(addr2, ps * 3);
+ }
+ }
+}
+
+static int get_shared_writable_file_expected_error(const char *path)
+{
+ if (get_fs_type(path) == RAMFS_MAGIC)
+ return EPERM; /* ramfs failed */
+
+ return 0;
+}
+
+static int pre_conf_trim_page(void)
+{
+ return set_nr_trim_pages("0\n");
+}
+
+static int post_conf_trim_page(int value)
+{
+ char buf[32];
+
+ if (snprintf(buf, sizeof(buf), "%d\n", value) < 0)
+ return -EIO;
+
+ return set_nr_trim_pages(buf);
+}
+
+struct mremap_case_t {
+ const char *name;
+ const char *pathname;
+ int open_flags;
+ int mmap_prot;
+ int mmap_flags;
+ int exp_err;
+ int (*resolve_exp_err)(const char *path);
+ unsigned int old_pages;
+ unsigned int new_pages;
+ int (*pre_hook)(void);
+ int (*post_hook)(int value);
+};
+
+static struct mremap_case_t mremap_cases[] = {
+ {
+ .name = "anonymous shrink (r--)",
+ .pathname = NULL,
+ .open_flags = O_CREAT | O_RDWR | O_EXCL,
+ .mmap_prot = PROT_READ,
+ .mmap_flags = MAP_ANONYMOUS | MAP_PRIVATE,
+ .exp_err = 0,
+ .resolve_exp_err = 0,
+ },
+ {
+ .name = "shared file shrink (r--)",
+ .pathname = "/tmp/ksft.nommu-remap-XXXXXX",
+ .open_flags = O_CREAT | O_RDWR | O_EXCL,
+ .mmap_prot = PROT_READ,
+ .mmap_flags = MAP_SHARED,
+ .exp_err = 0,
+#ifdef CONFIG_NOMMU
+ .resolve_exp_err = get_shared_writable_file_expected_error,
+#else
+ .resolve_exp_err = 0,
+#endif
+ },
+ {
+ .name = "zero device shrink (r--)",
+ .pathname = "/dev/zero",
+ .open_flags = O_RDONLY,
+ .mmap_prot = PROT_READ,
+ .mmap_flags = MAP_PRIVATE,
+ .exp_err = 0,
+ .resolve_exp_err = 0,
+ },
+ {
+ .name = "private file unchanged length (r-)",
+ .pathname = "/tmp/ksft.nommu-remap-XXXXXX",
+ .open_flags = O_CREAT | O_RDWR | O_EXCL,
+ .mmap_prot = PROT_READ,
+ .mmap_flags = MAP_PRIVATE,
+#ifdef CONFIG_NOMMU
+ .exp_err = EPERM,
+#else
+ .exp_err = 0,
+#endif
+ .resolve_exp_err = 0,
+ .old_pages = 4,
+ .new_pages = 4,
+ },
+ {
+ .name = "private file unchanged length (rw-)",
+ .pathname = "/tmp/ksft.nommu-remap-XXXXXX",
+ .open_flags = O_CREAT | O_RDWR | O_EXCL,
+ .mmap_prot = PROT_READ | PROT_WRITE,
+ .mmap_flags = MAP_PRIVATE,
+ .exp_err = 0,
+ .resolve_exp_err = 0,
+ .old_pages = 4,
+ .new_pages = 4,
+ },
+ {
+ .name = "private file growth (r-)",
+ .pathname = "/tmp/ksft.nommu-remap-XXXXXX",
+ .open_flags = O_CREAT | O_RDWR | O_EXCL,
+ .mmap_prot = PROT_READ,
+ .mmap_flags = MAP_PRIVATE,
+#ifdef CONFIG_NOMMU
+ .exp_err = EPERM,
+#else
+ .exp_err = 0,
+#endif
+ .resolve_exp_err = 0,
+ .old_pages = 4,
+ .new_pages = 8,
+ },
+ {
+ .name = "private file growth with reserved capacity (rw-)",
+ .pathname = "/tmp/ksft.nommu-remap-XXXXXX",
+ .open_flags = O_CREAT | O_RDWR | O_EXCL,
+ .mmap_prot = PROT_READ | PROT_WRITE,
+ .mmap_flags = MAP_PRIVATE,
+ .exp_err = 0,
+ .resolve_exp_err = 0,
+ .old_pages = 3,
+ .new_pages = 4,
+#ifdef CONFIG_NOMMU
+ .pre_hook = pre_conf_trim_page,
+ .post_hook = post_conf_trim_page,
+#endif
+ },
+ {
+ .name = "private file growth (rw-)",
+ .pathname = "/tmp/ksft.nommu-remap-XXXXXX",
+ .open_flags = O_CREAT | O_RDWR | O_EXCL,
+ .mmap_prot = PROT_READ | PROT_WRITE,
+ .mmap_flags = MAP_PRIVATE,
+#ifdef CONFIG_NOMMU
+ .exp_err = ENOMEM,
+#else
+ .exp_err = 0,
+#endif
+ .resolve_exp_err = 0,
+ .old_pages = 4,
+ .new_pages = 8,
+ },
+};
+
+static int run_mremap_test(struct mremap_case_t *tcase)
+{
+ int fd = -1;
+ void *addr, *addr2;
+ char pb[PATH_MAX];
+ int rc = KSFT_PASS;
+ int expected_error;
+ unsigned int old_pages = tcase->old_pages ?: 4;
+ unsigned int new_pages = tcase->new_pages ?: 2;
+ unsigned int file_pages = old_pages > new_pages ?
+ old_pages : new_pages;
+ int orig_nr_trip_pages = -1;
+
+ ksft_print_msg("[RUN] Testing mremap: %s\n", tcase->name);
+
+ if (tcase->pathname && strstr(tcase->pathname, "XXXXXX")) {
+ strncpy(pb, tcase->pathname, sizeof(pb) - 1);
+ pb[sizeof(pb) - 1] = '\0';
+ fd = mkstemp(pb);
+ if (fd < 0) {
+ ksft_test_result_skip("Failed to setup file backing\n");
+ return KSFT_SKIP;
+ }
+ if (ftruncate(fd, ps * file_pages) != 0) {
+ ksft_test_result_fail("Failed to setup file backing\n");
+ close(fd);
+ unlink(pb);
+ return KSFT_FAIL;
+ }
+
+ if ((tcase->mmap_flags & MAP_SHARED) && get_fs_type(pb) != RAMFS_MAGIC) {
+ ksft_test_result_skip("Skip the test under non-ramfs filesystem (%s)\n",
+ pb);
+ close(fd);
+ unlink(pb);
+ return KSFT_SKIP;
+ }
+ } else if (tcase->pathname) {
+ fd = open(tcase->pathname, tcase->open_flags, 0600);
+ if (fd < 0) {
+ ksft_test_result_skip("Backing node not accessible\n");
+ return KSFT_SKIP;
+ }
+
+ if ((tcase->mmap_flags & MAP_SHARED) &&
+ get_fs_type(tcase->pathname) != RAMFS_MAGIC) {
+ ksft_test_result_skip("Skip the test under non-ramfs filesystem (%s)\n",
+ tcase->pathname);
+ close(fd);
+ return KSFT_SKIP;
+ }
+ }
+
+ if (tcase->pre_hook) {
+ orig_nr_trip_pages = tcase->pre_hook();
+ if (orig_nr_trip_pages < 0) {
+ ksft_print_msg("pre-hook failed %s(%d)\n",
+ strerror(errno), errno);
+ rc = KSFT_FAIL;
+ goto out;
+ }
+ }
+
+ addr = mmap(NULL, ps * old_pages, tcase->mmap_prot,
+ tcase->mmap_flags, fd, 0);
+ if (addr == MAP_FAILED) {
+ ksft_print_msg("mmap mapping failed %s(%d)\n", strerror(errno), errno);
+ rc = KSFT_FAIL;
+ goto out;
+ }
+
+ expected_error = tcase->exp_err;
+ if (tcase->resolve_exp_err && fd >= 0)
+ expected_error = tcase->resolve_exp_err(pb);
+
+ addr2 = mremap(addr, ps * old_pages, ps * new_pages,
+ MREMAP_MAYMOVE);
+
+ if (expected_error != 0) {
+ if (addr2 != MAP_FAILED || errno != expected_error) {
+ ksft_print_msg("Expected error %d, got %s(%d)\n",
+ expected_error, strerror(errno), errno);
+ rc = KSFT_FAIL;
+ } else {
+ ksft_print_msg("%s/%s: Handled expected error path (errno=%d)\n",
+ __func__, tcase->name, expected_error);
+ }
+ } else if (addr2 == MAP_FAILED) {
+ ksft_print_msg("mremap shrink failed unexpectedly: %s\n",
+ strerror(errno));
+ rc = KSFT_FAIL;
+ } else {
+ ksft_print_msg("%s/%s step successful\n", __func__, tcase->name);
+ }
+
+ /* clean up */
+ if (munmap(addr2 == MAP_FAILED ? addr : addr2,
+ addr2 == MAP_FAILED ? ps * old_pages : ps * new_pages)) {
+ ksft_print_msg("munmap failed: %s\n", strerror(errno));
+ rc = KSFT_FAIL;
+ }
+
+out:
+ if (fd >= 0) {
+ close(fd);
+ if (tcase->pathname && strstr(tcase->pathname, "XXXXXX"))
+ unlink(pb);
+ }
+
+ if (tcase->post_hook) {
+ if (tcase->post_hook(orig_nr_trip_pages) < 0) {
+ ksft_print_msg("post-hook failed %s(%d)\n",
+ strerror(errno), errno);
+ rc = KSFT_FAIL;
+ }
+ }
+
+ ksft_test_result_report(rc, "%s:%s\n", __func__, tcase->name);
+ return rc;
+}
+
+int main(int argc, char **argv)
+{
+ int res = KSFT_PASS;
+ int i;
+
+ ps = sysconf(_SC_PAGESIZE);
+ ksft_print_header();
+ ksft_set_plan(ARRAY_SIZE(mremap_cases) + 3);
+
+ munmap_shrink_test();
+ mremap_shrink_test();
+ zero_middle_munmap_test();
+
+ for (i = 0; i < (int)ARRAY_SIZE(mremap_cases); i++) {
+ if (run_mremap_test(&mremap_cases[i]) == KSFT_FAIL)
+ res = KSFT_FAIL;
+ }
+
+ if (res == KSFT_PASS)
+ ksft_finished();
+
+ ksft_exit_fail();
+}
|
| 39/3 | 2026/08/13 07:00 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 19150 output: 32 thoughts: 1276 Thoughts: **KMSAN Fuzzing Session Justification**
Okay, so I need to assess whether this patch series warrants a dedicated KMSAN fuzzing session. My goal is to catch reads of uninitialized memory (on stack, heap, or page allocations) and any kernel-to-user memory leaks.
Let's break this down. The patch touches `mm/nommu.c`, `mm/filemap.c`, `drivers/char/mem.c`, and adds tests in `tools/testing/selftests/mm/`, which suggests they are addressing mmap preparation, as well as `mremap` and `munmap` shrinking within the NOMMU memory management code.
Looking at `mm/nommu.c`, I see these key changes: additions to handle vma interval trees, a new `is_file_anonymous` check, modifications to `do_mmap_private` to use `nommu_read_iter` instead of `kernel_read`, and the addition of `nommu_read_iter` itself, which reads from a file into a buffer using `vfs_iter_read`. `do_mmap` gets modified to call `mmap_prepare` if available, and also clears uninitialized data for anonymous mappings. There are changes to split, shrink, and remap VMAs, and also to handle the `mremap` function.
The `do_mremap` code caught my eye:
```c
+ /* read contents of extended map from file, or zero-filled if !vm_file */
+ if (vma->vm_file) {
+ loff_t fpos;
+
+ fpos = (loff_t)vma->vm_pgoff << PAGE_SHIFT;
+ fpos += old_end - vma->vm_start;
+
+ ret = nommu_read_iter(vma->vm_file, (void *)old_end,
+ grow_len, &fpos);
+ if (ret < 0)
+ return (unsigned long)ret;
+
+ if (ret < grow_len)
+ memset((char *)old_end + ret, 0, grow_len - ret);
+ } else {
+ memset((void *)old_end, 0, grow_len);
+ }
```
If `vma->vm_file` is present, it reads from the file using `nommu_read_iter`. If `ret < grow_len`, it zero-fills the rest of the buffer. If `!vma->vm_file`, it zero-fills the whole `grow_len`.
And then `nommu_read_iter`:
```c
+static ssize_t nommu_read_iter(struct file *file, void *buf,
+ size_t count, loff_t *pos)
+{
+ struct iov_iter iter;
+ ssize_t ret;
+ size_t done = 0;
+
+ while (done < count) {
+ struct kvec iov = {
+ .iov_base = buf + done,
+ .iov_len = min_t(size_t, count - done, MAX_RW_COUNT),
+ };
+
+ iov_iter_kvec(&iter, ITER_DEST, &iov, 1, iov.iov_len);
+ ret = vfs_iter_read(file, &iter, pos, 0);
+ if (ret <= 0)
+ return done ? done : ret;
+ done += ret;
+ }
+
+ return done;
+}
```
This reads from a file into a kernel buffer `buf`. The question here is whether `buf` could contain uninitialized memory, because this would trigger KMSAN. In `do_mremap`, `old_end` points to the end of the old VMA, within the `vm_region`. In NOMMU, memory is allocated contiguously by `do_mmap`. When `do_mremap` grows the VMA, it enforces that:
```c
+ /* vm_region->vm_top != vm_region->vm_end when sysctl_nr_trim_pages is 0 (default: 1) */
+ if (new_len > vma->vm_region->vm_top - vma->vm_region->vm_start)
return (unsigned long) -ENOMEM;
```
This
|
| 40/3 | 2026/08/13 07:00 | tool | grepper |
0mError:no matches Args: {
"Expression": "kmalloc.*vm_region",
"PathPrefix": "mm/nommu.c"
}Results: {
"error": "no matches"
} |
| 41/3 | 2026/08/13 07:00 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 21497 output: 31 thoughts: 23 Thoughts: Here's my thought process, as if I were the one thinking it: **Digging into Nommu VM Region Allocation** Okay, so I need to understand how `vm_region` allocations work in the NOMMU memory management setup. My immediate instinct is to start by looking for the allocation function itself. Given the typical naming conventions in the kernel, and knowing this is related to memory management (`mm/`), I should search for `alloc` within the `mm/nommu.c` file, specifically in relation to `vm_region`. That's the most likely place to find the core allocation logic. Let's start there. |
| 42/3 | 2026/08/13 07:00 | tool | grepper |
0mArgs:{
"Expression": "alloc.*vm_region",
"PathPrefix": "mm/nommu.c"
}Results: {
"Output": "mm/nommu.c=1071=unsigned long do_mmap(struct file *file,\n--\nmm/nommu.c-1106-\t/* we're going to need to record the mapping */\nmm/nommu.c:1107:\tregion = kmem_cache_zalloc(vm_region_jar, GFP_KERNEL);\nmm/nommu.c-1108-\tif (!region)\n--\nmm/nommu.c=1399=static int split_vma(struct vma_iterator *vmi, struct vm_area_struct *vma,\n--\nmm/nommu.c-1415-\nmm/nommu.c:1416:\tregion = kmem_cache_alloc(vm_region_jar, GFP_KERNEL);\nmm/nommu.c-1417-\tif (!region)\n"
} |
| 43/3 | 2026/08/13 07:00 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 21910 output: 32 thoughts: 41 Thoughts: Okay, here's my interpretation of those thoughts, presented as my own thinking process: **Deeper Dive into Memory Mapping** Hmm, so `vm_region`... right, it's just a *descriptor* of a memory region, not the actual physical allocation itself. I need to remember that. The real memory allocation happens elsewhere. Okay, that makes sense. Let's trace this back. Where does this region actually *get* its backing memory? Alright, let's zero in on `do_mmap` then. That's the core entry point for memory mapping, which makes perfect sense. Let me pull up `mm/nommu.c` and dig into that function. See what it's *actually* doing. Gotta understand where the rubber meets the road, so to speak. |
| 44/3 | 2026/08/13 07:00 | tool | grepper |
0mArgs:{
"Expression": "kmalloc|alloc_pages",
"PathPrefix": "mm/nommu.c"
}Results: {
"Output": "mm/nommu.c=66=unsigned int kobjsize(const void *objp)\n--\nmm/nommu.c-80-\t * If the allocator sets PageSlab, we know the pointer came from\nmm/nommu.c:81:\t * kmalloc().\nmm/nommu.c-82-\t */\n--\nmm/nommu.c-101-\t * The ksize() function is only guaranteed to work for pointers\nmm/nommu.c:102:\t * returned by kmalloc(). So handle arbitrary pointers here.\nmm/nommu.c-103-\t */\n--\nmm/nommu.c=113=void *__vmalloc_noprof(unsigned long size, gfp_t gfp_mask)\n--\nmm/nommu.c-115-\t/*\nmm/nommu.c:116:\t * You can't specify __GFP_HIGHMEM with kmalloc() since kmalloc()\nmm/nommu.c-117-\t * returns only a logical address.\nmm/nommu.c-118-\t */\nmm/nommu.c:119:\treturn kmalloc_noprof(size, (gfp_mask | __GFP_COMP) \u0026 ~__GFP_HIGHMEM);\nmm/nommu.c-120-}\n--\nmm/nommu.c=973=static int do_mmap_private(struct vm_area_struct *vma,\n--\nmm/nommu.c-1017-\nmm/nommu.c:1018:\tbase = alloc_pages_exact(total \u003c\u003c PAGE_SHIFT, GFP_KERNEL);\nmm/nommu.c-1019-\tif (!base)\n"
} |
| 45/3 | 2026/08/13 07:00 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 22587 output: 35 thoughts: 40 Thoughts: Okay, here's my interpretation of that thought process, assuming I'm deep in the kernel's memory management code: **Diving into the mmap Implementation** Right, so I'm looking at `do_mmap_private` and it's calling `alloc_pages_exact`. That's the key: `alloc_pages_exact` is *exactly* what's grabbing the physical pages needed to back the memory mapping. It's the allocation function that's being used to supply the physical memory for this private mapping. Need to dig into that and what the arguments are here. What are the constraints? How is it initializing things? I need to understand this page allocation process in the context of the entire `do_mmap_private` workflow. Where does it fit? Let's trace it and see how it works within the structure of this function. I'll need to understand how the pages are handled, how the mapping is established, and how this relates to other memory management structures. |
| 46/3 | 2026/08/13 07:00 | tool | read-file |
0mArgs:{
"File": "mm/nommu.c",
"FirstLine": 973,
"LineCount": 100
}Results: {
"Contents": " 973:\tstatic int do_mmap_private(struct vm_area_struct *vma,\n 974:\t\t\t\t struct vm_region *region,\n 975:\t\t\t\t unsigned long len,\n 976:\t\t\t\t unsigned long capabilities)\n 977:\t{\n 978:\t\tunsigned long total, point;\n 979:\t\tvoid *base;\n 980:\t\tint ret, order;\n 981:\t\n 982:\t\t/*\n 983:\t\t * Invoke the file's mapping function so that it can keep track of\n 984:\t\t * shared mappings on devices or memory. VM_MAYOVERLAY will be set if\n 985:\t\t * it may attempt to share, which will make is_nommu_shared_mapping()\n 986:\t\t * happy.\n 987:\t\t */\n 988:\t\tif (capabilities \u0026 NOMMU_MAP_DIRECT) {\n 989:\t\t\tret = mmap_file(vma-\u003evm_file, vma);\n 990:\t\t\t/* shouldn't return success if we're not sharing */\n 991:\t\t\tif (WARN_ON_ONCE(!is_nommu_shared_mapping(vma-\u003evm_flags)))\n 992:\t\t\t\tret = -ENOSYS;\n 993:\t\t\tif (ret == 0) {\n 994:\t\t\t\tvma-\u003evm_region-\u003evm_top = vma-\u003evm_region-\u003evm_end;\n 995:\t\t\t\treturn 0;\n 996:\t\t\t}\n 997:\t\t\tif (ret != -ENOSYS)\n 998:\t\t\t\treturn ret;\n 999:\t\n1000:\t\t\t/* getting an ENOSYS error indicates that direct mmap isn't\n1001:\t\t\t * possible (as opposed to tried but failed) so we'll try to\n1002:\t\t\t * make a private copy of the data and map that instead */\n1003:\t\t}\n1004:\t\n1005:\t\n1006:\t\t/* allocate some memory to hold the mapping\n1007:\t\t * - note that this may not return a page-aligned address if the object\n1008:\t\t * we're allocating is smaller than a page\n1009:\t\t */\n1010:\t\torder = get_order(len);\n1011:\t\ttotal = 1 \u003c\u003c order;\n1012:\t\tpoint = len \u003e\u003e PAGE_SHIFT;\n1013:\t\n1014:\t\t/* we don't want to allocate a power-of-2 sized page set */\n1015:\t\tif (sysctl_nr_trim_pages \u0026\u0026 total - point \u003e= sysctl_nr_trim_pages)\n1016:\t\t\ttotal = point;\n1017:\t\n1018:\t\tbase = alloc_pages_exact(total \u003c\u003c PAGE_SHIFT, GFP_KERNEL);\n1019:\t\tif (!base)\n1020:\t\t\tgoto enomem;\n1021:\t\n1022:\t\tatomic_long_add(total, \u0026mmap_pages_allocated);\n1023:\t\n1024:\t\tvm_flags_set(vma, VM_MAPPED_COPY);\n1025:\t\tregion-\u003evm_flags = vma-\u003evm_flags;\n1026:\t\tregion-\u003evm_start = (unsigned long) base;\n1027:\t\tregion-\u003evm_end = region-\u003evm_start + len;\n1028:\t\tregion-\u003evm_top = region-\u003evm_start + (total \u003c\u003c PAGE_SHIFT);\n1029:\t\n1030:\t\tvma-\u003evm_start = region-\u003evm_start;\n1031:\t\tvma-\u003evm_end = region-\u003evm_start + len;\n1032:\t\n1033:\t\tif (vma-\u003evm_file) {\n1034:\t\t\t/* read the contents of a file into the copy */\n1035:\t\t\tloff_t fpos;\n1036:\t\n1037:\t\t\tfpos = vma-\u003evm_pgoff;\n1038:\t\t\tfpos \u003c\u003c= PAGE_SHIFT;\n1039:\t\n1040:\t\t\tret = nommu_read_iter(vma-\u003evm_file, base, len, \u0026fpos);\n1041:\t\t\tif (ret \u003c 0)\n1042:\t\t\t\tgoto error_free;\n1043:\t\n1044:\t\t\t/* clear the last little bit */\n1045:\t\t\tif (ret \u003c len)\n1046:\t\t\t\tmemset(base + ret, 0, len - ret);\n1047:\t\n1048:\t\t} else {\n1049:\t\t\tvma_set_anonymous(vma);\n1050:\t\t}\n1051:\t\n1052:\t\treturn 0;\n1053:\t\n1054:\terror_free:\n1055:\t\tfree_page_series(region-\u003evm_start, region-\u003evm_top);\n1056:\t\tregion-\u003evm_start = vma-\u003evm_start = 0;\n1057:\t\tregion-\u003evm_end = vma-\u003evm_end = 0;\n1058:\t\tregion-\u003evm_top = 0;\n1059:\t\treturn ret;\n1060:\t\n1061:\tenomem:\n1062:\t\tpr_err(\"Allocation of length %lu from process %d (%s) failed\\n\",\n1063:\t\t len, current-\u003epid, current-\u003ecomm);\n1064:\t\tshow_mem();\n1065:\t\treturn -ENOMEM;\n1066:\t}\n1067:\t\n1068:\t/*\n1069:\t * handle mapping creation for uClinux\n1070:\t */\n1071:\tunsigned long do_mmap(struct file *file,\n1072:\t\t\t\tunsigned long addr,\n"
} |
| 47/3 | 2026/08/13 07:00 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 24385 output: 36 thoughts: 139 Thoughts: **My Deep Dive into Anonymous Memory Allocation** Okay, so I'm looking at how `do_mmap_private` handles memory allocation, specifically for private mappings. The first thing I see is that it uses `alloc_pages_exact`. This makes sense, using `GFP_KERNEL` for the allocation. But that throws up a flag immediately – `GFP_KERNEL` *doesn't* include `__GFP_ZERO`. So, by default, the allocated pages aren't zeroed. That's good for performance, assuming we don't need it. But hold on, it seems to branch depending on whether `vma->vm_file` is set. If it is set, it reads the data from the file and then zeroes whatever remains after reading. But if it's *not* a file-backed mapping, it just calls `vma_set_anonymous(vma)`. The key question is, where exactly does the zeroing happen for these *anonymous* mappings? `vma_set_anonymous()` probably *doesn't* do it. I'll need to dig deeper. Right, let's look at `do_mmap` in `mm/nommu.c` to see if it sets anything up there... Gotta follow the code path from here. Let's see what that reveals. |
| 48/3 | 2026/08/13 07:00 | tool | read-file |
0mArgs:{
"File": "mm/nommu.c",
"FirstLine": 1250,
"LineCount": 100
}Results: {
"Contents": "1250:\t\n1251:\t\tvma-\u003evm_region = region;\n1252:\t\n1253:\t\t/* set up the mapping\n1254:\t\t * - the region is filled in if NOMMU_MAP_DIRECT is still set\n1255:\t\t */\n1256:\t\tif (file \u0026\u0026 vma-\u003evm_flags \u0026 VM_SHARED)\n1257:\t\t\tret = do_mmap_shared_file(vma);\n1258:\t\telse\n1259:\t\t\tret = do_mmap_private(vma, region, len, capabilities);\n1260:\t\tif (ret \u003c 0)\n1261:\t\t\tgoto error_just_free;\n1262:\t\tadd_nommu_region(region);\n1263:\t\n1264:\t\t/* clear anonymous mappings that don't ask for uninitialized data */\n1265:\t\tif (is_file_anonymous(vma-\u003evm_file) \u0026\u0026\n1266:\t\t (!IS_ENABLED(CONFIG_MMAP_ALLOW_UNINITIALIZED) ||\n1267:\t\t !(flags \u0026 MAP_UNINITIALIZED)))\n1268:\t\t\tmemset((void *)region-\u003evm_start, 0,\n1269:\t\t\t region-\u003evm_end - region-\u003evm_start);\n1270:\t\n1271:\t\t/* okay... we have a mapping; now we have to register it */\n1272:\t\tresult = vma-\u003evm_start;\n1273:\t\n1274:\t\tcurrent-\u003emm-\u003etotal_vm += len \u003e\u003e PAGE_SHIFT;\n1275:\t\n1276:\tshare:\n1277:\t\tBUG_ON(!vma-\u003evm_region);\n1278:\t\tvma_iter_config(\u0026vmi, vma-\u003evm_start, vma-\u003evm_end);\n1279:\t\tif (vma_iter_prealloc(\u0026vmi, vma))\n1280:\t\t\tgoto error_just_free;\n1281:\t\n1282:\t\tsetup_vma_to_mm(vma, current-\u003emm);\n1283:\t\tcurrent-\u003emm-\u003emap_count++;\n1284:\t\t/* add the VMA to the tree */\n1285:\t\tvma_iter_store_new(\u0026vmi, vma);\n1286:\t\n1287:\t\t/* we flush the region from the icache only when the first executable\n1288:\t\t * mapping of it is made */\n1289:\t\tif (vma-\u003evm_flags \u0026 VM_EXEC \u0026\u0026 !region-\u003evm_icache_flushed) {\n1290:\t\t\tflush_icache_user_range(region-\u003evm_start, region-\u003evm_end);\n1291:\t\t\tregion-\u003evm_icache_flushed = true;\n1292:\t\t}\n1293:\t\n1294:\t\tup_write(\u0026nommu_region_sem);\n1295:\t\n1296:\t\treturn result;\n1297:\t\n1298:\terror_just_free:\n1299:\t\tup_write(\u0026nommu_region_sem);\n1300:\terror:\n1301:\t\tvma_iter_free(\u0026vmi);\n1302:\t\tif (region-\u003evm_file)\n1303:\t\t\tfput(region-\u003evm_file);\n1304:\t\tkmem_cache_free(vm_region_jar, region);\n1305:\t\tif (vma-\u003evm_file)\n1306:\t\t\tfput(vma-\u003evm_file);\n1307:\t\tvm_area_free(vma);\n1308:\t\treturn ret;\n1309:\t\n1310:\tsharing_violation:\n1311:\t\tup_write(\u0026nommu_region_sem);\n1312:\t\tpr_warn(\"Attempt to share mismatched mappings\\n\");\n1313:\t\tret = -EINVAL;\n1314:\t\tgoto error;\n1315:\t\n1316:\terror_mmap_prepare:\n1317:\t\tif (region-\u003evm_file)\n1318:\t\t\tfput(region-\u003evm_file);\n1319:\t\tkmem_cache_free(vm_region_jar, region);\n1320:\t\tif (vma-\u003evm_file)\n1321:\t\t\tfput(vma-\u003evm_file);\n1322:\t\tvm_area_free(vma);\n1323:\t\n1324:\t\tpr_warn(\"mmap_prepare failed for %lu byte allocation from process %d\\n\",\n1325:\t\t\t\tlen, current-\u003epid);\n1326:\t\treturn ret;\n1327:\t\n1328:\terror_getting_vma:\n1329:\t\tkmem_cache_free(vm_region_jar, region);\n1330:\t\tpr_warn(\"Allocation of vma for %lu byte allocation from process %d failed\\n\",\n1331:\t\t\t\tlen, current-\u003epid);\n1332:\t\tshow_mem();\n1333:\t\treturn -ENOMEM;\n1334:\t\n1335:\terror_getting_region:\n1336:\t\tpr_warn(\"Allocation of vm region for %lu byte allocation from process %d failed\\n\",\n1337:\t\t\t\tlen, current-\u003epid);\n1338:\t\tshow_mem();\n1339:\t\treturn -ENOMEM;\n1340:\t}\n1341:\t\n1342:\tunsigned long ksys_mmap_pgoff(unsigned long addr, unsigned long len,\n1343:\t\t\t\t unsigned long prot, unsigned long flags,\n1344:\t\t\t\t unsigned long fd, unsigned long pgoff)\n1345:\t{\n1346:\t\tstruct file *file = NULL;\n1347:\t\tunsigned long retval = -EBADF;\n1348:\t\n1349:\t\taudit_mmap_fd(fd, flags);\n"
} |
| 49/3 | 2026/08/13 07:00 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 26384 output: 27 thoughts: 6122 Thoughts: **Analysis of Memory Initialization in `do_mmap` and `do_mremap` for NOMMU Systems** Okay, so I've been tracing through this code, focusing on how memory is initialized and protected in a NOMMU environment, especially considering potential scenarios that could expose uninitialized memory to user space. The core concern seems to be: are there any gaps? Can we guarantee that every mapped memory region is zeroed or initialized from its backing file before the user gets a handle on it? My initial concern was around `do_mmap`'s clearing of anonymous mappings using `memset` when `!MAP_UNINITIALIZED`. I noted the edge case when `region->vm_top > region->vm_end`, but quickly saw that the allocated region size and the `memset` region were consistent. The real key here is in `do_mremap` with the new logic when growing a VMA. Specifically, if a `vma->vm_file` exists (meaning the memory is backed by a file, even `/dev/zero`), then we read from that file using `nommu_read_iter`. The crucial part is how `nommu_read_iter` interacts with `grow_len`. If `nommu_read_iter` returns less than `grow_len`, then the leftover bytes are zeroed with `memset`. This prevents the "exposed uninitialized memory" situation. I checked for the behavior with various file types: normal files, `/dev/zero`, and anonymous files. In all cases, `nommu_read_iter` coupled with the final `memset` ensures that the newly exposed memory is initialized. Then, I looked at the implementation of `nommu_read_iter` itself and confirmed that it uses `vfs_iter_read` to read the data, that the loop handles partial reads, and that `vfs_iter_read` eventually calls `call_read_iter`, which handles `ITER_KVEC` correctly, so there is no obvious issue there. I also verified that the kernel buffers are not reused. I saw that `nommu_read_iter` is essentially a more robust version of `kernel_read` that prevents short reads and provides initialization for the missing bytes. I also looked at `mmap_prepare`, and how it interacts with the VMAs, and confirmed the behavior with `/dev/zero`, since it sets `VMA_ANON_BIT` in the memory descriptor. I then considered `vmi_shrink_vma` and `split_vma`. Neither seemed problematic from a memory initialization perspective as they are essentially bookkeeping for the VMA and the `vm_region`. `split_vma` used to prevent splitting non-anonymous VMAs and that was updated, which makes sense, as the splitting just rearranges existing mappings, it doesn't create uninitialized pages. Finally, I checked through all the conditional branches in `do_mremap`, to ensure that I covered all cases and I think I have. I could not find any way to expose uninitialized memory. |
| 50/3 | 2026/08/13 07:01 | tool | grepper |
0mError:no matches Args: {
"Expression": "VMA_ANON_BIT",
"PathPrefix": ""
}Results: {
"error": "no matches"
} |
| 51/3 | 2026/08/13 07:01 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 33202 output: 26 thoughts: 36 Thoughts: Okay, here's my interpretation of that thought process, from an expert's perspective: **Debugging a Missing Define** Alright, that's odd. `VMA_ANON_BIT` isn't resolving? That usually means a header file is missing, the define is misspelled, or there's a scope issue. Hmm... let's not waste any time chasing the wrong rabbit. I need to figure out what's causing this compile error. Instead of digging directly into `VMA_ANON_BIT` for now, I'll broaden my search. Maybe it's a related definition I can trace from. Let's try searching the codebase for a more generic term like `VMA_ANON` – perhaps I'm dealing with a different naming convention within the same family of macros. Or, even more broadly, I'll look for `vma_desc_set`. That function, structure, or macro is likely related to anonymous allocations, which is what `VMA_ANON_BIT` would likely be used for, and might help illuminate the issue if I can trace where *that* definition is coming from. Time to get to work and solve this problem. |
| 52/3 | 2026/08/13 07:01 | tool | grepper |
0mArgs:{
"Expression": "vma_desc_set",
"PathPrefix": ""
}Results: {
"Output": "Documentation/filesystems/mmap_prepare.rst=84=mapping, and you can update the mutable and writable fields, for instance:\n--\nDocumentation/filesystems/mmap_prepare.rst-98- desc-\u003evm_ops = \u0026ext4_dax_vm_ops;\nDocumentation/filesystems/mmap_prepare.rst:99: vma_desc_set_flags(desc, VMA_HUGEPAGE_BIT);\nDocumentation/filesystems/mmap_prepare.rst-100- } else {\n--\nDocumentation/filesystems/mmap_prepare.rst=124=and use one of (where ``desc`` is a pointer to struct vm_area_desc):\n--\nDocumentation/filesystems/mmap_prepare.rst-129- otherwise ``false``.\nDocumentation/filesystems/mmap_prepare.rst:130:* ``vma_desc_set_flags(desc, ...)`` - Update the VMA descriptor flags to set\nDocumentation/filesystems/mmap_prepare.rst-131- additional flags specified by a comma-separated list,\nDocumentation/filesystems/mmap_prepare.rst:132: e.g. - ``vma_desc_set_flags(desc, VMA_PFNMAP_BIT, VMA_IO_BIT)``.\nDocumentation/filesystems/mmap_prepare.rst-133-* ``vma_desc_clear_flags(desc, ...)`` - Update the VMA descriptor flags to clear\n--\ndrivers/char/mem.c=501=static int mmap_zero_prepare(struct vm_area_desc *desc)\n--\ndrivers/char/mem.c-511-\t */\ndrivers/char/mem.c:512:\tvma_desc_set_anonymous(desc);\ndrivers/char/mem.c-513-\treturn 0;\n--\ndrivers/dax/device.c=273=static int dax_mmap_prepare(struct vm_area_desc *desc)\n--\ndrivers/dax/device.c-292-\tdesc-\u003evm_ops = \u0026dax_vm_ops;\ndrivers/dax/device.c:293:\tvma_desc_set_flags(desc, VMA_HUGEPAGE_BIT);\ndrivers/dax/device.c-294-\treturn 0;\n--\ndrivers/hwtracing/stm/core.c=702=static int stm_char_mmap_prepare(struct vm_area_desc *desc)\n--\ndrivers/hwtracing/stm/core.c-727-\tdesc-\u003epage_prot = pgprot_noncached(desc-\u003epage_prot);\ndrivers/hwtracing/stm/core.c:728:\tvma_desc_set_flags(desc, VMA_IO_BIT, VMA_DONTEXPAND_BIT,\ndrivers/hwtracing/stm/core.c-729-\t\t\t VMA_DONTDUMP_BIT);\n--\ndrivers/misc/open-dice.c=89=static int open_dice_mmap_prepare(struct vm_area_desc *desc)\n--\ndrivers/misc/open-dice.c-103-\tdesc-\u003epage_prot = pgprot_writecombine(desc-\u003epage_prot);\ndrivers/misc/open-dice.c:104:\tvma_desc_set_flags(desc, VMA_DONTCOPY_BIT, VMA_DONTDUMP_BIT);\ndrivers/misc/open-dice.c-105-\tmmap_action_simple_ioremap(desc, drvdata-\u003ermem-\u003ebase,\n--\ndrivers/target/target_core_user.c=1939=static int tcmu_mmap_prepare(struct uio_info *info, struct vm_area_desc *desc)\n--\ndrivers/target/target_core_user.c-1942-\ndrivers/target/target_core_user.c:1943:\tvma_desc_set_flags(desc, VMA_DONTEXPAND_BIT, VMA_DONTDUMP_BIT);\ndrivers/target/target_core_user.c-1944-\tdesc-\u003evm_ops = \u0026tcmu_vm_ops;\n--\nfs/aio.c=448=static int aio_ring_mmap_prepare(struct vm_area_desc *desc)\nfs/aio.c-449-{\nfs/aio.c:450:\tvma_desc_set_flags(desc, VMA_DONTEXPAND_BIT, VMA_DONTCOPY_BIT);\nfs/aio.c-451-\tdesc-\u003evm_ops = \u0026aio_ring_vm_ops;\n--\nfs/erofs/data.c=494=static int erofs_file_mmap_prepare(struct vm_area_desc *desc)\n--\nfs/erofs/data.c-502-\tdesc-\u003evm_ops = \u0026erofs_dax_vm_ops;\nfs/erofs/data.c:503:\tvma_desc_set_flags(desc, VMA_HUGEPAGE_BIT);\nfs/erofs/data.c-504-\treturn 0;\n--\nfs/ext4/file.c=819=static int ext4_file_mmap_prepare(struct vm_area_desc *desc)\n--\nfs/ext4/file.c-842-\t\tdesc-\u003evm_ops = \u0026ext4_dax_vm_ops;\nfs/ext4/file.c:843:\t\tvma_desc_set_flags(desc, VMA_HUGEPAGE_BIT);\nfs/ext4/file.c-844-\t} else {\n--\nfs/orangefs/file.c=401=static int orangefs_file_mmap_prepare(struct vm_area_desc *desc)\n--\nfs/orangefs/file.c-413-\t/* set the sequential readahead hint */\nfs/orangefs/file.c:414:\tvma_desc_set_flags(desc, VMA_SEQ_READ_BIT);\nfs/orangefs/file.c-415-\tvma_desc_clear_flags(desc, VMA_RAND_READ_BIT);\n--\nfs/xfs/xfs_file.c=2062=xfs_file_mmap_prepare(\n--\nfs/xfs/xfs_file.c-2079-\tif (IS_DAX(inode))\nfs/xfs/xfs_file.c:2080:\t\tvma_desc_set_flags(desc, VMA_HUGEPAGE_BIT);\nfs/xfs/xfs_file.c-2081-\treturn 0;\n--\ninclude/linux/mm.h=1477=static __always_inline bool vma_desc_test_all_mask(const struct vm_area_desc *desc,\n--\ninclude/linux/mm.h-1492-/* Helper to set all VMA flags in a VMA descriptor. */\ninclude/linux/mm.h:1493:static __always_inline void vma_desc_set_flags_mask(struct vm_area_desc *desc,\ninclude/linux/mm.h-1494-\t\tvma_flags_t flags)\n--\ninclude/linux/mm.h-1502- *\ninclude/linux/mm.h:1503: * vma_desc_set_flags(desc, VMA_IO_BIT, VMA_PFNMAP_BIT, VMA_DONTEXPAND_BIT,\ninclude/linux/mm.h-1504- * \t\tVMA_DONTDUMP_BIT);\ninclude/linux/mm.h-1505- */\ninclude/linux/mm.h:1506:#define vma_desc_set_flags(desc, ...) \\\ninclude/linux/mm.h:1507:\tvma_desc_set_flags_mask(desc, mk_vma_flags(__VA_ARGS__))\ninclude/linux/mm.h-1508-\n--\ninclude/linux/mm.h=1526=static inline void vma_set_anonymous(struct vm_area_struct *vma)\n--\ninclude/linux/mm.h-1530-\ninclude/linux/mm.h:1531:static inline void vma_desc_set_anonymous(struct vm_area_desc *desc)\ninclude/linux/mm.h-1532-{\n--\nkernel/relay.c=82=static int relay_mmap_prepare_buf(struct rchan_buf *buf,\n--\nkernel/relay.c-93-\tdesc-\u003evm_ops = \u0026relay_file_mmap_ops;\nkernel/relay.c:94:\tvma_desc_set_flags(desc, VMA_DONTEXPAND_BIT);\nkernel/relay.c-95-\tdesc-\u003eprivate_data = buf;\n--\nmm/memory.c=2518=int map_kernel_pages_prepare(struct vm_area_desc *desc)\n--\nmm/memory.c-2526-\t\tVM_WARN_ON_ONCE(vma_desc_test(desc, VMA_PFNMAP_BIT));\nmm/memory.c:2527:\t\tvma_desc_set_flags(desc, VMA_MIXEDMAP_BIT);\nmm/memory.c-2528-\t}\n--\nmm/memory.c=3152=int remap_pfn_range_prepare(struct vm_area_desc *desc)\n--\nmm/memory.c-3168-\nmm/memory.c:3169:\tvma_desc_set_flags_mask(desc, VMA_REMAP_FLAGS);\nmm/memory.c-3170-\treturn 0;\n--\nmm/secretmem.c=121=static int secretmem_mmap_prepare(struct vm_area_desc *desc)\n--\nmm/secretmem.c-127-\nmm/secretmem.c:128:\tvma_desc_set_flags(desc, VMA_LOCKED_BIT, VMA_DONTDUMP_BIT);\nmm/secretmem.c-129-\tif (!mlock_future_ok(desc-\u003emm, /*is_vma_locked=*/ true, len))\n--\ntools/testing/vma/include/dup.h=1120=static __always_inline bool vma_desc_test_all_mask(const struct vm_area_desc *desc,\n--\ntools/testing/vma/include/dup.h-1128-\ntools/testing/vma/include/dup.h:1129:static __always_inline void vma_desc_set_flags_mask(struct vm_area_desc *desc,\ntools/testing/vma/include/dup.h-1130-\t\tvma_flags_t flags)\n--\ntools/testing/vma/include/dup.h-1134-\ntools/testing/vma/include/dup.h:1135:#define vma_desc_set_flags(desc, ...) \\\ntools/testing/vma/include/dup.h:1136:\tvma_desc_set_flags_mask(desc, mk_vma_flags(__VA_ARGS__))\ntools/testing/vma/include/dup.h-1137-\n--\ntools/testing/vma/tests/vma.c=63=static bool test_vma_flags_unchanged(void)\n--\ntools/testing/vma/tests/vma.c-93-\t\t/* Same for VMA descriptor. */\ntools/testing/vma/tests/vma.c:94:\t\tvma_desc_set_flags(\u0026desc, bit);\ntools/testing/vma/tests/vma.c-95-\t\tASSERT_TRUE(compare_legacy_flags(legacy_flags, desc.vma_flags));\ntools/testing/vma/tests/vma.c:96:\t\tvma_desc_set_flags_mask(\u0026desc, mask);\ntools/testing/vma/tests/vma.c-97-\t\tASSERT_TRUE(compare_legacy_flags(legacy_flags, desc.vma_flags));\n--\ntools/testing/vma/tests/vma.c=337=static bool test_vma_flags_clear(void)\n--\ntools/testing/vma/tests/vma.c-367-\tvma_set_flags(\u0026vma, VMA_EXEC_BIT, 64);\ntools/testing/vma/tests/vma.c:368:\tvma_desc_set_flags(\u0026desc, VMA_EXEC_BIT, 64);\ntools/testing/vma/tests/vma.c-369-#endif\n--\ntools/testing/vma/tests/vma.c-383-\tvma_set_flags(\u0026vma, __VA_ARGS__);\t\t\t\t\\\ntools/testing/vma/tests/vma.c:384:\tvma_desc_set_flags(\u0026desc, __VA_ARGS__)\ntools/testing/vma/tests/vma.c-385-\n"
} |
| 53/3 | 2026/08/13 07:01 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 36739 output: 36 thoughts: 42 Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience: **Analyzing the Anonymous VMA Description Set** Right, so `vma_desc_set_anonymous(desc)` is firing off. My first instinct is to dive into the core definition. Time to crack open `include/linux/mm.h` and take a look at the declaration of `vma_desc_set_anonymous`. I'm guessing it's a relatively straightforward function, probably setting a flag within the `vma_desc` structure to indicate that we're dealing with an anonymous VMA, but I want to confirm the details. I'll be looking for where and how that bit is set, and if there are any additional associated initializations or side effects. This could be a critical point of the VM setup path that's worth keeping an eye on as I continue debugging. This will likely give me some insight into how anonymous memory regions are described and handled by the kernel. Is it using a simple bit-field, or are there any more complex data structures involved? Is it using any specific flags for HugeTLB support or NUMA awareness? This is the starting point to understand the specifics. |
| 54/3 | 2026/08/13 07:01 | tool | read-file |
0mArgs:{
"File": "include/linux/mm.h",
"FirstLine": 1526,
"LineCount": 20
}Results: {
"Contents": "1526:\tstatic inline void vma_set_anonymous(struct vm_area_struct *vma)\n1527:\t{\n1528:\t\tvma-\u003evm_ops = NULL;\n1529:\t}\n1530:\t\n1531:\tstatic inline void vma_desc_set_anonymous(struct vm_area_desc *desc)\n1532:\t{\n1533:\t\tdesc-\u003evm_ops = NULL;\n1534:\t}\n1535:\t\n1536:\tstatic inline bool vma_is_anonymous(struct vm_area_struct *vma)\n1537:\t{\n1538:\t\treturn !vma-\u003evm_ops;\n1539:\t}\n1540:\t\n1541:\t/*\n1542:\t * Indicate if the VMA is a heap for the given task; for\n1543:\t * /proc/PID/maps that is the heap of the main task.\n1544:\t */\n1545:\tstatic inline bool vma_is_initial_heap(const struct vm_area_struct *vma)\n"
} |
| 55/3 | 2026/08/13 07:01 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 37371 output: 28 thoughts: 390 Thoughts: **Analyzing Memory Mapping Operations** Okay, so I'm looking at how `/dev/zero` and regular file mappings are handled. The function `vma_desc_set_anonymous` sets `desc->vm_ops = NULL`. Now, the crucial part is understanding what happens after this. `__compat_vma_mmap` gets called and copies `desc->vm_ops` to `vma->vm_ops`. Consequently, `vma_is_anonymous(vma)` will return true, seemingly making `/dev/zero` behave like a normal anonymous mapping for the purpose of splitting and shrinking. But, I need to consider other file types. Let's think about `mmap_prepare`, and regular files. Regular files, generally, don't implement the `mmap_prepare` operation. Wait a second, looking into `mm/filemap.c`, I see `generic_file_mmap_prepare`. Let me check that... Ah, it used to return `-ENOSYS`, but now it returns `0`. And `generic_file_readonly_mmap_prepare` is similar: it also returns `0`. So, if `mmap_prepare` returns `0`, it triggers `__compat_vma_mmap` too, just like the `/dev/zero` case. This suggests that the same core mechanism is used for both. I need to be sure. What exactly *does* `__compat_vma_mmap` *do*? I'm going to have to check that function directly to solidify my understanding. |
| 56/3 | 2026/08/13 07:01 | tool | grepper |
0mArgs:{
"Expression": "__compat_vma_mmap",
"PathPrefix": ""
}Results: {
"Output": "drivers/hv/vmbus_drv.c=1923=static int hv_mmap_ring_buffer_wrapper(struct file *filp, struct kobject *kobj,\n--\ndrivers/hv/vmbus_drv.c-1939-\ndrivers/hv/vmbus_drv.c:1940:\treturn __compat_vma_mmap(\u0026desc, vma);\ndrivers/hv/vmbus_drv.c-1941-}\n--\ndrivers/uio/uio.c=820=static int uio_mmap(struct file *filep, struct vm_area_struct *vma)\n--\ndrivers/uio/uio.c-859-\t\t\tgoto out;\ndrivers/uio/uio.c:860:\t\tret = __compat_vma_mmap(\u0026desc, vma);\ndrivers/uio/uio.c-861-\t\tgoto out;\n--\ninclude/linux/fs.h=2056=void compat_set_desc_from_vma(struct vm_area_desc *desc, const struct file *file,\ninclude/linux/fs.h-2057-\t\t\t const struct vm_area_struct *vma);\ninclude/linux/fs.h:2058:int __compat_vma_mmap(struct vm_area_desc *desc, struct vm_area_struct *vma);\ninclude/linux/fs.h-2059-int compat_vma_mmap(struct file *file, struct vm_area_struct *vma);\n--\nmm/nommu.c=1071=unsigned long do_mmap(struct file *file,\n--\nmm/nommu.c-1142-\nmm/nommu.c:1143:\t\tret = __compat_vma_mmap(\u0026desc, vma);\nmm/nommu.c-1144-\t\tif (ret)\n--\nmm/util.c=1200=EXPORT_SYMBOL(compat_set_desc_from_vma);\n--\nmm/util.c-1202-/**\nmm/util.c:1203: * __compat_vma_mmap() - Similar to compat_vma_mmap(), only it allows\nmm/util.c-1204- * flexibility as to how the mmap_prepare callback is invoked, which is useful\n--\nmm/util.c-1223- */\nmm/util.c:1224:int __compat_vma_mmap(struct vm_area_desc *desc,\nmm/util.c-1225-\t\t struct vm_area_struct *vma)\n--\nmm/util.c-1237-}\nmm/util.c:1238:EXPORT_SYMBOL(__compat_vma_mmap);\nmm/util.c-1239-\n--\nmm/util.c=1266=int compat_vma_mmap(struct file *file, struct vm_area_struct *vma)\n--\nmm/util.c-1280-\nmm/util.c:1281:\treturn __compat_vma_mmap(\u0026desc, vma);\nmm/util.c-1282-}\n--\ntools/testing/vma/include/dup.h=1304=static inline int vfs_mmap_prepare(struct file *file, struct vm_area_desc *desc)\n--\ntools/testing/vma/include/dup.h-1308-\ntools/testing/vma/include/dup.h:1309:static inline int __compat_vma_mmap(struct vm_area_desc *desc,\ntools/testing/vma/include/dup.h-1310-\t\tstruct vm_area_struct *vma)\n--\ntools/testing/vma/include/dup.h=1324=static inline int compat_vma_mmap(struct file *file, struct vm_area_struct *vma)\n--\ntools/testing/vma/include/dup.h-1338-\ntools/testing/vma/include/dup.h:1339:\treturn __compat_vma_mmap(\u0026desc, vma);\ntools/testing/vma/include/dup.h-1340-}\n"
} |
| 57/3 | 2026/08/13 07:01 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 39188 output: 34 thoughts: 22 Thoughts: Here's my summary, as though I'm reading and thinking about the code: **Diving into `__compat_vma_mmap`** Okay, time to dig into `__compat_vma_mmap` in `mm/util.c`. This is likely where the kernel handles the compatibility kludge for 32-bit processes interacting with 64-bit systems, and more specifically how they deal with memory mapping. Given the function name, I suspect it's responsible for managing Virtual Memory Area (VMA) mmap operations, but specifically in a context where there's an ABI difference or other compatibility concern. I need to see how it handles things like address space translation, flag adjustments, or other critical adjustments. I expect to see checks for the architecture, process flags, or maybe even version-specific routines. I'm especially interested in how it deals with the potential for misaligned addresses or differing page sizes between the two architectures. Time to see what the implementation reveals. I'll be looking for any potential vulnerabilities or performance bottlenecks inherent in the compatibility layer as well. |
| 58/3 | 2026/08/13 07:02 | tool | read-file |
0mArgs:{
"File": "mm/util.c",
"FirstLine": 1224,
"LineCount": 20
}Results: {
"Contents": "1224:\tint __compat_vma_mmap(struct vm_area_desc *desc,\n1225:\t\t\t struct vm_area_struct *vma)\n1226:\t{\n1227:\t\tint err;\n1228:\t\n1229:\t\t/* Perform any preparatory tasks for mmap action. */\n1230:\t\terr = mmap_action_prepare(desc);\n1231:\t\tif (err)\n1232:\t\t\treturn err;\n1233:\t\t/* Update the VMA from the descriptor. */\n1234:\t\tcompat_set_vma_from_desc(vma, desc);\n1235:\t\t/* Complete any specified mmap actions. */\n1236:\t\treturn mmap_action_complete(vma, \u0026desc-\u003eaction, /*is_compat=*/true);\n1237:\t}\n1238:\tEXPORT_SYMBOL(__compat_vma_mmap);\n1239:\t\n1240:\t/**\n1241:\t * compat_vma_mmap() - Apply the file's .mmap_prepare() hook to an\n1242:\t * existing VMA and execute any requested actions.\n1243:\t * @file: The file which possesss an f_op-\u003emmap_prepare() hook.\n"
} |
| 59/3 | 2026/08/13 07:02 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 39820 output: 32 thoughts: 359 Thoughts: **My Analysis of `/dev/zero` and `mremap` Behavior**
Okay, so I'm looking at how `__compat_vma_mmap` handles things, and it's updating the `vma` structure based on the `desc`. A key thing here is that `vma->vm_ops` gets set from `desc->vm_ops`, and for `/dev/zero`, that's going to be `NULL`. This implies that `/dev/zero` mappings effectively become anonymous memory regions from the kernel's perspective.
But hold on... what about `mremap`? I need to consider that. Specifically, let's dive into `do_mremap`. I remember seeing this bit:
```c
/* read contents of extended map from file, or zero-filled if !vm_file */
if (vma->vm_file) {
loff_t fpos;
fpos = (loff_t)vma->vm_pgoff << PAGE_SHIFT;
fpos += old_end - vma->vm_start;
ret = nommu_read_iter(vma->vm_file, (void *)old_end,
grow_len, &fpos);
if (ret < 0)
return (unsigned long)ret;
if (ret < grow_len)
memset((char *)old_end + ret, 0, grow_len - ret);
} else {
memset((void *)old_end, 0, grow_len);
}
```
If `vma->vm_file` *is* `/dev/zero`, then `nommu_read_iter` will try to read from the `/dev/zero` device. And `/dev/zero`'s `read_iter` implementation should be `read_iter_zero` in `drivers/char/mem.c`. I'll need to check the exact behavior of `read_iter_zero` to fully understand what happens during an `mremap` with a `/dev/zero` mapping. This is where I'll find if it actually reads from `/dev/zero` or not.
|
| 60/3 | 2026/08/13 07:02 | tool | grepper |
0mArgs:{
"Expression": "read_iter_zero",
"PathPrefix": "drivers/char/mem.c"
}Results: {
"Output": "drivers/char/mem.c=445=static int uring_cmd_null(struct io_uring_cmd *ioucmd, unsigned int issue_flags)\n--\ndrivers/char/mem.c-449-\ndrivers/char/mem.c:450:static ssize_t read_iter_zero(struct kiocb *iocb, struct iov_iter *iter)\ndrivers/char/mem.c-451-{\n--\ndrivers/char/mem.c=665=static const struct file_operations zero_fops = {\n--\ndrivers/char/mem.c-667-\t.write\t\t= write_zero,\ndrivers/char/mem.c:668:\t.read_iter\t= read_iter_zero,\ndrivers/char/mem.c-669-\t.read\t\t= read_zero,\n--\ndrivers/char/mem.c=680=static const struct file_operations full_fops = {\ndrivers/char/mem.c-681-\t.llseek\t\t= full_lseek,\ndrivers/char/mem.c:682:\t.read_iter\t= read_iter_zero,\ndrivers/char/mem.c-683-\t.write\t\t= write_full,\n"
} |
| 61/3 | 2026/08/13 07:02 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 41014 output: 35 thoughts: 11 Thoughts: Okay, here's my interpretation of that thought process, assuming I'm an expert in this field, and speaking from the first person perspective: **Diving into `read_iter_zero`** Alright, let's take a look at `read_iter_zero`. Right off the bat, I know what that function likely implies: it’s an iterator-based reading function, probably one that starts at, or at least aims to handle, the 'zero' case, or an empty input. This tells me we're likely dealing with something that requires efficient handling of potentially empty datasets or starting points. Perhaps this handles a file, a stream, a buffer, or some other iterable data source. My mind immediately begins to check a checklist of considerations: How is this zero-length case handled? Does it return a specific error, a null iterator, or something else entirely? Does it correctly avoid potential index out of bounds errors or other related issues in more complex operations? I'm curious if this function is optimized for edge cases, or if it makes simplifying assumptions and requires specific handling higher up the call stack. This naming convention feels familiar, so I'm quickly pulling up similar functions in other libraries to perform a mental comparison. This is the place to start, so let's dig deeper to see exactly how it's implemented. |
| 62/3 | 2026/08/13 07:02 | tool | read-file |
0mArgs:{
"File": "drivers/char/mem.c",
"FirstLine": 450,
"LineCount": 20
}Results: {
"Contents": " 450:\tstatic ssize_t read_iter_zero(struct kiocb *iocb, struct iov_iter *iter)\n 451:\t{\n 452:\t\tsize_t written = 0;\n 453:\t\n 454:\t\twhile (iov_iter_count(iter)) {\n 455:\t\t\tsize_t chunk = iov_iter_count(iter), n;\n 456:\t\n 457:\t\t\tif (chunk \u003e PAGE_SIZE)\n 458:\t\t\t\tchunk = PAGE_SIZE;\t/* Just for latency reasons */\n 459:\t\t\tn = iov_iter_zero(chunk, iter);\n 460:\t\t\tif (!n \u0026\u0026 iov_iter_count(iter))\n 461:\t\t\t\treturn written ? written : -EFAULT;\n 462:\t\t\twritten += n;\n 463:\t\t\tif (signal_pending(current))\n 464:\t\t\t\treturn written ? written : -ERESTARTSYS;\n 465:\t\t\tif (!need_resched())\n 466:\t\t\t\tcontinue;\n 467:\t\t\tif (iocb-\u003eki_flags \u0026 IOCB_NOWAIT)\n 468:\t\t\t\treturn written ? written : -EAGAIN;\n 469:\t\t\tcond_resched();\n"
} |
| 63/3 | 2026/08/13 07:02 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 41673 output: 30 thoughts: 1413 Thoughts: **Uninitialized Memory in NOMMU Mappings: A Deep Dive** Okay, so I'm thinking about how memory is initialized in this NOMMU environment, specifically when dealing with `mmap` and `mremap`. My initial thought was that `iov_iter_zero` would handle the buffer zeroing, and that using `/dev/zero` in `nommu_read_iter` would correctly initialize the memory. But then I started to question if there were other ways for uninitialized memory to be exposed. I considered the case where the `vma->vm_file` isn't properly handled, maybe doesn't have a `read_iter` or `read` implementation. In that case, `vfs_iter_read` would return `-EINVAL`, which would then propagate up to `do_mremap`, which would also return `-EINVAL`, so no problem there, the mapping fails. So that's safe. What about a short read from a file? If `vma->vm_file` returns fewer bytes than requested, `nommu_read_iter` would return the number of bytes read. Then `do_mremap` correctly calls `memset` to zero the rest. So, still safe. Now, my mind shifts to `do_mmap_private`. Here, if a file is associated with the mapping (`vma->vm_file`), the contents are read, and the remainder of the buffer is zeroed. If it's an anonymous mapping (no `vm_file`), `vma_set_anonymous` is called. That makes me think about `do_mmap`... In `do_mmap`, if it's an anonymous mapping, and the code isn't explicitly allowing uninitialized data (`!MAP_UNINITIALIZED`), then memory from `region->vm_start` to `region->vm_end` is zeroed. But then the question hits me: what about the memory between `region->vm_end` and `region->vm_top`? That's not explicitly zeroed, but it is not accessible to userspace yet. I dig into `do_mremap` to see how it grows a VMA, or how it affects this space between `region->vm_end` and `region->vm_top`. The check for `new_len` compared to `vm_top` is crucial. If `new_len` is within the existing `vm_top`, then it's fine. The memory *isn't* initialized by `mmap`, but *is* initialized by `mremap` as a result of that `mremap` call which either zeros or reads from the file. The key is that the memory between `vm_end` and `vm_top` *isn't* accessible to user space *until* `mremap` expands the VMA. And when `mremap` does grow the VMA, it initializes the newly exposed memory. Therefore, no uninitialized memory leak. Okay, is there *any* other way user space could reach that uninitialized memory? No, because the VMA only covers up to `vm_end`. Any access beyond `vm_end` would cause a segmentation fault (or something similar in NOMMU). Then the NOMMU realization hits! Wait a minute, NOMMU *doesn't* have an MMU, so user space *can* access any memory! But, KMSAN will track uninitialized memory, won't it? If the memory is uninitialized, KMSAN will report it. Then I realized, it's out of bounds and not zeroed. So this is going to be caught by KASAN! (but, wait...) KASAN is often not available in NOMMU environments! So, I think, what about KMSAN vs KASAN. KMSAN requires an MMU, right? and this patch is *for* `mm/nommu.c`, so KMSAN can't even run here, and the build system checks for that! That's it! I need to see if KMSAN depends on MMU and is mutually exclusive with NOMMU! Now let me check the kernel config... Yep, `CONFIG_NOMMU` is mutually exclusive with `CONFIG_KMSAN`. So, no KMSAN to worry about! The memory between `vm_end` and `vm_top` will either not be accessed by userspace, or, if a userspace process attempts to access that memory it will be an out-of-bounds access and KASAN will catch it (if it is enabled). |
| 64/3 | 2026/08/13 07:02 | tool | grepper |
0mArgs:{
"Expression": "config KMSAN",
"PathPrefix": "lib/Kconfig.kmsan"
}Results: {
"Output": "lib/Kconfig.kmsan=5=config HAVE_KMSAN_COMPILER\n--\nlib/Kconfig.kmsan-7-\nlib/Kconfig.kmsan:8:config KMSAN\nlib/Kconfig.kmsan-9-\tbool \"KMSAN: detector of uninitialized values use\"\n--\nlib/Kconfig.kmsan=26=if KMSAN\nlib/Kconfig.kmsan-27-\nlib/Kconfig.kmsan:28:config KMSAN_CHECK_PARAM_RETVAL\nlib/Kconfig.kmsan-29-\tbool \"Check for uninitialized values passed to and returned from functions\"\n--\nlib/Kconfig.kmsan-41-\nlib/Kconfig.kmsan:42:config KMSAN_KUNIT_TEST\nlib/Kconfig.kmsan-43-\ttristate \"KMSAN integration test suite\" if !KUNIT_ALL_TESTS\n"
} |
| 65/3 | 2026/08/13 07:02 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 44337 output: 33 thoughts: 12 Thoughts: Here's my thought process, as if I were considering the dependencies of KMSAN: **KMSAN Dependency Deep Dive** Okay, so let's dig into the dependencies of KMSAN. My immediate concern, as always, is understanding the full scope of what KMSAN relies on, and what that implies for its integration and potential impact on the kernel. I need to know: are there any implicit dependencies buried deep down? Are there any libraries, kernel modules, or features KMSAN *must* have to function correctly? I'm thinking about the low-level stuff first. Does it require any specific compiler flags or runtime libraries? Does it hook into particular kernel subsystems – memory management, interrupt handling, or maybe even something more obscure? If it touches any of those, I need to know how extensively. Is this a straightforward integration, or are we dealing with some significant kernel modifications that require careful consideration? I'll have to consider how KMSAN interacts with other instrumentation tools. Is there any conflict with existing debuggers or sanitizers? Does it share data structures or infrastructure? Understanding these interactions is critical to avoiding potential conflicts or performance regressions. Then there's the question of upstreaming and maintainability. What are the external dependencies of KMSAN? Do we need to manage or curate these? If it relies on third-party libraries, are those libraries well-maintained and compatible with our target kernel versions? Any licensing concerns? Are we on the hook for keeping those upstreamed? Finally, performance. I know that kernel sanitizers often introduce overhead. But what's the expected overhead from KMSAN? What are the factors that influence performance – is it CPU-bound, memory-bound, or network-bound? We need to have a concrete idea of this, as that will influence all the future decisions around its usage. |
| 66/3 | 2026/08/13 07:02 | tool | read-file |
0mArgs:{
"File": "lib/Kconfig.kmsan",
"FirstLine": 8,
"LineCount": 20
}Results: {
"Contents": " 8:\tconfig KMSAN\n 9:\t\tbool \"KMSAN: detector of uninitialized values use\"\n 10:\t\tdepends on HAVE_ARCH_KMSAN \u0026\u0026 HAVE_KMSAN_COMPILER\n 11:\t\tdepends on DEBUG_KERNEL \u0026\u0026 !KASAN \u0026\u0026 !KCSAN\n 12:\t\tdepends on !PREEMPT_RT\n 13:\t\tselect STACKDEPOT\n 14:\t\tselect STACKDEPOT_ALWAYS_INIT\n 15:\t\thelp\n 16:\t\t KernelMemorySanitizer (KMSAN) is a dynamic detector of uses of\n 17:\t\t uninitialized values in the kernel. It is based on compiler\n 18:\t\t instrumentation provided by Clang and thus requires Clang to build.\n 19:\t\n 20:\t\t An important note is that KMSAN is not intended for production use,\n 21:\t\t because it drastically increases kernel memory footprint and slows\n 22:\t\t the whole system down.\n 23:\t\n 24:\t\t See \u003cfile:Documentation/dev-tools/kmsan.rst\u003e for more details.\n 25:\t\n 26:\tif KMSAN\n 27:\t\n"
} |
| 67/3 | 2026/08/13 07:02 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 45063 output: 28 thoughts: 31 Thoughts: Okay, here's my take on those thoughts, framed as my own: **Analyzing Kernel Configuration for KMSAN Support** Alright, let's get down to brass tacks. The first thing I'm checking is the kernel configuration variable, `HAVE_ARCH_KMSAN`. This is the gating factor, isn't it? It essentially determines whether a specific architecture is even *capable* of supporting Kernel Memory Sanitizer (KMSAN). So, my initial thought process is entirely focused on *that* definition, and not any details of KMSAN itself. Now, I need to understand which architectures this applies to. I need a quick overview to understand the scope, the landscapes of kernel architectures. I have a mental checklist that starts firing, thinking about the obvious candidates – likely the more modern and flexible ones, I'm guessing. My next action is to query the available data. I know where to find it. Time to run a quick script, or pull up the relevant documentation. Let's see what this reveals! |
| 68/3 | 2026/08/13 07:02 | tool | grepper |
0mArgs:{
"Expression": "select HAVE_ARCH_KMSAN",
"PathPrefix": ""
}Results: {
"Output": "arch/s390/Kconfig=75=config S390\n--\narch/s390/Kconfig-185-\tselect HAVE_ARCH_KCSAN\narch/s390/Kconfig:186:\tselect HAVE_ARCH_KMSAN\narch/s390/Kconfig-187-\tselect HAVE_ARCH_KFENCE\n--\narch/x86/Kconfig=58=config X86\n--\narch/x86/Kconfig-198-\tselect HAVE_ARCH_KFENCE\narch/x86/Kconfig:199:\tselect HAVE_ARCH_KMSAN\t\t\tif X86_64\narch/x86/Kconfig-200-\tselect HAVE_ARCH_KGDB\n"
} |
| 69/3 | 2026/08/13 07:02 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 45539 output: 23 thoughts: 67 Thoughts: **My Analysis of KMSAN and NOMMU Support** Okay, so I'm looking at this code and it's clear that only `s390` and `x86_64` currently define `HAVE_ARCH_KMSAN`. Interesting. Now, the documentation is pretty clear on the fact that neither of those architectures supports `NOMMU`. That strikes me as a significant constraint. My next step here is definitely to verify that claim. I need to explicitly check if it's even *possible* to configure a build for either `x86_64` or `s390` to include `NOMMU`. It seems improbable, given the information I have, but a direct confirmation is the only way to be sure. This has implications for the overall KMSAN implementation strategy. |
| 70/3 | 2026/08/13 07:02 | tool | grepper |
0mArgs:{
"Expression": "config NOMMU",
"PathPrefix": ""
}Results: {
"Output": "arch/sh/mm/Kconfig=4=config MMU\n--\narch/sh/mm/Kconfig-18-\narch/sh/mm/Kconfig:19:config NOMMU\narch/sh/mm/Kconfig-20-\tdef_bool !MMU\n--\nmm/Kconfig=788=config HWPOISON_INJECT\n--\nmm/Kconfig-792-\nmm/Kconfig:793:config NOMMU_INITIAL_TRIM_EXCESS\nmm/Kconfig-794-\tint \"Turn on mmap() excess space trimming before booting\"\n"
} |
| 71/3 | 2026/08/13 07:02 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 45955 output: 31 thoughts: 35 Thoughts: Here's my summary, as though I'm thinking through this myself: **Investigating the `NOMMU` Definition** Okay, so the `NOMMU` macro... that's almost always a direct negation of `MMU`, right? Like, it usually means "No Memory Management Unit." But I need to be *absolutely* sure. This kind of detail is critical for these low-level configurations. I don't want to make an assumption that'll bite me later. Right, so where's the best place to check the definitive source? That's it: I need to dive into the kernel's Kconfig files. Specifically, since we're dealing with x86 architecture, I'll go straight to `arch/x86/Kconfig`. That's where I can confirm, *unequivocally*, how the kernel defines and uses the `MMU` option and, by extension, `NOMMU`. I'll cross-reference the definitions and make sure I fully understand it. |
| 72/3 | 2026/08/13 07:02 | tool | grepper |
0mArgs:{
"Expression": "config MMU",
"PathPrefix": "arch/x86/Kconfig"
}Results: {
"Output": "arch/x86/Kconfig=352=config STACKTRACE_SUPPORT\n--\narch/x86/Kconfig-354-\narch/x86/Kconfig:355:config MMU\narch/x86/Kconfig-356-\tdef_bool y\n"
} |
| 73/3 | 2026/08/13 07:02 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 46323 output: 32 thoughts: 36 Thoughts: Okay, here's my interpretation of those thoughts, framed as if they are my own: **My Immediate Analysis of MMU and NOMMU Systems** Right, so, taking a quick look at this, the fundamental concept I'm working with here is the Memory Management Unit, or MMU. On x86, it's pretty much a given that an MMU is present. Thinking about it in terms of `def_bool y`, which I read as defining a boolean value, for x86 and the way things are architected, `y` is the MMU and its state is always set to true. Therefore, `NOMMU` is effectively impossible. It's not a viable configuration. The hardware, at its core, is designed with memory management at its heart. The same principle, I'd say, logically extends to s390. I haven't gone into it in as much depth recently, but the general architectural approach to memory management is consistent. There might be some edge cases in extremely low-level firmware, but generally speaking it's unlikely a truly `NOMMU` system would be designed for s390. |
| 74/3 | 2026/08/13 07:02 | tool | grepper |
0mArgs:{
"Expression": "config MMU",
"PathPrefix": "arch/s390/Kconfig"
}Results: {
"Output": "arch/s390/Kconfig-1-# SPDX-License-Identifier: GPL-2.0\narch/s390/Kconfig:2:config MMU\narch/s390/Kconfig-3-\tdef_bool y\n"
} |
| 75/3 | 2026/08/13 07:02 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 46705 output: 35 thoughts: 296 Thoughts: **My Analysis of KMSAN and File Mapping** Okay, so I'm processing this information about `NOMMU` on s390, and it's immediately clear that KMSAN is just out of the question on `NOMMU` code. No way, that's a dead end. But, hold on a second... this patch is touching `mm/filemap.c` and `drivers/char/mem.c` too. That's unexpected. Let's dig into `mm/filemap.c`. Hmm, I see modifications to `generic_file_mmap_prepare` and `generic_file_readonly_mmap_prepare`. The patch is *removing* the `-ENOSYS` return and replacing it with `0` in one and `generic_file_mmap_prepare(desc)` in the other. That's a significant change! But... *wait a minute*. These functions are *inside* a `#endif /* CONFIG_MMU */` block. That's... unusual, to say the least. So, these changes are only affecting builds *without* MMU support? That's really something to unpack and that changes my approach entirely. Time to revisit that file and confirm these are indeed only used for NOMMU systems! |
| 76/3 | 2026/08/13 07:03 | tool | read-file |
0mArgs:{
"File": "mm/filemap.c",
"FirstLine": 4070,
"LineCount": 25
}Results: {
"Contents": "4070:\tvm_fault_t filemap_page_mkwrite(struct vm_fault *vmf)\n4071:\t{\n4072:\t\treturn VM_FAULT_SIGBUS;\n4073:\t}\n4074:\tint generic_file_mmap(struct file *file, struct vm_area_struct *vma)\n4075:\t{\n4076:\t\treturn -ENOSYS;\n4077:\t}\n4078:\tint generic_file_mmap_prepare(struct vm_area_desc *desc)\n4079:\t{\n4080:\t\treturn 0;\n4081:\t}\n4082:\tint generic_file_readonly_mmap(struct file *file, struct vm_area_struct *vma)\n4083:\t{\n4084:\t\treturn -ENOSYS;\n4085:\t}\n4086:\tint generic_file_readonly_mmap_prepare(struct vm_area_desc *desc)\n4087:\t{\n4088:\t\tif (is_shared_maywrite(\u0026desc-\u003evma_flags))\n4089:\t\t\treturn -EINVAL;\n4090:\t\treturn generic_file_mmap_prepare(desc);\n4091:\t}\n4092:\t#endif /* CONFIG_MMU */\n4093:\t\n4094:\tEXPORT_SYMBOL(filemap_page_mkwrite);\n"
} |
| 77/3 | 2026/08/13 07:03 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 47692 output: 291 thoughts: 1526 Thoughts: **Analysis of the Impact of the Patch Series on KMSAN Coverage**
Okay, let's break this down. My initial assessment is that these patches, while potentially significant in functionality (introducing `mmap_prepare` and `mremap` support for NOMMU architectures), are *entirely* irrelevant to KMSAN's capabilities. Let's see...
I'm starting by tracing where this code lives. Right, `#ifndef CONFIG_MMU` suggests this functionality is for NOMMU kernels. The `#endif` at line 4092 confirms it. Got it. This makes immediate sense. So, the core changes are likely centered in `mm/nommu.c`, `mm/filemap.c` and potentially in `drivers/char/mem.c`.
Alright, let's dive into that `drivers/char/mem.c` file. It seems this patch touched `mmap_zero_prepare` to make it work for both MMU and NOMMU. Looking at the code: Before this patch, the function would return `-ENOSYS` if `CONFIG_MMU` was not set, effectively disabling the function for NOMMU. Now, the patch removes the `#ifndef CONFIG_MMU` check. However, *for* MMU builds, it was just doing: `shmem_zero_setup_desc(desc)` or `vma_desc_set(desc, VMA_ANON_BIT)` and returning. Wait... the function *already* did that for MMU builds. Removing the `#ifndef CONFIG_MMU` simply ensures the logic is now *also* executed on NOMMU.
Now, let's step back and consider KMSAN. KMSAN, as I understand it, *requires* an MMU. KMSAN needs to be enabled during the build, and it uses compiler instrumentation and runtime checks to detect uninitialized memory reads and other memory errors. It does not work on NOMMU architectures. So, any code guarded by `#ifndef CONFIG_MMU` is *by definition* not going to be tested by KMSAN.
So, in `drivers/char/mem.c`, The change in `mmap_zero_prepare` is basically:
* Before:
```c
static int mmap_zero_prepare(struct vm_area_desc *desc)
{
#ifndef CONFIG_MMU
return -ENOSYS;
#endif
if (vma_desc_test(desc, VMA_SHARED_BIT))
return shmem_zero_setup_desc(desc);
/*
* This is a highly unique situation where we mark a MAP_PRIVATE mapping
* as anonymous.
*/
vma_desc_set_anonymous(desc);
return 0;
}
```
* After:
```c
static int mmap_zero_prepare(struct vm_area_desc *desc)
{
#ifdef CONFIG_MMU
if (vma_desc_test(desc, VMA_SHARED_BIT))
return shmem_zero_setup_desc(desc);
#endif
/*
* This is a highly unique situation where we mark a MAP_PRIVATE mapping
* as anonymous.
*/
vma_desc_set_anonymous(desc);
return 0;
}
```
For `CONFIG_MMU`, the behavior is *identical*. It's either `shmem_zero_setup_desc` or `vma_desc_set_anonymous`. For NOMMU, the code now *does* something (sets the anonymous bit).
So, the key points:
1. This patch series focuses on NOMMU architectures. The main changes are under `#ifndef CONFIG_MMU`.
2. The only change for MMU code is in `drivers/char/mem.c`. This *doesn't* introduce new functionality or logic paths; it just removes an exclusion, so the MMU version is the same.
3. KMSAN *cannot* run on NOMMU builds. It's a fundamental architectural limitation.
4. Therefore, any code modified by this patch series won't be executed in a KMSAN build.
5. In the case of the changes to `drivers/char/mem.c`, the behavior for MMU is exactly the same before and after the patch. The NOMMU side gains *basic* functionality and no uninitialized memory issues.
Therefore, running KMSAN on this patch series is, frankly, pointless. It's not going to test the NOMMU code, because KMSAN won
|
| 78/3 | 2026/08/13 07:03 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch series introduces `mmap_prepare` and `mremap` shrinking/growing support specifically for NOMMU architectures (`mm/nommu.c`, `mm/filemap.c` under `#ifndef CONFIG_MMU`, and `drivers/char/mem.c` for NOMMU). The only change to code compiled with `CONFIG_MMU` is in `drivers/char/mem.c`, where an `#ifndef CONFIG_MMU` block was replaced with an `#ifdef CONFIG_MMU` block, resulting in identical behavior for MMU builds.\n\nKMSAN is a dynamic analysis tool that requires compiler instrumentation and is currently only supported on architectures with an MMU (x86_64 and s390). It cannot be enabled on NOMMU builds. Because the functional changes in this patch series are exclusively for NOMMU configurations, they will never be compiled or executed in a KMSAN-enabled kernel.\n\nFurthermore, even within the NOMMU code, the patch carefully initializes newly exposed memory during `mremap` (either by reading from the backing file via `nommu_read_iter` or zero-filling) and correctly handles anonymous mappings, preventing uninitialized memory leaks. Therefore, a dedicated KMSAN fuzzing session is not justified.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch series introduces `mmap_prepare` and `mremap` shrinking/growing support specifically for NOMMU architectures (`mm/nommu.c`, `mm/filemap.c` under `#ifndef CONFIG_MMU`, and `drivers/char/mem.c` for NOMMU). The only change to code compiled with `CONFIG_MMU` is in `drivers/char/mem.c`, where an `#ifndef CONFIG_MMU` block was replaced with an `#ifdef CONFIG_MMU` block, resulting in identical behavior for MMU builds.\n\nKMSAN is a dynamic analysis tool that requires compiler instrumentation and is currently only supported on architectures with an MMU (x86_64 and s390). It cannot be enabled on NOMMU builds. Because the functional changes in this patch series are exclusively for NOMMU configurations, they will never be compiled or executed in a KMSAN-enabled kernel.\n\nFurthermore, even within the NOMMU code, the patch carefully initializes newly exposed memory during `mremap` (either by reading from the backing file via `nommu_read_iter` or zero-filling) and correctly handles anonymous mappings, preventing uninitialized memory leaks. Therefore, a dedicated KMSAN fuzzing session is not justified.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|