Linux-mm Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 0/3] mm,kernfs,proc: Unmap mmaps of removed files via file->f_mapping
@ 2026-07-25 21:05 Krzysztof Wilczyński
  2026-07-25 21:05 ` [PATCH v2 1/3] mm: Add unmap_mapping_file() helper Krzysztof Wilczyński
                   ` (4 more replies)
  0 siblings, 5 replies; 10+ messages in thread
From: Krzysztof Wilczyński @ 2026-07-25 21:05 UTC (permalink / raw)
  To: Andrew Morton, David Hildenbrand, Greg Kroah-Hartman, Tejun Heo,
	Bjorn Helgaas
  Cc: Bjorn Helgaas, Manivannan Sadhasivam, Lorenzo Pieralisi,
	Liam R . Howlett, Baoquan He, Pratyush Yadav, Pasha Tatashin,
	Jaroslav Kysela, Takashi Iwai, Lorenzo Stoakes, Michal Hocko,
	Mike Rapoport, Simona Vetter, Suren Baghdasaryan, Vlastimil Babka,
	Dave Young, linux-mm, linux-pci, linux-sound, kexec, driver-core

Hello,

The PCI resource files in sysfs and the /proc/bus/pci device files swap
their f_mapping to the shared iomem address space at open time, so that
revoke_iomem() can unmap userspace mappings when a driver claims a
region, see commit 636b21b50152 ("PCI: Revoke mappings like devmem").

Their VMAs are therefore attached to the shared address space, which
neither removal path reaches: kernfs_drain_open_files() unmaps the
sysfs inode's own mapping, which contains none of them, and
proc_entry_rundown() does not unmap anything at all.

As a result, userspace mappings of PCI BARs survive device removal,
and also survive a BAR resize on the sysfs side, keeping stale PTEs
into physical address space that the kernel may have reassigned since.
A mapping made before the device is removed still returns the previous
register value after the device has been released, through both
interfaces.

This series adds unmap_mapping_file(), which unmaps the pages of the
VMAs created through a given file, rather than a page offset window of
an address space, and then uses it in the kernfs drain and in the
procfs rundown.  Mappings of unrelated files on the shared address
space are left intact, and the claim-time revocation through
revoke_iomem() keeps working, as the VMAs stay on the shared address
space.

For kernfs this restores the behaviour these files had before
the f_mapping swap was introduced.  For procfs the unmapping is new
behaviour, which matches kernfs.  Besides /proc/bus/pci, only
/proc/vmcore, the ALSA information entries and /proc/powerpc/systemcfg
set a proc_mmap() hook.  Mappings of /proc/vmcore are now also removed
when the entry goes away, while the ALSA proc_mmap() hook calls a
per-entry callback that no entry implements, so these cannot be mapped.
The systemcfg entry has no proc_release() hook, so it is not tracked
and is not affected.

A BAR resize is covered by the same path on the sysfs side, as the
resize removes the resource groups and thus drains the open files.
Mappings of the same BAR made through /dev/mem are left alone, as
these are unmapped by physical range when a driver claims the region.

	Krzysztof

---
Changes in v2:
  https://lore.kernel.org/linux-pci/20260721185252.1670958-1-kwilczynski@kernel.org/

  - Moved the handling of files with and without a swapped f_mapping
    inside unmap_mapping_file(), so that callers no longer need to
    tell the two apart, as per David Hildenbrand's feedback.
  - Updated the kernel-doc for unmap_mapping_file() to describe
    unmapping folios within the VMAs, rather than unmapping the mmaps
    and VMAs themselves, as per David Hildenbrand's feedback.
  - Dropped the EXPORT_SYMBOL_GPL() for unmap_mapping_file(), as both
    callers are built-in and there is no modular user.
  - Added a should_zap_file_vma() helper to select the VMAs to zap,
    following the existing should_zap_cows() and should_zap_folio()
    convention.
  - Added a WARN_ON_ONCE() to skip HugeTLB VMAs, as zapping those
    takes i_mmap_rwsem for write in hugetlb_zap_begin() and would
    deadlock against the read lock held over the walk.
  - Added a cond_resched() to the walk, as the VMAs skipped by the
    filter do not reach the reschedule point in the zap path.
  - Added a new patch to unmap the mappings made through the
    /proc/bus/pci device files, as these also survive device removal,
    along with a new pde_is_removed() helper used to tell a rundown
    from a regular close().
  - Updated the commit logs across the series, so that each patch
    describes the f_mapping swap and its effect the same way.
  - Added the /proc/vmcore and ALSA maintainers to the list of
    recipients for visibility.

Krzysztof Wilczyński (3):
  mm: Add unmap_mapping_file() helper
  kernfs: Unmap mmaps of removed files via file->f_mapping
  proc: Unmap mmaps of removed files via file->f_mapping

 fs/kernfs/file.c   |  4 +---
 fs/proc/generic.c  |  4 ++--
 fs/proc/inode.c    |  2 ++
 fs/proc/internal.h |  5 +++++
 include/linux/mm.h |  2 ++
 mm/memory.c        | 48 ++++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 60 insertions(+), 5 deletions(-)

-- 
2.55.0



^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v2 1/3] mm: Add unmap_mapping_file() helper
  2026-07-25 21:05 [PATCH v2 0/3] mm,kernfs,proc: Unmap mmaps of removed files via file->f_mapping Krzysztof Wilczyński
