The VM_SPECIAL / VMA_SPECIAL_FLAGS mask conflates several unrelated
properties:
* Is this kernel-owned, whether MMIO, kernel-allocated pages, or ordinary
pages a driver maps itself?
* Can it be expanded or merged?
* Is this a 'weird' case like mlock where migration might race and we
'have' to set invalid flags to notify?
* Is it another 'weird' case where we just want to stop GUP from touching
it?
Driver writers have often been confused about this, and who can blame them?
It also interacts badly with the eternal edgecase known as hugetlb - which
sets VMA_DONTEXPAND_BIT but doesn't also want to be treated like a
'special' flag.
Another issue is that we cannot make sensible assumptions about flag
use. It's not possible to assume VMA_IO_BIT means iommu because drivers
abuse it and mlock abuses it.
Special is also an overloaded term in mm. VDSO and VVAR mappings are also
called 'special' but they're special in a... special way.
Sometimes things are called special that are a subset of
VMA_SPECIAL_FLAGS (VMA_PFNMAP_BIT and VMA_MIXEDMAP_BIT for instance when it
comes to zapping or vm_normal_folio()).
There's a specific kind of special for THP too, which considers
PFN map, mixed map 'special' but DAX not.
It's all rather a mess.
This series brings some order to things by both limiting what drivers can
do with VMA flags and switching to using predicates that describe
behaviour, not arbitrary flags.
It establishes the invariant that only kernel-owned mappings may set
VMA_IO_BIT or clear VMA_MAYWRITE_BIT in an mmap hook, enforcing this by
validating VMA state after every mmap and mmap_prepare hook.
It updates usbmon and sg to mmap_prepare in order to do so, adding a new
mmap action for mapping discontiguous kernel pages, and has hfi1 and the
ALSA PCM status page map their pages eagerly instead.
It also establishes the invariant that VMA_MIXEDMAP_BIT be set when mapping
kernel memory, something that is usually the case but happens not to be for
some users - specifically defio, cmt_speech, uprobes and the bpf arena, all
of which are updated to do the right thing.
It replaces VM_SPECIAL and arbitrary flag tests with predicates that say
what is actually being tested:
vma_is_kernel_owned() Does a driver or kernel code manage a VMA's
life cycle?
vma_is_fixed_mapping() Is the VMA not permitted to be expanded or
merged?
vma_is_persistent() Do bytes written to the VMA stay written, and
bytes read stay the same unless userland changes
them?
vma_can_merge() Can the VMA be merged with a compatible
neighbour?
vma_can_gup() Can GUP obtain pages from the VMA, i.e. is it
neither a PFN map nor memory-mapped I/O?
Remaining raw VMA_IO_BIT, VMA_PFNMAP_BIT and VMA_MIXEDMAP_BIT tests
scattered across mm are also converted to predicates where it makes sense
to do so.
And also the opportunity is taken to eliminate THP's vma_is_special_huge()
which was an existing source of confusion.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
v3:
* Fixed up bug in patch 1 as reported by Mike - have to delay setting
map->vma_flags until after action prepare, though map->vm_file needs to
be set before for correct reference count management.
* Updated 4/40 to add a symmetric vm_end check as well as vm_start in case
of a dangerously insane driver, as per Sashiko.
* Updated 8/40 to check if a driver did something REALLY stupid like having
a NULL discontig_kernel_page_ops ptr, as per Sashiko.
* Updated 15/40 to trivially synchronise userland test comments.
* Updated 17/40 to correctly duplicate code to the userland VMA tests as
per Sashiko.
v2:
* Rebased on mm-unstable.
* Introduced new patch to fix various mmap_prepare and file interactions
that weren't quite right as per Sashiko. None impact anything upstream
yet so it doesn't need to be a fix.
* Restore vma->vm_start if an mmap hook has moved it before tearing the
VMA down, so we unmap the range we established rather than the one the
hook invented, as per Sashiko.
* Reject a discontiguous kernel page batch of zero pages rather than
looping forever, and bound batches by the pages remaining in the VMA,
as per Sashiko.
* Add the missing map_kernel_discontig member to the userland VMA tests'
copy of struct mmap_action as per Sashiko.
* Set VM_DONTEXPAND on hfi1's RCV_HDRQ, RCV_EGRBUF and RTAIL mappings, as
dma_mmap_coherent() doesn't on the IOMMU-DMA path, as per Sashiko.
* Recompute vma->vm_page_prot in snd_pcm_mmap_status() after clearing
VM_WRITE, as vm_insert_page() uses it immediately rather than at fault
time, as per Sashiko.
* munlock_vma_folio() now tests VMA_LOCKED_MASK so an unmap racing the
mlock walk still munlocks folios the walk has already counted, as per
Sashiko.
https://lore.kernel.org/r/20260914-b4-mmap-prepare-vma-flag-sanify-v2-0-7d9781ed5361@kernel.org
v1:
https://lore.kernel.org/r/20260908-b4-mmap-prepare-vma-flag-sanify-v1-0-dacf19cce22b@kernel.org
---
Lorenzo Stoakes (ARM) (40):
mm/vma: fix mmap_prepare file handling, remove file_doesnt_need_get
mm/vma: predicate setting mmap_prepare VMA fields on new vma alloc
mm/vma: introduce and use vma_[flags_]can_merge()
mm: consistently validate VMA state after mmap[_prepare] hooks
mm/vma: ensure mmap_prepare doesn't set actions on a mergeable vma
mm: make map_kernel_pages_[prepare,complete] internal and unexported
mm/vma: tidy up map kernel pages enum values
mm: add mmap action for discontiguous kernel page mapping
docs: filesystems: update mmap_prepare docs for discontig kernel pgs
drivers/usb/mon: update to use mmap_prepare + map kernel pages
infiniband: update hfi1 to use remap_vmalloc_range()
selinux: reject writable opens of policy file, drop mmap shared/write check
ALSA: pcm: use vm_insert_page() to map PCM status page
bpf: arena: mark arena_map_mmap() mappings VM_MIXEDMAP
mm/vma: add vma[_flags]_is_kernel_owned() predicates
mm/vma: only allow mmap to clear VMA_MAYWRITE_BIT if kernel-owned
mm/vma: add and use vma_[flags]_is_fixed_mapping
scsi: sg: convert mmap hook to mmap_prepare and rework
fbdev: defio: assert FBINFO_VIRTFB, drop VM_IO, add VM_MIXEDMAP
HSI: cmt_speech: convert mmap hook to mmap_prepare, refactor
mm/gup: error out early on !VMA_MAYREAD_BIT VMAs
uprobes: remove VM_IO, set VM_MIXEDMAP for mapped kernel pages
mm/mlock: clear VMA_LOCKED_MASK over mmap callback
mm/mlock: eliminate weird VMA_IO_BIT abuse and simplify
mm/vma: enforce that only kernel-owned mappings may set VMA_IO_BIT
mm: remove VMA_IO_BIT check in vma[_flags]_is_kernel_owned()
mm: remove hugetlb_inline.h
mm: rename is_vm_hugetlb_page() to vma_is_hugetlb()
mm: drop some redundant checks around hugetlb VMAs
mm/madvise: update is_valid_guard_vma() to use vma_can_merge()
mm/vma: introduce vma[_flags]_is_persistent()
mm/uffd: use predicates for userfaultfd checks
mm/madvise: use predicates for madvise(..., MADV_DOFORK)
mm: eliminate VMA_SPECIAL_FLAGS usage when hugetlb explicitly tested
mm: eliminate VMA_SPECIAL_FLAGS check in lru_gen_look_around()
mm: avoid use of VMA_SPECIAL_FLAGS in migrate_vma_setup()
mm: eliminate VM_SPECIAL, VMA_SPECIAL_FLAGS
fuse: dax: do not set VM_MIXEDMAP
mm/huge_memory: remove vma_is_special_huge()
mm/vma: introduce and use vma[_flags]_can_gup()
Documentation/filesystems/mmap_prepare.rst | 81 +++++++++
arch/arm64/kvm/mmu.c | 4 +-
arch/powerpc/mm/book3s64/radix_tlb.c | 6 +-
arch/powerpc/mm/nohash/e500_hugetlbpage.c | 2 +-
arch/powerpc/mm/nohash/tlb.c | 2 +-
arch/riscv/kvm/mmu.c | 2 +-
arch/riscv/mm/tlbflush.c | 2 +-
arch/s390/mm/gmap_helpers.c | 6 +-
arch/sparc/mm/init_64.c | 2 +-
arch/x86/kernel/uprobes.c | 2 +-
drivers/gpu/drm/drm_gpusvm.c | 5 +-
drivers/hsi/clients/cmt_speech.c | 33 +---
drivers/infiniband/hw/hfi1/file_ops.c | 83 +++------
drivers/scsi/sg.c | 115 ++++++-------
drivers/usb/mon/mon_bin.c | 82 +++++----
drivers/video/fbdev/core/fb_defio.c | 6 +-
drivers/video/fbdev/ssd1307fb.c | 2 +
fs/coredump.c | 6 +-
fs/fuse/dax.c | 2 +-
fs/hugetlbfs/inode.c | 2 +-
fs/proc/task_mmu.c | 8 +-
include/asm-generic/tlb.h | 4 +-
include/linux/hugetlb.h | 5 +-
include/linux/hugetlb_inline.h | 28 ---
include/linux/mm.h | 266 +++++++++++++++++++++++++++--
include/linux/mm_types.h | 50 +++++-
include/linux/pagemap.h | 1 -
include/linux/rmap.h | 2 +-
include/linux/userfaultfd_k.h | 1 -
kernel/bpf/arena.c | 3 +-
kernel/events/core.c | 2 +-
kernel/events/uprobes.c | 4 +-
kernel/sched/fair.c | 3 +-
mm/folio.c | 2 +-
mm/gup.c | 15 +-
mm/hmm.c | 3 +-
mm/huge_memory.c | 31 ++--
mm/hugetlb.c | 14 +-
mm/internal.h | 81 +++++----
mm/ksm.c | 4 +-
mm/madvise.c | 28 +--
mm/memory.c | 142 ++++++++++++---
mm/mempolicy.c | 5 +-
mm/migrate_device.c | 12 +-
mm/mlock.c | 51 +++---
mm/mmap.c | 2 +-
mm/mmu_gather.c | 2 +-
mm/mprotect.c | 5 +-
mm/mremap.c | 11 +-
mm/page_vma_mapped.c | 4 +-
mm/pagewalk.c | 2 +-
mm/rmap.c | 4 +-
mm/swapfile.c | 2 +-
mm/userfaultfd.c | 45 +++--
mm/util.c | 29 +++-
mm/vma.c | 246 +++++++++++++++++++-------
mm/vma.h | 31 +++-
mm/vma_internal.h | 1 -
mm/vmscan.c | 9 +-
security/selinux/selinuxfs.c | 11 +-
sound/core/pcm_native.c | 38 ++---
tools/testing/vma/include/dup.h | 80 +++++++--
tools/testing/vma/include/stubs.h | 2 +-
tools/testing/vma/tests/merge.c | 10 +-
64 files changed, 1169 insertions(+), 575 deletions(-)
---
base-commit: 6b41451631cabf9ea3b384c2a099088e1598f963
change-id: 20260721-b4-mmap-prepare-vma-flag-sanify-2100425df5aa
Best regards,
--
Lorenzo Stoakes (ARM) [off-list ref]
The map->file_doesnt_need_get flag is confusing and the existing
implementation has holes.
Drivers are permitted to change the owning file of a mapping. If they do
so, they are required to take a reference on that file.
The mmap() operation which ultimately invokes __mmap_region() is guaranteed
to drop the refcount for the original file the mapping was made under, but
this is not true for the replaced file.
This has been addressed so far by tracking map->file_doesnt_need_get, which
is rather poorly named and unfortunately fails to correctly track whether
or not an additional put were needed in a number of cases.
Make life easier by removing this flag, and instead drop the reference for
both mmap_prepare and the deprecated mmap callback in a new function
put_map().
Track whether this needs to be done by aligning mmap_state with
vm_area_desc and store the original file in the map->file field, keeping
the updated file in map->vm_file.
In order to have the same behaviour for both types of hooks, only drop the
reference __mmap_new_file_vma() itself took in its error path, deferring
the replaced file's reference to put_map().
To make this work correctly, map->vm_file has to be updated before any
error handling, so update __mmap_new_file_vma() and call_mmap_prepare() to
set this field first.
Also when mmap_prepare() changes the file and is then merged, the reference
count also must be decremented, so update the logic to call put_map() in
this case too.
Also update __compat_vma_mmap() to manually perform this step for stacked
file systems using the compatibility layer, and update
compat_set_vma_from_desc() to replace vma_set_file() with a correct
refcount/file update.
No in-tree driver is impacted by the incorrect implementation of this
currently (no driver that does this is mergeable for one), so this does not
need to be a fix.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/internal.h | 1 +
mm/util.c | 5 +++-
mm/vma.c | 83 +++++++++++++++++++++++++++++++++++------------------------
mm/vma.h | 6 +++--
4 files changed, 59 insertions(+), 36 deletions(-)
@@ -1228,8 +1228,11 @@ int __compat_vma_mmap(struct vm_area_desc *desc,/* Perform any preparatory tasks for mmap action. */err=mmap_action_prepare(desc);-if(err)+if(err){+if(desc->vm_file!=vma->vm_file)+fput(desc->vm_file);returnerr;+}/* Update the VMA from the descriptor. */compat_set_vma_from_desc(vma,desc);/* Complete any specified mmap actions. */
@@ -24,7 +24,8 @@ struct mmap_state {vm_flags_tvm_flags;vma_flags_tvma_flags;};-structfile*file;+structfile*file;/* mmap()-specified file. */+structfile*vm_file;/* May be updated by mmap_prepare. */pgprot_tpage_prot;/* User-defined fields, perhaps updated by .mmap_prepare(). */
@@ -43,8 +44,6 @@ struct mmap_state {/* Determine if we can check KSM flags early in mmap() logic. */boolcheck_ksm_early:1;-/* If .mmap_prepare changed the file, we don't need to pin. */-boolfile_doesnt_need_get:1;};#define MMAP_STATE(name, mm_, vmi_, addr_, len_, pgoff_, anon_pgoff_, vma_flags_, file_) \
@@ -2593,20 +2597,23 @@ static int __mmap_new_file_vma(struct mmap_state *map,structvma_iterator*vmi=map->vmi;interror;-vma->vm_file=map->file;-if(!map->file_doesnt_need_get)-get_file(map->file);+vma->vm_file=map->vm_file;+if(map_same_file(map))+get_file(map->vm_file);-if(!map->file->f_op->mmap)+if(!map->vm_file->f_op->mmap)return0;error=mmap_file(vma->vm_file,vma);+map->vm_file=vma->vm_file;+if(error){UNMAP_STATE(unmap,vmi,vma,vma->vm_start,vma->vm_end,map->prev,map->next);-fput(vma->vm_file);-vma->vm_file=NULL;+if(map_same_file(map))+fput(map->vm_file);+vma->vm_file=NULL;vma_iter_set(vmi,vma->vm_end);/* Undo any partial mapping done by a device driver. */unmap_region(&unmap);
@@ -2623,7 +2630,6 @@ static int __mmap_new_file_vma(struct mmap_state *map,!vma_flags_test(&map->vma_flags,VMA_MAYWRITE_BIT)&&vma_test(vma,VMA_MAYWRITE_BIT));-map->file=vma->vm_file;map->vma_flags=vma->flags;return0;
@@ -2631,7 +2637,7 @@ static int __mmap_new_file_vma(struct mmap_state *map,staticvoidmap_set_anon(structmmap_state*map){-map->file=NULL;+map->vm_file=NULL;map->vm_ops=NULL;map->pgoff=map->addr>>PAGE_SHIFT;}
@@ -2797,11 +2803,15 @@ static int call_mmap_prepare(struct mmap_state *map,interr;/* Invoke the hook. */-err=vfs_mmap_prepare(map->file,desc);+err=vfs_mmap_prepare(map->vm_file,desc);if(err)returnerr;-/* It's invalid for mmap_preprare hooks to clear vm_ops. */+/* Update first so file refcount tracked correctly. */+if(desc->vm_file!=map->vm_file)+map->vm_file=desc->vm_file;++/* It's invalid for mmap_prepare hooks to clear vm_ops. */if(!desc->vm_ops)return-EINVAL;
@@ -2811,10 +2821,6 @@ static int call_mmap_prepare(struct mmap_state *map,/* Update fields permitted to be changed. */map->pgoff=desc->pgoff;-if(desc->vm_file!=map->file){-map->file_doesnt_need_get=true;-map->file=desc->vm_file;-}map->vma_flags=desc->vma_flags;map->page_prot=desc->page_prot;/* User-defined fields. */
@@ -2826,7 +2832,7 @@ static int call_mmap_prepare(struct mmap_state *map,*anonymousmappings.Ratherthanallowingthesemappingstobeodd*outliers,simplymakethemtrulyanonymous.*/-if(map_is_private(map)&&file_is_dev_zero(map->file))+if(map_is_private(map)&&file_is_dev_zero(map->vm_file))map_set_anon(map);return0;
@@ -2845,7 +2851,7 @@ static void set_vma_user_defined_fields(struct vm_area_struct *vma,*/staticboolcan_set_ksm_flags_early(structmmap_state*map){-structfile*file=map->file;+structfile*file=map->vm_file;/* Anonymous mappings have no driver which can change them. */if(!file)
@@ -2922,7 +2942,10 @@ static unsigned long __mmap_region(struct file *file, unsigned long addr,__mmap_complete(&map,vma);-if(have_mmap_prepare&&allocated_new){+if(!allocated_new){+/* Merged, so need to drop refcount. */+put_map(&map);+}elseif(have_mmap_prepare){error=mmap_action_complete(vma,&desc.action,/*is_compat=*/false);if(error)
@@ -2936,13 +2959,7 @@ static unsigned long __mmap_region(struct file *file, unsigned long addr,if(map.charged)vm_unacct_memory(map.charged);abort_munmap:-/*-*Thisindicatesthat.mmap_preparehassetanewfile,differingfrom-*desc->vm_file.Butsincewe'reabortingtheoperation,onlythe-*originalfilewillbecleanedup.Ensurewecleanupboth.-*/-if(map.file_doesnt_need_get)-fput(map.file);+put_map(&map);vms_abort_munmap_vmas(&map.vms,&map.mas_detach);returnerror;}
It only makes sense to manipulate VMA fields if we allocated a new VMA,
rather than merged it.
VMA merging does not compare vm_ops or vm_private_data, so a merged VMA
keeps its own, which is also what the legacy f_op->mmap path does since it
never touches an existing VMA. Previously set_vma_user_defined_fields()
overwrote the merged VMA's fields with those set for the new mapping. In
practice these are the same values, with rare exceptions such as shmem
selecting vm_ops based on whether the file has been unlinked, so no
user-visible change is expected.
Make this dependency explicit, and additionally constify have_mmap_prepare
while we're here.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/vma.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
Replace the open-coded VMA_SPECIAL_FLAGS check in the VMA merge logic with
two new functions vma_flags_can_merge() and vma_can_merge() and update the
merge logic to use the former.
This abstracts the check and expresses it in terms of the desired behaviour
rather than an arbitrary and confusing VMA flag.
This also lays the groundwork for making further improvements in VMA flag
usage.
Also update the userland VMA tests to reflect the change.
No functional change intended.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
include/linux/mm.h | 21 +++++++++++++++++++++
mm/vma.c | 19 +++++++++++--------
tools/testing/vma/include/dup.h | 5 +++++
3 files changed, 37 insertions(+), 8 deletions(-)
@@ -1152,9 +1153,11 @@ struct vm_area_struct *vma_merge_new_range(struct vma_merge_struct *vmg)vmg->state=VMA_MERGE_NOMERGE;-/* Special VMAs are unmergeable, also if no prev/next. */-if(vma_flags_test_any_mask(&vmg->vma_flags,VMA_SPECIAL_FLAGS)||-(!prev&&!next))+if(!vma_flags_can_merge(&vmg->vma_flags))+returnNULL;++/* VMAs with no prev/next are unmergeable. */+if(!prev&&!next)returnNULL;can_merge_left=can_vma_merge_left(vmg);
When the f_op->mmap_prepare or deprecated f_op->mmap hooks are invoked, the
driver might have done something crazy that is not permitted by the kernel.
Currently we check for three such cases in __mmap_new_file_vma(), but only
if the legacy f_op->mmap hook is used:
* Did sparc ADI result in invalid flags?
* Did the driver alter vma->vm_start?
* Did the driver make a file-backed mapping on a read-only file writable?
Generalise these checks for both mmap_prepare and mmap and apply to all
invocations of mmap_file(), the f_op->mmap and f_op->mmap_prepare handling
in the core VMA code and the mmap_prepare compatibility layer.
Also extend the vm_start check to vm_end also - drivers must not change the
VMA range at all.
We also WARN_ON_ONCE() on these conditions as they are things that should
simply not occur in the kernel and it's important to call it out when it
does.
We invoke mmap_prepare_validate() after mmap_action_prepare(), as mmap
actions often manipulate state in the descriptor thus providing the final
state the VMA will be derived from.
Also call mmap_validate_vma_flags() in insert_vm_struct() to ensure that
special regions which are inserted (such as a VDSO or VVAR) also satisfy
the sanity checks.
This way every VMA established through an mmap hook, whether via mmap() or
the compatibility layer, or inserted via insert_vm_struct(), has been
validated. brk() VMAs never pass through a driver hook and so need no such
check.
While we're here, also fixup a couple disjoint blocks of #ifdef CONFIG_MMU.
Finally, update the VMA userland tests to reflect the change.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/internal.h | 51 ++++++++++++--------
mm/util.c | 19 ++++++--
mm/vma.c | 100 ++++++++++++++++++++++++++++++++++------
mm/vma.h | 25 ++++++++--
tools/testing/vma/include/dup.h | 10 ++++
5 files changed, 163 insertions(+), 42 deletions(-)
@@ -1224,19 +1224,28 @@ EXPORT_SYMBOL(compat_set_desc_from_vma);int__compat_vma_mmap(structvm_area_desc*desc,structvm_area_struct*vma){+structvm_area_descprev_desc;interr;+/* Derive state prior to mmap_prepare hook. */+compat_set_desc_from_vma(&prev_desc,desc->file,vma);/* Perform any preparatory tasks for mmap action. */err=mmap_action_prepare(desc);-if(err){-if(desc->vm_file!=vma->vm_file)-fput(desc->vm_file);-returnerr;-}+if(err)+gotoerr_put;+/* Check the caller did nothing crazy. */+err=mmap_prepare_validate(&prev_desc,desc);+if(err)+gotoerr_put;/* Update the VMA from the descriptor. */compat_set_vma_from_desc(vma,desc);/* Complete any specified mmap actions. */returnmmap_action_complete(vma,&desc->action,/*is_compat=*/true);++err_put:+if(desc->vm_file!=vma->vm_file)+fput(desc->vm_file);+returnerr;}EXPORT_SYMBOL(__compat_vma_mmap);
@@ -2623,16 +2623,6 @@ static int __mmap_new_file_vma(struct mmap_state *map,returnerror;}-/* Drivers cannot alter the address of the VMA. */-WARN_ON_ONCE(map->addr!=vma->vm_start);-/*-*Driversshouldnotpermitwritabilitywhenpreviouslyitwas-*disallowed.-*/-VM_WARN_ON_ONCE(!vma_flags_same_pair(&map->vma_flags,&vma->flags)&&-!vma_flags_test(&map->vma_flags,VMA_MAYWRITE_BIT)&&-vma_test(vma,VMA_MAYWRITE_BIT));-map->vma_flags=vma->flags;return0;
@@ -2710,11 +2700,6 @@ static int __mmap_new_vma(struct mmap_state *map, struct vm_area_struct **vmap,vma->flags=map->vma_flags;}-#ifdef CONFIG_SPARC64-/* TODO: Fix SPARC ADI! */-WARN_ON_ONCE(!arch_validate_flags(map->vm_flags));-#endif-/* Lock the VMA since it is modified after insertion into VMA tree */vma_start_write(vma);vma_iter_store_new(vmi,vma);
@@ -2777,6 +2762,80 @@ static void __mmap_complete(struct mmap_state *map, struct vm_area_struct *vma)vma_set_page_prot(vma);}+/* Check to ensure that the VMA flags of a newly mapped VMA are sane. */+staticintmmap_validate_vma_flags(constvma_flags_t*flags)+{+#ifdef CONFIG_SPARC64+constvm_flags_tlegacy_flags=vma_flags_to_legacy(*flags);++/* TODO: Fix SPARC ADI! */+if(WARN_ON_ONCE(!arch_validate_flags(legacy_flags)))+return-EINVAL;+#endif++return0;+}++/* Check to ensure a driver hasn't done something crazy. */+staticintmmap_validate(unsignedlongprev_start,unsignedlongprev_end,+unsignedlongcurr_start,unsignedlongcurr_end,+constvma_flags_t*prev_flags,+constvma_flags_t*curr_flags)+{+boolwas_maywrite,is_maywrite;++/* Drivers cannot alter the range of the VMA. */+if(WARN_ON_ONCE(prev_start!=curr_start||prev_end!=curr_end))+return-EINVAL;++was_maywrite=vma_flags_test(prev_flags,VMA_MAYWRITE_BIT);+is_maywrite=vma_flags_test(curr_flags,VMA_MAYWRITE_BIT);++/* A driver may not make a previously unwritable mapping writable. */+if(WARN_ON_ONCE(!was_maywrite&&is_maywrite))+return-EINVAL;++returnmmap_validate_vma_flags(curr_flags);+}++/**+*mmap_prepare_validate()-Ensurethedriverhasn'tviolatedinvariantsinits+*f_op->mmap_preparehook.+*@prev_desc:TheVMAdescriptorpriortothemmap_preparehookbeingcalled.+*@desc:TheVMAdescriptorafterthemmap_preparehookhasbeencalled.+*+*Returns:0onsuccess,otherwiseanerror.+*/+intmmap_prepare_validate(conststructvm_area_desc*prev_desc,+conststructvm_area_desc*desc)+{+returnmmap_validate(prev_desc->start,prev_desc->end,+desc->start,desc->end,+&prev_desc->vma_flags,&desc->vma_flags);+}++/**+*mmap_hook_validate()-Ensurethedriverhasn'tviolatedinvariantsin+*itsf_op->mmaphook.+*@prev_start:Thestartofthemappingpriortothemmaphook.+*@prev_end:Theendofthemappingpriortothemmaphook.+*@prev_flags:TheVMAflagssetfortheVMApriortothemmaphook.+*@vma:TheVMAafterthehookhasbeenapplied.+*+*Returns:0onsuccess,otherwiseanerror.+*/+intmmap_hook_validate(unsignedlongprev_start,unsignedlongprev_end,+constvma_flags_t*prev_flags,+conststructvm_area_struct*vma)+{+constunsignedlongstart=vma->vm_start;+constunsignedlongend=vma->vm_end;+constvma_flags_t*flags=&vma->flags;++returnmmap_validate(prev_start,prev_end,start,end,prev_flags,+flags);+}+staticintcall_action_prepare(structmmap_state*map,structvm_area_desc*desc){
@@ -2803,6 +2862,7 @@ static int call_action_prepare(struct mmap_state *map,staticintcall_mmap_prepare(structmmap_state*map,structvm_area_desc*desc){+conststructvm_area_descprev_desc=*desc;interr;/* Invoke the hook. */
@@ -2822,6 +2882,11 @@ static int call_mmap_prepare(struct mmap_state *map,if(err)returnerr;+/* Check the caller did nothing crazy. */+err=mmap_prepare_validate(&prev_desc,desc);+if(err)+returnerr;+/* Update fields permitted to be changed. */map->pgoff=desc->pgoff;map->vma_flags=desc->vma_flags;
@@ -3457,10 +3522,15 @@ int __vm_munmap(unsigned long start, size_t len, bool unlock)intinsert_vm_struct(structmm_struct*mm,structvm_area_struct*vma){unsignedlongcharged=vma_pages(vma);+interr;if(find_vma_intersection(mm,vma->vm_start,vma->vm_end))return-ENOMEM;+err=mmap_validate_vma_flags(&vma->flags);+if(err)+returnerr;+if(vma_test(vma,VMA_ACCOUNT_BIT)&&security_vm_enough_memory_mm(mm,charged))return-ENOMEM;
@@ -1359,13 +1359,23 @@ static inline int vfs_mmap_prepare(struct file *file, struct vm_area_desc *desc)returnfile->f_op->mmap_prepare(desc);}+intmmap_prepare_validate(conststructvm_area_desc*prev_desc,+conststructvm_area_desc*desc);+staticinlineint__compat_vma_mmap(structvm_area_desc*desc,structvm_area_struct*vma){+structvm_area_descprev_desc;interr;+/* Derive state prior to mmap_prepare hook. */+compat_set_desc_from_vma(&prev_desc,desc->file,vma);/* Perform any preparatory tasks for mmap action. */err=mmap_action_prepare(desc);+if(err)+returnerr;+/* Check the caller did nothing crazy. */+err=mmap_prepare_validate(&prev_desc,desc);if(err)returnerr;/* Update the VMA from the descriptor. */
When a user requests an mmap_action be performed in mmap_prepare, this
involves populating the VMA range with data.
However, if the VMA is mergeable, it might then mistakenly be merged with
another VMA without having populated the range.
Every mmap action currently available sets VMA flags such that the VMA
cannot be merged.
However, to ensure that no future mmap action falls foul of this, assert
that this is the case upon mmap_prepare validation.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/vma.c | 9 +++++++++
1 file changed, 9 insertions(+)
@@ -2809,6 +2809,15 @@ static int mmap_validate(unsigned long prev_start, unsigned long prev_end,intmmap_prepare_validate(conststructvm_area_desc*prev_desc,conststructvm_area_desc*desc){+/*+*ItisnotvalidtoexecutemmapactionsforVMAswhichcanbemerged,+*asanysuchmergewouldleaveportionsofthemappingincorrectly+*unmapped.+*/+if(vma_flags_can_merge(&desc->vma_flags)&&+WARN_ON_ONCE(desc->action.type!=MMAP_NOTHING))+return-EINVAL;+returnmmap_validate(prev_desc->start,prev_desc->end,desc->start,desc->end,&prev_desc->vma_flags,&desc->vma_flags);
There's no reason to export the symbols for these functions which are only
called from internal mm logic, additionally there's no reason for them to
be declared in mm.h.
This patch therefore removes the exports and moves the declarations to
mm/internal.h.
No functional change intended.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
include/linux/mm.h | 3 ---
mm/internal.h | 3 +++
mm/memory.c | 2 --
3 files changed, 3 insertions(+), 5 deletions(-)
MMAP_MAP_KERNEL_PAGES is a mouthful, discard the MAP_ as that's implied by
MMAP.
Also while we're here delete useless comments for mmap actions whose names
clearly indicate what they are for.
Also update the userland VMA tests to reflect this change.
No functional change intended.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
include/linux/mm.h | 2 +-
include/linux/mm_types.h | 8 ++++----
mm/util.c | 8 ++++----
tools/testing/vma/include/dup.h | 8 ++++----
4 files changed, 13 insertions(+), 13 deletions(-)
@@ -815,11 +815,11 @@ struct pfnmap_track_ctx {/* What action should be taken after an .mmap_prepare call is complete? */enummmap_action_type{-MMAP_NOTHING,/* Mapping is complete, no further action. */-MMAP_REMAP_PFN,/* Remap PFN range. */-MMAP_IO_REMAP_PFN,/* I/O remap PFN range. */+MMAP_NOTHING,+MMAP_REMAP_PFN,+MMAP_IO_REMAP_PFN,MMAP_SIMPLE_IO_REMAP,/* I/O remap with guardrails. */-MMAP_MAP_KERNEL_PAGES,/* Map kernel page range from array. */+MMAP_KERNEL_PAGES,/* Map kernel page range from array. */};/*
@@ -454,11 +454,11 @@ static __always_inline bool vma_flags_empty(const vma_flags_t *flags)/* What action should be taken after an .mmap_prepare call is complete? */enummmap_action_type{-MMAP_NOTHING,/* Mapping is complete, no further action. */-MMAP_REMAP_PFN,/* Remap PFN range. */-MMAP_IO_REMAP_PFN,/* I/O remap PFN range. */+MMAP_NOTHING,+MMAP_REMAP_PFN,+MMAP_IO_REMAP_PFN,MMAP_SIMPLE_IO_REMAP,/* I/O remap with guardrails. */-MMAP_MAP_KERNEL_PAGES,/* Map kernel page range from an array. */+MMAP_KERNEL_PAGES,/* Map kernel page range from array. */};/*
The existing kernel page mapping mmap actions allow for partial and full
mapping of an array of struct page pointers.
However some drivers require the mapping of discontiguous ranges. Permit
this by providing discontig_kernel_page_ops which allows a driver to
specify how the operation should begin and how batches of pages should be
retrieved.
It uses the minimum exposed interface to do so, providing address, page
offset and both vm_private_data state and a local private state object.
ops->init can establish state for the operation, and ops->get outputs the
pages to map and their count. Should an error arise the core unmaps the
VMA, invoking vm_ops->close, which is therefore where any state established
by ops->init is released.
Batches may not exceed the VMA, but may map less than its full range in
case the driver wishes to allow the user to map an area larger than the
available data.
To use it, users invoke mmap_action_map_discontig_kernel_pages() with
initial local private state and a set of operations.
Users can then use one of the provided helper functions to perform an
action:
* discontig_kernel_map_abort() - Abort and leave the mapping as it has
been accumulated so far.
* discontig_kernel_map_page() - Map a single page, or a compound page given
its head page.
* discontig_kernel_map_page_range() - Maps a struct page ** array of a
specified count.
The userland VMA tests are updated accordingly.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
include/linux/mm.h | 45 +++++++++++++++++
include/linux/mm_types.h | 44 +++++++++++++++-
mm/internal.h | 3 ++
mm/memory.c | 108 ++++++++++++++++++++++++++++++++++++++--
mm/util.c | 7 +++
tools/testing/vma/include/dup.h | 11 +++-
6 files changed, 209 insertions(+), 9 deletions(-)
@@ -4647,10 +4647,55 @@ static inline void mmap_action_map_kernel_pages_full(struct vm_area_desc *desc,vma_desc_pages(desc));}+staticinline+voidmmap_action_map_discontig_kernel_pages(structvm_area_desc*desc,+void*init_private,conststructdiscontig_kernel_page_ops*ops)+{+structmmap_action*action=&desc->action;++action->type=MMAP_DISCONTIG_KERNEL_PAGES;+action->map_kernel_discontig.init_private=init_private;+action->map_kernel_discontig.ops=ops;+}+intmmap_action_prepare(structvm_area_desc*desc);intmmap_action_complete(structvm_area_struct*vma,structmmap_action*action,boolis_compat);+staticinlinevoid+discontig_kernel_map_abort(structdiscontig_kernel_page_state*state)+{+state->action=DISCONTIG_KERNEL_PAGE_ABORT;+}++staticinlinevoid+discontig_kernel_map_page(structdiscontig_kernel_page_state*state,+structpage*page)+{+structfolio*folio=page_folio(page);++if(folio_test_large(folio)){+VM_WARN_ON_ONCE(page!=folio_page(folio,0));+state->action=DISCONTIG_KERNEL_PAGE_MAP_COMPOUND_PAGE;+state->__folio=folio;+state->__nr_pages=min(state->nr_pages_remain,+folio_nr_pages(folio));+}else{+state->action=DISCONTIG_KERNEL_PAGE_MAP_PAGE;+state->__page=page;+state->__nr_pages=1;+}+}++staticinlinevoid+discontig_kernel_map_page_range(structdiscontig_kernel_page_state*state,+structpage**page_arr,unsignedlongnr_pages)+{+state->action=DISCONTIG_KERNEL_PAGE_MAP_PAGE_RANGE;+state->__page_arr=page_arr;+state->__nr_pages=nr_pages;+}+/* Look up the first VMA which exactly match the interval vm_start ... vm_end */staticinlinestructvm_area_struct*find_exact_vma(structmm_struct*mm,unsignedlongvm_start,unsignedlongvm_end)
@@ -818,8 +818,44 @@ enum mmap_action_type {MMAP_NOTHING,MMAP_REMAP_PFN,MMAP_IO_REMAP_PFN,-MMAP_SIMPLE_IO_REMAP,/* I/O remap with guardrails. */-MMAP_KERNEL_PAGES,/* Map kernel page range from array. */+MMAP_SIMPLE_IO_REMAP,/* I/O remap with guardrails. */+MMAP_KERNEL_PAGES,/* Map kernel page range from array. */+MMAP_DISCONTIG_KERNEL_PAGES,/* Map kernel discontig page range. */+};++enumdiscontig_kernel_page_action{+DISCONTIG_KERNEL_PAGE_ABORT,+DISCONTIG_KERNEL_PAGE_MAP_PAGE,+DISCONTIG_KERNEL_PAGE_MAP_COMPOUND_PAGE,+DISCONTIG_KERNEL_PAGE_MAP_PAGE_RANGE,+};++structdiscontig_kernel_page_state{+/* Map state. */+constunsignedlongstart;/* Start address of VMA. */+constunsignedlongend;/* End address of VMA. */+unsignedlongaddr;/* The current address to be mapped. */+pgoff_tpgoff;/* The current pgoff to be mapped. */+unsignedlongnr_pages_mapped;/* The number of pages mapped. */+unsignedlongnr_pages_remain;/* The number of pages remaining. */++/* User-defined state. */+void*vm_private_data;/* VMA private data. */+void*private;/* Mapping private data. */++/* Users should not touch these, use discontig_kernel_map_*() helpers. */+enumdiscontig_kernel_page_actionaction;+union{+structpage*__page;+structfolio*__folio;+structpage**__page_arr;+};+unsignedlong__nr_pages;+};++structdiscontig_kernel_page_ops{+int(*init)(void*vm_private_data,void**private);+int(*get)(structdiscontig_kernel_page_state*state);};/*
@@ -2609,17 +2609,23 @@ int vm_insert_pages(struct vm_area_struct *vma, unsigned long addr,}EXPORT_SYMBOL(vm_insert_pages);+staticvoid__map_kernel_pages_prepare(structvm_area_desc*desc)+{+if(vma_desc_test(desc,VMA_MIXEDMAP_BIT))+return;++VM_WARN_ON_ONCE(mmap_read_trylock(desc->mm));+VM_WARN_ON_ONCE(vma_desc_test(desc,VMA_PFNMAP_BIT));+vma_desc_set_flags(desc,VMA_MIXEDMAP_BIT);+}+intmap_kernel_pages_prepare(structvm_area_desc*desc){conststructmmap_action*action=&desc->action;constunsignedlongaddr=action->map_kernel.start;unsignedlongnr_pages,end;-if(!vma_desc_test(desc,VMA_MIXEDMAP_BIT)){-VM_WARN_ON_ONCE(mmap_read_trylock(desc->mm));-VM_WARN_ON_ONCE(vma_desc_test(desc,VMA_PFNMAP_BIT));-vma_desc_set_flags(desc,VMA_MIXEDMAP_BIT);-}+__map_kernel_pages_prepare(desc);nr_pages=action->map_kernel.nr_pages;end=addr+PAGE_SIZE*nr_pages;
@@ -2640,6 +2646,98 @@ int map_kernel_pages_complete(struct vm_area_struct *vma,&nr_pages,vma->vm_page_prot);}+intmap_discontig_kernel_pages_prepare(structvm_area_desc*desc)+{+conststructmmap_action*action=&desc->action;+conststructdiscontig_kernel_page_ops*ops=+action->map_kernel_discontig.ops;++/* At minimum need to be able to get pages. */+if(WARN_ON_ONCE(!ops||!ops->get))+return-EINVAL;++__map_kernel_pages_prepare(desc);+return0;+}++staticintapply_discontig_action(structvm_area_struct*vma,+structdiscontig_kernel_page_state*state)+{+unsignedlongnr_pages=state->__nr_pages;+unsignedlongaddr=state->addr;+unsignedlongi;++if(state->action==DISCONTIG_KERNEL_PAGE_MAP_PAGE)+returninsert_page(vma,addr,state->__page,+vma->vm_page_prot,/*mkwrite=*/false);+if(state->action==DISCONTIG_KERNEL_PAGE_MAP_PAGE_RANGE)+returninsert_pages(vma,addr,state->__page_arr,+&nr_pages,vma->vm_page_prot);++/* Compound folio - have to iterate through each page. */+for(i=0;i<nr_pages;i++,addr+=PAGE_SIZE){+structpage*page=folio_page(state->__folio,i);+interr;++err=insert_page(vma,addr,page,vma->vm_page_prot,+/*mkwrite=*/false);+if(err)+returnerr;+}+return0;+}++intmap_discontig_kernel_pages_complete(structvm_area_struct*vma,+structmmap_action*action)+{+conststructdiscontig_kernel_page_ops*ops=+action->map_kernel_discontig.ops;+structdiscontig_kernel_page_statestate={+.start=vma->vm_start,+.end=vma->vm_end,+.addr=vma->vm_start,+.pgoff=vma->vm_pgoff,+.nr_pages_mapped=0,+.nr_pages_remain=vma_pages(vma),+.vm_private_data=vma->vm_private_data,+.private=action->map_kernel_discontig.init_private,+};+interr=0;++if(ops->init)+err=ops->init(vma->vm_private_data,&state.private);+if(err)+returnerr;++do{+unsignedlongend,pgoff_end;+unsignedlongnr_pages;++/* Default to abort. */+state.action=DISCONTIG_KERNEL_PAGE_ABORT;+err=ops->get(&state);+if(err||state.action==DISCONTIG_KERNEL_PAGE_ABORT)+returnerr;+nr_pages=state.__nr_pages;++if(!nr_pages||nr_pages>state.nr_pages_remain)+return-EINVAL;+end=state.addr+PAGE_SIZE*nr_pages;+pgoff_end=state.pgoff+nr_pages;++err=apply_discontig_action(vma,&state);+if(err)+returnerr;++state.addr=end;+state.pgoff=pgoff_end;+state.nr_pages_mapped+=nr_pages;+state.nr_pages_remain-=nr_pages;+}while(state.addr<vma->vm_end);++return0;+}+/***vm_insert_page-insertsinglepageintouservma*@vma:uservmatomapto
@@ -1469,6 +1469,8 @@ int mmap_action_prepare(struct vm_area_desc *desc)returnsimple_ioremap_prepare(desc);caseMMAP_KERNEL_PAGES:returnmap_kernel_pages_prepare(desc);+caseMMAP_DISCONTIG_KERNEL_PAGES:+returnmap_discontig_kernel_pages_prepare(desc);}WARN_ON_ONCE(1);
@@ -1501,6 +1503,9 @@ int mmap_action_complete(struct vm_area_struct *vma,caseMMAP_KERNEL_PAGES:err=map_kernel_pages_complete(vma,action);break;+caseMMAP_DISCONTIG_KERNEL_PAGES:+err=map_discontig_kernel_pages_complete(vma,action);+break;caseMMAP_IO_REMAP_PFN:caseMMAP_SIMPLE_IO_REMAP:/* Should have been delegated. */
Describe the newly introduced discontiguous kernel page mapping mechanism,
detailing how to use it sensibly and how the API looks.
Explicitly detail the various discontiguous actions available and how to
use them.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
Documentation/filesystems/mmap_prepare.rst | 81 ++++++++++++++++++++++++++++++
1 file changed, 81 insertions(+)
@@ -164,5 +164,86 @@ pointer. These are: sufficient entries in the page array to cover the entire range of the described VMA.+* mmap_action_map_discontig_kernel_pages() - Maps a discontiguous range of+`struct page` pointers over the VMA. They must span from the start of the VMA,+ but may terminate prior to the end (leaving the remainder unmapped).+**NOTE:** The ``action`` field should never normally be manipulated directly, rather you ought to use one of these helpers.++Discontiguous Actions+=====================++Some actions can be performed across discontiguous ranges.++Map kernel pages+----------------++To map kernel pages discontiguously, you must provide hooks using ``struct+discontig_kernel_page_ops``:++..code-block:: C++ struct discontig_kernel_page_ops {+ int (*init)(void *vm_private_data, void **private);+ int (*get)(struct discontig_kernel_page_state *state);+ };++The ``init`` hook is optional and allows state to be established before the+operation starts, for instance taking a reference count. Nothing is invoked+after the operation, so ``init`` must not leave locks held, and state that must+be released once the mapping goes away should be released in+``vm_ops->close``.++The ``init`` hook, if provided, is invoked prior to the operation starting. It+may update what is pointed to by ``vm_private_data`` and/or ``private``. If an+error is returned, then the operation is aborted. The ``private`` field can be+reassigned.++**NOTE:** The operation may sleep between invocations of ``get``, so locks+needed to stabilise state must be taken and released within each hook.++The ``get`` handler is the key means through which the operation is+executed. The current state of the operation is provided through ``struct+discontig_kernel_page_state``:++..code-block:: C++ struct discontig_kernel_page_state {+ /* Map state. */+ unsigned long start; /* Start address of VMA. */+ unsigned long end; /* End address of VMA. */+ unsigned long addr; /* The current address to be mapped. */+ pgoff_t pgoff; /* The current pgoff to be mapped. */+ unsigned long nr_pages_mapped; /* The number of pages mapped. */+ unsigned long nr_pages_remain; /* The number of pages remaining. */++ /* User-defined state. */+ void *vm_private_data; /* VMA private data. */+ void *private; /* Mapping private data. */++ /* Users should not touch these, use discontig_kernel_map_*() helpers. */+ ... internal fields ...+ };++With ``private`` being an additional user-controllable state variable,+initialised via ``mmap_action_map_discontig_kernel_pages()``, and+``vm_private_data`` being equal to the ``desc->private_data`` field set in+the ``mmap_prepare()`` hook.++In the ``get`` hook, the user must choose how to map kernel pages:++*``discontig_kernel_map_abort()`` - Call this to abort the operation, whatever+ has been mapped so far will be retained, the rest of the mapping will SIGBUS+ if accessed.+*``discontig_kernel_map_page()`` - Maps a single page, correctly handling+ compound pages (if the compound page is bigger than the remaining pages in the+ VMA, then only those pages that fit will be mapped). For a compound page, the+ head page must be passed.+*``discontig_kernel_map_page_range()`` - Map an array of pages of a specified+ size. Note that if the number of pages specified exceeds the VMA size then an+ error will arise.++If an error arises after ``init`` succeeded, the core unmaps the VMA, invoking+``vm_ops->close`` if set, which is therefore the place to release any state+that ``init`` established.
Replace the deprecated .mmap hook with its replacement .mmap_prepare. As
part of this change, additionally take the approach of mapping pages upon
mmap rather than providing a fault handler.
The page span cannot be mutated when an mmap mapping is in place, so this
is safe to do in advance (the MON_IOCT_RING_SIZE ioctl operation exits
-EBUSY if it's attempted, gated by the rp->mmap_active reference count).
Utilise the newly introduced mmap_action_map_discontig_kernel_pages() to do
this, which allows for iteration over pages in mon_bin_discontig_get().
mon_bin_discontig_init() increments the rp->mmap_active reference count to
stabilise page spans. Should an error arise the core unmaps the VMA and
mon_bin_vma_close() drops the reference again.
The vm_ops->close hook implemented in mon_bin_vma_close() will ensure
correct reference count arithmetic upon unmap (with mon_bin_vma_open()
accounting for splitting).
The existing semantics are all retained, including not mapping past the
range of available pages, with a SIGBUS being raised in a userland process
that attempts to access past this point.
Ultimately insert_page() is invoked to insert each page, which increments
the reference count on each mapped page. This mimics what was being done
previously, only we pre-map the entire range rather than doing so on
demand.
The existing fault handler did nothing that required demand paging, and was
presumably implemented this way for historical reasons.
One behavioural difference: pages are no longer faulted in on demand, so a
page discarded with MADV_DONTNEED is not repopulated and a subsequent
access raises SIGBUS, as with other pre-populated kernel mappings.
Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
drivers/usb/mon/mon_bin.c | 82 ++++++++++++++++++++++++++++++-----------------
1 file changed, 53 insertions(+), 29 deletions(-)
@@ -1226,64 +1235,79 @@ mon_bin_poll(struct file *file, struct poll_table_struct *wait)staticvoidmon_bin_vma_open(structvm_area_struct*vma){structmon_reader_bin*rp=vma->vm_private_data;-unsignedlongflags;-spin_lock_irqsave(&rp->b_lock,flags);-rp->mmap_active++;-spin_unlock_irqrestore(&rp->b_lock,flags);+__mon_bin_vma_open(rp);}-staticvoidmon_bin_vma_close(structvm_area_struct*vma)+staticvoid__mon_bin_vma_close(structmon_reader_bin*rp){unsignedlongflags;-structmon_reader_bin*rp=vma->vm_private_data;spin_lock_irqsave(&rp->b_lock,flags);rp->mmap_active--;spin_unlock_irqrestore(&rp->b_lock,flags);}-/*-*Mapringpagestouserspace.-*/-staticvm_fault_tmon_bin_vma_fault(structvm_fault*vmf)+staticvoidmon_bin_vma_close(structvm_area_struct*vma){-structmon_reader_bin*rp=vmf->vma->vm_private_data;+structmon_reader_bin*rp=vma->vm_private_data;++__mon_bin_vma_close(rp);+}++staticconststructvm_operations_structmon_bin_vm_ops={+.open=mon_bin_vma_open,+.close=mon_bin_vma_close,+};++staticintmon_bin_discontig_init(void*vm_private_data,void**private)+{+structmon_reader_bin*rp=vm_private_data;++/* Dropped by mon_bin_vma_close() on unmap, including on error. */+__mon_bin_vma_open(rp);+return0;+}++staticintmon_bin_discontig_get(structdiscontig_kernel_page_state*state)+{+structmon_reader_bin*rp=state->vm_private_data;unsignedlongoffset,chunk_idx;-structpage*pageptr;unsignedlongflags;spin_lock_irqsave(&rp->b_lock,flags);-offset=vmf->pgoff<<PAGE_SHIFT;++offset=state->pgoff<<PAGE_SHIFT;if(offset>=rp->b_size){spin_unlock_irqrestore(&rp->b_lock,flags);-returnVM_FAULT_SIGBUS;+discontig_kernel_map_abort(state);+return0;}chunk_idx=offset/CHUNK_SIZE;-pageptr=rp->b_vec[chunk_idx].pg;-get_page(pageptr);-vmf->page=pageptr;+discontig_kernel_map_page(state,rp->b_vec[chunk_idx].pg);+spin_unlock_irqrestore(&rp->b_lock,flags);return0;}-staticconststructvm_operations_structmon_bin_vm_ops={-.open=mon_bin_vma_open,-.close=mon_bin_vma_close,-.fault=mon_bin_vma_fault,+staticconststructdiscontig_kernel_page_opsmon_discontig_ops={+.init=mon_bin_discontig_init,+.get=mon_bin_discontig_get,};-staticintmon_bin_mmap(structfile*filp,structvm_area_struct*vma)+staticintmon_bin_mmap_prepare(structvm_area_desc*desc){-/* don't do anything here: "fault" will set up page table entries */-vma->vm_ops=&mon_bin_vm_ops;+conststructfile*filp=desc->file;-if(vma->vm_flags&VM_WRITE)+if(vma_desc_test(desc,VMA_WRITE_BIT))return-EPERM;-vm_flags_mod(vma,VM_DONTEXPAND|VM_DONTDUMP,VM_MAYWRITE);-vma->vm_private_data=filp->private_data;-mon_bin_vma_open(vma);+desc->vm_ops=&mon_bin_vm_ops;+vma_desc_clear_flags(desc,VMA_MAYWRITE_BIT);+vma_desc_set_flags(desc,VMA_DONTEXPAND_BIT,VMA_DONTDUMP_BIT);+desc->private_data=filp->private_data;++mmap_action_map_discontig_kernel_pages(desc,NULL,&mon_discontig_ops);return0;}
In cases which map chip memory from vmalloc()'d ranges, the hfi1 infiniband
drivers currently installs a fault handler, and then smuggles the kernel
virtual address of this range in vma->vm_pgoff.
This is exposing KASLR-sensitive internal kernel state in the VMA, and is
entirely unnecessary.
Instead, use remap_vmalloc_range() to remap the VMA to the span, and
eliminate the fault handler altogether.
remap_vmalloc_range() checks that the VMA does not extend beyond the
vmalloc area, and the driver already requires the VMA to exactly match the
span of the memory being mapped, so this has no impact.
The memory is all preallocated so not having a fault handler has no impact
either, other than pre-mapping the ranges which is beneficial.
We also remove the VM_IO flag as it's not appropriate here, and the
VM_DONTEXPAND flag as remap_vmalloc_range() will set it (and also mark the
range correctly as a mixed map).
We also update the vmalloc paths to place the virtual kernel address in
memvirt, rather than overloading the physical address memaddr. We predicate
the vmalloc handling on the vmalloc flag before we check memvirt for the
virtual address-derived PFN remap path, so this works fine.
remap_vmalloc_range() requires that the vmalloc()'d areas were all
allocated using vmalloc_user() - each of cq->comps,
uctxt->subctxt_rcvegrbuf, uctxt->subctxt_rcvhdr_base,
uctxt->subctxt_uregbase and dd->events were allocated this way, so that
requirement is satisfied.
We also remove VM_IO and VM_DONTEXPAND from the STATUS command, as these
are both set on remap.
Finally, we remove VM_DONTEXPAND from the PIO_BUFS, PIO_BUFS_SOP and UREGS
commands, as these are also all set on remap. PIO_CRED retains it, as
dma_mmap_coherent() may map via vm_insert_page() on the IOMMU-DMA path,
which sets only VM_MIXEDMAP. The RCV_HDRQ, RCV_EGRBUF and RTAIL commands
also map via dma_mmap_coherent() and never set VM_DONTEXPAND, so set it for
them for the same reason.
Note that we retain expected behaviour throughout - the vmalloc remapped
ranges set VM_MIXEDMAP | VM_DONTDUMP | VM_DONTEXPAND for each range.
VM_IO was never appropriate as the ranges are explicitly not MMIO, and the
reference to the v3.7 VM_RESERVED semantics map on to VM_MIXEDMAP |
VM_DONTDUMP | VM_DONTEXPAND correctly - no core dump, unmergeable, no
normal vm page for purposes of reclaim/migration/etc.
There is a change in behaviour in that pages mapped using
remap_vmalloc_range() will now have normal GUP-able pages, however this
should have no impact as there is no reason not to allow this.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
drivers/infiniband/hw/hfi1/file_ops.c | 83 +++++++++++------------------------
1 file changed, 25 insertions(+), 58 deletions(-)
The policy file has no write method and is exposed read-only (S_IRUGO in
selinux_files[]), yet sel_open_policy() performs no open mode check, so a
CAP_DAC_OVERRIDE caller can open it O_RDWR. Reject FMODE_WRITE at open, as
kernfs does.
The file can then never be mapped with FMODE_WRITE, so do_mmap() always
clears VM_MAYWRITE and VM_SHARED for MAP_SHARED mappings and the VM_SHARED
check in sel_mmap_policy() cannot be reached. Remove it.
This also stops sel_mmap_policy() clearing VM_MAYWRITE on a mapping that is
neither a PFN map nor a mixed map, ahead of the core enforcing that only
such mappings may do so.
Acked-by: Stephen Smalley <stephen.smalley.work@gmail.com>
Reviewed-by: Jann Horn <jannh@google.com>
Acked-by: Paul Moore <paul@paul-moore.com>
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
security/selinux/selinuxfs.c | 11 +++--------
1 file changed, 3 insertions(+), 8 deletions(-)
@@ -424,14 +427,6 @@ static const struct vm_operations_struct sel_mmap_policy_ops = {staticintsel_mmap_policy(structfile*filp,structvm_area_struct*vma){-if(vma->vm_flags&VM_SHARED){-/* do not allow mprotect to make mapping writable */-vm_flags_clear(vma,VM_MAYWRITE);--if(vma->vm_flags&VM_WRITE)-return-EACCES;-}-vm_flags_set(vma,VM_DONTEXPAND|VM_DONTDUMP);vma->vm_ops=&sel_mmap_policy_ops;
There's no need to keep a fault handler around for this, instead map on
mmap.
While we're here, rename area to vma to be consistent.
This correctly makes the mapping a mixed map mapping.
This works towards establishing the invariant that only PFN mapped or mixed
map mappings may clear the VM_MAYWRITE flag. The status page mapping clears
VM_MAYWRITE, so it must be kernel-owned; the control page mapping remains
writable and is left fault-based.
The assumption is made that the struct pcm_mmap_status structure is at most
a page in size, which is asserted as a build bug.
This is safe to assume, as the size of the structure is 56 bytes at most.
Acked-by: Takashi Iwai <redacted>
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
sound/core/pcm_native.c | 38 +++++++++++++-------------------------
1 file changed, 13 insertions(+), 25 deletions(-)
The bpf_map->ops->map_mmap callback invoked by bpf_map_mmap() can be set to
one of ringbuf_map_mmap_kern(), ringbuf_map_mmap_user(), array_map_mmap()
or arena_map_mmap().
It is convention in mm to mark mappings whose pages the kernel manages
itself with VM_MIXEDMAP, so the core mm knows not to treat them as ordinary
page cache or anonymous memory.
The map_mmap callbacks ringbuf_map_mmap_kern() and ringbuf_map_mmap_user()
use remap_vmalloc_range(), which ultimately invokes vm_insert_page() and so
marks the ranges VM_MIXEDMAP, and array_map_mmap() sets VM_MIXEDMAP
explicitly.
However, the exception to this is arena_map_mmap(), which doesn't set the
flag.
This patch corrects this and updates the comment to reflect it.
The pages are refcounted and vm_normal_page() finds them regardless of the
flag, and VM_DONTEXPAND remains set (marking the memory as VM_SPECIAL and
thus unmergeable). The one effect is that NUMA balancing now skips these
VMAs, as it already does for the other bpf map mappings, which is the
reason array_map_mmap() gives for setting the flag.
The intent of this patch is to be able to establish the invariant that only
PFN-mapped or mixed map ranges may clear the VM_MAYWRITE flag, as is done
in bpf_map_mmap().
Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
kernel/bpf/arena.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
Rather than referring to VMA flags with uncertain meaning, add a new
predicate that explicitly describes what possession of the VMA_PFNMAP_BIT
or VMA_MIXEDMAP_BIT flags mean, and then refer to that function for
determining VMA mergeability.
Either flag means the contents of the mapping are owned by the kernel,
usually a driver, rather than by the core mm: the memory may be MMIO,
kernel-allocated pages or even ordinary pages the driver maps itself, but
the core must not populate, reclaim, migrate, copy-on-write or merge the
range on its own initiative.
We initially also include VMA_IO_BIT here, as by implication, these must be
kernel-owned. (mlock() also sets VMA_IO_BIT transiently on ordinary VMAs
while locking them, which is addressed later in this series.)
However the intent is to in future remove this, as no mapping should be
marked as an I/O mapping without also being marked with VMA_PFNMAP_BIT.
This forms the basis of further work intended to improve how we express VMA
properties such as this.
Also update the VMA userland tests to reflect the change.
No functional change intended.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
include/linux/mm.h | 56 ++++++++++++++++++++++++++++++++++++++++-
tools/testing/vma/include/dup.h | 29 ++++++++++++++++++++-
2 files changed, 83 insertions(+), 2 deletions(-)
For ordinary files the only way the VMA_MAYWRITE_BIT flag is cleared is if
the underlying file is itself read-only.
This means that mprotect() cannot mark a shared mapping of a read-only file
as read/write, as doing so would violate the read only attribute, and
permit writes.
In general, we do not want file systems to be able to do this for
read/write files.
Doing so would violate fundamental user expectation of file attributes and
likely break userspace.
However, drivers pose a tricky problem here - the /dev/xxx file may be
read/write but provide access to a resource which is fundamentally
read-only.
Therefore we must allow drivers to be able to clear VMA_MAYWRITE_BIT.
To achieve both of these things, restrict this ability to kernel-owned
mappings as identified by vma_flags_is_kernel_owned().
This constrains this ability to drivers which own the mapping's contents,
whether memory-mapped I/O, kernel-allocated pages, or ordinary pages they
map themselves, and so define its semantics.
Every in-tree mmap hook which clears VMA_MAYWRITE_BIT, some twenty sites
across drivers, filesystems and bpf, establishes a kernel-owned mapping,
with usbmon and the ALSA PCM status page converted earlier in this series
to do so.
Note that drivers may, if they do not gate on VMA_SHARED_BIT, be able to
disable MAP_PRIVATE-file-backed mapping CoW semantics.
This is perhaps not always intended, but we retain this capacity to
maintain existing behaviour.
As all drivers which clear VMA_MAYWRITE_BIT establish kernel-owned
mappings, no functional change is intended.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/vma.c | 5 +++++
1 file changed, 5 insertions(+)
@@ -2795,6 +2795,11 @@ static int mmap_validate(unsigned long prev_start, unsigned long prev_end,if(WARN_ON_ONCE(!was_maywrite&&is_maywrite))return-EINVAL;+/* Only kernel-owned mappings may clear VMA_MAYWRITE_BIT. */+if(!vma_flags_is_kernel_owned(curr_flags)&&+WARN_ON_ONCE(was_maywrite&&!is_maywrite))+return-EINVAL;+returnmmap_validate_vma_flags(curr_flags);}
This determines whether a VMA cannot be expanded or merged because what
they mapped was determined to be a set size at mmap time.
This typically refers to kernel-owned mappings, however VMA_DONTEXPAND_BIT
is not reliably set alongside VMA_PFNMAP_BIT or VMA_MIXEDMAP_BIT, so we
must explicitly test for this for now.
We also explicitly test for VMA_PFNMAP_BIT as VMA_DONTEXPAND_BIT may not be
set for VMA_PFNMAP_BIT's despite the one implying the other.
Use this predicate in vma_flags_can_merge() and in check_prep_vma() in the
mremap logic testing to see if mremap() can expand the VMA. The criteria
for khugepaged and MADV_COLLAPSE eligibility in
__thp_vma_allowable_orders() are precisely those for mergeability, so use
vma_can_merge() there (with an expanded comment).
This obviates the need for the VM_NO_KHUGEPAGED mask, so remove it.
Hugetlb VMAs remain excluded from khugepaged as hugetlbfs always sets
VMA_DONTEXPAND_BIT.
Also update the userland VMA tests to reflect the change.
No functional change intended.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
include/linux/mm.h | 39 +++++++++++++++++++++++++++++++++++----
mm/huge_memory.c | 11 +++++++----
mm/mremap.c | 5 ++---
tools/testing/vma/include/dup.h | 16 +++++++++++++++-
4 files changed, 59 insertions(+), 12 deletions(-)
@@ -600,9 +600,6 @@ enum {#define VMA_REMAP_FLAGS mk_vma_flags(VMA_IO_BIT, VMA_PFNMAP_BIT, \VMA_DONTEXPAND_BIT,VMA_DONTDUMP_BIT)-/* This mask prevents VMA from being scanned with khugepaged */-#define VM_NO_KHUGEPAGED (VM_SPECIAL | VM_HUGETLB)-/* This mask defines which mm->def_flags a process can inherit its parent */#define VM_INIT_DEF_MASK VM_NOHUGEPAGE
Move from the deprecated mmap hook to the new mmap_prepare hook.
We are mapping kernel pages here, so use the discontiguous kernel mapping
mmap action to do so.
Unwind the rather confusing loop and instead map as many pages as we can at
one time.
Note that we do not need to pay attention to rsv_schp->k_use_sg here, as
the pages are populated for the length of the buffer at
rsv_schp->page_order granularity as compound pages.
The discontiguous kernel page mapping logic handles the compound pages for
us.
sfp->mmap_called keeps the buffer stable for us. As before it is never
cleared, so a failed mmap also leaves it set.
We also remove some useless vma, vma->vm_file NULL checks - these will
always be non-NULL if you reached the mmap hook logic.
We retain log output for consistency, but change what's output on page
mapping to indicate that sg_discontig_get() does the work now.
Note that we drop the VMA_IO_BIT flag for the VMA here. It was never
necessary as we invoke alloc_pages() which gives us refcounted folios that
are fine for GUP to access (VMA_IO_BIT would prevent that).
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
drivers/scsi/sg.c | 115 ++++++++++++++++++++++++------------------------------
1 file changed, 51 insertions(+), 64 deletions(-)
Currently all drivers which use defio allocate system memory. All of them
also set FBINFO_VIRTFB, other than ssd1307fb, however this driver allocates
system RAM, so simply failed to set this flag when it ought to.
This patch sets FBINFO_VIRTFB on ssd1307fb probe, then drops setting VM_IO
in fb_deferred_io_mmap() and instead requires FBINFO_VIRTFB to be set,
erroring out with a kernel warning if not.
The logic requires a page from the driver and since commit 1ecbc7dd2902
("fbdev/deferred-io: Always call get_page() for framebuffer pages") has
always required it to be refcounted, so this was implicitly already the
case.
Finally this patch sets VM_MIXEDMAP, as the logic is mapping
kernel-allocated memory so this is appropriate.
Reviewed-by: Thomas Zimmermann <tzimmermann@suse.de>
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
drivers/video/fbdev/core/fb_defio.c | 6 +++---
drivers/video/fbdev/ssd1307fb.c | 2 ++
2 files changed, 5 insertions(+), 3 deletions(-)
@@ -366,13 +366,13 @@ int fb_deferred_io_mmap(struct fb_info *info, struct vm_area_struct *vma){vma->vm_page_prot=pgprot_decrypted(vma->vm_page_prot);+if(WARN_ON_ONCE(!(info->flags&FBINFO_VIRTFB)))+return-EINVAL;if(!try_module_get(THIS_MODULE))return-EINVAL;vma->vm_ops=&fb_deferred_io_vm_ops;-vm_flags_set(vma,VM_DONTEXPAND|VM_DONTDUMP);-if(!(info->flags&FBINFO_VIRTFB))-vm_flags_set(vma,VM_IO);+vm_flags_set(vma,VM_MIXEDMAP|VM_DONTEXPAND|VM_DONTDUMP);vma->vm_private_data=info->fbdefio_state;fb_deferred_io_state_get(info->fbdefio_state);/* released in vma->vm_ops->close() */
Use the mmap_prepare in favour of the deprecated mmap hook as part of the
work to convert one to another.
Since this is simply a refcounted kernel page that has been allocated, it
should not be marked VM_IO and should be inserted using the kernel page
insertion mechanism, so convert it to do this instead.
Use the VMA descriptor's private data field as a scratch buffer to store
the page in - this stays valid throughout the kernel page mapping
operation.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
drivers/hsi/clients/cmt_speech.c | 33 +++++++++------------------------
1 file changed, 9 insertions(+), 24 deletions(-)
When populating a VMA range via the aptly named populate_vma_page_range()
an unreadable VMA will always eventually fail with -EFAULT.
That a VMA is accessible is always checked, however VMA_MAYREAD_BIT is not.
All user mappings always have VMA_MAYREAD_BIT set, so this check only
impacts kernel mappings.
It is implemented specifically to disallow population of uprobes XOL
mappings which are exec-only.
A nasty interaction with these mappings may occur if they are mlocked, so
actively disallow this early.
This allows a subsequent commit to remove the VM_IO check in
__mm_populate() which otherwise requires non-MMIO mappings to be wrongly
flagged simply as a workaround.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/gup.c | 4 ++++
1 file changed, 4 insertions(+)
@@ -1836,6 +1836,10 @@ long populate_vma_page_range(struct vm_area_struct *vma,if(!vma_is_accessible(vma))return-EFAULT;+/* Unreadable VMAs also cannot be faulted in. */+if(!vma_test(vma,VMA_MAYREAD_BIT))+return-EFAULT;+gup_flags=FOLL_TOUCH;/**Wewanttotouchwritablemappingswithawritefaultinorder
These are not MMIO pages so VMA_IO_BIT is an inappropriate flag to set.
Instead, set them VMA_MIXEDMAP_BIT as they are kernel mappings and this is
the appropriate flag to set for those.
This provides the semantics required - no VMA merging is permitted, but
does not prevent GUP.
However this has no meaningful impact as these are refcounted and thus can
be GUPed.
A previous commit already prevented __mm_populate() from being invoked on
XOL areas, which VMA_IO_BIT was previously relied upon to do, so that is no
longer required.
Both VMAs set a VMA name, so always_dump_vma() returns true before
vma_dump_size() reaches its VMA_IO_BIT check, and thus there is no change
in core dump behaviour.
Change this for both the core xol_add_vma() function and the x86-specific
get_uprobe_trampoline() function.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
arch/x86/kernel/uprobes.c | 2 +-
kernel/events/uprobes.c | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
Currently there's a confusing mess around VMA_LOCKED_BIT and
VMA_LOCKONFAULT_BIT.
It is permitted for drivers to set any flags they like, with the VMA
already possessing lock flags.
This results in the absurd situation of a VMA possessing both
VMA_SPECIAL_FLAGS and VMA_LOCKED_MASK flags, which is not permitted.
This has resulted in mlock_vma_folio() having a very silly check for this
scenario to work around it.
There is no need for this - just clear the flags before invoking the hook
and reinstate them afterwards if they are required.
Nothing relies upon this being set during the mmap operation.
mmap_prepare is unaffected by this so requires no fix.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/internal.h | 9 +--------
mm/vma.c | 14 ++++++++++++++
2 files changed, 15 insertions(+), 8 deletions(-)
@@ -2607,6 +2607,11 @@ static int __mmap_new_file_vma(struct mmap_state *map,if(!map->vm_file->f_op->mmap)return0;+/*+*Driver-specifiedflagsmaymakethelockflagsinvalid,soclear+*VMA_LOCKED_MASKandreinstateitafterwardsifappropriate.+*/+vma_clear_flags_mask(vma,VMA_LOCKED_MASK);error=mmap_file(vma->vm_file,vma);map->vm_file=vma->vm_file;
@@ -2623,6 +2628,15 @@ static int __mmap_new_file_vma(struct mmap_state *map,returnerror;}+/* If VMA flags still valid for locked mask, reinstate. */+if(vma_supports_mlock(vma)){+constvma_flags_tmask=+vma_flags_and_mask(&map->vma_flags,+VMA_LOCKED_MASK);++vma_set_flags_mask(vma,mask);+}+map->vma_flags=vma->flags;return0;
When performing mlock() or munlock() otherwise normal VMAs have VMA_IO_BIT
solely to fix a race with migration which might otherwise double-count
mlock VMAs.
This is unnecessary - at the point of applying folio mlock state, whether
setting or clearing PG_mlocked, we know whether or not we are locking.
Solve this in two ways - thread a boolean through the page table walk
indicating whether a lock or unlock is being performed, and run a locking
walk with VMA_LOCKONFAULT_BIT set and VMA_LOCKED_BIT cleared.
This state never occurs otherwise, as VMA_LOCKONFAULT_BIT always implies
VMA_LOCKED_BIT. These are also always cleared together.
Then, update folio_add_lru_vma() and mlock_folio() to check only for
VMA_LOCKED_BIT, and update try_to_unmap_one() to check for VMA_LOCKED_MASK
instead.
Also remove the useless invocation of allow_mlock_munlock() which simply
returns true if unlocking and instead rename it to allow_mlock() and only
call it when locking.
Finally, with the other mlock abuse of VMA_IO_BIT addressed, update
mlock_vma_folio() and folio_add_lru_vma() to simply test for
VMA_LOCKED_BIT. munlock_vma_folio() tests VMA_LOCKED_MASK instead, as an
unmap racing with the locking walk must still munlock folios the walk has
already counted.
While here, also replace some deprecated VMA flag predicates.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/folio.c | 2 +-
mm/internal.h | 10 +++++++---
mm/mlock.c | 51 +++++++++++++++++++--------------------------------
mm/rmap.c | 4 +++-
4 files changed, 30 insertions(+), 37 deletions(-)
@@ -316,22 +316,10 @@ static inline unsigned int folio_mlock_step(struct folio *folio,returnfolio_pte_batch(folio,pte,ptent,count);}-staticinlineboolallow_mlock_munlock(structfolio*folio,+staticinlineboolallow_mlock(structfolio*folio,structvm_area_struct*vma,unsignedlongstart,unsignedlongend,unsignedintstep){-/*-*Forunlock,allowmunlocklargefoliowhichispartially-*mappedtoVMA.Asit'spossiblethatlargefoliois-*mlockedandVMAissplitlater.-*-*Duringmemorypressure,suchkindoflargefoliocan-*besplit.AndthepagesarenotinVM_LOCKedVMA-*canbereclaimed.-*/-if(!vma_test(vma,VMA_LOCKED_BIT))-returntrue;-/* folio_within_range() cannot take KSM, but any small folio is OK */if(!folio_test_large(folio))returntrue;
@@ -352,6 +340,7 @@ static int mlock_pte_range(pmd_t *pmd, unsigned long addr,{structvm_area_struct*vma=walk->vma;+constboollock=walk->private;spinlock_t*ptl;pte_t*start_pte,*pte;pte_tptent;
@@ -368,7 +357,7 @@ static int mlock_pte_range(pmd_t *pmd, unsigned long addr,folio=pmd_folio(*pmd);if(folio_is_zone_device(folio))gotoout;-if(vma_test(vma,VMA_LOCKED_BIT))+if(lock)mlock_folio(folio);elsemunlock_folio(folio);
@@ -390,10 +379,10 @@ static int mlock_pte_range(pmd_t *pmd, unsigned long addr,continue;step=folio_mlock_step(folio,pte,addr,end);-if(!allow_mlock_munlock(folio,vma,start,end,step))+if(lock&&!allow_mlock(folio,vma,start,end,step))gotonext_entry;-if(vma_test(vma,VMA_LOCKED_BIT))+if(lock)mlock_folio(folio);elsemunlock_folio(folio);
It makes no sense for a mapping whose contents the kernel does not own to
specify that the range is MMIO.
Prior to this patch, all in-tree drivers which did so have been updated
such that they are marked as kernel-owned. The check WARNs and fails the
mmap for any out-of-tree driver that still sets VMA_IO_BIT without a kernel
mapping.
No functional change intended for in-tree code.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/vma.c | 6 ++++++
1 file changed, 6 insertions(+)
@@ -2787,6 +2787,12 @@ static int mmap_validate_vma_flags(const vma_flags_t *flags)return-EINVAL;#endif+if(!vma_flags_test_any(flags,VMA_PFNMAP_BIT,VMA_MIXEDMAP_BIT)){+/* Only kernel-owned mappings may set VMA_IO_BIT. */+if(WARN_ON_ONCE(vma_flags_test(flags,VMA_IO_BIT)))+return-EINVAL;+}+return0;}
We have now made it such that every driver which sets VMA_IO_BIT marks it
as kernel-owned.
However, vma_flags_is_kernel_owned() currently checks for VMA_IO_BIT. This
was a product of drivers previously marking a range as kernel-owned by
setting VMA_IO_BIT alone.
Fix this by removing the VMA_IO_BIT check in vma_flags_is_kernel_owned(),
and update mmap_validate_vma_flags() to use vma_flags_is_kernel_owned()
rather than open-coding the VMA_PFNMAP_BIT, VMA_MIXEDMAP_BIT check.
This change means that vma[_flags]_can_merge() doesn't check VMA_IO_BIT any
longer (which is now redundant) as it calls vma_flags_is_kernel_owned().
Now that the predicate means precisely VMA_PFNMAP_BIT or VMA_MIXEDMAP_BIT,
also use it at the other sites which open-code that pair, so the intent is
stated rather than the flags, with no functional change:
zap_special_vma_range() only zaps kernel-owned mappings, as drivers use it
to tear down ranges they established themselves.
The mprotect() arch PFN modification check applies to kernel-owned
mappings, which may map PFNs without struct pages.
NUMA balancing skips VM_MIXEDMAP mappings having already excluded VM_IO
and VM_PFNMAP mappings via vma_migratable(), so it skips exactly the
kernel-owned mappings - say so.
Finally, update the VMA userland merge 'special' flag tests to no longer
assert that VMA_IO_BIT prevents merge as VMA_PFNMAP_BIT, VMA_MIXEDMAP_BIT
now suffices.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
include/linux/mm.h | 3 +--
kernel/sched/fair.c | 2 +-
mm/memory.c | 6 +++---
mm/mprotect.c | 3 +--
mm/vma.c | 2 +-
tools/testing/vma/include/dup.h | 3 +--
tools/testing/vma/tests/merge.c | 10 ++--------
7 files changed, 10 insertions(+), 19 deletions(-)
@@ -2787,7 +2787,7 @@ static int mmap_validate_vma_flags(const vma_flags_t *flags)return-EINVAL;#endif-if(!vma_flags_test_any(flags,VMA_PFNMAP_BIT,VMA_MIXEDMAP_BIT)){+if(!vma_flags_is_kernel_owned(flags)){/* Only kernel-owned mappings may set VMA_IO_BIT. */if(WARN_ON_ONCE(vma_flags_test(flags,VMA_IO_BIT)))return-EINVAL;
@@ -496,17 +496,11 @@ static bool test_vma_merge_special_flags(void).mm=&mm,.vmi=&vmi,};-vma_flag_tspecial_flags[]={VMA_IO_BIT,VMA_DONTEXPAND_BIT,+vma_flag_tspecial_flags[]={VMA_DONTEXPAND_BIT,VMA_PFNMAP_BIT,VMA_MIXEDMAP_BIT};-vma_flags_tall_special_flags=EMPTY_VMA_FLAGS;inti;structvm_area_struct*vma_left,*vma;-/* Make sure there aren't new VM_SPECIAL flags. */-for(i=0;i<ARRAY_SIZE(special_flags);i++)-vma_flags_set(&all_special_flags,special_flags[i]);-ASSERT_FLAGS_SAME_MASK(&all_special_flags,VMA_SPECIAL_FLAGS);-/**01234*AAA
This header really makes little sense - every place it is included mm.h is
also included, and the header itself includes mm.h, so it does nothing to
reduce header size.
It also oddly does an #ifdef around checking VMA_HUGETLB_BIT, however
VMA_HUGETLB_BIT is unconditionally available, and will never be set if
hugetlb is not enabled.
Simply remove the header, eliminate the odd ifdeffery and place the
predicates in mm.h.
The naming of these predicates is odd, but to keep changes separate, we
will address this in a separate patch.
The file was never put into MAINTAINERS so there's no change required
there.
No functional change intended.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
drivers/gpu/drm/drm_gpusvm.c | 2 +-
include/asm-generic/tlb.h | 2 +-
include/linux/hugetlb.h | 1 -
include/linux/hugetlb_inline.h | 28 ----------------------------
include/linux/mm.h | 11 +++++++++++
include/linux/pagemap.h | 1 -
include/linux/userfaultfd_k.h | 1 -
kernel/sched/fair.c | 1 -
mm/vma_internal.h | 1 -
9 files changed, 13 insertions(+), 35 deletions(-)
@@ -18,7 +18,6 @@#include<linux/swap.h>#include<linux/leafops.h>#include<asm-generic/pgtable_uffd.h>-#include<linux/hugetlb_inline.h>/* The set of all possible UFFD-related VM flags. */#define __VM_UFFD_FLAGS (VM_UFFD_MISSING | VM_UFFD_MINOR | \
@@ -627,7 +627,7 @@ void radix__local_flush_tlb_page(struct vm_area_struct *vma, unsigned long vmadd{#ifdef CONFIG_HUGETLB_PAGE/* need the return fix for nohash.c */-if(is_vm_hugetlb_page(vma))+if(vma_is_hugetlb(vma))returnradix__local_flush_hugetlb_page(vma,vmaddr);#endifradix__local_flush_tlb_page_psize(vma->vm_mm,vmaddr,mmu_virtual_psize);
@@ -102,7 +102,7 @@ __context_unsafe(/* pte_unmap_unlock() not instrumented */)/* Find the vm address for the guest address */vma=vma_lookup(mm,vmaddr);-if(!vma||is_vm_hugetlb_page(vma))+if(!vma||vma_is_hugetlb(vma))return;/* Get pointer to the page table entry */
@@ -3015,7 +3015,7 @@ static int pagemap_scan_pte_hole(unsigned long addr, unsigned long end,*hugetlbdiffers,seepagemap_hugetlb_category().*/categories=p->cur_vma_category;-if(userfaultfd_wp(vma)&&!is_vm_hugetlb_page(vma))+if(userfaultfd_wp(vma)&&!vma_is_hugetlb(vma))categories|=PAGE_IS_WRITTEN;if(!pagemap_scan_is_interesting_page(categories,p))
@@ -3028,7 +3028,7 @@ static int pagemap_scan_pte_hole(unsigned long addr, unsigned long end,if(~p->arg.flags&PM_SCAN_WP_MATCHING)returnret;-if(is_vm_hugetlb_page(vma))+if(vma_is_hugetlb(vma))err=pagemap_scan_hugetlb_hole_wp(vma,addr,end);elseerr=uffd_wp_range(vma,addr,end-addr,true);
@@ -3470,7 +3470,7 @@ static int show_numa_map(struct seq_file *m, void *v)seq_puts(m," stack");}-if(is_vm_hugetlb_page(vma))+if(vma_is_hugetlb(vma))seq_puts(m," huge");/* Skip walking pages if gate VMA */
@@ -888,7 +888,7 @@ struct page_vma_mapped_walk {staticinlinevoidpage_vma_mapped_walk_done(structpage_vma_mapped_walk*pvmw){/* HugeTLB pte is set to the relevant page table entry without pte_mapped. */-if(pvmw->pte&&!is_vm_hugetlb_page(pvmw->vma))+if(pvmw->pte&&!vma_is_hugetlb(pvmw->vma))pte_unmap(pvmw->pte);if(pvmw->ptl)spin_unlock(pvmw->ptl);
@@ -480,7 +480,7 @@ void tlb_gather_mmu_vma(struct mmu_gather *tlb, struct vm_area_struct *vma){tlb_gather_mmu(tlb,vma->vm_mm);tlb_update_vma_flags(tlb,vma);-if(is_vm_hugetlb_page(vma))+if(vma_is_hugetlb(vma))/* All entries have the same size. */tlb_change_page_size(tlb,huge_page_size(hstate_vma(vma)));}
@@ -206,7 +206,7 @@ bool page_vma_mapped_walk(struct page_vma_mapped_walk *pvmw)if(pvmw->pmd&&!pvmw->pte)returnnot_found(pvmw);-if(unlikely(is_vm_hugetlb_page(vma))){+if(unlikely(vma_is_hugetlb(vma))){structhstate*hstate=hstate_vma(vma);unsignedlongsize=huge_page_size(hstate);/* The only possible mapping was handled on last iteration */
@@ -408,7 +408,7 @@ static int __walk_page_range(unsigned long start, unsigned long end,interr=0;structvm_area_struct*vma=walk->vma;conststructmm_walk_ops*ops=walk->ops;-boolis_hugetlb=is_vm_hugetlb_page(vma);+boolis_hugetlb=vma_is_hugetlb(vma);/* We do not support hugetlb PTE installation. */if(ops->install_pte&&is_hugetlb)
@@ -2236,7 +2236,7 @@ bool vma_wants_writenotify(struct vm_area_struct *vma, pgprot_t vm_page_prot)*Doweneedtotracksoftdirty?hugetlbdoesnotsupportsoftdirty*trackingyet.*/-if(vma_soft_dirty_enabled(vma)&&!is_vm_hugetlb_page(vma))+if(vma_soft_dirty_enabled(vma)&&!vma_is_hugetlb(vma))returntrue;/* Do we need write faults for uffd-wp tracking? */
@@ -2355,7 +2355,7 @@ int mm_take_all_locks(struct mm_struct *mm)if(signal_pending(current))gotoout_unlock;if(vma->vm_file&&vma->vm_file->f_mapping&&-is_vm_hugetlb_page(vma))+vma_is_hugetlb(vma))vm_lock_mapping(mm,vma->vm_file->f_mapping);}
@@ -2364,7 +2364,7 @@ int mm_take_all_locks(struct mm_struct *mm)if(signal_pending(current))gotoout_unlock;if(vma->vm_file&&vma->vm_file->f_mapping&&-!is_vm_hugetlb_page(vma))+!vma_is_hugetlb(vma))vm_lock_mapping(mm,vma->vm_file->f_mapping);}
@@ -3471,7 +3471,7 @@ static int should_skip_vma(unsigned long start, unsigned long end, struct mm_walif(!vma_is_accessible(vma))returntrue;-if(is_vm_hugetlb_page(vma))+if(vma_is_hugetlb(vma))returntrue;if(!vma_has_recency(vma))
Adjust code which inadvertently perform redundant checks on hugetlb VMAs
and clean them up:
* hugetlb VMAs have VMA_DONTEXPAND_BIT set so a VMA_SPECIAL_FLAGS check
suffices. (migrate_vma_setup() regains an explicit hugetlb test later in
the series, once VMA_SPECIAL_FLAGS is removed.)
* hugetlb VMAs unconditionally set vma->vm_ops, so they are never
anonymous.
* hugetlb VMAs do not set VMA_PFNMAP_BIT so checking for this is redundant.
While we're here also drop a VM_BUG_ON() which the simplified check above
makes unreachable, and use the new VMA flag API.
No functional change intended.
Acked-by: Marc Zyngier <maz@kernel.org>
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
arch/arm64/kvm/mmu.c | 4 +---
drivers/gpu/drm/drm_gpusvm.c | 3 +--
mm/migrate_device.c | 4 ++--
3 files changed, 4 insertions(+), 7 deletions(-)
We currently disallow the installation of lightweight guard regions in VMAs
whose flags intersect VMA_SPECIAL_FLAGS or VMA_HUGETLB_BIT, or
VMA_LOCKED_BIT unless allow_locked is set.
hugetlb VMAs set VMA_DONTEXPAND_BIT so this was already redundant,
VMA_SPECIAL_FLAGS already sufficed.
However, now that VMA_IO_BIT is only set if VMA_PFNMAP or VMA_MIXEDMAP_BIT
is set, this check collapses to being the equivalent of
!vma_can_merge().
Update is_valid_guard_vma() to reflect this.
No functional change intended.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/madvise.c | 20 +++++++++++++-------
1 file changed, 13 insertions(+), 7 deletions(-)
Introduce vma[_flags]_is_persistent() for the purposes of identifying
mappings that are persistent in the sense that bytes to the mapping stay
there, and bytes read from the mapping are the same unless changed by
actions taken by userland.
Kernel-owned mappings do not fall into this category, as their owner may
change the contents without the user having initiated it, and nor of course
does memory-mapped I/O.
We exclude fixed mappings as these are singled out as being unmergeable and
so cannot be guaranteed to persist user data.
hugetlb mappings are fixed mappings, but their contents are entirely the
user's, so they are explicitly carved out as persistent, as the MADV_DODUMP
check already does.
It excludes droppable mappings, which by their nature are ephemeral.
Use this functionality to update the madvise MADV_DODUMP check to test for
persistence rather than open-coding this.
This replaces the VM_SPECIAL check which means it no longer checks for
VMA_IO_BIT, however this is safe as we have established the invariant that
only kernel-owned mappings may set VMA_IO_BIT, so we implicitly include
these.
No functional change intended.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
include/linux/mm.h | 44 ++++++++++++++++++++++++++++++++++++++++++++
mm/madvise.c | 4 ++--
2 files changed, 46 insertions(+), 2 deletions(-)
@@ -1741,6 +1741,50 @@ static inline bool vma_can_merge(const struct vm_area_struct *vma)returnvma_flags_can_merge(&vma->flags);}+/**+*vma_flags_is_persistent()-DothespecifiedVMAflagsimplythattheVMA+*containspersistentdata?+*@flags:TheVMAflagstotest.+*+*Persistentinthesensethat-ifyouwritebytestothemapping-dothey+*staywritten?+*+*Ifthekerneloradevicecouldwritetothememoryindependentlyof+*userland,orthekernelcouldarbitrarilydiscardit,thenitisnot+*persistent.+*+*Returns:trueiftheflagsimplythisVMAispersistent,otherwisefalse.+*/+staticinlineboolvma_flags_is_persistent(constvma_flags_t*flags)+{+/* hugetlb is a fixed mapping, but its contents are the user's own. */+if(vma_flags_is_hugetlb(flags))+returntrue;+/*+*MMIOmappingsmaynotstorewhatiswrittenandmaybechangedbythe+*device.Kernel-ownedandfixedmappingsmaybechangedbytheirowner+*withouttheuserhavinginitiatedit.+*/+if(vma_flags_is_kernel_owned(flags)||+vma_flags_is_fixed_mapping(flags))+returnfalse;+/* Droppable memory is discardable by definition. */+return!vma_flags_test_single_mask(flags,VMA_DROPPABLE);+}++/**+*vma_is_persistent()-DoestheVMAcontainpersistentdata?+*@vma:TheVMAtotest.+*+*Seevma_flags_is_persistent()fordetails.+*+*Returns:trueiftheVMAispersistent,otherwisefalse.+*/+staticinlineboolvma_is_persistent(conststructvm_area_struct*vma)+{+returnvma_flags_is_persistent(&vma->flags);+}+/***vma_kernel_pagesize-DefaultpagesizegranularityforthisVMA.*@vma:Theusermapping.
Rather than directly checking VMA flags, use the newly introduced
vma_is_kernel_owned() and vma_is_persistent() helpers in userfaultfd when
assessing VMA suitability for userfaultfd and UFFDIO_MOVE.
Update vma_move_compatible() so it's expressed in terms of VMA
characteristics rather than arbitrary flags.
Additionally, update the use of the deprecated VMA flag API when checking
VMA_SHADOW_STACK_BIT.
A VMA_IO_BIT check is no longer required but that is fine as a hard
invariant has been established that only kernel-owned mappings may set
VMA_IO_BIT so the check is now redundant.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/userfaultfd.c | 21 +++++++++++++++------
1 file changed, 15 insertions(+), 6 deletions(-)
@@ -1754,10 +1754,18 @@ static inline bool move_splits_huge_pmd(unsigned long dst_addr,}#endif-staticinlineboolvma_move_compatible(structvm_area_struct*vma)+staticinlineboolvma_move_compatible(conststructvm_area_struct*vma){-return!(vma->vm_flags&(VM_PFNMAP|VM_IO|VM_HUGETLB|-VM_MIXEDMAP|VM_SHADOW_STACK));+/* uffd is generally incompatible with kernel-owned mappings. */+if(vma_is_kernel_owned(vma))+returnfalse;+/* The shadow stack should not be written to by userspace. */+if(vma_test_single_mask(vma,VMA_SHADOW_STACK))+returnfalse;+/* hugetlb mappings cannot be safely moved. */+if(vma_is_hugetlb(vma))+returnfalse;+returntrue;}staticintvalidate_move_areas(structuserfaultfd_ctx*ctx,
@@ -2146,10 +2154,11 @@ static bool vma_can_userfault(struct vm_area_struct *vma, vm_flags_t vm_flags,{conststructvm_uffd_ops*ops=vma_uffd_ops(vma);-if(vma->vm_flags&(VM_DROPPABLE|VM_SHADOW_STACK))+/* Non-persistent memory is inherently not controllable by userspace. */+if(!vma_is_persistent(vma))returnfalse;--if(!vma_is_hugetlb(vma)&&(vma->vm_flags&VM_SPECIAL))+/* The shadow stack should not be written to by userspace. */+if(vma_test_single_mask(vma,VMA_SHADOW_STACK))returnfalse;vm_flags&=__VM_UFFD_FLAGS;
Make it clear what we're blocking in MADV_DOFORK. Previously we simply
disallowed VM_SPECIAL i.e. kernel-owned mappings, fixed mappings and
VMA_IO_BIT.
Now the invariant is established that only kernel-owned mappings can set
VMA_IO_BIT, the VMA_IO_BIT check is redundant.
The rest is equivalent to testing for a kernel-owned or fixed mapping,
i.e. exactly the same check as whether the VMA is permitted to be merged.
This was established by commit 0b2758f48f22 ("Require (reasonably) normal
mappings for MADV_DOFORK") containing my hands-down favourite call out of
all time.
Express the same thing differently - if we wouldn't be allowed to merge it,
then we aren't allowed to manipulate CoW behaviour on fork.
No functional change intended.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/madvise.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
It is now an invariant that VMA_IO_BIT is not set except by kernel-owned
mappings, so each existing VMA_SPECIAL_FLAGS test need only test for
VMA_DONTEXPAND_BIT, VMA_PFNMAP_BIT and VMA_MIXEDMAP_BIT.
This is precisely a test for a kernel-owned or fixed mapping.
Update a number of callsites which already explicitly handle hugetlb
mappings.
vma_supports_mlock() and ksm_compatible() also explicitly bail on droppable
mappings - detecting kernel-owned, fixed or droppable mappings is handled
by vma_is_persistent(), so in these cases use this predicate.
should_skip_vma() tests for locked, kernel-owned or fixed memory (having
already excluded hugetlb mappings) so simply test for those there.
No functional change intended.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/internal.h | 4 +---
mm/ksm.c | 4 +---
mm/vmscan.c | 3 ++-
3 files changed, 4 insertions(+), 7 deletions(-)
@@ -3477,7 +3477,8 @@ static int should_skip_vma(unsigned long start, unsigned long end, struct mm_walif(!vma_has_recency(vma))returntrue;-if(vma->vm_flags&(VM_LOCKED|VM_SPECIAL))+if(vma_test(vma,VMA_LOCKED_BIT)||vma_is_kernel_owned(vma)||+vma_is_fixed_mapping(vma))returntrue;if(vma==get_gate_vma(vma->vm_mm))
A kernel-owned or fixed mapping is one which sets VMA_PFNMAP_BIT,
VMA_MIXEDMAP_BIT or VMA_DONTEXPAND_BIT, which is precisely what
VMA_SPECIAL_FLAGS tests for other than VMA_IO_BIT, which is safe to drop as
only kernel-owned mappings may set it.
Using these predicates rather than VMA_SPECIAL_FLAGS makes the check
self-documenting and helps eliminate the confusion around 'special' flags.
No functional change intended.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/vmscan.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
@@ -4418,8 +4418,8 @@ bool lru_gen_look_around(struct page_vma_mapped_walk *pvmw, unsigned int nr)if(spin_is_contended(pvmw->ptl))returntrue;-/* exclude special VMAs containing anon pages from COW */-if(vma->vm_flags&VM_SPECIAL)+/* exclude kernel-owned and fixed VMAs containing anon pages from COW */+if(vma_is_kernel_owned(vma)||vma_is_fixed_mapping(vma))returntrue;/* avoid taking the LRU lock under the PTL when possible */
Now we have the expressive vma_is_kernel_owned() and vma_is_fixed_mapping()
predicates, use them to determine whether to proceed with migration. This
drops the VMA_IO_BIT test, which is safe as only kernel-owned mappings may
set it.
hugetlb mappings remain excluded, as they are fixed mappings.
No functional change intended.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/migrate_device.c | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
Every user of the VM_SPECIAL or VMA_SPECIAL_FLAGS has now been converted to
predicates which explicitly express what is actually being checked for
rather than the nebulous concept of possessing 'special' VMA flags.
In any case 'special' is not so special a term of art in mm - it includes
VDSO/VVAR mappings, special in the sense of vm_normal_folio() and probably
other cases too.
Therefore make things less special by eliminating these now unused flags.
No functional change intended.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
include/linux/mm.h | 8 --------
tools/testing/vma/include/dup.h | 8 --------
2 files changed, 16 deletions(-)
Commit e1fb4a086495 ("dax: remove VM_MIXEDMAP for fsdax and device dax")
prevented fsdax and device-dax from setting VM_MIXEDMAP, as DAX no longer
relies on it to direct core mm paths.
The fuse DAX implementation, added later, copied the old pattern and still
sets it.
Fuse DAX maps pages the same way fsdax does, via dax_iomap_fault() and
ultimately vmf_insert_page_mkwrite() and vmf_insert_folio_pmd(), which
insert ordinary refcounted pages and so do not require VM_MIXEDMAP.
Setting it only serves to mark the mapping as kernel-owned, making fuse DAX
the sole DAX implementation whose mappings are unmergeable, cannot be
mlock()'d, eagerly copy page tables on fork and reject MADV_DOFORK and
MADV_DODUMP.
It also requires vma_is_special_huge() in mm/huge_memory.c to carve DAX out
of its kernel-owned check explicitly.
There is no reason for fuse DAX to keep on using this flag so drop it.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
fs/fuse/dax.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
vma_is_special_huge() tests whether either the VMA_PFNMAP_BIT or
VMA_MIXEDMAP_BIT is set (i.e. whether the VMA is a kernel-owned mapping),
but with a DAX carve-out.
DAX however no longer sets VMA_MIXEDMAP_BIT, so this carve-out is no longer
required.
Therefore test for vma_is_kernel_owned() instead and also drop the
VMA_IO_BIT check, as it is now redundant since it is enforced that only
kernel-owned mappings can set this flag.
This also eliminates another overloaded use of 'special' within mm.
No functional change intended.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/huge_memory.c | 18 ++++--------------
1 file changed, 4 insertions(+), 14 deletions(-)
@@ -110,14 +110,6 @@ static inline bool file_thp_enabled(const struct vm_area_struct *vma)returnS_ISREG(inode->i_mode);}-/* If returns true, we are unable to access the VMA's folios. */-staticboolvma_is_special_huge(conststructvm_area_struct*vma)-{-if(vma_is_dax(vma))-returnfalse;-returnvma_test_any(vma,VMA_PFNMAP_BIT,VMA_MIXEDMAP_BIT);-}-staticboolvma_file_bypass_thp_tuneables(conststructvm_area_struct*vma,enumtva_typetype){
@@ -192,7 +184,7 @@ unsigned long __thp_vma_allowable_orders(struct vm_area_struct *vma,/* Check the intersection of requested and supported orders. */if(vma_is_anonymous(vma))supported_orders=THP_ORDERS_ALL_ANON;-elseif(vma_is_dax(vma)||vma_is_special_huge(vma))+elseif(vma_is_dax(vma)||vma_is_kernel_owned(vma))supported_orders=THP_ORDERS_ALL_SPECIAL_DAX;elsesupported_orders=THP_ORDERS_ALL_FILE_DEFAULT;
@@ -3066,7 +3058,7 @@ int zap_huge_pud(struct mmu_gather *tlb, struct vm_area_struct *vma,orig_pud=pudp_huge_get_and_clear_full(vma,addr,pud,tlb->fullmm);arch_check_zapped_pud(vma,orig_pud);tlb_remove_pud_tlb_entry(tlb,pud,addr);-if(vma_is_special_huge(vma)){+if(vma_is_kernel_owned(vma)){spin_unlock(ptl);/* No zero page support yet */}else{
GUP cannot be used for VMAs which set VMA_IO_BIT - because memory-mapped
I/O must not be accessed on the user's behalf - or VMA_PFNMAP_BIT - because
PFN maps have no folios which the kernel is permitted to access.
Rather than keeping these checks open-coded, abstract them to
vma_flags_can_gup() and its VMA wrapper vma_can_gup().
A number of other places make the same check to decide whether a mapping
can be populated or accessed as GUP would, so update those too.
While here, drop a reference to 'special' and replace a use of the
deprecated VMA flags API in vma_dump_size().
No functional change intended.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
fs/coredump.c | 4 ++--
include/linux/mm.h | 29 +++++++++++++++++++++++++++++
mm/gup.c | 7 +++----
mm/hmm.c | 3 +--
mm/memory.c | 14 ++++++++------
mm/mempolicy.c | 3 ++-
6 files changed, 45 insertions(+), 15 deletions(-)
@@ -1616,8 +1616,8 @@ static unsigned long vma_dump_size(struct vm_area_struct *vma,return0;}-/* Do not dump I/O mapped devices or special mappings */-if(vma->vm_flags&VM_IO)+/* Do not dump memory-mapped I/O, which may have side effects on read. */+if(vma_test(vma,VMA_IO_BIT))return0;/* By default, dump shared memory if mapped from an anonymous file. */
@@ -1204,7 +1204,7 @@ static int check_vma_flags(struct vm_area_struct *vma, unsigned long gup_flags)intforeign=(gup_flags&FOLL_REMOTE);boolvma_anon=vma_is_anonymous(vma);-if(vm_flags&(VM_IO|VM_PFNMAP))+if(!vma_can_gup(vma))return-EFAULT;if((gup_flags&FOLL_ANON)&&!vma_anon)
@@ -1955,7 +1955,7 @@ int __mm_populate(unsigned long start, unsigned long len, int ignore_errors)*rangewiththefirstVMA.Also,skipundesirableVMAtypes.*/nend=min(end,vma->vm_end);-if(vma->vm_flags&(VM_IO|VM_PFNMAP))+if(!vma_can_gup(vma))continue;if(nstart<vma->vm_start)nstart=vma->vm_start;
@@ -2017,8 +2017,7 @@ static long __get_user_pages_locked(struct mm_struct *mm, unsigned long start,break;/* protect what we can, including chardevs */-if((vma->vm_flags&(VM_IO|VM_PFNMAP))||-!(vm_flags&vma->vm_flags))+if(!vma_can_gup(vma)||!(vm_flags&vma->vm_flags))break;if(pages){
@@ -595,8 +595,7 @@ static int hmm_vma_walk_test(unsigned long start, unsigned long end,structhmm_range*range=hmm_vma_walk->range;structvm_area_struct*vma=walk->vma;-if(!(vma->vm_flags&(VM_IO|VM_PFNMAP))&&-vma->vm_flags&VM_READ)+if(vma_can_gup(vma)&&vma_test(vma,VMA_READ_BIT))return0;/*
@@ -7116,7 +7116,8 @@ int follow_pfnmap_start(struct follow_pfnmap_args *args)if(unlikely(address<vma->vm_start||address>=vma->vm_end))gotoout;-if(!(vma->vm_flags&(VM_IO|VM_PFNMAP)))+/* Only mappings GUP cannot handle are followed here. */+if(vma_can_gup(vma))gotoout;retry:pgdp=pgd_offset(mm,address);
@@ -7316,8 +7317,9 @@ static int __access_remote_vm(struct mm_struct *mm, unsigned long addr,}/*-*CheckifthisisaVM_IO|VM_PFNMAPVMA,which-*wecanaccessusingslightlydifferentcode.+*GUPfailed,perhapsbecausethisisamappingit+*cannothandle(seevma_can_gup())-suchmappingsmay+*provideaccessviavm_ops->access()instead.*/bytes=0;#ifdef CONFIG_HAVE_IOREMAP_PROT
The VM_SPECIAL / VMA_SPECIAL_FLAGS mask conflates several unrelated
properties:
* Is this kernel-owned, whether MMIO, kernel-allocated pages, or ordinary
pages a driver maps itself?
* Can it be expanded or merged?
* Is this a 'weird' case like mlock where migration might race and we
'have' to set invalid flags to notify?
* Is it another 'weird' case where we just want to stop GUP from touching
it?
...
This series brings some order to things by both limiting what drivers can
do with VMA flags and switching to using predicates that describe
behaviour, not arbitrary flags.
Thanks, I've updated mm.git's mm-unstable branch to this version.
v3:
* Fixed up bug in patch 1 as reported by Mike - have to delay setting
map->vma_flags until after action prepare, though map->vm_file needs to
be set before for correct reference count management.
* Updated 4/40 to add a symmetric vm_end check as well as vm_start in case
of a dangerously insane driver, as per Sashiko.
* Updated 8/40 to check if a driver did something REALLY stupid like having
a NULL discontig_kernel_page_ops ptr, as per Sashiko.
* Updated 15/40 to trivially synchronise userland test comments.
* Updated 17/40 to correctly duplicate code to the userland VMA tests as
per Sashiko.
@@ -263,9 +264,10 @@ static inline int mmap_file(struct fileif(unlikely(err))returnerr;-err=mmap_hook_validate(prev_start,&prev_flags,vma);+err=mmap_hook_validate(prev_start,prev_end,&prev_flags,vma);if(unlikely(err)){vma->vm_start=prev_start;+vma->vm_end=prev_end;vma_close(vma);}---a/mm/memory.c~b+++a/mm/memory.c
@@ -2653,7 +2653,7 @@ int map_discontig_kernel_pages_prepare(saction->map_kernel_discontig.ops;/* At minimum need to be able to get pages. */-if(WARN_ON_ONCE(!ops->get))+if(WARN_ON_ONCE(!ops||!ops->get))return-EINVAL;__map_kernel_pages_prepare(desc);---a/mm/vma.c~b+++a/mm/vma.c
@@ -2797,15 +2797,15 @@ static int mmap_validate_vma_flags(const}/* Check to ensure a driver hasn't done something crazy. */-staticintmmap_validate(unsignedlongprev_start,-unsignedlongcurr_start,+staticintmmap_validate(unsignedlongprev_start,unsignedlongprev_end,+unsignedlongcurr_start,unsignedlongcurr_end,constvma_flags_t*prev_flags,constvma_flags_t*curr_flags){boolwas_maywrite,is_maywrite;-/* Drivers cannot alter the address of the VMA. */-if(WARN_ON_ONCE(prev_start!=curr_start))+/* Drivers cannot alter the range of the VMA. */+if(WARN_ON_ONCE(prev_start!=curr_start||prev_end!=curr_end))return-EINVAL;was_maywrite=vma_flags_test(prev_flags,VMA_MAYWRITE_BIT);
@@ -2843,7 +2843,8 @@ int mmap_prepare_validate(const struct vWARN_ON_ONCE(desc->action.type!=MMAP_NOTHING))return-EINVAL;-returnmmap_validate(prev_desc->start,desc->start,+returnmmap_validate(prev_desc->start,prev_desc->end,+desc->start,desc->end,&prev_desc->vma_flags,&desc->vma_flags);}
@@ -2851,19 +2852,22 @@ int mmap_prepare_validate(const struct v*mmap_hook_validate()-Ensurethedriverhasn'tviolatedinvariantsin*itsf_op->mmaphook.*@prev_start:Thestartofthemappingpriortothemmaphook.+*@prev_end:Theendofthemappingpriortothemmaphook.*@prev_flags:TheVMAflagssetfortheVMApriortothemmaphook.*@vma:TheVMAafterthehookhasbeenapplied.**Returns:0onsuccess,otherwiseanerror.*/-intmmap_hook_validate(unsignedlongprev_start,+intmmap_hook_validate(unsignedlongprev_start,unsignedlongprev_end,constvma_flags_t*prev_flags,conststructvm_area_struct*vma){constunsignedlongstart=vma->vm_start;+constunsignedlongend=vma->vm_end;constvma_flags_t*flags=&vma->flags;-returnmmap_validate(prev_start,start,prev_flags,flags);+returnmmap_validate(prev_start,prev_end,start,end,prev_flags,+flags);}staticintcall_action_prepare(structmmap_state*map,
@@ -2900,15 +2904,9 @@ static int call_mmap_prepare(struct mmapif(err)returnerr;-/* Update fields permitted to be changed. */-map->pgoff=desc->pgoff;+/* Update first so file refcount tracked correctly. */if(desc->vm_file!=map->vm_file)map->vm_file=desc->vm_file;-map->vma_flags=desc->vma_flags;-map->page_prot=desc->page_prot;-/* User-defined fields. */-map->vm_ops=desc->vm_ops;-map->vm_private_data=desc->private_data;/* It's invalid for mmap_prepare hooks to clear vm_ops. */if(!desc->vm_ops)
@@ -2923,6 +2921,14 @@ static int call_mmap_prepare(struct mmapif(err)returnerr;+/* Update fields permitted to be changed. */+map->pgoff=desc->pgoff;+map->vma_flags=desc->vma_flags;+map->page_prot=desc->page_prot;+/* User-defined fields. */+map->vm_ops=desc->vm_ops;+map->vm_private_data=desc->private_data;+/**MAP_PRIVATE-/dev/zeromappingsareanancientwayofgetting*anonymousmappings.Ratherthanallowingthesemappingstobeodd---a/mm/vma.h~b+++a/mm/vma.h
I know everybody is very busy but I'd appreciate if people could take the time
to have a look at this if possible!
I asked an LLM to look at review replies per week in mm (see below), and
clearly 7.3 is an insane cycle, which I understand.
(And dropping the THP M, remarkably, has not resulted in a drop in review
workload for me).
However, I'd also ask people to perhaps 'give a little back' to those who
are doing a lot of review also :) review can be thankless at the best of
times, but having your own work sit there unreviewed for weeks while you
are working so hard to review others' work is a little much.
Thanks!
┌───────────────────┬──────┬──────┬──────┬──────┬──────┬──────┬───────┐
│ person │ 6.17 │ 6.18 │ 6.19 │ 7.0 │ 7.1 │ 7.2 │ 7.3 │
├───────────────────┼──────┼──────┼──────┼──────┼──────┼──────┼───────┤
│ David Hildenbrand │ 84.8 │ 70.8 │ 44.5 │ 74.7 │ 80.3 │ 85.7 │ 107.0 │
├───────────────────┼──────┼──────┼──────┼──────┼──────┼──────┼───────┤
│ Lorenzo Stoakes │ 50.9 │ 25.1 │ 16.0 │ 45.4 │ 35.2 │ 40.1 │ 52.1 │
├───────────────────┼──────┼──────┼──────┼──────┼──────┼──────┼───────┤
│ Mike Rapoport │ 11.8 │ 17.7 │ 12.8 │ 17.8 │ 30.1 │ 21.0 │ 24.1 │
├───────────────────┼──────┼──────┼──────┼──────┼──────┼──────┼───────┤
│ Zi Yan │ 17.3 │ 12.8 │ 15.0 │ 14.4 │ 10.2 │ 24.0 │ 28.7 │
├───────────────────┼──────┼──────┼──────┼──────┼──────┼──────┼───────┤
│ Vlastimil Babka │ 15.6 │ 19.3 │ 12.5 │ 23.8 │ 17.8 │ 25.2 │ 14.7 │
├───────────────────┼──────┼──────┼──────┼──────┼──────┼──────┼───────┤
│ SeongJae Park │ 16.2 │ 7.7 │ 13.3 │ 23.4 │ 22.8 │ 17.0 │ 19.2 │
├───────────────────┼──────┼──────┼──────┼──────┼──────┼──────┼───────┤
│ Shakeel Butt │ 13.0 │ 5.3 │ 11.7 │ 9.6 │ 7.2 │ 8.2 │ 14.5 │
├───────────────────┼──────┼──────┼──────┼──────┼──────┼──────┼───────┤
│ Harry Yoo │ 10.6 │ 15.0 │ 11.7 │ 15.0 │ 10.9 │ 11.3 │ 6.8 │
├───────────────────┼──────┼──────┼──────┼──────┼──────┼──────┼───────┤
│ Baolin Wang │ 6.0 │ 3.0 │ 4.1 │ 7.1 │ 6.7 │ 6.4 │ 17.4 │
├───────────────────┼──────┼──────┼──────┼──────┼──────┼──────┼───────┤
│ Kiryl Shutsemau │ 5.6 │ 3.3 │ 2.2 │ 3.0 │ 0.9 │ 2.9 │ 16.2 │
└───────────────────┴──────┴──────┴──────┴──────┴──────┴──────┴───────┘
On Thu, Sep 17, 2026 at 9:23 AM Lorenzo Stoakes (ARM) [off-list ref] wrote:
quoted hunk
The map->file_doesnt_need_get flag is confusing and the existing
implementation has holes.
Drivers are permitted to change the owning file of a mapping. If they do
so, they are required to take a reference on that file.
The mmap() operation which ultimately invokes __mmap_region() is guaranteed
to drop the refcount for the original file the mapping was made under, but
this is not true for the replaced file.
This has been addressed so far by tracking map->file_doesnt_need_get, which
is rather poorly named and unfortunately fails to correctly track whether
or not an additional put were needed in a number of cases.
Make life easier by removing this flag, and instead drop the reference for
both mmap_prepare and the deprecated mmap callback in a new function
put_map().
Track whether this needs to be done by aligning mmap_state with
vm_area_desc and store the original file in the map->file field, keeping
the updated file in map->vm_file.
In order to have the same behaviour for both types of hooks, only drop the
reference __mmap_new_file_vma() itself took in its error path, deferring
the replaced file's reference to put_map().
To make this work correctly, map->vm_file has to be updated before any
error handling, so update __mmap_new_file_vma() and call_mmap_prepare() to
set this field first.
Also when mmap_prepare() changes the file and is then merged, the reference
count also must be decremented, so update the logic to call put_map() in
this case too.
Also update __compat_vma_mmap() to manually perform this step for stacked
file systems using the compatibility layer, and update
compat_set_vma_from_desc() to replace vma_set_file() with a correct
refcount/file update.
No in-tree driver is impacted by the incorrect implementation of this
currently (no driver that does this is mergeable for one), so this does not
need to be a fix.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/internal.h | 1 +
mm/util.c | 5 +++-
mm/vma.c | 83 +++++++++++++++++++++++++++++++++++------------------------
mm/vma.h | 6 +++--
4 files changed, 59 insertions(+), 36 deletions(-)
@@ -1228,8 +1228,11 @@ int __compat_vma_mmap(struct vm_area_desc *desc,/* Perform any preparatory tasks for mmap action. */err=mmap_action_prepare(desc);-if(err)+if(err){+if(desc->vm_file!=vma->vm_file)+fput(desc->vm_file);returnerr;+}/* Update the VMA from the descriptor. */compat_set_vma_from_desc(vma,desc);/* Complete any specified mmap actions. */
@@ -24,7 +24,8 @@ struct mmap_state {vm_flags_tvm_flags;vma_flags_tvma_flags;};-structfile*file;+structfile*file;/* mmap()-specified file. */+structfile*vm_file;/* May be updated by mmap_prepare. */
Overall I like the change but I think you could avoid extra churn by
keeping the name of `file` as is and add `struct file *orig_file`
instead as:
+ struct file *orig_file; /* mmap()-specified original file. */
- struct file *file;
+ struct file *file; /* May be updated by mmap_prepare. */
Then many uses of map->file would stay unchanged.
quoted hunk
pgprot_t page_prot;
/* User-defined fields, perhaps updated by .mmap_prepare(). */
@@ -43,8 +44,6 @@ struct mmap_state { /* Determine if we can check KSM flags early in mmap() logic. */ bool check_ksm_early :1;- /* If .mmap_prepare changed the file, we don't need to pin. */- bool file_doesnt_need_get :1; }; #define MMAP_STATE(name, mm_, vmi_, addr_, len_, pgoff_, anon_pgoff_, vma_flags_, file_) \
@@ -2826,7 +2832,7 @@ static int call_mmap_prepare(struct mmap_state *map, * anonymous mappings. Rather than allowing these mappings to be odd * outliers, simply make them truly anonymous. */- if (map_is_private(map) && file_is_dev_zero(map->file))+ if (map_is_private(map) && file_is_dev_zero(map->vm_file)) map_set_anon(map); return 0;
@@ -2845,7 +2851,7 @@ static void set_vma_user_defined_fields(struct vm_area_struct *vma, */ static bool can_set_ksm_flags_early(struct mmap_state *map) {- struct file *file = map->file;+ struct file *file = map->vm_file; /* Anonymous mappings have no driver which can change them. */ if (!file)
+{+ /*+ * An error occurred or the VMA was merged.
It's a bit weird that the function explains when it is being used.
Having an appropriate comment at the call site seems better to me.
quoted hunk
+ *+ * If the file was changed by the driver (which is required to increment+ * the replacement file's reference count), drop its reference count.+ *+ * On error, the caller always drops the original file regardless.+ */+ if (map->vm_file && !map_same_file(map))+ fput(map->vm_file);+}+ static unsigned long __mmap_region(struct file *file, unsigned long addr, unsigned long len, vma_flags_t vma_flags, unsigned long pgoff, struct list_head *uf)
@@ -2922,7 +2942,10 @@ static unsigned long __mmap_region(struct file *file, unsigned long addr, __mmap_complete(&map, vma);- if (have_mmap_prepare && allocated_new) {+ if (!allocated_new) {+ /* Merged, so need to drop refcount. */+ put_map(&map);+ } else if (have_mmap_prepare) { error = mmap_action_complete(vma, &desc.action, /*is_compat=*/false); if (error)
@@ -2936,13 +2959,7 @@ static unsigned long __mmap_region(struct file *file, unsigned long addr, if (map.charged) vm_unacct_memory(map.charged); abort_munmap:- /*- * This indicates that .mmap_prepare has set a new file, differing from- * desc->vm_file. But since we're aborting the operation, only the- * original file will be cleaned up. Ensure we clean up both.- */- if (map.file_doesnt_need_get)- fput(map.file);+ put_map(&map); vms_abort_munmap_vmas(&map.vms, &map.mas_detach); return error; }
On Thu, Sep 17, 2026 at 9:24 AM Lorenzo Stoakes (ARM) [off-list ref] wrote:
It only makes sense to manipulate VMA fields if we allocated a new VMA,
rather than merged it.
VMA merging does not compare vm_ops or vm_private_data, so a merged VMA
keeps its own, which is also what the legacy f_op->mmap path does since it
never touches an existing VMA. Previously set_vma_user_defined_fields()
overwrote the merged VMA's fields with those set for the new mapping. In
practice these are the same values, with rare exceptions such as shmem
selecting vm_ops based on whether the file has been unlinked, so no
user-visible change is expected.
Make this dependency explicit, and additionally constify have_mmap_prepare
while we're here.
The fact that we might be overriding attributes of an existing VMA
that we merged with is technically a bug even if we never hit it,
right? If so, should we have:
Fixes: c84bf6dd2b83 ("mm: introduce new .mmap_prepare() file callback")
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
On Wed, Sep 23, 2026 at 08:21:58AM -0700, Suren Baghdasaryan wrote:
On Thu, Sep 17, 2026 at 9:23 AM Lorenzo Stoakes (ARM) [off-list ref] wrote:
quoted
The map->file_doesnt_need_get flag is confusing and the existing
implementation has holes.
Drivers are permitted to change the owning file of a mapping. If they do
so, they are required to take a reference on that file.
The mmap() operation which ultimately invokes __mmap_region() is guaranteed
to drop the refcount for the original file the mapping was made under, but
this is not true for the replaced file.
This has been addressed so far by tracking map->file_doesnt_need_get, which
is rather poorly named and unfortunately fails to correctly track whether
or not an additional put were needed in a number of cases.
Make life easier by removing this flag, and instead drop the reference for
both mmap_prepare and the deprecated mmap callback in a new function
put_map().
Track whether this needs to be done by aligning mmap_state with
vm_area_desc and store the original file in the map->file field, keeping
the updated file in map->vm_file.
In order to have the same behaviour for both types of hooks, only drop the
reference __mmap_new_file_vma() itself took in its error path, deferring
the replaced file's reference to put_map().
To make this work correctly, map->vm_file has to be updated before any
error handling, so update __mmap_new_file_vma() and call_mmap_prepare() to
set this field first.
Also when mmap_prepare() changes the file and is then merged, the reference
count also must be decremented, so update the logic to call put_map() in
this case too.
Also update __compat_vma_mmap() to manually perform this step for stacked
file systems using the compatibility layer, and update
compat_set_vma_from_desc() to replace vma_set_file() with a correct
refcount/file update.
No in-tree driver is impacted by the incorrect implementation of this
currently (no driver that does this is mergeable for one), so this does not
need to be a fix.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/internal.h | 1 +
mm/util.c | 5 +++-
mm/vma.c | 83 +++++++++++++++++++++++++++++++++++------------------------
mm/vma.h | 6 +++--
4 files changed, 59 insertions(+), 36 deletions(-)
@@ -1228,8 +1228,11 @@ int __compat_vma_mmap(struct vm_area_desc *desc,/* Perform any preparatory tasks for mmap action. */err=mmap_action_prepare(desc);-if(err)+if(err){+if(desc->vm_file!=vma->vm_file)+fput(desc->vm_file);returnerr;+}/* Update the VMA from the descriptor. */compat_set_vma_from_desc(vma,desc);/* Complete any specified mmap actions. */
@@ -24,7 +24,8 @@ struct mmap_state {vm_flags_tvm_flags;vma_flags_tvma_flags;};-structfile*file;+structfile*file;/* mmap()-specified file. */+structfile*vm_file;/* May be updated by mmap_prepare. */
Overall I like the change but I think you could avoid extra churn by
keeping the name of `file` as is and add `struct file *orig_file`
instead as:
+ struct file *orig_file; /* mmap()-specified original file. */
- struct file *file;
+ struct file *file; /* May be updated by mmap_prepare. */
Then many uses of map->file would stay unchanged.
I didn't actually want to do the churny version of this but the problem
with doing something else is that would then contradicts what's in
vm_area_desc:
struct vm_area_desc {
/* Immutable state. */
...
struct file *file; /* May vary from vm_file in stacked callers. */
...
/* Mutable fields. Populated with initial state. */
...
struct file *vm_file;
...
};
And suddenly what is 'file' there (inherited file) is the modifiable one
here and it's more confusing.
As per the commit msg:
Track whether this needs to be done by aligning mmap_state with
vm_area_desc and store the original file in the map->file field, keeping
the updated file in map->vm_file.
But maybe I need to make that decision clearer there?
quoted
pgprot_t page_prot;
/* User-defined fields, perhaps updated by .mmap_prepare(). */
@@ -43,8 +44,6 @@ struct mmap_state { /* Determine if we can check KSM flags early in mmap() logic. */ bool check_ksm_early :1;- /* If .mmap_prepare changed the file, we don't need to pin. */- bool file_doesnt_need_get :1; }; #define MMAP_STATE(name, mm_, vmi_, addr_, len_, pgoff_, anon_pgoff_, vma_flags_, file_) \
@@ -2826,7 +2832,7 @@ static int call_mmap_prepare(struct mmap_state *map, * anonymous mappings. Rather than allowing these mappings to be odd * outliers, simply make them truly anonymous. */- if (map_is_private(map) && file_is_dev_zero(map->file))+ if (map_is_private(map) && file_is_dev_zero(map->vm_file)) map_set_anon(map); return 0;
@@ -2845,7 +2851,7 @@ static void set_vma_user_defined_fields(struct vm_area_struct *vma, */ static bool can_set_ksm_flags_early(struct mmap_state *map) {- struct file *file = map->file;+ struct file *file = map->vm_file; /* Anonymous mappings have no driver which can change them. */ if (!file)
+{+ /*+ * An error occurred or the VMA was merged.
It's a bit weird that the function explains when it is being used.
Having an appropriate comment at the call site seems better to me.
Ack will change.
quoted
+ *+ * If the file was changed by the driver (which is required to increment+ * the replacement file's reference count), drop its reference count.+ *+ * On error, the caller always drops the original file regardless.+ */+ if (map->vm_file && !map_same_file(map))+ fput(map->vm_file);+}+ static unsigned long __mmap_region(struct file *file, unsigned long addr, unsigned long len, vma_flags_t vma_flags, unsigned long pgoff, struct list_head *uf)
@@ -2922,7 +2942,10 @@ static unsigned long __mmap_region(struct file *file, unsigned long addr, __mmap_complete(&map, vma);- if (have_mmap_prepare && allocated_new) {+ if (!allocated_new) {+ /* Merged, so need to drop refcount. */+ put_map(&map);+ } else if (have_mmap_prepare) { error = mmap_action_complete(vma, &desc.action, /*is_compat=*/false); if (error)
@@ -2936,13 +2959,7 @@ static unsigned long __mmap_region(struct file *file, unsigned long addr, if (map.charged) vm_unacct_memory(map.charged); abort_munmap:- /*- * This indicates that .mmap_prepare has set a new file, differing from- * desc->vm_file. But since we're aborting the operation, only the- * original file will be cleaned up. Ensure we clean up both.- */- if (map.file_doesnt_need_get)- fput(map.file);+ put_map(&map); vms_abort_munmap_vmas(&map.vms, &map.mas_detach); return error; }
On Wed, Sep 23, 2026 at 08:32:43AM -0700, Suren Baghdasaryan wrote:
On Thu, Sep 17, 2026 at 9:24 AM Lorenzo Stoakes (ARM) [off-list ref] wrote:
quoted
It only makes sense to manipulate VMA fields if we allocated a new VMA,
rather than merged it.
VMA merging does not compare vm_ops or vm_private_data, so a merged VMA
keeps its own, which is also what the legacy f_op->mmap path does since it
never touches an existing VMA. Previously set_vma_user_defined_fields()
overwrote the merged VMA's fields with those set for the new mapping. In
practice these are the same values, with rare exceptions such as shmem
selecting vm_ops based on whether the file has been unlinked, so no
user-visible change is expected.
Make this dependency explicit, and additionally constify have_mmap_prepare
while we're here.
The fact that we might be overriding attributes of an existing VMA
that we merged with is technically a bug even if we never hit it,
right? If so, should we have:
Fixes: c84bf6dd2b83 ("mm: introduce new .mmap_prepare() file callback")
It's not a bug, it is an in-built assumption that the state used to assess
mergeability implies the same properties.
And if it was, it'd need fixing a different way (check the field for instance)
and would apply to the legacy mmap hook also.
This change is needed for the series though.
quoted
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
On Wed, Sep 23, 2026 at 8:47 AM Lorenzo Stoakes (ARM) [off-list ref] wrote:
On Wed, Sep 23, 2026 at 08:21:58AM -0700, Suren Baghdasaryan wrote:
quoted
On Thu, Sep 17, 2026 at 9:23 AM Lorenzo Stoakes (ARM) [off-list ref] wrote:
quoted
The map->file_doesnt_need_get flag is confusing and the existing
implementation has holes.
Drivers are permitted to change the owning file of a mapping. If they do
so, they are required to take a reference on that file.
The mmap() operation which ultimately invokes __mmap_region() is guaranteed
to drop the refcount for the original file the mapping was made under, but
this is not true for the replaced file.
This has been addressed so far by tracking map->file_doesnt_need_get, which
is rather poorly named and unfortunately fails to correctly track whether
or not an additional put were needed in a number of cases.
Make life easier by removing this flag, and instead drop the reference for
both mmap_prepare and the deprecated mmap callback in a new function
put_map().
Track whether this needs to be done by aligning mmap_state with
vm_area_desc and store the original file in the map->file field, keeping
the updated file in map->vm_file.
In order to have the same behaviour for both types of hooks, only drop the
reference __mmap_new_file_vma() itself took in its error path, deferring
the replaced file's reference to put_map().
To make this work correctly, map->vm_file has to be updated before any
error handling, so update __mmap_new_file_vma() and call_mmap_prepare() to
set this field first.
Also when mmap_prepare() changes the file and is then merged, the reference
count also must be decremented, so update the logic to call put_map() in
this case too.
Also update __compat_vma_mmap() to manually perform this step for stacked
file systems using the compatibility layer, and update
compat_set_vma_from_desc() to replace vma_set_file() with a correct
refcount/file update.
No in-tree driver is impacted by the incorrect implementation of this
currently (no driver that does this is mergeable for one), so this does not
need to be a fix.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/internal.h | 1 +
mm/util.c | 5 +++-
mm/vma.c | 83 +++++++++++++++++++++++++++++++++++------------------------
mm/vma.h | 6 +++--
4 files changed, 59 insertions(+), 36 deletions(-)
@@ -1228,8 +1228,11 @@ int __compat_vma_mmap(struct vm_area_desc *desc,/* Perform any preparatory tasks for mmap action. */err=mmap_action_prepare(desc);-if(err)+if(err){+if(desc->vm_file!=vma->vm_file)+fput(desc->vm_file);returnerr;+}/* Update the VMA from the descriptor. */compat_set_vma_from_desc(vma,desc);/* Complete any specified mmap actions. */
@@ -24,7 +24,8 @@ struct mmap_state {vm_flags_tvm_flags;vma_flags_tvma_flags;};-structfile*file;+structfile*file;/* mmap()-specified file. */+structfile*vm_file;/* May be updated by mmap_prepare. */
Overall I like the change but I think you could avoid extra churn by
keeping the name of `file` as is and add `struct file *orig_file`
instead as:
+ struct file *orig_file; /* mmap()-specified original file. */
- struct file *file;
+ struct file *file; /* May be updated by mmap_prepare. */
Then many uses of map->file would stay unchanged.
I didn't actually want to do the churny version of this but the problem
with doing something else is that would then contradicts what's in
vm_area_desc:
struct vm_area_desc {
/* Immutable state. */
...
struct file *file; /* May vary from vm_file in stacked callers. */
...
/* Mutable fields. Populated with initial state. */
...
struct file *vm_file;
...
};
And suddenly what is 'file' there (inherited file) is the modifiable one
here and it's more confusing.
As per the commit msg:
Track whether this needs to be done by aligning mmap_state with
vm_area_desc and store the original file in the map->file field, keeping
the updated file in map->vm_file.
But maybe I need to make that decision clearer there?
Ah, I see. The description is clear, I was just too focused on the
churn, I guess.
The reasoning to rename makes sense to me.
quoted
quoted
pgprot_t page_prot;
/* User-defined fields, perhaps updated by .mmap_prepare(). */
@@ -43,8 +44,6 @@ struct mmap_state { /* Determine if we can check KSM flags early in mmap() logic. */ bool check_ksm_early :1;- /* If .mmap_prepare changed the file, we don't need to pin. */- bool file_doesnt_need_get :1; }; #define MMAP_STATE(name, mm_, vmi_, addr_, len_, pgoff_, anon_pgoff_, vma_flags_, file_) \
@@ -2826,7 +2832,7 @@ static int call_mmap_prepare(struct mmap_state *map, * anonymous mappings. Rather than allowing these mappings to be odd * outliers, simply make them truly anonymous. */- if (map_is_private(map) && file_is_dev_zero(map->file))+ if (map_is_private(map) && file_is_dev_zero(map->vm_file)) map_set_anon(map); return 0;
@@ -2845,7 +2851,7 @@ static void set_vma_user_defined_fields(struct vm_area_struct *vma, */ static bool can_set_ksm_flags_early(struct mmap_state *map) {- struct file *file = map->file;+ struct file *file = map->vm_file; /* Anonymous mappings have no driver which can change them. */ if (!file)
+{+ /*+ * An error occurred or the VMA was merged.
It's a bit weird that the function explains when it is being used.
Having an appropriate comment at the call site seems better to me.
Ack will change.
quoted
quoted
+ *+ * If the file was changed by the driver (which is required to increment+ * the replacement file's reference count), drop its reference count.+ *+ * On error, the caller always drops the original file regardless.+ */+ if (map->vm_file && !map_same_file(map))+ fput(map->vm_file);+}+ static unsigned long __mmap_region(struct file *file, unsigned long addr, unsigned long len, vma_flags_t vma_flags, unsigned long pgoff, struct list_head *uf)
@@ -2922,7 +2942,10 @@ static unsigned long __mmap_region(struct file *file, unsigned long addr, __mmap_complete(&map, vma);- if (have_mmap_prepare && allocated_new) {+ if (!allocated_new) {+ /* Merged, so need to drop refcount. */+ put_map(&map);+ } else if (have_mmap_prepare) { error = mmap_action_complete(vma, &desc.action, /*is_compat=*/false); if (error)
@@ -2936,13 +2959,7 @@ static unsigned long __mmap_region(struct file *file, unsigned long addr, if (map.charged) vm_unacct_memory(map.charged); abort_munmap:- /*- * This indicates that .mmap_prepare has set a new file, differing from- * desc->vm_file. But since we're aborting the operation, only the- * original file will be cleaned up. Ensure we clean up both.- */- if (map.file_doesnt_need_get)- fput(map.file);+ put_map(&map); vms_abort_munmap_vmas(&map.vms, &map.mas_detach); return error; }
On Wed, Sep 23, 2026 at 8:54 AM Lorenzo Stoakes (ARM) [off-list ref] wrote:
On Wed, Sep 23, 2026 at 08:32:43AM -0700, Suren Baghdasaryan wrote:
quoted
On Thu, Sep 17, 2026 at 9:24 AM Lorenzo Stoakes (ARM) [off-list ref] wrote:
quoted
It only makes sense to manipulate VMA fields if we allocated a new VMA,
rather than merged it.
VMA merging does not compare vm_ops or vm_private_data, so a merged VMA
keeps its own, which is also what the legacy f_op->mmap path does since it
never touches an existing VMA. Previously set_vma_user_defined_fields()
overwrote the merged VMA's fields with those set for the new mapping. In
practice these are the same values, with rare exceptions such as shmem
selecting vm_ops based on whether the file has been unlinked, so no
user-visible change is expected.
Make this dependency explicit, and additionally constify have_mmap_prepare
while we're here.
The fact that we might be overriding attributes of an existing VMA
that we merged with is technically a bug even if we never hit it,
right? If so, should we have:
Fixes: c84bf6dd2b83 ("mm: introduce new .mmap_prepare() file callback")
It's not a bug, it is an in-built assumption that the state used to assess
mergeability implies the same properties.
Hmm. What prevents two VMAs with different vm_private_data members to
be merged? IIUC is_mergeable_vma() does not check vm_private_data. In
such a case set_vma_user_defined_fields() would override
vm_private_data of an existing VMA, no?
And if it was, it'd need fixing a different way (check the field for instance)
and would apply to the legacy mmap hook also.
This change is needed for the series though.
quoted
quoted
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
On Thu, Sep 17, 2026 at 9:24 AM Lorenzo Stoakes (ARM) [off-list ref] wrote:
Replace the open-coded VMA_SPECIAL_FLAGS check in the VMA merge logic with
two new functions vma_flags_can_merge() and vma_can_merge() and update the
merge logic to use the former.
This abstracts the check and expresses it in terms of the desired behaviour
rather than an arbitrary and confusing VMA flag.
This also lays the groundwork for making further improvements in VMA flag
usage.
Also update the userland VMA tests to reflect the change.
No functional change intended.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
@@ -1152,9 +1153,11 @@ struct vm_area_struct *vma_merge_new_range(struct vma_merge_struct *vmg)vmg->state=VMA_MERGE_NOMERGE;-/* Special VMAs are unmergeable, also if no prev/next. */-if(vma_flags_test_any_mask(&vmg->vma_flags,VMA_SPECIAL_FLAGS)||-(!prev&&!next))+if(!vma_flags_can_merge(&vmg->vma_flags))+returnNULL;++/* VMAs with no prev/next are unmergeable. */+if(!prev&&!next)returnNULL;can_merge_left=can_vma_merge_left(vmg);
On Thu, Sep 17, 2026 at 9:25 AM Lorenzo Stoakes (ARM) [off-list ref] wrote:
When the f_op->mmap_prepare or deprecated f_op->mmap hooks are invoked, the
driver might have done something crazy that is not permitted by the kernel.
Currently we check for three such cases in __mmap_new_file_vma(), but only
if the legacy f_op->mmap hook is used:
* Did sparc ADI result in invalid flags?
* Did the driver alter vma->vm_start?
* Did the driver make a file-backed mapping on a read-only file writable?
Generalise these checks for both mmap_prepare and mmap and apply to all
invocations of mmap_file(), the f_op->mmap and f_op->mmap_prepare handling
in the core VMA code and the mmap_prepare compatibility layer.
Also extend the vm_start check to vm_end also - drivers must not change the
VMA range at all.
We also WARN_ON_ONCE() on these conditions as they are things that should
simply not occur in the kernel and it's important to call it out when it
does.
We invoke mmap_prepare_validate() after mmap_action_prepare(), as mmap
actions often manipulate state in the descriptor thus providing the final
state the VMA will be derived from.
Also call mmap_validate_vma_flags() in insert_vm_struct() to ensure that
special regions which are inserted (such as a VDSO or VVAR) also satisfy
the sanity checks.
This way every VMA established through an mmap hook, whether via mmap() or
the compatibility layer, or inserted via insert_vm_struct(), has been
validated. brk() VMAs never pass through a driver hook and so need no such
check.
While we're here, also fixup a couple disjoint blocks of #ifdef CONFIG_MMU.
Finally, update the VMA userland tests to reflect the change.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
nit: Might be just me but when I see prev_XXX in VMA-related code I
picture previous VMA in the address space. Maybe call these orig_XXX?
quoted hunk
+ int err;+ err = vfs_mmap(file, vma); /* * Either we tried to call the file hook for mmap() and an error arose * or a driver set vma->vm_ops = NULL intending there to be no VMA
@@ -239,26 +261,17 @@ static inline int mmap_file(struct file *file, struct vm_area_struct *vma) */ if (unlikely(err || !vma->vm_ops)) vma->vm_ops = &vma_dummy_vm_ops;+ if (unlikely(err))+ return err;- return err;-}--/*- * If the VMA has a close hook then close it, and since closing it might leave- * it in an inconsistent state which makes the use of any hooks suspect, clear- * them down by installing dummy empty hooks.- */-static inline void vma_close(struct vm_area_struct *vma)-{- if (vma->vm_ops && vma->vm_ops->close) {- vma->vm_ops->close(vma);-- /*- * The mapping is in an inconsistent state, and no further hooks- * may be invoked upon it.- */- vma->vm_ops = &vma_dummy_vm_ops;+ err = mmap_hook_validate(prev_start, prev_end, &prev_flags, vma);+ if (unlikely(err)) {+ vma->vm_start = prev_start;+ vma->vm_end = prev_end;+ vma_close(vma); }++ return err; } /* unmap_vmas is in mm/memory.c */
@@ -1224,19 +1224,28 @@ EXPORT_SYMBOL(compat_set_desc_from_vma);int__compat_vma_mmap(structvm_area_desc*desc,structvm_area_struct*vma){+structvm_area_descprev_desc;interr;+/* Derive state prior to mmap_prepare hook. */+compat_set_desc_from_vma(&prev_desc,desc->file,vma);/* Perform any preparatory tasks for mmap action. */err=mmap_action_prepare(desc);-if(err){-if(desc->vm_file!=vma->vm_file)-fput(desc->vm_file);-returnerr;-}+if(err)+gotoerr_put;+/* Check the caller did nothing crazy. */+err=mmap_prepare_validate(&prev_desc,desc);+if(err)+gotoerr_put;/* Update the VMA from the descriptor. */compat_set_vma_from_desc(vma,desc);/* Complete any specified mmap actions. */returnmmap_action_complete(vma,&desc->action,/*is_compat=*/true);++err_put:+if(desc->vm_file!=vma->vm_file)+fput(desc->vm_file);+returnerr;}EXPORT_SYMBOL(__compat_vma_mmap);
@@ -2623,16 +2623,6 @@ static int __mmap_new_file_vma(struct mmap_state *map,returnerror;}-/* Drivers cannot alter the address of the VMA. */-WARN_ON_ONCE(map->addr!=vma->vm_start);-/*-*Driversshouldnotpermitwritabilitywhenpreviouslyitwas-*disallowed.-*/-VM_WARN_ON_ONCE(!vma_flags_same_pair(&map->vma_flags,&vma->flags)&&-!vma_flags_test(&map->vma_flags,VMA_MAYWRITE_BIT)&&-vma_test(vma,VMA_MAYWRITE_BIT));-map->vma_flags=vma->flags;return0;
@@ -2710,11 +2700,6 @@ static int __mmap_new_vma(struct mmap_state *map, struct vm_area_struct **vmap,vma->flags=map->vma_flags;}-#ifdef CONFIG_SPARC64-/* TODO: Fix SPARC ADI! */-WARN_ON_ONCE(!arch_validate_flags(map->vm_flags));-#endif-/* Lock the VMA since it is modified after insertion into VMA tree */vma_start_write(vma);vma_iter_store_new(vmi,vma);
@@ -2777,6 +2762,80 @@ static void __mmap_complete(struct mmap_state *map, struct vm_area_struct *vma)vma_set_page_prot(vma);}+/* Check to ensure that the VMA flags of a newly mapped VMA are sane. */+staticintmmap_validate_vma_flags(constvma_flags_t*flags)+{+#ifdef CONFIG_SPARC64+constvm_flags_tlegacy_flags=vma_flags_to_legacy(*flags);++/* TODO: Fix SPARC ADI! */+if(WARN_ON_ONCE(!arch_validate_flags(legacy_flags)))+return-EINVAL;+#endif++return0;+}++/* Check to ensure a driver hasn't done something crazy. */+staticintmmap_validate(unsignedlongprev_start,unsignedlongprev_end,+unsignedlongcurr_start,unsignedlongcurr_end,+constvma_flags_t*prev_flags,+constvma_flags_t*curr_flags)+{+boolwas_maywrite,is_maywrite;++/* Drivers cannot alter the range of the VMA. */+if(WARN_ON_ONCE(prev_start!=curr_start||prev_end!=curr_end))+return-EINVAL;++was_maywrite=vma_flags_test(prev_flags,VMA_MAYWRITE_BIT);+is_maywrite=vma_flags_test(curr_flags,VMA_MAYWRITE_BIT);++/* A driver may not make a previously unwritable mapping writable. */+if(WARN_ON_ONCE(!was_maywrite&&is_maywrite))+return-EINVAL;++returnmmap_validate_vma_flags(curr_flags);+}++/**+*mmap_prepare_validate()-Ensurethedriverhasn'tviolatedinvariantsinits+*f_op->mmap_preparehook.+*@prev_desc:TheVMAdescriptorpriortothemmap_preparehookbeingcalled.+*@desc:TheVMAdescriptorafterthemmap_preparehookhasbeencalled.+*+*Returns:0onsuccess,otherwiseanerror.+*/+intmmap_prepare_validate(conststructvm_area_desc*prev_desc,+conststructvm_area_desc*desc)+{+returnmmap_validate(prev_desc->start,prev_desc->end,+desc->start,desc->end,+&prev_desc->vma_flags,&desc->vma_flags);+}++/**+*mmap_hook_validate()-Ensurethedriverhasn'tviolatedinvariantsin+*itsf_op->mmaphook.+*@prev_start:Thestartofthemappingpriortothemmaphook.+*@prev_end:Theendofthemappingpriortothemmaphook.+*@prev_flags:TheVMAflagssetfortheVMApriortothemmaphook.+*@vma:TheVMAafterthehookhasbeenapplied.+*+*Returns:0onsuccess,otherwiseanerror.+*/+intmmap_hook_validate(unsignedlongprev_start,unsignedlongprev_end,+constvma_flags_t*prev_flags,+conststructvm_area_struct*vma)+{+constunsignedlongstart=vma->vm_start;+constunsignedlongend=vma->vm_end;+constvma_flags_t*flags=&vma->flags;++returnmmap_validate(prev_start,prev_end,start,end,prev_flags,+flags);+}+staticintcall_action_prepare(structmmap_state*map,structvm_area_desc*desc){
@@ -2803,6 +2862,7 @@ static int call_action_prepare(struct mmap_state *map,staticintcall_mmap_prepare(structmmap_state*map,structvm_area_desc*desc){+conststructvm_area_descprev_desc=*desc;interr;/* Invoke the hook. */
@@ -2822,6 +2882,11 @@ static int call_mmap_prepare(struct mmap_state *map,if(err)returnerr;+/* Check the caller did nothing crazy. */+err=mmap_prepare_validate(&prev_desc,desc);+if(err)+returnerr;+/* Update fields permitted to be changed. */map->pgoff=desc->pgoff;map->vma_flags=desc->vma_flags;
@@ -3457,10 +3522,15 @@ int __vm_munmap(unsigned long start, size_t len, bool unlock)intinsert_vm_struct(structmm_struct*mm,structvm_area_struct*vma){unsignedlongcharged=vma_pages(vma);+interr;if(find_vma_intersection(mm,vma->vm_start,vma->vm_end))return-ENOMEM;+err=mmap_validate_vma_flags(&vma->flags);+if(err)+returnerr;+if(vma_test(vma,VMA_ACCOUNT_BIT)&&security_vm_enough_memory_mm(mm,charged))return-ENOMEM;
@@ -1359,13 +1359,23 @@ static inline int vfs_mmap_prepare(struct file *file, struct vm_area_desc *desc)returnfile->f_op->mmap_prepare(desc);}+intmmap_prepare_validate(conststructvm_area_desc*prev_desc,+conststructvm_area_desc*desc);+staticinlineint__compat_vma_mmap(structvm_area_desc*desc,structvm_area_struct*vma){+structvm_area_descprev_desc;interr;+/* Derive state prior to mmap_prepare hook. */+compat_set_desc_from_vma(&prev_desc,desc->file,vma);/* Perform any preparatory tasks for mmap action. */err=mmap_action_prepare(desc);+if(err)+returnerr;+/* Check the caller did nothing crazy. */+err=mmap_prepare_validate(&prev_desc,desc);if(err)returnerr;/* Update the VMA from the descriptor. */--
On Wed, Sep 23, 2026 at 09:47:20AM -0700, Suren Baghdasaryan wrote:
On Thu, Sep 17, 2026 at 9:25 AM Lorenzo Stoakes (ARM) [off-list ref] wrote:
quoted
When the f_op->mmap_prepare or deprecated f_op->mmap hooks are invoked, the
driver might have done something crazy that is not permitted by the kernel.
Currently we check for three such cases in __mmap_new_file_vma(), but only
if the legacy f_op->mmap hook is used:
* Did sparc ADI result in invalid flags?
* Did the driver alter vma->vm_start?
* Did the driver make a file-backed mapping on a read-only file writable?
Generalise these checks for both mmap_prepare and mmap and apply to all
invocations of mmap_file(), the f_op->mmap and f_op->mmap_prepare handling
in the core VMA code and the mmap_prepare compatibility layer.
Also extend the vm_start check to vm_end also - drivers must not change the
VMA range at all.
We also WARN_ON_ONCE() on these conditions as they are things that should
simply not occur in the kernel and it's important to call it out when it
does.
We invoke mmap_prepare_validate() after mmap_action_prepare(), as mmap
actions often manipulate state in the descriptor thus providing the final
state the VMA will be derived from.
Also call mmap_validate_vma_flags() in insert_vm_struct() to ensure that
special regions which are inserted (such as a VDSO or VVAR) also satisfy
the sanity checks.
This way every VMA established through an mmap hook, whether via mmap() or
the compatibility layer, or inserted via insert_vm_struct(), has been
validated. brk() VMAs never pass through a driver hook and so need no such
check.
While we're here, also fixup a couple disjoint blocks of #ifdef CONFIG_MMU.
Finally, update the VMA userland tests to reflect the change.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
nit: Might be just me but when I see prev_XXX in VMA-related code I
picture previous VMA in the address space. Maybe call these orig_XXX?
Sure, will change.
quoted
+ int err;+ err = vfs_mmap(file, vma); /* * Either we tried to call the file hook for mmap() and an error arose * or a driver set vma->vm_ops = NULL intending there to be no VMA
@@ -239,26 +261,17 @@ static inline int mmap_file(struct file *file, struct vm_area_struct *vma) */ if (unlikely(err || !vma->vm_ops)) vma->vm_ops = &vma_dummy_vm_ops;+ if (unlikely(err))+ return err;- return err;-}--/*- * If the VMA has a close hook then close it, and since closing it might leave- * it in an inconsistent state which makes the use of any hooks suspect, clear- * them down by installing dummy empty hooks.- */-static inline void vma_close(struct vm_area_struct *vma)-{- if (vma->vm_ops && vma->vm_ops->close) {- vma->vm_ops->close(vma);-- /*- * The mapping is in an inconsistent state, and no further hooks- * may be invoked upon it.- */- vma->vm_ops = &vma_dummy_vm_ops;+ err = mmap_hook_validate(prev_start, prev_end, &prev_flags, vma);+ if (unlikely(err)) {+ vma->vm_start = prev_start;+ vma->vm_end = prev_end;+ vma_close(vma); }++ return err; } /* unmap_vmas is in mm/memory.c */
@@ -1224,19 +1224,28 @@ EXPORT_SYMBOL(compat_set_desc_from_vma);int__compat_vma_mmap(structvm_area_desc*desc,structvm_area_struct*vma){+structvm_area_descprev_desc;interr;+/* Derive state prior to mmap_prepare hook. */+compat_set_desc_from_vma(&prev_desc,desc->file,vma);/* Perform any preparatory tasks for mmap action. */err=mmap_action_prepare(desc);-if(err){-if(desc->vm_file!=vma->vm_file)-fput(desc->vm_file);-returnerr;-}+if(err)+gotoerr_put;+/* Check the caller did nothing crazy. */+err=mmap_prepare_validate(&prev_desc,desc);+if(err)+gotoerr_put;/* Update the VMA from the descriptor. */compat_set_vma_from_desc(vma,desc);/* Complete any specified mmap actions. */returnmmap_action_complete(vma,&desc->action,/*is_compat=*/true);++err_put:+if(desc->vm_file!=vma->vm_file)+fput(desc->vm_file);+returnerr;}EXPORT_SYMBOL(__compat_vma_mmap);
@@ -2623,16 +2623,6 @@ static int __mmap_new_file_vma(struct mmap_state *map,returnerror;}-/* Drivers cannot alter the address of the VMA. */-WARN_ON_ONCE(map->addr!=vma->vm_start);-/*-*Driversshouldnotpermitwritabilitywhenpreviouslyitwas-*disallowed.-*/-VM_WARN_ON_ONCE(!vma_flags_same_pair(&map->vma_flags,&vma->flags)&&-!vma_flags_test(&map->vma_flags,VMA_MAYWRITE_BIT)&&-vma_test(vma,VMA_MAYWRITE_BIT));-map->vma_flags=vma->flags;return0;
@@ -2710,11 +2700,6 @@ static int __mmap_new_vma(struct mmap_state *map, struct vm_area_struct **vmap,vma->flags=map->vma_flags;}-#ifdef CONFIG_SPARC64-/* TODO: Fix SPARC ADI! */-WARN_ON_ONCE(!arch_validate_flags(map->vm_flags));-#endif-/* Lock the VMA since it is modified after insertion into VMA tree */vma_start_write(vma);vma_iter_store_new(vmi,vma);
@@ -2777,6 +2762,80 @@ static void __mmap_complete(struct mmap_state *map, struct vm_area_struct *vma)vma_set_page_prot(vma);}+/* Check to ensure that the VMA flags of a newly mapped VMA are sane. */+staticintmmap_validate_vma_flags(constvma_flags_t*flags)+{+#ifdef CONFIG_SPARC64+constvm_flags_tlegacy_flags=vma_flags_to_legacy(*flags);++/* TODO: Fix SPARC ADI! */+if(WARN_ON_ONCE(!arch_validate_flags(legacy_flags)))+return-EINVAL;+#endif++return0;+}++/* Check to ensure a driver hasn't done something crazy. */+staticintmmap_validate(unsignedlongprev_start,unsignedlongprev_end,+unsignedlongcurr_start,unsignedlongcurr_end,+constvma_flags_t*prev_flags,+constvma_flags_t*curr_flags)+{+boolwas_maywrite,is_maywrite;++/* Drivers cannot alter the range of the VMA. */+if(WARN_ON_ONCE(prev_start!=curr_start||prev_end!=curr_end))+return-EINVAL;++was_maywrite=vma_flags_test(prev_flags,VMA_MAYWRITE_BIT);+is_maywrite=vma_flags_test(curr_flags,VMA_MAYWRITE_BIT);++/* A driver may not make a previously unwritable mapping writable. */+if(WARN_ON_ONCE(!was_maywrite&&is_maywrite))+return-EINVAL;++returnmmap_validate_vma_flags(curr_flags);+}++/**+*mmap_prepare_validate()-Ensurethedriverhasn'tviolatedinvariantsinits+*f_op->mmap_preparehook.+*@prev_desc:TheVMAdescriptorpriortothemmap_preparehookbeingcalled.+*@desc:TheVMAdescriptorafterthemmap_preparehookhasbeencalled.+*+*Returns:0onsuccess,otherwiseanerror.+*/+intmmap_prepare_validate(conststructvm_area_desc*prev_desc,+conststructvm_area_desc*desc)+{+returnmmap_validate(prev_desc->start,prev_desc->end,+desc->start,desc->end,+&prev_desc->vma_flags,&desc->vma_flags);+}++/**+*mmap_hook_validate()-Ensurethedriverhasn'tviolatedinvariantsin+*itsf_op->mmaphook.+*@prev_start:Thestartofthemappingpriortothemmaphook.+*@prev_end:Theendofthemappingpriortothemmaphook.+*@prev_flags:TheVMAflagssetfortheVMApriortothemmaphook.+*@vma:TheVMAafterthehookhasbeenapplied.+*+*Returns:0onsuccess,otherwiseanerror.+*/+intmmap_hook_validate(unsignedlongprev_start,unsignedlongprev_end,+constvma_flags_t*prev_flags,+conststructvm_area_struct*vma)+{+constunsignedlongstart=vma->vm_start;+constunsignedlongend=vma->vm_end;+constvma_flags_t*flags=&vma->flags;++returnmmap_validate(prev_start,prev_end,start,end,prev_flags,+flags);+}+staticintcall_action_prepare(structmmap_state*map,structvm_area_desc*desc){
@@ -2803,6 +2862,7 @@ static int call_action_prepare(struct mmap_state *map,staticintcall_mmap_prepare(structmmap_state*map,structvm_area_desc*desc){+conststructvm_area_descprev_desc=*desc;interr;/* Invoke the hook. */
@@ -2822,6 +2882,11 @@ static int call_mmap_prepare(struct mmap_state *map,if(err)returnerr;+/* Check the caller did nothing crazy. */+err=mmap_prepare_validate(&prev_desc,desc);+if(err)+returnerr;+/* Update fields permitted to be changed. */map->pgoff=desc->pgoff;map->vma_flags=desc->vma_flags;
@@ -3457,10 +3522,15 @@ int __vm_munmap(unsigned long start, size_t len, bool unlock)intinsert_vm_struct(structmm_struct*mm,structvm_area_struct*vma){unsignedlongcharged=vma_pages(vma);+interr;if(find_vma_intersection(mm,vma->vm_start,vma->vm_end))return-ENOMEM;+err=mmap_validate_vma_flags(&vma->flags);+if(err)+returnerr;+if(vma_test(vma,VMA_ACCOUNT_BIT)&&security_vm_enough_memory_mm(mm,charged))return-ENOMEM;
@@ -1359,13 +1359,23 @@ static inline int vfs_mmap_prepare(struct file *file, struct vm_area_desc *desc)returnfile->f_op->mmap_prepare(desc);}+intmmap_prepare_validate(conststructvm_area_desc*prev_desc,+conststructvm_area_desc*desc);+staticinlineint__compat_vma_mmap(structvm_area_desc*desc,structvm_area_struct*vma){+structvm_area_descprev_desc;interr;+/* Derive state prior to mmap_prepare hook. */+compat_set_desc_from_vma(&prev_desc,desc->file,vma);/* Perform any preparatory tasks for mmap action. */err=mmap_action_prepare(desc);+if(err)+returnerr;+/* Check the caller did nothing crazy. */+err=mmap_prepare_validate(&prev_desc,desc);if(err)returnerr;/* Update the VMA from the descriptor. */--
On Wed, Sep 23, 2026 at 09:09:38AM -0700, Suren Baghdasaryan wrote:
On Wed, Sep 23, 2026 at 8:54 AM Lorenzo Stoakes (ARM) [off-list ref] wrote:
quoted
On Wed, Sep 23, 2026 at 08:32:43AM -0700, Suren Baghdasaryan wrote:
quoted
On Thu, Sep 17, 2026 at 9:24 AM Lorenzo Stoakes (ARM) [off-list ref] wrote:
quoted
It only makes sense to manipulate VMA fields if we allocated a new VMA,
rather than merged it.
VMA merging does not compare vm_ops or vm_private_data, so a merged VMA
keeps its own, which is also what the legacy f_op->mmap path does since it
never touches an existing VMA. Previously set_vma_user_defined_fields()
overwrote the merged VMA's fields with those set for the new mapping. In
practice these are the same values, with rare exceptions such as shmem
selecting vm_ops based on whether the file has been unlinked, so no
user-visible change is expected.
Make this dependency explicit, and additionally constify have_mmap_prepare
while we're here.
The fact that we might be overriding attributes of an existing VMA
that we merged with is technically a bug even if we never hit it,
right? If so, should we have:
Fixes: c84bf6dd2b83 ("mm: introduce new .mmap_prepare() file callback")
It's not a bug, it is an in-built assumption that the state used to assess
mergeability implies the same properties.
Hmm. What prevents two VMAs with different vm_private_data members to
be merged? IIUC is_mergeable_vma() does not check vm_private_data. In
such a case set_vma_user_defined_fields() would override
vm_private_data of an existing VMA, no?
I think you're right that it should be a fix patch, I'll put it out of the
series and send it as one.
The issue here is more so vm_private_data than vm_ops. And really it's that
vm_ops->mapped() wasn't called so you could have a refcount go to zero and
stay at zero when it shouldn't have been, for instance.
But in general though if you remove mmap_prepare and ask the same question:
What prevents a merge of 2 existing VMAs that were mapped using the
traditional mmap hook which somehow have entirely distinct vm_private_data
and vm_ops but the same file?
The answer is nothing prevents that, but there's an underlying assumption
that this state is fungible for a VMA over a given range given the same
file.
In that case, for anything where an allocation or e.g. refcount change
occurred, then vm_ops->close() will handle the decrement, and the original
VMA's state should suffice.
But here it's a problem because you overwrite it + don't call
vm_ops->mapped()...
quoted
And if it was, it'd need fixing a different way (check the field for instance)
and would apply to the legacy mmap hook also.
This change is needed for the series though.
quoted
quoted
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
From: Zi Yan <ziy@nvidia.com> Date: 2026-09-23 20:06:25
On 17 Sep 2026, at 12:22, Lorenzo Stoakes (ARM) wrote:
quoted hunk
When performing mlock() or munlock() otherwise normal VMAs have VMA_IO_BIT
solely to fix a race with migration which might otherwise double-count
mlock VMAs.
This is unnecessary - at the point of applying folio mlock state, whether
setting or clearing PG_mlocked, we know whether or not we are locking.
Solve this in two ways - thread a boolean through the page table walk
indicating whether a lock or unlock is being performed, and run a locking
walk with VMA_LOCKONFAULT_BIT set and VMA_LOCKED_BIT cleared.
This state never occurs otherwise, as VMA_LOCKONFAULT_BIT always implies
VMA_LOCKED_BIT. These are also always cleared together.
Then, update folio_add_lru_vma() and mlock_folio() to check only for
VMA_LOCKED_BIT, and update try_to_unmap_one() to check for VMA_LOCKED_MASK
instead.
Also remove the useless invocation of allow_mlock_munlock() which simply
returns true if unlocking and instead rename it to allow_mlock() and only
call it when locking.
Finally, with the other mlock abuse of VMA_IO_BIT addressed, update
mlock_vma_folio() and folio_add_lru_vma() to simply test for
VMA_LOCKED_BIT. munlock_vma_folio() tests VMA_LOCKED_MASK instead, as an
unmap racing with the locking walk must still munlock folios the walk has
already counted.
While here, also replace some deprecated VMA flag predicates.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/folio.c | 2 +-
mm/internal.h | 10 +++++++---
mm/mlock.c | 51 +++++++++++++++++++--------------------------------
mm/rmap.c | 4 +++-
4 files changed, 30 insertions(+), 37 deletions(-)
I think it is worth documenting VMA_LOCKONFAULT_BIT alone means mlock in
progress, like you did in munlock_vma_folio(). Just to keep the protocol
explicit for all the readers.
@@ -971,8 +971,7 @@ void mlock_folio(struct folio *folio);staticinlinevoidmlock_vma_folio(structfolio*folio,structvm_area_struct*vma){-/* The VM_IO check prevents migration from double-counting during mlock. */-if(unlikely((vma->vm_flags&(VM_LOCKED|VM_SPECIAL))==VM_LOCKED))+if(vma_test(vma,VMA_LOCKED_BIT))
Ditto.
quoted hunk
mlock_folio(folio);
}
@@ -989,7 +988,12 @@ static inline void munlock_vma_folio(struct folio *folio, * always munlock the folio and page reclaim will correct it * if it's wrong. */- if (unlikely(vma->vm_flags & VM_LOCKED))+ /*+ * VMA_LOCKONFAULT_BIT alone marks an mlock walk in progress, see+ * mlock_vma_pages_range(). An unmap racing with the walk must still+ * munlock folios the walk has already counted.+ */+ if (unlikely(vma_test_any_mask(vma, VMA_LOCKED_MASK))) munlock_folio(folio); }
Why I am commenting in the middle of the series? Because I am taking
a quiz given by LLM based on this series to get myself enough background
knowledge to review this series. This mlock part came up at part E
and I only have part F left before I can do the full review. :)
Best Regards,
Yan, Zi
From: Zi Yan <ziy@nvidia.com> Date: 2026-09-24 02:20:55
On 17 Sep 2026, at 12:22, Lorenzo Stoakes (ARM) wrote:
quoted hunk
The map->file_doesnt_need_get flag is confusing and the existing
implementation has holes.
Drivers are permitted to change the owning file of a mapping. If they do
so, they are required to take a reference on that file.
The mmap() operation which ultimately invokes __mmap_region() is guaranteed
to drop the refcount for the original file the mapping was made under, but
this is not true for the replaced file.
This has been addressed so far by tracking map->file_doesnt_need_get, which
is rather poorly named and unfortunately fails to correctly track whether
or not an additional put were needed in a number of cases.
Make life easier by removing this flag, and instead drop the reference for
both mmap_prepare and the deprecated mmap callback in a new function
put_map().
Track whether this needs to be done by aligning mmap_state with
vm_area_desc and store the original file in the map->file field, keeping
the updated file in map->vm_file.
In order to have the same behaviour for both types of hooks, only drop the
reference __mmap_new_file_vma() itself took in its error path, deferring
the replaced file's reference to put_map().
To make this work correctly, map->vm_file has to be updated before any
error handling, so update __mmap_new_file_vma() and call_mmap_prepare() to
set this field first.
Also when mmap_prepare() changes the file and is then merged, the reference
count also must be decremented, so update the logic to call put_map() in
this case too.
Also update __compat_vma_mmap() to manually perform this step for stacked
file systems using the compatibility layer, and update
compat_set_vma_from_desc() to replace vma_set_file() with a correct
refcount/file update.
No in-tree driver is impacted by the incorrect implementation of this
currently (no driver that does this is mergeable for one), so this does not
need to be a fix.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/internal.h | 1 +
mm/util.c | 5 +++-
mm/vma.c | 83 +++++++++++++++++++++++++++++++++++------------------------
mm/vma.h | 6 +++--
4 files changed, 59 insertions(+), 36 deletions(-)
@@ -1228,8 +1228,11 @@ int __compat_vma_mmap(struct vm_area_desc *desc,/* Perform any preparatory tasks for mmap action. */err=mmap_action_prepare(desc);-if(err)+if(err){+if(desc->vm_file!=vma->vm_file)+fput(desc->vm_file);returnerr;+}/* Update the VMA from the descriptor. */compat_set_vma_from_desc(vma,desc);/* Complete any specified mmap actions. */
IIUC, file is never assigned other than MMAP_STATE() and should not
change after mmap(). Could it be made const to prevent any change?
I assume mmap_state will not need to handle the nesting issue like
vm_area_desc, so file can be const.
quoted hunk
+ struct file *vm_file; /* May be updated by mmap_prepare. */ pgprot_t page_prot; /* User-defined fields, perhaps updated by .mmap_prepare(). */
@@ -43,8 +44,6 @@ struct mmap_state { /* Determine if we can check KSM flags early in mmap() logic. */ bool check_ksm_early :1;- /* If .mmap_prepare changed the file, we don't need to pin. */- bool file_doesnt_need_get :1; }; #define MMAP_STATE(name, mm_, vmi_, addr_, len_, pgoff_, anon_pgoff_, vma_flags_, file_) \
On Thu Sep 17, 2026 at 12:22 PM EDT, Lorenzo Stoakes (ARM) wrote:
It only makes sense to manipulate VMA fields if we allocated a new VMA,
rather than merged it.
VMA merging does not compare vm_ops or vm_private_data, so a merged VMA
keeps its own, which is also what the legacy f_op->mmap path does since it
never touches an existing VMA. Previously set_vma_user_defined_fields()
overwrote the merged VMA's fields with those set for the new mapping. In
practice these are the same values, with rare exceptions such as shmem
selecting vm_ops based on whether the file has been unlinked, so no
user-visible change is expected.
Make this dependency explicit, and additionally constify have_mmap_prepare
while we're here.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/vma.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
Makse sense.
Acked-by: Zi Yan <ziy@nvidia.com>
--
Best Regards,
Yan, Zi
On Thu Sep 17, 2026 at 12:22 PM EDT, Lorenzo Stoakes (ARM) wrote:
Replace the open-coded VMA_SPECIAL_FLAGS check in the VMA merge logic with
two new functions vma_flags_can_merge() and vma_can_merge() and update the
merge logic to use the former.
This abstracts the check and expresses it in terms of the desired behaviour
rather than an arbitrary and confusing VMA flag.
This also lays the groundwork for making further improvements in VMA flag
usage.
Also update the userland VMA tests to reflect the change.
No functional change intended.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
include/linux/mm.h | 21 +++++++++++++++++++++
mm/vma.c | 19 +++++++++++--------
tools/testing/vma/include/dup.h | 5 +++++
3 files changed, 37 insertions(+), 8 deletions(-)
LGTM.
Reviewed-by: Zi Yan <ziy@nvidia.com>
--
Best Regards,
Yan, Zi
On Thu Sep 17, 2026 at 12:22 PM EDT, Lorenzo Stoakes (ARM) wrote:
When the f_op->mmap_prepare or deprecated f_op->mmap hooks are invoked, the
driver might have done something crazy that is not permitted by the kernel.
Currently we check for three such cases in __mmap_new_file_vma(), but only
if the legacy f_op->mmap hook is used:
* Did sparc ADI result in invalid flags?
* Did the driver alter vma->vm_start?
* Did the driver make a file-backed mapping on a read-only file writable?
Generalise these checks for both mmap_prepare and mmap and apply to all
invocations of mmap_file(), the f_op->mmap and f_op->mmap_prepare handling
in the core VMA code and the mmap_prepare compatibility layer.
Also extend the vm_start check to vm_end also - drivers must not change the
VMA range at all.
We also WARN_ON_ONCE() on these conditions as they are things that should
simply not occur in the kernel and it's important to call it out when it
does.
We invoke mmap_prepare_validate() after mmap_action_prepare(), as mmap
actions often manipulate state in the descriptor thus providing the final
state the VMA will be derived from.
Also call mmap_validate_vma_flags() in insert_vm_struct() to ensure that
special regions which are inserted (such as a VDSO or VVAR) also satisfy
the sanity checks.
This way every VMA established through an mmap hook, whether via mmap() or
the compatibility layer, or inserted via insert_vm_struct(), has been
validated. brk() VMAs never pass through a driver hook and so need no such
check.
While we're here, also fixup a couple disjoint blocks of #ifdef CONFIG_MMU.
Finally, update the VMA userland tests to reflect the change.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/internal.h | 51 ++++++++++++--------
mm/util.c | 19 ++++++--
mm/vma.c | 100 ++++++++++++++++++++++++++++++++++------
mm/vma.h | 25 ++++++++--
tools/testing/vma/include/dup.h | 10 ++++
5 files changed, 163 insertions(+), 42 deletions(-)
<snip>
quoted hunk
++/* Check to ensure a driver hasn't done something crazy. */+static int mmap_validate(unsigned long prev_start, unsigned long prev_end,+ unsigned long curr_start, unsigned long curr_end,+ const vma_flags_t *prev_flags,+ const vma_flags_t *curr_flags)+{+ bool was_maywrite, is_maywrite;++ /* Drivers cannot alter the range of the VMA. */+ if (WARN_ON_ONCE(prev_start != curr_start || prev_end != curr_end))+ return -EINVAL;++ was_maywrite = vma_flags_test(prev_flags, VMA_MAYWRITE_BIT);+ is_maywrite = vma_flags_test(curr_flags, VMA_MAYWRITE_BIT);++ /* A driver may not make a previously unwritable mapping writable. */+ if (WARN_ON_ONCE(!was_maywrite && is_maywrite))
Is it driver specific or generally applicable to all mmap(_preppare)
operations? Is the comment too specific?
During my LLM quiz, making memfd write seals writable via a
hypothetically wrong shmem_mmap_prepare() implementation is an example
for this WARN_ON_ONCE. It is not driver related. Let me know if I get it
wrong.
Otherwise, LGTM.
Reviewed-by: Zi Yan <ziy@nvidia.com>
--
Best Regards,
Yan, Zi
On Wed, Sep 23, 2026 at 10:20:43PM -0400, Zi Yan wrote:
On 17 Sep 2026, at 12:22, Lorenzo Stoakes (ARM) wrote:
quoted
The map->file_doesnt_need_get flag is confusing and the existing
implementation has holes.
Drivers are permitted to change the owning file of a mapping. If they do
so, they are required to take a reference on that file.
The mmap() operation which ultimately invokes __mmap_region() is guaranteed
to drop the refcount for the original file the mapping was made under, but
this is not true for the replaced file.
This has been addressed so far by tracking map->file_doesnt_need_get, which
is rather poorly named and unfortunately fails to correctly track whether
or not an additional put were needed in a number of cases.
Make life easier by removing this flag, and instead drop the reference for
both mmap_prepare and the deprecated mmap callback in a new function
put_map().
Track whether this needs to be done by aligning mmap_state with
vm_area_desc and store the original file in the map->file field, keeping
the updated file in map->vm_file.
In order to have the same behaviour for both types of hooks, only drop the
reference __mmap_new_file_vma() itself took in its error path, deferring
the replaced file's reference to put_map().
To make this work correctly, map->vm_file has to be updated before any
error handling, so update __mmap_new_file_vma() and call_mmap_prepare() to
set this field first.
Also when mmap_prepare() changes the file and is then merged, the reference
count also must be decremented, so update the logic to call put_map() in
this case too.
Also update __compat_vma_mmap() to manually perform this step for stacked
file systems using the compatibility layer, and update
compat_set_vma_from_desc() to replace vma_set_file() with a correct
refcount/file update.
No in-tree driver is impacted by the incorrect implementation of this
currently (no driver that does this is mergeable for one), so this does not
need to be a fix.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/internal.h | 1 +
mm/util.c | 5 +++-
mm/vma.c | 83 +++++++++++++++++++++++++++++++++++------------------------
mm/vma.h | 6 +++--
4 files changed, 59 insertions(+), 36 deletions(-)
@@ -1228,8 +1228,11 @@ int __compat_vma_mmap(struct vm_area_desc *desc,/* Perform any preparatory tasks for mmap action. */err=mmap_action_prepare(desc);-if(err)+if(err){+if(desc->vm_file!=vma->vm_file)+fput(desc->vm_file);returnerr;+}/* Update the VMA from the descriptor. */compat_set_vma_from_desc(vma,desc);/* Complete any specified mmap actions. */
IIUC, file is never assigned other than MMAP_STATE() and should not
change after mmap(). Could it be made const to prevent any change?
I assume mmap_state will not need to handle the nesting issue like
vm_area_desc, so file can be const.
Oh I had assumed that this couldn't be const and I thought I'd checked that, but
seems not, it can be :)
Will fix that up for v4 thanks!
quoted
+ struct file *vm_file; /* May be updated by mmap_prepare. */ pgprot_t page_prot; /* User-defined fields, perhaps updated by .mmap_prepare(). */
@@ -43,8 +44,6 @@ struct mmap_state { /* Determine if we can check KSM flags early in mmap() logic. */ bool check_ksm_early :1;- /* If .mmap_prepare changed the file, we don't need to pin. */- bool file_doesnt_need_get :1; }; #define MMAP_STATE(name, mm_, vmi_, addr_, len_, pgoff_, anon_pgoff_, vma_flags_, file_) \
On Wed, Sep 23, 2026 at 10:52:06PM -0400, Zi Yan wrote:
On Thu Sep 17, 2026 at 12:22 PM EDT, Lorenzo Stoakes (ARM) wrote:
quoted
When the f_op->mmap_prepare or deprecated f_op->mmap hooks are invoked, the
driver might have done something crazy that is not permitted by the kernel.
Currently we check for three such cases in __mmap_new_file_vma(), but only
if the legacy f_op->mmap hook is used:
* Did sparc ADI result in invalid flags?
* Did the driver alter vma->vm_start?
* Did the driver make a file-backed mapping on a read-only file writable?
Generalise these checks for both mmap_prepare and mmap and apply to all
invocations of mmap_file(), the f_op->mmap and f_op->mmap_prepare handling
in the core VMA code and the mmap_prepare compatibility layer.
Also extend the vm_start check to vm_end also - drivers must not change the
VMA range at all.
We also WARN_ON_ONCE() on these conditions as they are things that should
simply not occur in the kernel and it's important to call it out when it
does.
We invoke mmap_prepare_validate() after mmap_action_prepare(), as mmap
actions often manipulate state in the descriptor thus providing the final
state the VMA will be derived from.
Also call mmap_validate_vma_flags() in insert_vm_struct() to ensure that
special regions which are inserted (such as a VDSO or VVAR) also satisfy
the sanity checks.
This way every VMA established through an mmap hook, whether via mmap() or
the compatibility layer, or inserted via insert_vm_struct(), has been
validated. brk() VMAs never pass through a driver hook and so need no such
check.
While we're here, also fixup a couple disjoint blocks of #ifdef CONFIG_MMU.
Finally, update the VMA userland tests to reflect the change.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/internal.h | 51 ++++++++++++--------
mm/util.c | 19 ++++++--
mm/vma.c | 100 ++++++++++++++++++++++++++++++++++------
mm/vma.h | 25 ++++++++--
tools/testing/vma/include/dup.h | 10 ++++
5 files changed, 163 insertions(+), 42 deletions(-)
<snip>
quoted
++/* Check to ensure a driver hasn't done something crazy. */+static int mmap_validate(unsigned long prev_start, unsigned long prev_end,+ unsigned long curr_start, unsigned long curr_end,+ const vma_flags_t *prev_flags,+ const vma_flags_t *curr_flags)+{+ bool was_maywrite, is_maywrite;++ /* Drivers cannot alter the range of the VMA. */+ if (WARN_ON_ONCE(prev_start != curr_start || prev_end != curr_end))+ return -EINVAL;++ was_maywrite = vma_flags_test(prev_flags, VMA_MAYWRITE_BIT);+ is_maywrite = vma_flags_test(curr_flags, VMA_MAYWRITE_BIT);++ /* A driver may not make a previously unwritable mapping writable. */+ if (WARN_ON_ONCE(!was_maywrite && is_maywrite))
Is it driver specific or generally applicable to all mmap(_preppare)
operations? Is the comment too specific?
During my LLM quiz, making memfd write seals writable via a
hypothetically wrong shmem_mmap_prepare() implementation is an example
for this WARN_ON_ONCE. It is not driver related. Let me know if I get it
wrong.
Driver is taken to mean anything with an mmap or mmap_prepare hook, like a
general term for that.
If we start getting into calling it different if it's a file system or memfd or
something then it becomes quite hard to talk about it.
And yeah I hate that it's not a good name because driver makes you think
something in drivers/* or an OOT one or something but the kernel makes it vague
:)
Naming is hard...
Otherwise, LGTM.
Reviewed-by: Zi Yan <ziy@nvidia.com>
On Wed, Sep 23, 2026 at 04:06:14PM -0400, Zi Yan wrote:
On 17 Sep 2026, at 12:22, Lorenzo Stoakes (ARM) wrote:
quoted
When performing mlock() or munlock() otherwise normal VMAs have VMA_IO_BIT
solely to fix a race with migration which might otherwise double-count
mlock VMAs.
This is unnecessary - at the point of applying folio mlock state, whether
setting or clearing PG_mlocked, we know whether or not we are locking.
Solve this in two ways - thread a boolean through the page table walk
indicating whether a lock or unlock is being performed, and run a locking
walk with VMA_LOCKONFAULT_BIT set and VMA_LOCKED_BIT cleared.
This state never occurs otherwise, as VMA_LOCKONFAULT_BIT always implies
VMA_LOCKED_BIT. These are also always cleared together.
Then, update folio_add_lru_vma() and mlock_folio() to check only for
VMA_LOCKED_BIT, and update try_to_unmap_one() to check for VMA_LOCKED_MASK
instead.
Also remove the useless invocation of allow_mlock_munlock() which simply
returns true if unlocking and instead rename it to allow_mlock() and only
call it when locking.
Finally, with the other mlock abuse of VMA_IO_BIT addressed, update
mlock_vma_folio() and folio_add_lru_vma() to simply test for
VMA_LOCKED_BIT. munlock_vma_folio() tests VMA_LOCKED_MASK instead, as an
unmap racing with the locking walk must still munlock folios the walk has
already counted.
While here, also replace some deprecated VMA flag predicates.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/folio.c | 2 +-
mm/internal.h | 10 +++++++---
mm/mlock.c | 51 +++++++++++++++++++--------------------------------
mm/rmap.c | 4 +++-
4 files changed, 30 insertions(+), 37 deletions(-)
I think it is worth documenting VMA_LOCKONFAULT_BIT alone means mlock in
progress, like you did in munlock_vma_folio(). Just to keep the protocol
explicit for all the readers.
Well I'm not sure it's necessary here honestly, because this never checked
VMA_LOCKED_MASK anyway, and VMA_LOCKONFAULT_BIT never made a difference.
So the meaning of VMA_LOCKED_BIT here is strictly 'is it locked' and it's
correctly handled.
And I fear that it becomes whack-a-mole - the neat thing about this change is
that you no longer have to special case the stupid VM_SPECIAL thing, and can in
fact do the 'normal' thing of _just checking_ VMA_LOCKED_BIT :)
So I think it's better not to.
@@ -971,8 +971,7 @@ void mlock_folio(struct folio *folio);staticinlinevoidmlock_vma_folio(structfolio*folio,structvm_area_struct*vma){-/* The VM_IO check prevents migration from double-counting during mlock. */-if(unlikely((vma->vm_flags&(VM_LOCKED|VM_SPECIAL))==VM_LOCKED))+if(vma_test(vma,VMA_LOCKED_BIT))
Ditto.
Similar reasoning to above.
quoted
mlock_folio(folio);
}
@@ -989,7 +988,12 @@ static inline void munlock_vma_folio(struct folio *folio, * always munlock the folio and page reclaim will correct it * if it's wrong. */- if (unlikely(vma->vm_flags & VM_LOCKED))+ /*+ * VMA_LOCKONFAULT_BIT alone marks an mlock walk in progress, see+ * mlock_vma_pages_range(). An unmap racing with the walk must still+ * munlock folios the walk has already counted.+ */
Here it's worth mentioning, as it's specifically relying on the new
behaviour. Although it's neatly using the VMA_LOCKED_MASK to handle both the
locked case and the 'being locked' case :)
quoted
+ if (unlikely(vma_test_any_mask(vma, VMA_LOCKED_MASK))) munlock_folio(folio); }
Why I am commenting in the middle of the series? Because I am taking
a quiz given by LLM based on this series to get myself enough background
knowledge to review this series. This mlock part came up at part E
and I only have part F left before I can do the full review. :)
Thanks! :) I really appreciate you taking the time to look at this! Sorry it's
so large.
I held this series back from last cycle to help with review load, then spent
some time fixing various AI-discovered things, and all the patches are necessary
(well for the most part) to get where the series needs to go.
I think the change is worth it though!
From: Zi Yan <ziy@nvidia.com> Date: 2026-09-24 15:50:34
On 24 Sep 2026, at 6:21, Lorenzo Stoakes (ARM) wrote:
On Wed, Sep 23, 2026 at 04:06:14PM -0400, Zi Yan wrote:
quoted
On 17 Sep 2026, at 12:22, Lorenzo Stoakes (ARM) wrote:
quoted
When performing mlock() or munlock() otherwise normal VMAs have VMA_IO_BIT
solely to fix a race with migration which might otherwise double-count
mlock VMAs.
This is unnecessary - at the point of applying folio mlock state, whether
setting or clearing PG_mlocked, we know whether or not we are locking.
Solve this in two ways - thread a boolean through the page table walk
indicating whether a lock or unlock is being performed, and run a locking
walk with VMA_LOCKONFAULT_BIT set and VMA_LOCKED_BIT cleared.
This state never occurs otherwise, as VMA_LOCKONFAULT_BIT always implies
VMA_LOCKED_BIT. These are also always cleared together.
Then, update folio_add_lru_vma() and mlock_folio() to check only for
VMA_LOCKED_BIT, and update try_to_unmap_one() to check for VMA_LOCKED_MASK
instead.
Also remove the useless invocation of allow_mlock_munlock() which simply
returns true if unlocking and instead rename it to allow_mlock() and only
call it when locking.
Finally, with the other mlock abuse of VMA_IO_BIT addressed, update
mlock_vma_folio() and folio_add_lru_vma() to simply test for
VMA_LOCKED_BIT. munlock_vma_folio() tests VMA_LOCKED_MASK instead, as an
unmap racing with the locking walk must still munlock folios the walk has
already counted.
While here, also replace some deprecated VMA flag predicates.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/folio.c | 2 +-
mm/internal.h | 10 +++++++---
mm/mlock.c | 51 +++++++++++++++++++--------------------------------
mm/rmap.c | 4 +++-
4 files changed, 30 insertions(+), 37 deletions(-)
I think it is worth documenting VMA_LOCKONFAULT_BIT alone means mlock in
progress, like you did in munlock_vma_folio(). Just to keep the protocol
explicit for all the readers.
Well I'm not sure it's necessary here honestly, because this never checked
VMA_LOCKED_MASK anyway, and VMA_LOCKONFAULT_BIT never made a difference.
So the meaning of VMA_LOCKED_BIT here is strictly 'is it locked' and it's
correctly handled.
And I fear that it becomes whack-a-mole - the neat thing about this change is
that you no longer have to special case the stupid VM_SPECIAL thing, and can in
fact do the 'normal' thing of _just checking_ VMA_LOCKED_BIT :)
So I think it's better not to.
@@ -971,8 +971,7 @@ void mlock_folio(struct folio *folio);staticinlinevoidmlock_vma_folio(structfolio*folio,structvm_area_struct*vma){-/* The VM_IO check prevents migration from double-counting during mlock. */-if(unlikely((vma->vm_flags&(VM_LOCKED|VM_SPECIAL))==VM_LOCKED))+if(vma_test(vma,VMA_LOCKED_BIT))
Ditto.
Similar reasoning to above.
quoted
quoted
mlock_folio(folio);
}
@@ -989,7 +988,12 @@ static inline void munlock_vma_folio(struct folio *folio, * always munlock the folio and page reclaim will correct it * if it's wrong. */- if (unlikely(vma->vm_flags & VM_LOCKED))+ /*+ * VMA_LOCKONFAULT_BIT alone marks an mlock walk in progress, see+ * mlock_vma_pages_range(). An unmap racing with the walk must still+ * munlock folios the walk has already counted.+ */
Here it's worth mentioning, as it's specifically relying on the new
behaviour. Although it's neatly using the VMA_LOCKED_MASK to handle both the
locked case and the 'being locked' case :)
quoted
quoted
+ if (unlikely(vma_test_any_mask(vma, VMA_LOCKED_MASK))) munlock_folio(folio); }
Why I am commenting in the middle of the series? Because I am taking
a quiz given by LLM based on this series to get myself enough background
knowledge to review this series. This mlock part came up at part E
and I only have part F left before I can do the full review. :)
Thanks! :) I really appreciate you taking the time to look at this! Sorry it's
so large.
Sure. It is great learning material for me. Thank you for the patches.
I held this series back from last cycle to help with review load, then spent
some time fixing various AI-discovered things, and all the patches are necessary
(well for the most part) to get where the series needs to go.
I think the change is worth it though!
Of course, great to see hacky code being removed by this series.
For this patch, feel free to add
Reviewed-by: Zi Yan <ziy@nvidia.com>
Best Regards,
Yan, Zi
On Thu, Sep 17, 2026 at 05:22:12PM +0100, Lorenzo Stoakes (ARM) wrote:
Replace the open-coded VMA_SPECIAL_FLAGS check in the VMA merge logic with
two new functions vma_flags_can_merge() and vma_can_merge() and update the
merge logic to use the former.
This abstracts the check and expresses it in terms of the desired behaviour
rather than an arbitrary and confusing VMA flag.
This also lays the groundwork for making further improvements in VMA flag
usage.
Also update the userland VMA tests to reflect the change.
No functional change intended.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
I indepdeantly validated the sashiko report on this chunk. Seems like
close() should be deferred until after __map_new_file_vma() calls
unmap_region().
suggested fix is to drop vma_close() from mmap_file() and update the
cleanup in __mmap_new_file_vma()
if (error) {
UNMAP_STATE(unmap, vmi, vma, vma->vm_start, vma->vm_end,
map->prev, map->next);
vma_iter_set(vmi, vma->vm_end);
unmap_region(&unmap);
/* Release driver state only after its mappings are gone. */
vma_close(vma);
if (map_same_file(map))
fput(map->vm_file);
vma->vm_file = NULL;
return error;
}
Example race:
Thread A Thread B
mmap(MAP_FIXED, address A)
driver remap_pfn_range(A, page P)
load/store at known address A
hardware finds the new present PTE
validation fails
->close() frees page P
UAF
unmap_region()
TLB shootdown
With that fix
Reviewed-by: Gregory Price (Meta) <gourry@gourry.net>
~Gregory
On Thu, Sep 17, 2026 at 05:22:14PM +0100, Lorenzo Stoakes (ARM) wrote:
quoted hunk
When a user requests an mmap_action be performed in mmap_prepare, this
involves populating the VMA range with data.
However, if the VMA is mergeable, it might then mistakenly be merged with
another VMA without having populated the range.
Every mmap action currently available sets VMA flags such that the VMA
cannot be merged.
However, to ensure that no future mmap action falls foul of this, assert
that this is the case upon mmap_prepare validation.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/vma.c | 9 +++++++++
1 file changed, 9 insertions(+)
@@ -2809,6 +2809,15 @@ static int mmap_validate(unsigned long prev_start, unsigned long prev_end,intmmap_prepare_validate(conststructvm_area_desc*prev_desc,conststructvm_area_desc*desc){+/*+*ItisnotvalidtoexecutemmapactionsforVMAswhichcanbemerged,+*asanysuchmergewouldleaveportionsofthemappingincorrectly+*unmapped.+*/+if(vma_flags_can_merge(&desc->vma_flags)&&+WARN_ON_ONCE(desc->action.type!=MMAP_NOTHING))+return-EINVAL;+
If you wanted to make this unit-testable, you could pull it out into a
separate function:
static bool mmap_action_is_valid(const struct vm_area_desc *desc)
{
return desc->action.type == MMAP_NOTHING ||
!vma_flags_can_merge(&desc->vma_flags);
}
then write:
if (WARN_ON_ONCE(!mmap_action_is_valid(desc)))
return -EINVAL;
And you can write a unit test directly against mmap_action_is_valid
otherwise
Reviewed-by: Gregory Price (Meta) <gourry@gourry.net>
From: "Liam R. Howlett" <liam@infradead.org> Date: 2026-09-24 19:01:56
On 26/09/17 05:22PM, Lorenzo Stoakes (ARM) wrote:
quoted hunk
The map->file_doesnt_need_get flag is confusing and the existing
implementation has holes.
Drivers are permitted to change the owning file of a mapping. If they do
so, they are required to take a reference on that file.
The mmap() operation which ultimately invokes __mmap_region() is guaranteed
to drop the refcount for the original file the mapping was made under, but
this is not true for the replaced file.
This has been addressed so far by tracking map->file_doesnt_need_get, which
is rather poorly named and unfortunately fails to correctly track whether
or not an additional put were needed in a number of cases.
Make life easier by removing this flag, and instead drop the reference for
both mmap_prepare and the deprecated mmap callback in a new function
put_map().
Track whether this needs to be done by aligning mmap_state with
vm_area_desc and store the original file in the map->file field, keeping
the updated file in map->vm_file.
In order to have the same behaviour for both types of hooks, only drop the
reference __mmap_new_file_vma() itself took in its error path, deferring
the replaced file's reference to put_map().
To make this work correctly, map->vm_file has to be updated before any
error handling, so update __mmap_new_file_vma() and call_mmap_prepare() to
set this field first.
Also when mmap_prepare() changes the file and is then merged, the reference
count also must be decremented, so update the logic to call put_map() in
this case too.
Also update __compat_vma_mmap() to manually perform this step for stacked
file systems using the compatibility layer, and update
compat_set_vma_from_desc() to replace vma_set_file() with a correct
refcount/file update.
No in-tree driver is impacted by the incorrect implementation of this
currently (no driver that does this is mergeable for one), so this does not
need to be a fix.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/internal.h | 1 +
mm/util.c | 5 +++-
mm/vma.c | 83 +++++++++++++++++++++++++++++++++++------------------------
mm/vma.h | 6 +++--
4 files changed, 59 insertions(+), 36 deletions(-)
@@ -1228,8 +1228,11 @@ int __compat_vma_mmap(struct vm_area_desc *desc,/* Perform any preparatory tasks for mmap action. */err=mmap_action_prepare(desc);-if(err)+if(err){+if(desc->vm_file!=vma->vm_file)+fput(desc->vm_file);returnerr;+}/* Update the VMA from the descriptor. */compat_set_vma_from_desc(vma,desc);/* Complete any specified mmap actions. */
I guess renaming file to mmaped_file would be a lot more changes.
quoted hunk
+ struct file *vm_file; /* May be updated by mmap_prepare. */ pgprot_t page_prot; /* User-defined fields, perhaps updated by .mmap_prepare(). */
@@ -43,8 +44,6 @@ struct mmap_state { /* Determine if we can check KSM flags early in mmap() logic. */ bool check_ksm_early :1;- /* If .mmap_prepare changed the file, we don't need to pin. */- bool file_doesnt_need_get :1; }; #define MMAP_STATE(name, mm_, vmi_, addr_, len_, pgoff_, anon_pgoff_, vma_flags_, file_) \
@@ -2826,7 +2832,7 @@ static int call_mmap_prepare(struct mmap_state *map, * anonymous mappings. Rather than allowing these mappings to be odd * outliers, simply make them truly anonymous. */- if (map_is_private(map) && file_is_dev_zero(map->file))+ if (map_is_private(map) && file_is_dev_zero(map->vm_file)) map_set_anon(map); return 0;
@@ -2845,7 +2851,7 @@ static void set_vma_user_defined_fields(struct vm_area_struct *vma, */ static bool can_set_ksm_flags_early(struct mmap_state *map) {- struct file *file = map->file;+ struct file *file = map->vm_file; /* Anonymous mappings have no driver which can change them. */ if (!file)
I like the put_map_file() instead, like Suren suggested.. but maybe
put_map_vm_file(), especially since it could be read as put to the file
pointer instead of vm_file.
quoted hunk
+{+ /*+ * An error occurred or the VMA was merged.+ *+ * If the file was changed by the driver (which is required to increment+ * the replacement file's reference count), drop its reference count.+ *+ * On error, the caller always drops the original file regardless.+ */+ if (map->vm_file && !map_same_file(map))+ fput(map->vm_file);+}+ static unsigned long __mmap_region(struct file *file, unsigned long addr, unsigned long len, vma_flags_t vma_flags, unsigned long pgoff, struct list_head *uf)
@@ -2922,7 +2942,10 @@ static unsigned long __mmap_region(struct file *file, unsigned long addr, __mmap_complete(&map, vma);- if (have_mmap_prepare && allocated_new) {+ if (!allocated_new) {+ /* Merged, so need to drop refcount. */+ put_map(&map);+ } else if (have_mmap_prepare) { error = mmap_action_complete(vma, &desc.action, /*is_compat=*/false); if (error)
@@ -2936,13 +2959,7 @@ static unsigned long __mmap_region(struct file *file, unsigned long addr, if (map.charged) vm_unacct_memory(map.charged); abort_munmap:- /*- * This indicates that .mmap_prepare has set a new file, differing from- * desc->vm_file. But since we're aborting the operation, only the- * original file will be cleaned up. Ensure we clean up both.- */- if (map.file_doesnt_need_get)- fput(map.file);+ put_map(&map); vms_abort_munmap_vmas(&map.vms, &map.mas_detach); return error; }
On Thu Sep 17, 2026 at 12:22 PM EDT, Lorenzo Stoakes (ARM) wrote:
quoted hunk
When a user requests an mmap_action be performed in mmap_prepare, this
involves populating the VMA range with data.
However, if the VMA is mergeable, it might then mistakenly be merged with
another VMA without having populated the range.
Every mmap action currently available sets VMA flags such that the VMA
cannot be merged.
However, to ensure that no future mmap action falls foul of this, assert
that this is the case upon mmap_prepare validation.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/vma.c | 9 +++++++++
1 file changed, 9 insertions(+)
@@ -2809,6 +2809,15 @@ static int mmap_validate(unsigned long prev_start, unsigned long prev_end,intmmap_prepare_validate(conststructvm_area_desc*prev_desc,conststructvm_area_desc*desc){+/*+*ItisnotvalidtoexecutemmapactionsforVMAswhichcanbemerged,
Is it better to say "for VMAs ... after mmap_action_prepare()"? When I
first read this, I wonder why the check is done after
mmap_action_prepare(), which does some work based on action.type. Then,
I realize mmap_action_prepare() changes desc->vma_flags and affect its
mergeablitiy.
quoted hunk
+ * as any such merge would leave portions of the mapping incorrectly+ * unmapped.+ */+ if (vma_flags_can_merge(&desc->vma_flags) &&+ WARN_ON_ONCE(desc->action.type != MMAP_NOTHING))+ return -EINVAL;+ return mmap_validate(prev_desc->start, prev_desc->end, desc->start, desc->end, &prev_desc->vma_flags, &desc->vma_flags);
Otherwise, LGTM.
Reviewed-by: Zi Yan <ziy@nvidia.com>
--
Best Regards,
Yan, Zi
On Thu Sep 17, 2026 at 12:22 PM EDT, Lorenzo Stoakes (ARM) wrote:
There's no reason to export the symbols for these functions which are only
called from internal mm logic, additionally there's no reason for them to
be declared in mm.h.
This patch therefore removes the exports and moves the declarations to
mm/internal.h.
No functional change intended.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
include/linux/mm.h | 3 ---
mm/internal.h | 3 +++
mm/memory.c | 2 --
3 files changed, 3 insertions(+), 5 deletions(-)
Makse sense.
Reviewed-by: Zi Yan <ziy@nvidia.com>
--
Best Regards,
Yan, Zi
On Thu Sep 17, 2026 at 12:22 PM EDT, Lorenzo Stoakes (ARM) wrote:
MMAP_MAP_KERNEL_PAGES is a mouthful, discard the MAP_ as that's implied by
MMAP.
Also while we're here delete useless comments for mmap actions whose names
clearly indicate what they are for.
Also update the userland VMA tests to reflect this change.
No functional change intended.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
include/linux/mm.h | 2 +-
include/linux/mm_types.h | 8 ++++----
mm/util.c | 8 ++++----
tools/testing/vma/include/dup.h | 8 ++++----
4 files changed, 13 insertions(+), 13 deletions(-)
LGTM.
Reviewed-by: Zi Yan <ziy@nvidia.com>
--
Best Regards,
Yan, Zi
On Thu, Sep 17, 2026 at 9:25 AM Lorenzo Stoakes (ARM) [off-list ref] wrote:
When a user requests an mmap_action be performed in mmap_prepare, this
involves populating the VMA range with data.
However, if the VMA is mergeable, it might then mistakenly be merged with
another VMA without having populated the range.
Every mmap action currently available sets VMA flags such that the VMA
cannot be merged.
However, to ensure that no future mmap action falls foul of this, assert
that this is the case upon mmap_prepare validation.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
@@ -2809,6 +2809,15 @@ static int mmap_validate(unsigned long prev_start, unsigned long prev_end,intmmap_prepare_validate(conststructvm_area_desc*prev_desc,conststructvm_area_desc*desc){+/*+*ItisnotvalidtoexecutemmapactionsforVMAswhichcanbemerged,+*asanysuchmergewouldleaveportionsofthemappingincorrectly+*unmapped.+*/+if(vma_flags_can_merge(&desc->vma_flags)&&+WARN_ON_ONCE(desc->action.type!=MMAP_NOTHING))
Any reason you chose this "if (A && WARN_ON_ONCE(B))" pattern instead
of a simpler "if (WARN_ON_ONCE(A && B))"? Unless there are races
between A and B updates, I think these would be equivalent, right?
On Thu, Sep 24, 2026 at 12:30 PM Zi Yan [off-list ref] wrote:
On Thu Sep 17, 2026 at 12:22 PM EDT, Lorenzo Stoakes (ARM) wrote:
quoted
There's no reason to export the symbols for these functions which are only
called from internal mm logic, additionally there's no reason for them to
be declared in mm.h.
This patch therefore removes the exports and moves the declarations to
mm/internal.h.
No functional change intended.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
include/linux/mm.h | 3 ---
mm/internal.h | 3 +++
mm/memory.c | 2 --
3 files changed, 3 insertions(+), 5 deletions(-)
On Thu, Sep 24, 2026 at 12:31 PM Zi Yan [off-list ref] wrote:
On Thu Sep 17, 2026 at 12:22 PM EDT, Lorenzo Stoakes (ARM) wrote:
quoted
MMAP_MAP_KERNEL_PAGES is a mouthful, discard the MAP_ as that's implied by
MMAP.
Also while we're here delete useless comments for mmap actions whose names
clearly indicate what they are for.
Also update the userland VMA tests to reflect this change.
No functional change intended.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
include/linux/mm.h | 2 +-
include/linux/mm_types.h | 8 ++++----
mm/util.c | 8 ++++----
tools/testing/vma/include/dup.h | 8 ++++----
4 files changed, 13 insertions(+), 13 deletions(-)
@@ -1228,8 +1228,11 @@ int __compat_vma_mmap(struct vm_area_desc *desc,/* Perform any preparatory tasks for mmap action. */err=mmap_action_prepare(desc);-if(err)+if(err){+if(desc->vm_file!=vma->vm_file)+fput(desc->vm_file);returnerr;+}/* Update the VMA from the descriptor. */compat_set_vma_from_desc(vma,desc);/* Complete any specified mmap actions. */
I guess renaming file to mmaped_file would be a lot more changes.
Yeah :) and want to keep things sync'd with vm_area_desc.
Can always obviously follow up later with renames sync'd across both.
<snip>
quoted
static int __mmap_new_file_vma(struct mmap_state *map,
struct vm_area_struct *vma)
@@ -2593,20 +2597,23 @@ static int __mmap_new_file_vma(struct mmap_state *map, struct vma_iterator *vmi = map->vmi; int error;- vma->vm_file = map->file;- if (!map->file_doesnt_need_get)- get_file(map->file);+ vma->vm_file = map->vm_file;+ if (map_same_file(map))+ get_file(map->vm_file);- if (!map->file->f_op->mmap)+ if (!map->vm_file->f_op->mmap) return 0; error = mmap_file(vma->vm_file, vma);+ map->vm_file = vma->vm_file;+
You set vma->vm_file to map->vm_file unconditionally above, is this
necessary?
Yeah, because the mmap hook can change vma->vm_file, and this is necessary
for the correct file refcount accounting.
The accounting is actually very tricky, because the file that was passed
via mmap() is fput() after the operation is done but if its swapped then
you have to make sure everything works out correctly on both error and
success paths.
Which this patch does (with a lot of AI review checking to make sure it's
not broken! FWIW)
<snip>
quoted
@@ -2688,7 +2694,7 @@ static int __mmap_new_vma(struct mmap_state *map, struct vm_area_struct **vmap, } /* Invoke callbacks. */- if (map->file)+ if (map->vm_file) error = __mmap_new_file_vma(map, vma); else if (!is_anon) error = shmem_zero_setup(vma);
@@ -2797,11 +2803,15 @@ static int call_mmap_prepare(struct mmap_state *map, int err; /* Invoke the hook. */- err = vfs_mmap_prepare(map->file, desc);+ err = vfs_mmap_prepare(map->vm_file, desc); if (err) return err;- /* It's invalid for mmap_preprare hooks to clear vm_ops. */+ /* Update first so file refcount tracked correctly. */+ if (desc->vm_file != map->vm_file)+ map->vm_file = desc->vm_file;++ /* It's invalid for mmap_prepare hooks to clear vm_ops. */
The less rare of prepare ;)
Haha 'It's a rare prepare that would dare' is what I'd LIKE to put here as a
comment but probably can't :P
<snip>
quoted
+static void put_map(struct mmap_state *map)
I like the put_map_file() instead, like Suren suggested.. but maybe
put_map_vm_file(), especially since it could be read as put to the file
pointer instead of vm_file.
Ack, and of course to bikeshed it a bit :P maybe map_put_vm_file() so the
'put vm_file' bit is clearer?
<snip>
No, at this point the VMA write lock is held and everything should be
pinned correctly.
The vma_set_file() dance was the problematic bit here as it didn't
handle the file refcount properly.
I think it is worth documenting VMA_LOCKONFAULT_BIT alone means mlock in
progress, like you did in munlock_vma_folio(). Just to keep the protocol
explicit for all the readers.
Well I'm not sure it's necessary here honestly, because this never checked
VMA_LOCKED_MASK anyway, and VMA_LOCKONFAULT_BIT never made a difference.
So the meaning of VMA_LOCKED_BIT here is strictly 'is it locked' and it's
correctly handled.
And I fear that it becomes whack-a-mole - the neat thing about this change is
that you no longer have to special case the stupid VM_SPECIAL thing, and can in
fact do the 'normal' thing of _just checking_ VMA_LOCKED_BIT :)
So I think it's better not to.
Your reasoning makes sense to me.
Thanks :)
quoted
quoted
Why I am commenting in the middle of the series? Because I am taking
a quiz given by LLM based on this series to get myself enough background
knowledge to review this series. This mlock part came up at part E
and I only have part F left before I can do the full review. :)
Thanks! :) I really appreciate you taking the time to look at this! Sorry it's
so large.
Sure. It is great learning material for me. Thank you for the patches.
No worries, and sorry for the size of this change...! :)
quoted
I held this series back from last cycle to help with review load, then spent
some time fixing various AI-discovered things, and all the patches are necessary
(well for the most part) to get where the series needs to go.
I think the change is worth it though!
Of course, great to see hacky code being removed by this series.
For this patch, feel free to add
Reviewed-by: Zi Yan <ziy@nvidia.com>