In order to use mmap_prepare() with MAP_PRIVATE mappings of /dev/zero without the success_hook hack we explicitly permitted mmap_prepare handlers to set NULL vm_ops.
However this is dangerous and we really only want to allow this for MAP_PRIVATE-mapped /dev/zero. Make it possible to explicitly identify /dev/zero by setting a global DEVZERO_MINOR device minor number then explicitly check for this in mmap code for a MAP_PRIVATE mapping and only set the VMA anonymous if we have positively identified it. Then remove all ability for mmap_prepare or mmap hooks to set a VMA anonymous and update mmap_zero_prepare() to leave it to the core mmap code to mark the VMA anonymous. Note that this disallows nested MAP_PRIVATE-mappings of /dev/zero regions. Doing this would be broken in any case. We therefore do not need to update the mmap_prepare() compatibility layer to reflect these changes, as the mmap hook check suffices to disallow this behaviour. Also update the VMA userland tests to reflect the change. Signed-off-by: Lorenzo Stoakes (ARM) <[email protected]> --- drivers/char/mem.c | 8 ++------ include/linux/mm.h | 3 +++ mm/internal.h | 4 ++++ mm/vma.c | 33 +++++++++++++++++++++++++++------ mm/vma_internal.h | 1 + tools/testing/vma/include/dup.h | 28 ++++++++++++++++++++++++++++ 6 files changed, 65 insertions(+), 12 deletions(-) diff --git a/drivers/char/mem.c b/drivers/char/mem.c index 63253d1de5d7..dcfd896b733d 100644 --- a/drivers/char/mem.c +++ b/drivers/char/mem.c @@ -506,11 +506,7 @@ static int mmap_zero_prepare(struct vm_area_desc *desc) if (vma_desc_test(desc, VMA_SHARED_BIT)) return shmem_zero_setup_desc(desc); - /* - * This is a highly unique situation where we mark a MAP_PRIVATE mapping - * of /dev/zero anonymous, despite it not being. - */ - vma_desc_set_anonymous(desc); + /* MAP_PRIVATE semantics are taken care for us by core mm. */ return 0; } @@ -698,7 +694,7 @@ static const struct memdev { #ifdef CONFIG_DEVPORT [4] = { "port", &port_fops, 0, 0 }, #endif - [5] = { "zero", &zero_fops, FMODE_NOWAIT, 0666 }, + [DEVZERO_MINOR] = { "zero", &zero_fops, FMODE_NOWAIT, 0666 }, [7] = { "full", &full_fops, 0, 0666 }, [8] = { "random", &random_fops, FMODE_NOWAIT, 0666 }, [9] = { "urandom", &urandom_fops, FMODE_NOWAIT, 0666 }, diff --git a/include/linux/mm.h b/include/linux/mm.h index b6503b5f0010..7614afe99c96 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -740,6 +740,9 @@ static inline bool fault_flag_allow_retry_first(enum fault_flag flags) { FAULT_FLAG_INTERRUPTIBLE, "INTERRUPTIBLE" }, \ { FAULT_FLAG_VMA_LOCK, "VMA_LOCK" } +/* /dev/zero minor device number. Special due to MAP_PRIVATE semantics. */ +#define DEVZERO_MINOR 5 + /* * vm_fault is filled by the pagefault handler and passed to the vma's * ->fault function. The vma's ->fault is responsible for returning a bitmask diff --git a/mm/internal.h b/mm/internal.h index 22f63cb85bd3..41561fdeb56d 100644 --- a/mm/internal.h +++ b/mm/internal.h @@ -240,6 +240,10 @@ static inline int mmap_file(struct file *file, struct vm_area_struct *vma) { int err = vfs_mmap(file, vma); + /* Hooks cannot mark themselves anonymous. */ + if (WARN_ON_ONCE(vma_is_anonymous(vma))) + err = -EINVAL; + if (likely(!err)) return 0; diff --git a/mm/vma.c b/mm/vma.c index 6dc095d03382..6ccd097b8813 100644 --- a/mm/vma.c +++ b/mm/vma.c @@ -2612,6 +2612,27 @@ static int __mmap_new_file_vma(struct mmap_state *map, return 0; } +static bool map_is_dev_zero(const struct mmap_state *map) +{ + const struct file *file = map->file; + const struct inode *inode = file_inode(file); + + return imajor(inode) == MEM_MAJOR && iminor(inode) == DEVZERO_MINOR; +} + +static bool map_is_private(const struct mmap_state *map) +{ + return !vma_flags_test(&map->vma_flags, VMA_SHARED_BIT); +} + +static bool map_is_anon(const struct mmap_state *map) +{ + if (!map_is_private(map)) + return false; + + return !map->file || map_is_dev_zero(map); +} + /* * __mmap_new_vma() - Allocate a new VMA for the region, as merging was not * possible. @@ -2625,8 +2646,7 @@ static int __mmap_new_file_vma(struct mmap_state *map, static int __mmap_new_vma(struct mmap_state *map, struct vm_area_struct **vmap, struct mmap_action *action) { - const bool is_anon = !map->file && - !vma_flags_test(&map->vma_flags, VMA_SHARED_BIT); + const bool is_anon = map_is_anon(map); struct vma_iterator *vmi = map->vmi; int error = 0; struct vm_area_struct *vma; @@ -2768,6 +2788,10 @@ static int call_mmap_prepare(struct mmap_state *map, if (err) return err; + /* Hooks cannot mark themselves anonymous. */ + if (!desc->vm_ops) + return -EINVAL; + err = call_action_prepare(map, desc); if (err) return err; @@ -2790,10 +2814,7 @@ static int call_mmap_prepare(struct mmap_state *map, static void set_vma_user_defined_fields(struct vm_area_struct *vma, struct mmap_state *map) { - if (map->vm_ops) - vma->vm_ops = map->vm_ops; - else /* Only /dev/zero should do this. */ - vma_set_anonymous(vma); + vma->vm_ops = map->vm_ops; vma->vm_private_data = map->vm_private_data; } diff --git a/mm/vma_internal.h b/mm/vma_internal.h index 4d300e7bbaf4..385c0ab13777 100644 --- a/mm/vma_internal.h +++ b/mm/vma_internal.h @@ -23,6 +23,7 @@ #include <linux/ksm.h> #include <linux/khugepaged.h> #include <linux/list.h> +#include <linux/major.h> #include <linux/maple_tree.h> #include <linux/mempolicy.h> #include <linux/mm.h> diff --git a/tools/testing/vma/include/dup.h b/tools/testing/vma/include/dup.h index 51605ade06fb..93049241b297 100644 --- a/tools/testing/vma/include/dup.h +++ b/tools/testing/vma/include/dup.h @@ -15,6 +15,15 @@ struct task_struct *get_current(void); #define MMF_HAS_MDWE 28 #define current get_current() +#define MINORBITS 20 +#define MINORMASK ((1U << MINORBITS) - 1) + +#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) +#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) + +#define MEM_MAJOR 1 +#define DEVZERO_MINOR 5 + /* * Define the task command name length as enum, then it can be visible to * BPF programs. @@ -45,6 +54,9 @@ struct address_space { unsigned long flags; atomic_t i_mmap_writable; }; +struct inode { + dev_t i_rdev; +}; struct file_operations { int (*mmap)(struct file *, struct vm_area_struct *); int (*mmap_prepare)(struct vm_area_desc *); @@ -52,6 +64,7 @@ struct file_operations { struct file { struct address_space *f_mapping; const struct file_operations *f_op; + struct inode *f_inode; }; struct anon_vma_chain { struct anon_vma *anon_vma; @@ -1632,3 +1645,18 @@ static inline pgoff_t linear_virt_page_index(const struct vm_area_struct *vma, return pgoff; } + +static inline struct inode *file_inode(const struct file *f) +{ + return f->f_inode; +} + +static inline unsigned iminor(const struct inode *inode) +{ + return MINOR(inode->i_rdev); +} + +static inline unsigned imajor(const struct inode *inode) +{ + return MAJOR(inode->i_rdev); +} -- 2.55.0