@ 2026-07-25 21:05 ` Krzysztof Wilczyński
  2026-07-27 16:36   ` David Hildenbrand (Arm)
  2026-07-30  8:23   ` Lorenzo Stoakes (ARM)
  2026-07-25 21:05 ` [PATCH v2 2/3] kernfs: Unmap mmaps of removed files via file->f_mapping Krzysztof Wilczyński
                   ` (3 subsequent siblings)
  4 siblings, 2 replies; 10+ messages in thread
From: Krzysztof Wilczyński @ 2026-07-25 21:05 UTC (permalink / raw)
  To: Andrew Morton, David Hildenbrand, Greg Kroah-Hartman, Tejun Heo,
	Bjorn Helgaas
  Cc: Bjorn Helgaas, Manivannan Sadhasivam, Lorenzo Pieralisi,
	Liam R . Howlett, Baoquan He, Pratyush Yadav, Pasha Tatashin,
	Jaroslav Kysela, Takashi Iwai, Lorenzo Stoakes, Michal Hocko,
	Mike Rapoport, Simona Vetter, Suren Baghdasaryan, Vlastimil Babka,
	Dave Young, linux-mm, linux-pci, linux-sound, kexec, driver-core

Currently, unmap_mapping_pages() and unmap_mapping_range() unmap a
page offset window of an address_space, and code that removes a file,
such as kernfs_drain_open_files(), relies on them to unmap the pages
of a file that is going away.

Files with f_mapping swapped to a shared address space at open time,
such as PCI resource files using iomem_get_mapping(), have their VMAs
attached to the shared mapping instead of the file's own inode mapping,
so unmapping the inode's address space cannot reach them, and they stay
live after the file is removed.  These VMAs can only be selected by the
struct file they were created through, which is recorded in
vma->vm_file.

Thus, add unmap_mapping_file(), which walks the VMA interval tree of the
file's f_mapping under i_mmap_lock_read() and unmaps the pages of the
VMAs selected by should_zap_file_vma(), using the same per-VMA zap as
unmap_mapping_pages().  When f_mapping is the file's own inode mapping,
every VMA is zapped, as unmap_mapping_range() does today.  When f_mapping
was swapped, only the VMAs with vm_file set to that file are zapped, so
callers do not need to tell the two apart.

HugeTLB VMAs are skipped with a WARN_ON_ONCE(), as zapping those takes
i_mmap_rwsem for write in hugetlb_zap_begin() and would deadlock against
the read lock held over the walk.

Signed-off-by: Krzysztof Wilczyński <kwilczynski@kernel.org>
---
 include/linux/mm.h |  2 ++
 mm/memory.c        | 48 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 50 insertions(+)

diff --git a/include/linux/mm.h b/include/linux/mm.h
index 485df9c2dbdd..e882a03eaddd 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -3183,6 +3183,7 @@ extern int fixup_user_fault(struct mm_struct *mm,
 			    bool *unlocked);
 void unmap_mapping_pages(struct address_space *mapping,
 		pgoff_t start, pgoff_t nr, bool even_cows);
+void unmap_mapping_file(struct file *file);
 void unmap_mapping_range(struct address_space *mapping,
 		loff_t const holebegin, loff_t const holelen, int even_cows);
 #else
@@ -3203,6 +3204,7 @@ static inline int fixup_user_fault(struct mm_struct *mm, unsigned long address,
 }
 static inline void unmap_mapping_pages(struct address_space *mapping,
 		pgoff_t start, pgoff_t nr, bool even_cows) { }
+static inline void unmap_mapping_file(struct file *file) { }
 static inline void unmap_mapping_range(struct address_space *mapping,
 		loff_t const holebegin, loff_t const holelen, int even_cows) { }
 #endif
diff --git a/mm/memory.c b/mm/memory.c
index ff338c2abe92..4a09d7b6aa6b 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -4423,6 +4423,54 @@ void unmap_mapping_pages(struct address_space *mapping, pgoff_t start,
 }
 EXPORT_SYMBOL_GPL(unmap_mapping_pages);
 
+static inline bool should_zap_file_vma(struct file *file,
+				       struct vm_area_struct *vma)
+{
+	/* On the file's own inode mapping, zap every VMA */
+	if (file->f_mapping == file_inode(file)->i_mapping)
+		return true;
+
+	/* A swapped mapping also holds VMAs of unrelated files, zap only ours */
+	return vma->vm_file == file;
+}
+
+/**
+ * unmap_mapping_file() - Unmap folios from all mmaps of a file.
+ * @file: The file to unmap.
+ *
+ * Unmap the folios of @file from every process that has them mapped.
+ *
+ * If f_mapping is the file's own inode mapping, they are unmapped
+ * from every VMA on that mapping, as unmap_mapping_range() would do.
+ *
+ * However, if f_mapping was swapped to a different address space at
+ * open time, only the VMAs with vm_file set to @file are considered,
+ * since that address space also holds mappings of unrelated files.
+ *
+ * Must not be used on HugeTLB files.  HugeTLB VMAs are skipped with
+ * a warning.
+ */
+void unmap_mapping_file(struct file *file)
+{
+	struct address_space *mapping = file->f_mapping;
+	struct vm_area_struct *vma;
+
+	i_mmap_lock_read(mapping);
+	if (unlikely(mapping_mapped(mapping))) {
+		vma_interval_tree_foreach(vma, &mapping->i_mmap, 0, ULONG_MAX) {
+			cond_resched();
+
+			if (!should_zap_file_vma(file, vma))
+				continue;
+			/* Zapping HugeTLB VMAs needs i_mmap_rwsem held for write */
+			if (WARN_ON_ONCE(is_vm_hugetlb_page(vma)))
+				continue;
+			zap_vma(vma);
+		}
+	}
+	i_mmap_unlock_read(mapping);
+}
+
 /**
  * unmap_mapping_range - unmap the portion of all mmaps in the specified
  * address_space corresponding to the specified byte range in the underlying
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH v2 2/3] kernfs: Unmap mmaps of removed files via file->f_mapping
  2026-07-25 21:05 [PATCH v2 0/3] mm,kernfs,proc: Unmap mmaps of removed files via file->f_mapping Krzysztof Wilczyński
  2026-07-25 21:05 ` [PATCH v2 1/3] mm: Add unmap_mapping_file() helper Krzysztof Wilczyński
@ 2026-07-25 21:05 ` Krzysztof Wilczyński
  2026-07-25 21:05 ` [PATCH v2 3/3] proc: " Krzysztof Wilczyński
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 10+ messages in thread
From: Krzysztof Wilczyński @ 2026-07-25 21:05 UTC (permalink / raw)
  To: Andrew Morton, David Hildenbrand, Greg Kroah-Hartman, Tejun Heo,
	Bjorn Helgaas
  Cc: Bjorn Helgaas, Manivannan Sadhasivam, Lorenzo Pieralisi,
	Liam R . Howlett, Baoquan He, Pratyush Yadav, Pasha Tatashin,
	Jaroslav Kysela, Takashi Iwai, Lorenzo Stoakes, Michal Hocko,
	Mike Rapoport, Simona Vetter, Suren Baghdasaryan, Vlastimil Babka,
	Dave Young, linux-mm, linux-pci, linux-sound, kexec, driver-core

Currently, kernfs_drain_open_files() unmaps the mapping of the sysfs
inode, file_inode(of->file)->i_mapping, when a node with mmapped open
files is removed.  Since commit 636b21b50152 ("PCI: Revoke mappings
like devmem"), PCI resource and legacy sysfs files swap their
f_mapping to iomem_get_mapping() at open time, so their VMAs are
attached to the shared iomem address space, which the drain never
unmaps.

As a result, userspace mappings of PCI BARs survive device removal
and BAR resize, keeping stale PTEs into physical address space that
the kernel may have reassigned since.

Thus, use unmap_mapping_file() for every open file, so that only the
VMAs of the node being removed are zapped, while unrelated mappings
are left intact.  The same helper also covers files without a swapped
f_mapping, and the driver-claim revocation through revoke_iomem() is
unaffected, as those VMAs remain on the shared address space.

This restores the behaviour these files had before the f_mapping swap
was introduced.  A read through a stale mapping after removal now
raises SIGBUS instead of returning stale data.

Fixes: 636b21b50152 ("PCI: Revoke mappings like devmem")
Signed-off-by: Krzysztof Wilczyński <kwilczynski@kernel.org>
---
 fs/kernfs/file.c | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/fs/kernfs/file.c b/fs/kernfs/file.c
index 8e0e90c93372..eb04439e9c3e 100644
--- a/fs/kernfs/file.c
+++ b/fs/kernfs/file.c
@@ -815,10 +815,8 @@ void kernfs_drain_open_files(struct kernfs_node *kn)
 	}
 
 	list_for_each_entry(of, &on->files, list) {
-		struct inode *inode = file_inode(of->file);
-
 		if (of->mmapped) {
-			unmap_mapping_range(inode->i_mapping, 0, 0, 1);
+			unmap_mapping_file(of->file);
 			of->mmapped = false;
 			on->nr_mmapped--;
 		}
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH v2 3/3] proc: Unmap mmaps of removed files via file->f_mapping
  2026-07-25 21:05 [PATCH v2 0/3] mm,kernfs,proc: Unmap mmaps of removed files via file->f_mapping Krzysztof Wilczyński
  2026-07-25 21:05 ` [PATCH v2 1/3] mm: Add unmap_mapping_file() helper Krzysztof Wilczyński
  2026-07-25 21:05 ` [PATCH v2 2/3] kernfs: Unmap mmaps of removed files via file->f_mapping Krzysztof Wilczyński
