Currently, unmap_mapping_pages() and unmap_mapping_range() unmap a page offset window of an address_space, and teardown paths such as kernfs_drain_open_files() rely on them to remove the mappings 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 a teardown that unmaps the inode's address space cannot reach them, and they stay live after the file is removed. As such, the only correct discriminator for these VMAs is the originating struct file recorded in vma->vm_file. Add unmap_mapping_file(), which walks the VMA interval tree of the file's f_mapping under i_mmap_lock_read() and zaps every VMA with vm_file set to the given file, using the same per-VMA zap teardown as unmap_mapping_pages(). Signed-off-by: Krzysztof Wilczyński --- include/linux/mm.h | 2 ++ mm/memory.c | 26 ++++++++++++++++++++++++++ 2 files changed, 28 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..f9cfb88ac4f7 100644 --- a/mm/memory.c +++ b/mm/memory.c @@ -4423,6 +4423,32 @@ void unmap_mapping_pages(struct address_space *mapping, pgoff_t start, } EXPORT_SYMBOL_GPL(unmap_mapping_pages); +/** + * unmap_mapping_file() - Unmap all mmaps of an open file. + * @file: The file to unmap. + * + * Unmap every VMA with vm_file set to @file, regardless of the address + * space it is attached to. This also covers files with f_mapping + * swapped to a different address space at open time, since such VMAs + * cannot be found through the file's own inode mapping. + */ +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) { + if (vma->vm_file != file) + continue; + zap_vma(vma); + } + } + i_mmap_unlock_read(mapping); +} +EXPORT_SYMBOL_GPL(unmap_mapping_file); + /** * 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 Currently, kernfs_drain_open_files() unmaps 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, and the drain walks the empty sysfs inode interval tree instead. 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() when the file's f_mapping no longer points at its own inode mapping, so the drain zaps exactly the VMAs created through the removed file, while unrelated mappings on the shared address space are left intact. Files with an unmodified inode mapping keep the existing unmap_mapping_range() behaviour, and the driver-claim revocation through revoke_iomem() is unaffected because the VMAs stay on the shared mapping. This restores the teardown 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 --- fs/kernfs/file.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fs/kernfs/file.c b/fs/kernfs/file.c index 8e0e90c93372..8d1e4acc6716 100644 --- a/fs/kernfs/file.c +++ b/fs/kernfs/file.c @@ -818,7 +818,10 @@ void kernfs_drain_open_files(struct kernfs_node *kn) struct inode *inode = file_inode(of->file); if (of->mmapped) { - unmap_mapping_range(inode->i_mapping, 0, 0, 1); + if (of->file->f_mapping != inode->i_mapping) + unmap_mapping_file(of->file); + else + unmap_mapping_range(inode->i_mapping, 0, 0, 1); of->mmapped = false; on->nr_mmapped--; } -- 2.55.0