@ 2026-07-25 21:05 ` Krzysztof Wilczyński
  2026-07-25 21:37 ` [PATCH v2 0/3] mm,kernfs,proc: " Andrew Morton
  2026-07-30 17:37 ` Lorenzo Stoakes (ARM)
  4 siblings, 0 replies; 10+ messages in thread
From: Krzysztof Wilczyński @ 2026-07-25 21:05 UTC (permalink / raw)
  To: Andrew Morton, David Hildenbrand, Greg Kroah-Hartman, Tejun Heo,
	Bjorn Helgaas
  Cc: Bjorn Helgaas, Manivannan Sadhasivam, Lorenzo Pieralisi,
	Liam R . Howlett, Baoquan He, Pratyush Yadav, Pasha Tatashin,
	Jaroslav Kysela, Takashi Iwai, Lorenzo Stoakes, Michal Hocko,
	Mike Rapoport, Simona Vetter, Suren Baghdasaryan, Vlastimil Babka,
	Dave Young, linux-mm, linux-pci, linux-sound, kexec, driver-core

Currently, proc_entry_rundown() releases every open file of an entry
that is being removed, but never unmaps the userspace mappings created
through those files, so these stay in place after the entry is gone.

For the /proc/bus/pci device files, the VMAs of such mappings are
attached to the shared iomem address space, as these files swap their
f_mapping to iomem_get_mapping() at open time, see commit 636b21b50152
("PCI: Revoke mappings like devmem").

As a result, userspace mappings of PCI BARs survive device removal,
keeping stale PTEs into physical address space that the kernel may
have reassigned since.

Thus, use unmap_mapping_file() in close_pdeo() to unmap the pages of
each open file, so that only the VMAs created through the removed file
are zapped, while unrelated mappings on the shared address space are
left intact.  Add a pde_is_removed() helper next to pde_is_permanent()
to tell a rundown from a regular close(), which leaves mappings alone,
and use it in place of the open-coded checks in proc_misc_d_revalidate()
and proc_misc_d_delete().

This reaches every removable entry with a proc_release() hook, not only
the PCI ones.  Other than /proc/bus/pci, the only entries that also set
a proc_mmap() hook are /proc/vmcore and the ALSA information entries.

Mappings of /proc/vmcore are now also unmapped when the entry is
removed.  The ALSA proc_mmap() hook calls a per-entry callback that no
entry implements, so these cannot be mapped and nothing changes for
them.

A read through a stale mapping after removal now raises SIGBUS instead
of returning stale data.

Signed-off-by: Krzysztof Wilczyński <kwilczynski@kernel.org>
---
 fs/proc/generic.c  | 4 ++--
 fs/proc/inode.c    | 2 ++
 fs/proc/internal.h | 5 +++++
 3 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/fs/proc/generic.c b/fs/proc/generic.c
index adc9b9a092b0..0d090e3f3a26 100644
--- a/fs/proc/generic.c
+++ b/fs/proc/generic.c
@@ -222,14 +222,14 @@ static int proc_misc_d_revalidate(struct inode *dir, const struct qstr *name,
 	if (flags & LOOKUP_RCU)
 		return -ECHILD;
 
-	if (atomic_read(&PDE(d_inode(dentry))->in_use) < 0)
+	if (pde_is_removed(PDE(d_inode(dentry))))
 		return 0; /* revalidate */
 	return 1;
 }
 
 static int proc_misc_d_delete(const struct dentry *dentry)
 {
-	return atomic_read(&PDE(d_inode(dentry))->in_use) < 0;
+	return pde_is_removed(PDE(d_inode(dentry)));
 }
 
 static const struct dentry_operations proc_misc_dentry_ops = {
diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index b7634f975d98..f1ebedc85b15 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -239,6 +239,8 @@ static void close_pdeo(struct proc_dir_entry *pde, struct pde_opener *pdeo)
 		spin_unlock(&pde->pde_unload_lock);
 
 		file = pdeo->file;
+		if (pde_is_removed(pde))
+			unmap_mapping_file(file);
 		pde->proc_ops->proc_release(file_inode(file), file);
 
 		spin_lock(&pde->pde_unload_lock);
diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index b232e1098117..53335381ae89 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -79,6 +79,11 @@ static inline bool pde_is_permanent(const struct proc_dir_entry *pde)
 	return pde->flags & PROC_ENTRY_PERMANENT;
 }
 
+static inline bool pde_is_removed(const struct proc_dir_entry *pde)
+{
+	return atomic_read(&pde->in_use) < 0;
+}
+
 /* This is for builtin code, not even for modules which are compiled in. */
 static inline void pde_make_permanent(struct proc_dir_entry *pde)
 {
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 10+ messages in thread

* Re: [PATCH v2 0/3] mm,kernfs,proc: Unmap mmaps of removed files via file->f_mapping
  2026-07-25 21:05 [PATCH v2 0/3] mm,kernfs,proc: Unmap mmaps of removed files via file->f_mapping Krzysztof Wilczyński
                   ` (2 preceding siblings ...)
  2026-07-25 21:05 ` [PATCH v2 3/3] proc: " Krzysztof Wilczyński
@ 2026-07-25 21:37 ` Andrew Morton
  2026-07-26  1:22   ` Krzysztof Wilczyński
  2026-07-30 17:37 ` Lorenzo Stoakes (ARM)
  4 siblings, 1 reply; 10+ messages in thread
From: Andrew Morton @ 2026-07-25 21:37 UTC (permalink / raw)
  To: Krzysztof Wilczyński
  Cc: David Hildenbrand, Greg Kroah-Hartman, Tejun Heo, Bjorn Helgaas,
	Bjorn Helgaas, Manivannan Sadhasivam, Lorenzo Pieralisi,
	Liam R . Howlett, Baoquan He, Pratyush Yadav, Pasha Tatashin,
	Jaroslav Kysela, Takashi Iwai, Lorenzo Stoakes, Michal Hocko,
	Mike Rapoport, Simona Vetter, Suren Baghdasaryan, Vlastimil Babka,
	Dave Young, linux-mm, linux-pci, linux-sound, kexec, driver-core

On Sat, 25 Jul 2026 21:05:46 +0000 Krzysztof Wilczyński <kwilczynski@kernel.org> wrote:

> Hello,
> 
> The PCI resource files in sysfs and the /proc/bus/pci device files swap
> their f_mapping to the shared iomem address space at open time, so that
> revoke_iomem() can unmap userspace mappings when a driver claims a
> region, see commit 636b21b50152 ("PCI: Revoke mappings like devmem").
> 
> Their VMAs are therefore attached to the shared address space, which
> neither removal path reaches: kernfs_drain_open_files() unmaps the
> sysfs inode's own mapping, which contains none of them, and
> proc_entry_rundown() does not unmap anything at all.
> 
> As a result, userspace mappings of PCI BARs survive device removal,
> and also survive a BAR resize on the sysfs side, keeping stale PTEs
> into physical address space that the kernel may have reassigned since.
> A mapping made before the device is removed still returns the previous
> register value after the device has been released, through both
> interfaces.

Thanks.  Can we please have full description of the userspace-visible
effects of this?

AI review might have found a few things:
	https://sashiko.dev/#/patchset/20260725210549.3716546-1-kwilczynski@kernel.org



^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v2 0/3] mm,kernfs,proc: Unmap mmaps of removed files via file->f_mapping
  2026-07-25 21:37 ` [PATCH v2 0/3] mm,kernfs,proc: " Andrew Morton
@ 2026-07-26  1:22   ` Krzysztof Wilczyński
  2026-07-30  8:24     ` Lorenzo Stoakes (ARM)
  0 siblings, 1 reply; 10+ messages in thread
From: Krzysztof Wilczyński @ 2026-07-26  1:22 UTC (permalink / raw)
  To: Andrew Morton
  Cc: David Hildenbrand, Greg Kroah-Hartman, Tejun Heo, Bjorn Helgaas,
	Bjorn Helgaas, Manivannan Sadhasivam, Lorenzo Pieralisi,
	Liam R . Howlett, Baoquan He, Pratyush Yadav, Pasha Tatashin,
	Jaroslav Kysela, Takashi Iwai, Lorenzo Stoakes, Michal Hocko,
	Mike Rapoport, Simona Vetter, Suren Baghdasaryan, Vlastimil Babka,
	Dave Young, linux-mm, linux-pci, linux-sound, kexec, driver-core

Hello,

> > The PCI resource files in sysfs and the /proc/bus/pci device files swap
> > their f_mapping to the shared iomem address space at open time, so that
> > revoke_iomem() can unmap userspace mappings when a driver claims a
> > region, see commit 636b21b50152 ("PCI: Revoke mappings like devmem").
> > 
> > Their VMAs are therefore attached to the shared address space, which
> > neither removal path reaches: kernfs_drain_open_files() unmaps the
> > sysfs inode's own mapping, which contains none of them, and
> > proc_entry_rundown() does not unmap anything at all.
> > 
> > As a result, userspace mappings of PCI BARs survive device removal,
> > and also survive a BAR resize on the sysfs side, keeping stale PTEs
> > into physical address space that the kernel may have reassigned since.
> > A mapping made before the device is removed still returns the previous
> > register value after the device has been released, through both
> > interfaces.
> 
> Thanks.  Can we please have full description of the userspace-visible
> effects of this?

Definitely.  From the PCI side, to put this into perspective:

Without this series, a process that maps a BAR keeps a fully working
mapping after the device is removed or its VFs torn down.  The same
applies when a BAR is resized, as the resize removes and recreates the
resource groups.  Nothing faults, so the process cannot tell the device
has gone.  A read returns the register contents as they were, because
removal clears Bus Master but leaves Memory Space Enable set, so the
device still decodes its BARs, and, as these attributes are writable,
a write is still accepted and reaches it.  Those addresses are released
on removal and can be assigned to another device, which a BAR resize
does explicitly, and the mapping then reaches whatever occupies them.

On the sysfs side this most likely has been a regression as the
kernfs_drain_open_files() unmapped these mappings correctly until
commit 636b21b50152 ("PCI: Revoke mappings like devmem") swapped
f_mapping to the shared iomem address space, after which the drain
walked an interval tree the VMAs were no longer on.  The procfs side
never unmapped anything, so there the behaviour is new.

A read here returns another device's register contents, and as read to
clear registers are common, it can also acknowledge an interrupt or clear
an error the owning driver has not seen.  A write can be worse, as an
offset that was a control register on the old device may be a doorbell,
a reset bit or a DMA descriptor address on the new one.

With this series the mapping is torn down as part of the removal, and
the next access raises SIGBUS, so userspace gets an error instead of
silently reading stale or unrelated registers.  Both interfaces behave
the same way afterwards.

Tested on 7.2-rc1 with and without the series applied.  In each case
the file is mmap'd, the device is removed while the mapping is held,
and the mapping is then accessed:

  - sysfs resource read after removal:
       before: returns 0x18140241, the value read before the removal
       after:  SIGBUS

   - sysfs resource written after removal:
       before: the write is accepted and reads back 0x00000000
       after:  SIGBUS on the write

   - sysfs resource mmap, fd closed, then read after removal:
       before: returns 0x18140241
       after:  SIGBUS

   - sysfs resource mmap, moved with mremap and split with munmap, then forked, both read after removal:
       before: parent and child both return 0x18140241
       after:  SIGBUS in both

   - two sysfs resources of different devices mmap, one device removed, both read:
       before: neither is unmapped, both return their values
       after:  the removed device raises SIGBUS, the other still returns 0x48140240

   - /proc/bus/pci used to mmap resource, read after removal:
       before: returns 0x18140241
       after:  SIGBUS

I hope this helps!

> AI review might have found a few things:
> 	https://sashiko.dev/#/patchset/20260725210549.3716546-1-kwilczynski@kernel.org

I have seen the reviews.  Will reply to each.

Thank you!

	Krzysztof


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v2 1/3] mm: Add unmap_mapping_file() helper
  2026-07-25 21:05 ` [PATCH v2 1/3] mm: Add unmap_mapping_file() helper Krzysztof Wilczyński
@ 2026-07-27 16:36   ` David Hildenbrand (Arm)
  2026-07-30  8:23   ` Lorenzo Stoakes (ARM)
  1 sibling, 0 replies; 10+ messages in thread
From: David Hildenbrand (Arm) @ 2026-07-27 16:36 UTC (permalink / raw)
  To: Krzysztof Wilczyński, Andrew Morton, Greg Kroah-Hartman,
	Tejun Heo, Bjorn Helgaas
  Cc: Bjorn Helgaas, Manivannan Sadhasivam, Lorenzo Pieralisi,
	Liam R . Howlett, Baoquan He, Pratyush Yadav, Pasha Tatashin,
	Jaroslav Kysela, Takashi Iwai, Lorenzo Stoakes, Michal Hocko,
	Mike Rapoport, Simona Vetter, Suren Baghdasaryan, Vlastimil Babka,
	Dave Young, linux-mm, linux-pci, linux-sound, kexec, driver-core

On 7/25/26 23:05, Krzysztof Wilczyński wrote:
> Currently, unmap_mapping_pages() and unmap_mapping_range() unmap a
> page offset window of an address_space, and code that removes a file,
> such as kernfs_drain_open_files(), relies on them to unmap the pages
> of a file that is going away.
> 
> Files with f_mapping swapped to a shared address space at open time,
> such as PCI resource files using iomem_get_mapping(), have their VMAs
> attached to the shared mapping instead of the file's own inode mapping,
> so unmapping the inode's address space cannot reach them, and they stay
> live after the file is removed.  These VMAs can only be selected by the
> struct file they were created through, which is recorded in
> vma->vm_file.
> 
> Thus, add unmap_mapping_file(), which walks the VMA interval tree of the
> file's f_mapping under i_mmap_lock_read() and unmaps the pages of the
> VMAs selected by should_zap_file_vma(), using the same per-VMA zap as
> unmap_mapping_pages().  When f_mapping is the file's own inode mapping,
> every VMA is zapped, as unmap_mapping_range() does today.  When f_mapping
> was swapped, only the VMAs with vm_file set to that file are zapped, so
> callers do not need to tell the two apart.
> 
> HugeTLB VMAs are skipped with a WARN_ON_ONCE(), as zapping those takes
> i_mmap_rwsem for write in hugetlb_zap_begin() and would deadlock against
> the read lock held over the walk.
> 
> Signed-off-by: Krzysztof Wilczyński <kwilczynski@kernel.org>
> ---
>  include/linux/mm.h |  2 ++
>  mm/memory.c        | 48 ++++++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 50 insertions(+)
> 
> diff --git a/include/linux/mm.h b/include/linux/mm.h
> index 485df9c2dbdd..e882a03eaddd 100644
> --- a/include/linux/mm.h
> +++ b/include/linux/mm.h
> @@ -3183,6 +3183,7 @@ extern int fixup_user_fault(struct mm_struct *mm,
>  			    bool *unlocked);
>  void unmap_mapping_pages(struct address_space *mapping,
>  		pgoff_t start, pgoff_t nr, bool even_cows);
> +void unmap_mapping_file(struct file *file);
>  void unmap_mapping_range(struct address_space *mapping,
>  		loff_t const holebegin, loff_t const holelen, int even_cows);
>  #else
> @@ -3203,6 +3204,7 @@ static inline int fixup_user_fault(struct mm_struct *mm, unsigned long address,
>  }
>  static inline void unmap_mapping_pages(struct address_space *mapping,
>  		pgoff_t start, pgoff_t nr, bool even_cows) { }
> +static inline void unmap_mapping_file(struct file *file) { }
>  static inline void unmap_mapping_range(struct address_space *mapping,
>  		loff_t const holebegin, loff_t const holelen, int even_cows) { }
>  #endif
> diff --git a/mm/memory.c b/mm/memory.c
> index ff338c2abe92..4a09d7b6aa6b 100644
> --- a/mm/memory.c
> +++ b/mm/memory.c
> @@ -4423,6 +4423,54 @@ void unmap_mapping_pages(struct address_space *mapping, pgoff_t start,
>  }
>  EXPORT_SYMBOL_GPL(unmap_mapping_pages);
>  
> +static inline bool should_zap_file_vma(struct file *file,
> +				       struct vm_area_struct *vma)
> +{
> +	/* On the file's own inode mapping, zap every VMA */
> +	if (file->f_mapping == file_inode(file)->i_mapping)
> +		return true;
> +

I assume Sashiko complain applies only when this function would be reuse in
other context.

But I wonder if that check is even required, shouldn't below check cover this
already? I'd expect that "vma->vm_file == file" must always hold.

> +	/* A swapped mapping also holds VMAs of unrelated files, zap only ours */
> +	return vma->vm_file == file;
> +}
> +
> +/**
> + * unmap_mapping_file() - Unmap folios from all mmaps of a file.
> + * @file: The file to unmap.
> + *
> + * Unmap the folios of @file from every process that has them mapped.
> + *
> + * If f_mapping is the file's own inode mapping, they are unmapped
> + * from every VMA on that mapping, as unmap_mapping_range() would do.
> + *
> + * However, if f_mapping was swapped to a different address space at
> + * open time, only the VMAs with vm_file set to @file are considered,
> + * since that address space also holds mappings of unrelated files.
> + *
> + * Must not be used on HugeTLB files.  HugeTLB VMAs are skipped with
> + * a warning.
> + */
> +void unmap_mapping_file(struct file *file)
> +{
> +	struct address_space *mapping = file->f_mapping;
> +	struct vm_area_struct *vma;
> +
> +	i_mmap_lock_read(mapping);
> +	if (unlikely(mapping_mapped(mapping))) {
> +		vma_interval_tree_foreach(vma, &mapping->i_mmap, 0, ULONG_MAX) {

Can we just use mapping_rmap_tree_foreach() here, similar to
unmap_mapping_range_tree() ?

> +			cond_resched();

Why that? unmap_mapping_range() doesn't seem to require one so far.

-- 
Cheers,

David


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v2 1/3] mm: Add unmap_mapping_file() helper
  2026-07-25 21:05 ` [PATCH v2 1/3] mm: Add unmap_mapping_file() helper Krzysztof Wilczyński
  2026-07-27 16:36   ` David Hildenbrand (Arm)
@ 2026-07-30  8:23   ` Lorenzo Stoakes (ARM)
  1 sibling, 0 replies; 10+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-07-30  8:23 UTC (permalink / raw)
  To: Krzysztof Wilczyński
  Cc: Andrew Morton, David Hildenbrand, Greg Kroah-Hartman, Tejun Heo,
	Bjorn Helgaas, Bjorn Helgaas, Manivannan Sadhasivam,
	Lorenzo Pieralisi, Liam R . Howlett, Baoquan He, Pratyush Yadav,
	Pasha Tatashin, Jaroslav Kysela, Takashi Iwai, Michal Hocko,
	Mike Rapoport, Simona Vetter, Suren Baghdasaryan, Vlastimil Babka,
	Dave Young, linux-mm, linux-pci, linux-sound, kexec, driver-core

On Sat, Jul 25, 2026 at 09:05:47PM +0000, Krzysztof Wilczyński wrote:
> Currently, unmap_mapping_pages() and unmap_mapping_range() unmap a
> page offset window of an address_space, and code that removes a file,
> such as kernfs_drain_open_files(), relies on them to unmap the pages
> of a file that is going away.
>
> Files with f_mapping swapped to a shared address space at open time,
> such as PCI resource files using iomem_get_mapping(), have their VMAs
> attached to the shared mapping instead of the file's own inode mapping,
> so unmapping the inode's address space cannot reach them, and they stay
> live after the file is removed.  These VMAs can only be selected by the
> struct file they were created through, which is recorded in
> vma->vm_file.

I'm confused about what the issue is here.

- file->f_mapping is swapped out at f_ops->open time -> shared mapping
- Mapping here gets you folios that have folio->mapping == iomem_inode's
  mapping
- /dev/port goes away and...?

Is it that /dev/port going away then potentially zaps stuff it shouldn't or
doesn't zap what it should?

Be good to clarify all this and add a specific example also.

>
> Thus, add unmap_mapping_file(), which walks the VMA interval tree of the

Now mapping interval tree, been renamed as per David.

> file's f_mapping under i_mmap_lock_read() and unmaps the pages of the
> VMAs selected by should_zap_file_vma(), using the same per-VMA zap as
> unmap_mapping_pages().  When f_mapping is the file's own inode mapping,
> every VMA is zapped, as unmap_mapping_range() does today.  When f_mapping
> was swapped, only the VMAs with vm_file set to that file are zapped, so
> callers do not need to tell the two apart.

This is really confusing, so I think serious levels of clarity are needed
here.

Also is this intended to replace kernfs

>
> HugeTLB VMAs are skipped with a WARN_ON_ONCE(), as zapping those takes
> i_mmap_rwsem for write in hugetlb_zap_begin() and would deadlock against
> the read lock held over the walk.

This kind of insane 'swapping' would never happen on such mappings anyway
right?

>
> Signed-off-by: Krzysztof Wilczyński <kwilczynski@kernel.org>
> ---
>  include/linux/mm.h |  2 ++
>  mm/memory.c        | 48 ++++++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 50 insertions(+)
>
> diff --git a/include/linux/mm.h b/include/linux/mm.h
> index 485df9c2dbdd..e882a03eaddd 100644
> --- a/include/linux/mm.h
> +++ b/include/linux/mm.h
> @@ -3183,6 +3183,7 @@ extern int fixup_user_fault(struct mm_struct *mm,
>  			    bool *unlocked);
>  void unmap_mapping_pages(struct address_space *mapping,
>  		pgoff_t start, pgoff_t nr, bool even_cows);
> +void unmap_mapping_file(struct file *file);
>  void unmap_mapping_range(struct address_space *mapping,
>  		loff_t const holebegin, loff_t const holelen, int even_cows);
>  #else
> @@ -3203,6 +3204,7 @@ static inline int fixup_user_fault(struct mm_struct *mm, unsigned long address,
>  }
>  static inline void unmap_mapping_pages(struct address_space *mapping,
>  		pgoff_t start, pgoff_t nr, bool even_cows) { }
> +static inline void unmap_mapping_file(struct file *file) { }
>  static inline void unmap_mapping_range(struct address_space *mapping,
>  		loff_t const holebegin, loff_t const holelen, int even_cows) { }
>  #endif
> diff --git a/mm/memory.c b/mm/memory.c
> index ff338c2abe92..4a09d7b6aa6b 100644
> --- a/mm/memory.c
> +++ b/mm/memory.c
> @@ -4423,6 +4423,54 @@ void unmap_mapping_pages(struct address_space *mapping, pgoff_t start,
>  }
>  EXPORT_SYMBOL_GPL(unmap_mapping_pages);
>
> +static inline bool should_zap_file_vma(struct file *file,

s/inline//

> +				       struct vm_area_struct *vma)
> +{
> +	/* On the file's own inode mapping, zap every VMA */
> +	if (file->f_mapping == file_inode(file)->i_mapping)
> +		return true;
> +
> +	/* A swapped mapping also holds VMAs of unrelated files, zap only ours */

I'd be careful about saying 'swapped' here :) for obvious confusion in mm
reasons.

But I'd actually explain clearly that file->f_mapping can be changed at
f_ops->open time and thus

> +	return vma->vm_file == file;
> +}
> +
> +/**
> + * unmap_mapping_file() - Unmap folios from all mmaps of a file.
> + * @file: The file to unmap.
> + *
> + * Unmap the folios of @file from every process that has them mapped.
> + *
> + * If f_mapping is the file's own inode mapping, they are unmapped
> + * from every VMA on that mapping, as unmap_mapping_range() would do.
> + *
> + * However, if f_mapping was swapped to a different address space at

Same comment on swapped...

> + * open time, only the VMAs with vm_file set to @file are considered,
> + * since that address space also holds mappings of unrelated files.
> + *
> + * Must not be used on HugeTLB files.  HugeTLB VMAs are skipped with
> + * a warning.

Couldn't you just catch this at the top with the gloriously named
is_file_hugepages()?

	if (WARN_ON_ONCE(is_file_hugepages(file)))
		return;

I'm not sure who's supposed to call this though, if it's literally anybody
including core mm that might want to unmap a hugetlbfs file maybe you'd
just take the write lock in this cae?

But probably better to be conservative first I guess.

> + */
> +void unmap_mapping_file(struct file *file)
> +{
> +	struct address_space *mapping = file->f_mapping;
> +	struct vm_area_struct *vma;
> +
> +	i_mmap_lock_read(mapping);
> +	if (unlikely(mapping_mapped(mapping))) {

(nitty)

I kinda hate mapping_mapped(mapping) as a thing, should have commented
about that on that patch, but then again what could it be :) naming is hard.

Anyway be nicer as a guard clause I think e.g.:

	i_mmap_lock_read(mapping);
	if (likely(!mapping_mapped(mapping)))
		goto out;

	...

out:
	i_mmap_unlock_read(mapping);

> +		vma_interval_tree_foreach(vma, &mapping->i_mmap, 0, ULONG_MAX) {

As David mentions this is now renamed to mapping_rmap_tree_foreach().

> +			cond_resched();
> +
> +			if (!should_zap_file_vma(file, vma))
> +				continue;
> +			/* Zapping HugeTLB VMAs needs i_mmap_rwsem held for write */

See above about file check.

> +			if (WARN_ON_ONCE(is_vm_hugetlb_page(vma)))

> +				continue;
> +			zap_vma(vma);
> +		}
> +	}
> +	i_mmap_unlock_read(mapping);
> +}
> +
>  /**
>   * unmap_mapping_range - unmap the portion of all mmaps in the specified
>   * address_space corresponding to the specified byte range in the underlying
> --
> 2.55.0
>

Thanks, Lorenzo


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v2 0/3] mm,kernfs,proc: Unmap mmaps of removed files via file->f_mapping
  2026-07-26  1:22   ` Krzysztof Wilczyński
@ 2026-07-30  8:24     ` Lorenzo Stoakes (ARM)
  0 siblings, 0 replies; 10+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-07-30  8:24 UTC (permalink / raw)
  To: Krzysztof Wilczyński
  Cc: Andrew Morton, David Hildenbrand, Greg Kroah-Hartman, Tejun Heo,
	Bjorn Helgaas, Bjorn Helgaas, Manivannan Sadhasivam,
	Lorenzo Pieralisi, Liam R . Howlett, Baoquan He, Pratyush Yadav,
	Pasha Tatashin, Jaroslav Kysela, Takashi Iwai, Michal Hocko,
	Mike Rapoport, Simona Vetter, Suren Baghdasaryan, Vlastimil Babka,
	Dave Young, linux-mm, linux-pci, linux-sound, kexec, driver-core

On Sun, Jul 26, 2026 at 10:22:52AM +0900, Krzysztof Wilczyński wrote:
> Hello,
>
> > > The PCI resource files in sysfs and the /proc/bus/pci device files swap
> > > their f_mapping to the shared iomem address space at open time, so that
> > > revoke_iomem() can unmap userspace mappings when a driver claims a
> > > region, see commit 636b21b50152 ("PCI: Revoke mappings like devmem").
> > >
> > > Their VMAs are therefore attached to the shared address space, which
> > > neither removal path reaches: kernfs_drain_open_files() unmaps the
> > > sysfs inode's own mapping, which contains none of them, and
> > > proc_entry_rundown() does not unmap anything at all.
> > >
> > > As a result, userspace mappings of PCI BARs survive device removal,
> > > and also survive a BAR resize on the sysfs side, keeping stale PTEs
> > > into physical address space that the kernel may have reassigned since.
> > > A mapping made before the device is removed still returns the previous
> > > register value after the device has been released, through both
> > > interfaces.
> >
> > Thanks.  Can we please have full description of the userspace-visible
> > effects of this?
>
> Definitely.  From the PCI side, to put this into perspective:
>
> Without this series, a process that maps a BAR keeps a fully working
> mapping after the device is removed or its VFs torn down.  The same
> applies when a BAR is resized, as the resize removes and recreates the
> resource groups.  Nothing faults, so the process cannot tell the device
> has gone.  A read returns the register contents as they were, because
> removal clears Bus Master but leaves Memory Space Enable set, so the
> device still decodes its BARs, and, as these attributes are writable,
> a write is still accepted and reaches it.  Those addresses are released
> on removal and can be assigned to another device, which a BAR resize
> does explicitly, and the mapping then reaches whatever occupies them.
>
> On the sysfs side this most likely has been a regression as the
> kernfs_drain_open_files() unmapped these mappings correctly until
> commit 636b21b50152 ("PCI: Revoke mappings like devmem") swapped
> f_mapping to the shared iomem address space, after which the drain
> walked an interval tree the VMAs were no longer on.  The procfs side
> never unmapped anything, so there the behaviour is new.
>
> A read here returns another device's register contents, and as read to
> clear registers are common, it can also acknowledge an interrupt or clear
> an error the owning driver has not seen.  A write can be worse, as an
> offset that was a control register on the old device may be a doorbell,
> a reset bit or a DMA descriptor address on the new one.
>
> With this series the mapping is torn down as part of the removal, and
> the next access raises SIGBUS, so userspace gets an error instead of
> silently reading stale or unrelated registers.  Both interfaces behave
> the same way afterwards.
>
> Tested on 7.2-rc1 with and without the series applied.  In each case
> the file is mmap'd, the device is removed while the mapping is held,
> and the mapping is then accessed:
>
>   - sysfs resource read after removal:
>        before: returns 0x18140241, the value read before the removal
>        after:  SIGBUS
>
>    - sysfs resource written after removal:
>        before: the write is accepted and reads back 0x00000000
>        after:  SIGBUS on the write
>
>    - sysfs resource mmap, fd closed, then read after removal:
>        before: returns 0x18140241
>        after:  SIGBUS
>
>    - sysfs resource mmap, moved with mremap and split with munmap, then forked, both read after removal:
>        before: parent and child both return 0x18140241
>        after:  SIGBUS in both
>
>    - two sysfs resources of different devices mmap, one device removed, both read:
>        before: neither is unmapped, both return their values
>        after:  the removed device raises SIGBUS, the other still returns 0x48140240
>
>    - /proc/bus/pci used to mmap resource, read after removal:
>        before: returns 0x18140241
>        after:  SIGBUS
>
> I hope this helps!
>
> > AI review might have found a few things:
> > 	https://sashiko.dev/#/patchset/20260725210549.3716546-1-kwilczynski@kernel.org
>
> I have seen the reviews.  Will reply to each.
>
> Thank you!
>
> 	Krzysztof

Thanks for the details, be good to put this concisely in the cover
letter/relevant commit messages on respin.

Cheers, Lorenzo


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v2 0/3] mm,kernfs,proc: Unmap mmaps of removed files via file->f_mapping
  2026-07-25 21:05 [PATCH v2 0/3] mm,kernfs,proc: Unmap mmaps of removed files via file->f_mapping Krzysztof Wilczyński
                   ` (3 preceding siblings ...)
  2026-07-25 21:37 ` [PATCH v2 0/3] mm,kernfs,proc: " Andrew Morton
@ 2026-07-30 17:37 ` Lorenzo Stoakes (ARM)
  4 siblings, 0 replies; 10+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-07-30 17:37 UTC (permalink / raw)
  To: Krzysztof Wilczyński
  Cc: Andrew Morton, David Hildenbrand, Greg Kroah-Hartman, Tejun Heo,
	Bjorn Helgaas, Bjorn Helgaas, Manivannan Sadhasivam,
	Lorenzo Pieralisi, Liam R . Howlett, Baoquan He, Pratyush Yadav,
	Pasha Tatashin, Jaroslav Kysela, Takashi Iwai, Michal Hocko,
	Mike Rapoport, Simona Vetter, Suren Baghdasaryan, Vlastimil Babka,
	Dave Young, linux-mm, linux-pci, linux-sound, kexec, driver-core

As discussed off-list I think this is fundamentally broken.

struct file is not a universal - multiple opens can map the same thing. And
'these VMAs can only be selected by the struct file they were created through'
is not so.

So you're not going to get every /dev/mem or /proc/bus/pci or whatever by
checking against an arbitrary file.

The shared f_mapping already gives you the shared space in which the actual
iomem range resides.

The way things are mapped through the file rmap is via (mapping, pgoff).

Really the 'swapping' is telling you that what is a shared resource whose range
needs to be removed.

So what you need is to, at the point of the BAR being removed, unmap the correct
range.

So something like:

unmap_mapping_range(iomem_get_mapping(), ... pfn range ..., 1);

It also seems revoke_iomem() is very conditional, only if
CONFIG_IO_STRICT_DEVMEM and iomem!=relaxed.

So the file thing just doesn't work + what you really need is to:

- Revoke at the right time
- Revoke the physical range that is actually revoked
- Revoke this everywhere

So. I apologise. But. Father forgive me for I have slopped :)

This isn't my area of the kernel so I got the AI to generate something, which I
enclose.

I left all the gigantic horrible comments in to help.

It includes a fix up on the pci_iobar_pfn() thing you mentioned off-list which
sets the wrong vma->vm_pgoff for removal (ugh).

The pci_mmap_legacy_page_range() stuff probably just needs converting somehow
(and if it's broken there today it'll just have to stay broken, the range needs
to be shared somehow).

Anyway it at least gives an outline of a different road. So treat it as a 'here
are some thoughts' thing.

Cheers, Lorenzo

----8<----

From 525ce7631fa69aa9297e9fff6289e13316376e01 Mon Sep 17 00:00:00 2001
From: "Lorenzo Stoakes (ARM)" <ljs@kernel.org>
Date: Thu, 30 Jul 2026 18:15:20 +0100
Subject: [PATCH] PCI: Revoke BAR mappings on device removal and BAR resize

Userspace mappings of a PCI BAR survive removal of the device and resizing
of the BAR.  Nothing faults afterwards, so the process cannot tell that the
device has gone: removal clears Bus Master but leaves Memory Space Enable
set, so the device still decodes its BARs and reads keep returning register
contents, and writes are still accepted.  The addresses are released on
removal, and explicitly reassigned by a resize, after which the mapping
reaches whatever now occupies them.

These mappings are established through three interfaces, all of which
attach their VMAs to the shared iomem address space:

 - the sysfs resourceN and resourceN_wc files, whose bin_attribute sets
   .f_mapping = iomem_get_mapping,
 - /proc/bus/pci/BB/DD.F, whose ->open() does the same, and
 - /dev/mem.

On the sysfs side this is a regression.  kernfs_drain_open_files() unmaps
file_inode(of->file)->i_mapping, which held these VMAs until commit
636b21b50152 ("PCI: Revoke mappings like devmem") swapped f_mapping to the
iomem address space, after which the drain unmaps an address space that
contains none of them.  procfs never unmapped anything at all, and /dev/mem
mappings are only revoked by revoke_iomem() when a driver claims the
region, which is compiled out unless CONFIG_IO_STRICT_DEVMEM, returns early
under iomem=relaxed, and in any case need not happen before the address is
handed to another device.

There is no need to reach these VMAs through the struct file they were
created through.  The iomem address space is indexed by PFN:
pci_mmap_resource_range() adds the BAR's physical start to vm_pgoff, which
is precisely how revoke_iomem() already finds every mapper of a physical
range with a plain unmap_mapping_range().

So factor that out as iomem_revoke_range(), without the
CONFIG_IO_STRICT_DEVMEM and devmem_is_allowed() gates - those express the
iomem= policy on whether /dev/mem may map busy regions, which has no
bearing on a device going away - and call it for each BAR from
pci_destroy_dev() and from the BAR resize path.  All three interfaces are
covered at once, and a read through a stale mapping now raises SIGBUS
instead of returning stale data.

pci_destroy_dev() revokes after device_del() has drained the sysfs
attributes and pci_proc_detach_device() has removed the procfs entry, so no
new mapping can be established, and before pci_free_resources() releases
the addresses, so dev->resource[] still describes what userspace was given.

I/O BARs are mapped at an arch-specific PFN rather than at the resource
address, so pci_iobar_pfn() now returns that PFN instead of adding it to a
VMA's vm_pgoff, which lets the revoke path use it as well.  All three
implementations already derived it from the pci_dev and the BAR number
alone, so this is mechanical.  Only powerpc, sparc and xtensa set
arch_can_pci_mmap_io(); elsewhere pci_iobar_pfn() remains a stub returning
-EINVAL, now a static inline rather than a macro so that it type-checks its
arguments.

Fixes: 636b21b50152 ("PCI: Revoke mappings like devmem")
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
Assisted-by: LLM # all of it...
---
 arch/powerpc/kernel/pci-common.c |  4 +--
 arch/sparc/kernel/pci.c          |  4 +--
 arch/xtensa/kernel/pci.c         |  4 +--
 drivers/pci/mmap.c               | 50 ++++++++++++++++++++++++++++++--
 drivers/pci/pci-sysfs.c          |  3 ++
 drivers/pci/pci.h                |  1 +
 drivers/pci/remove.c             |  9 ++++++
 include/linux/ioport.h           |  1 +
 include/linux/pci.h              | 13 +++++++--
 kernel/resource.c                | 33 ++++++++++++++++-----
 10 files changed, 105 insertions(+), 17 deletions(-)

diff --git a/arch/powerpc/kernel/pci-common.c b/arch/powerpc/kernel/pci-common.c
index 3c4ca90e2ab7..e983280c7248 100644
--- a/arch/powerpc/kernel/pci-common.c
+++ b/arch/powerpc/kernel/pci-common.c
@@ -501,7 +501,7 @@ static int pci_read_irq_line(struct pci_dev *pci_dev)
  * Platform support for /proc/bus/pci/X/Y mmap()s.
  *  -- paulus.
  */
-int pci_iobar_pfn(struct pci_dev *pdev, int bar, struct vm_area_struct *vma)
+int pci_iobar_pfn(struct pci_dev *pdev, int bar, unsigned long *pfn)
 {
 	struct pci_controller *hose = pci_bus_to_host(pdev->bus);
 	resource_size_t ioaddr = pci_resource_start(pdev, bar);
@@ -512,7 +512,7 @@ int pci_iobar_pfn(struct pci_dev *pdev, int bar, struct vm_area_struct *vma)
 	/* Convert to an offset within this PCI controller */
 	ioaddr -= (unsigned long)hose->io_base_virt - _IO_BASE;

-	vma->vm_pgoff += (ioaddr + hose->io_base_phys) >> PAGE_SHIFT;
+	*pfn = (ioaddr + hose->io_base_phys) >> PAGE_SHIFT;
 	return 0;
 }

diff --git a/arch/sparc/kernel/pci.c b/arch/sparc/kernel/pci.c
index 1603d50fdcad..8e4eedfd3ecb 100644
--- a/arch/sparc/kernel/pci.c
+++ b/arch/sparc/kernel/pci.c
@@ -753,7 +753,7 @@ struct pci_bus *pci_scan_one_pbm(struct pci_pbm_info *pbm,
 }

 /* Platform support for /proc/bus/pci/X/Y mmap()s. */
-int pci_iobar_pfn(struct pci_dev *pdev, int bar, struct vm_area_struct *vma)
+int pci_iobar_pfn(struct pci_dev *pdev, int bar, unsigned long *pfn)
 {
 	struct pci_pbm_info *pbm = pdev->dev.archdata.host_controller;
 	resource_size_t ioaddr = pci_resource_start(pdev, bar);
@@ -761,7 +761,7 @@ int pci_iobar_pfn(struct pci_dev *pdev, int bar, struct vm_area_struct *vma)
 	if (!pbm)
 		return -EINVAL;

-	vma->vm_pgoff += (ioaddr + pbm->io_space.start) >> PAGE_SHIFT;
+	*pfn = (ioaddr + pbm->io_space.start) >> PAGE_SHIFT;

 	return 0;
 }
diff --git a/arch/xtensa/kernel/pci.c b/arch/xtensa/kernel/pci.c
index 305031551136..ea9cf832362d 100644
--- a/arch/xtensa/kernel/pci.c
+++ b/arch/xtensa/kernel/pci.c
@@ -74,7 +74,7 @@ void pcibios_fixup_bus(struct pci_bus *bus)
  *  -- paulus.
  */

-int pci_iobar_pfn(struct pci_dev *pdev, int bar, struct vm_area_struct *vma)
+int pci_iobar_pfn(struct pci_dev *pdev, int bar, unsigned long *pfn)
 {
 	struct pci_controller *pci_ctrl = (struct pci_controller*) pdev->sysdata;
 	resource_size_t ioaddr = pci_resource_start(pdev, bar);
@@ -85,6 +85,6 @@ int pci_iobar_pfn(struct pci_dev *pdev, int bar, struct vm_area_struct *vma)
 	/* Convert to an offset within this PCI controller */
 	ioaddr -= (unsigned long)pci_ctrl->io_space.base;

-	vma->vm_pgoff += (ioaddr + pci_ctrl->io_space.start) >> PAGE_SHIFT;
+	*pfn = (ioaddr + pci_ctrl->io_space.start) >> PAGE_SHIFT;
 	return 0;
 }
diff --git a/drivers/pci/mmap.c b/drivers/pci/mmap.c
index 8da3347a95c4..1969a93f64e6 100644
--- a/drivers/pci/mmap.c
+++ b/drivers/pci/mmap.c
@@ -38,11 +38,16 @@ int pci_mmap_resource_range(struct pci_dev *pdev, int bar,
 		vma->vm_page_prot = pgprot_device(vma->vm_page_prot);

 	if (mmap_state == pci_mmap_io) {
-		ret = pci_iobar_pfn(pdev, bar, vma);
+		unsigned long pfn;
+
+		ret = pci_iobar_pfn(pdev, bar, &pfn);
 		if (ret)
 			return ret;
-	} else
+
+		vma->vm_pgoff += pfn;
+	} else {
 		vma->vm_pgoff += (pci_resource_start(pdev, bar) >> PAGE_SHIFT);
+	}

 	vma->vm_ops = &pci_phys_vm_ops;

@@ -79,3 +84,44 @@ int pci_mmap_fits(struct pci_dev *pdev, int resno, struct vm_area_struct *vma,
 }

 #endif
+
+/**
+ * pci_revoke_bar_mappings - revoke userspace mappings of a device's BARs
+ * @dev: Device whose BAR mappings are to be revoked.
+ *
+ * Unmap every userspace mapping of @dev's BARs, whichever interface
+ * established it: the sysfs resource files, /proc/bus/pci or /dev/mem.  All of
+ * these map through the shared iomem address space at the page offset the BAR
+ * is mapped at, so unmapping that range reaches them all.  Subsequent accesses
+ * raise SIGBUS.
+ *
+ * The caller must have made new mappings impossible, and must call this while
+ * @dev's resources still describe the addresses handed out to userspace.
+ */
+void pci_revoke_bar_mappings(struct pci_dev *dev)
+{
+	int i;
+
+	for (i = 0; i < PCI_STD_NUM_BARS; i++) {
+		struct resource *res = pci_resource_n(dev, i);
+		resource_size_t len = pci_resource_len(dev, i);
+		unsigned long pfn;
+
+		/*
+		 * An unassigned BAR is sized but starts at zero, so revoking
+		 * it would unmap unrelated mappings of low physical memory.
+		 */
+		if (!len || (res->flags & IORESOURCE_UNSET))
+			continue;
+
+		if (res->flags & IORESOURCE_MEM) {
+			iomem_revoke_range(res->start, len);
+		} else if (res->flags & IORESOURCE_IO) {
+			/* Fails unless the arch can mmap I/O BARs at all. */
+			if (pci_iobar_pfn(dev, i, &pfn))
+				continue;
+
+			iomem_revoke_range(PFN_PHYS(pfn), len);
+		}
+	}
+}
diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c
index 5ec0b245a69b..b4d138a484dc 100644
--- a/drivers/pci/pci-sysfs.c
+++ b/drivers/pci/pci-sysfs.c
@@ -1714,6 +1714,9 @@ static ssize_t __resource_resize_store(struct device *dev, int n,

 	sysfs_remove_groups(&pdev->dev.kobj, pci_dev_resource_attr_groups);

+	/* The BAR addresses below are about to be reassigned. */
+	pci_revoke_bar_mappings(pdev);
+
 	ret = pci_resize_resource(pdev, n, size, 0);
 	if (ret)
 		pci_warn(pdev, "Failed to resize BAR %d: %pe\n",
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index 4469e1a77f3c..266b41e95e02 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -229,6 +229,7 @@ enum pci_mmap_api {
 };
 int pci_mmap_fits(struct pci_dev *pdev, int resno, struct vm_area_struct *vmai,
 		  enum pci_mmap_api mmap_api);
+void pci_revoke_bar_mappings(struct pci_dev *dev);

 bool pci_reset_supported(struct pci_dev *dev);
 void pci_init_reset_methods(struct pci_dev *dev);
diff --git a/drivers/pci/remove.c b/drivers/pci/remove.c
index d8bffa21498a..a9414c4ad593 100644
--- a/drivers/pci/remove.c
+++ b/drivers/pci/remove.c
@@ -45,6 +45,15 @@ static void pci_destroy_dev(struct pci_dev *dev)

 	device_del(&dev->dev);

+	/*
+	 * The sysfs resource files are gone, drained by device_del(), and so is
+	 * the /proc/bus/pci entry, removed by pci_proc_detach_device(), so no
+	 * new mapping can be established.  Revoke the ones that remain before
+	 * the addresses are released below and possibly handed to another
+	 * device.
+	 */
+	pci_revoke_bar_mappings(dev);
+
 	down_write(&pci_bus_sem);
 	list_del(&dev->bus_list);
 	up_write(&pci_bus_sem);
diff --git a/include/linux/ioport.h b/include/linux/ioport.h
index f7930b3dfd0a..ec1b2d08556e 100644
--- a/include/linux/ioport.h
+++ b/include/linux/ioport.h
@@ -460,6 +460,7 @@ static inline void irqresource_disabled(struct resource *res, u32 irq)
 }

 extern struct address_space *iomem_get_mapping(void);
+void iomem_revoke_range(resource_size_t start, resource_size_t size);

 #endif /* __ASSEMBLY__ */
 #endif	/* _LINUX_IOPORT_H */
diff --git a/include/linux/pci.h b/include/linux/pci.h
index 64b308b6e61c..df426d054faa 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -2274,11 +2274,20 @@ int pci_mmap_resource_range(struct pci_dev *dev, int bar,
 #define arch_can_pci_mmap_wc()		0
 #endif

+/*
+ * pci_iobar_pfn() yields the PFN at which an I/O BAR is mapped in the shared
+ * iomem address space, which is where both the mmap paths and the revocation
+ * of those mappings need it.
+ */
 #ifndef arch_can_pci_mmap_io
 #define arch_can_pci_mmap_io()		0
-#define pci_iobar_pfn(pdev, bar, vma) (-EINVAL)
+static inline int pci_iobar_pfn(struct pci_dev *pdev, int bar,
+				unsigned long *pfn)
+{
+	return -EINVAL;
+}
 #else
-int pci_iobar_pfn(struct pci_dev *pdev, int bar, struct vm_area_struct *vma);
+int pci_iobar_pfn(struct pci_dev *pdev, int bar, unsigned long *pfn);
 #endif

 #ifndef pci_root_bus_fwnode
diff --git a/kernel/resource.c b/kernel/resource.c
index 3d17e3196a3e..6cf2a30e1109 100644
--- a/kernel/resource.c
+++ b/kernel/resource.c
@@ -1265,21 +1265,40 @@ static DECLARE_WAIT_QUEUE_HEAD(muxed_resource_wait);

 static struct inode *iomem_inode;

-#ifdef CONFIG_IO_STRICT_DEVMEM
-static void revoke_iomem(struct resource *res)
+/**
+ * iomem_revoke_range - unmap all userspace mappings of a physical range
+ * @start: First byte of the range.
+ * @size: Size of the range in bytes.
+ *
+ * Zap the page tables of every userspace mapping of [@start, @start + @size),
+ * however it was established: /dev/mem, the PCI sysfs resource files or
+ * /proc/bus/pci.  All of these attach their VMAs to the shared iomem address
+ * space, whose page offsets are PFNs, so a single range unmap reaches every
+ * mapper regardless of the file it came in through.
+ *
+ * The caller is responsible for ensuring that no new mapping of the range can
+ * be established.  These are VM_PFNMAP mappings with no fault handler, so
+ * userspace touching the range afterwards takes SIGBUS.
+ */
+void iomem_revoke_range(resource_size_t start, resource_size_t size)
 {
 	/* pairs with smp_store_release() in iomem_init_inode() */
 	struct inode *inode = smp_load_acquire(&iomem_inode);

 	/*
-	 * Check that the initialization has completed. Losing the race
-	 * is ok because it means drivers are claiming resources before
-	 * the fs_initcall level of init and prevent iomem_get_mapping users
-	 * from establishing mappings.
+	 * Check that the initialization has completed.  Losing the race is ok
+	 * because it means no iomem_get_mapping() user can have established a
+	 * mapping yet.
 	 */
 	if (!inode)
 		return;

+	unmap_mapping_range(inode->i_mapping, start, size, 1);
+}
+
+#ifdef CONFIG_IO_STRICT_DEVMEM
+static void revoke_iomem(struct resource *res)
+{
 	/*
 	 * The expectation is that the driver has successfully marked
 	 * the resource busy by this point, so devmem_is_allowed()
@@ -1295,7 +1314,7 @@ static void revoke_iomem(struct resource *res)
 		return;
 	}

-	unmap_mapping_range(inode->i_mapping, res->start, resource_size(res), 1);
+	iomem_revoke_range(res->start, resource_size(res));
 }
 #else
 static void revoke_iomem(struct resource *res) {}
--
2.55.0


^ permalink raw reply related	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2026-07-30 17:37 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-25 21:05 [PATCH v2 0/3] mm,kernfs,proc: Unmap mmaps of removed files via file->f_mapping Krzysztof Wilczyński
2026-07-25 21:05 ` [PATCH v2 1/3] mm: Add unmap_mapping_file() helper Krzysztof Wilczyński
2026-07-27 16:36   ` David Hildenbrand (Arm)
2026-07-30  8:23   ` Lorenzo Stoakes (ARM)
2026-07-25 21:05 ` [PATCH v2 2/3] kernfs: Unmap mmaps of removed files via file->f_mapping Krzysztof Wilczyński
2026-07-25 21:05 ` [PATCH v2 3/3] proc: " Krzysztof Wilczyński
2026-07-25 21:37 ` [PATCH v2 0/3] mm,kernfs,proc: " Andrew Morton
2026-07-26  1:22   ` Krzysztof Wilczyński
2026-07-30  8:24     ` Lorenzo Stoakes (ARM)
2026-07-30 17:37 ` Lorenzo Stoakes (ARM)

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox