On 9/2/26 20:00, Lorenzo Stoakes (ARM) wrote:
> 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.
>
> Therefore use the newly introduced file_is_dev_zero() to uniquely identify
> MAP_PRIVATE-/dev/zero mappings and only permit this behaviour for them.
>
> 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 do so.
>
> 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.
>
> Now we're setting vma->vm_ops to NULL for an mmap_prepare-initialised
> MAP_PRIVATE-/dev/zero mapping, we have to avoid a subtle issue when
> updating user-defined fields via set_vma_user_defined_fields().
>
> The default for vma->vm_ops for all mmap_prepare-initialised mappings is
> vma_dummy_vm_ops, so map->vm_ops will be set to this and setting
> vma->vm_ops to this will render the VMA mistakenly non-anon.
>
> In general, we should never be setting user-defined fields for an anonymous
> VMA, so explicitly check for this to avoid doing so for the one case where
> a mapping can be both mmap_prepare and anonymous.
>
> In the case of legacy ->mmap hooks some drivers may set vma->vm_ops NULL
> believing this is the equivalent of setting no VMA operations. Therefore
> update mmap_file() to correct this by setting dummy VMA operations if this
> occurs.
>
> An example of this is drm_gem_shmem_mmap() which deliberately clears
> vma->vm_ops before handing the VMA to dma-buf. Cases such as this will be
> updated when they are converted to mmap_prepare.
>
> Also, in order to avoid a single commit bisection hazard, add a temporary
> workaround to set the VMA anonymous only after vma->vm_file is assigned in
> __mmap_new_file_vma().
>
> This is because vma_set_range() calls vma_set_pgoff() and
> assert_sane_pgoff() in turn, prior to the vma->vm_file being assigned. If
> we set the VMA anonymous early then this assert will fail.
>
> This is removed in the subsequent commit.
>
> Signed-off-by: Lorenzo Stoakes (ARM) <[email protected]>
> ---
> mm/char-mem.c | 6 +-----
> mm/internal.h | 17 ++++++++++-------
> mm/vma.c | 33 +++++++++++++++++++++++++--------
> 3 files changed, 36 insertions(+), 20 deletions(-)
>
> diff --git a/mm/char-mem.c b/mm/char-mem.c
> index e53e89e6ddd8..c0b5fb019223 100644
> --- a/mm/char-mem.c
> +++ b/mm/char-mem.c
> @@ -508,11 +508,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 of for us by core mm. */
> return 0;
> }
>
> diff --git a/mm/internal.h b/mm/internal.h
> index 5d474e5f7709..da14c56fb24e 100644
> --- a/mm/internal.h
> +++ b/mm/internal.h
> @@ -226,15 +226,18 @@ static inline int mmap_file(struct file *file, struct
> vm_area_struct *vma)
> {
> int err = vfs_mmap(file, vma);
>
> - if (likely(!err))
> - return 0;
> -
> /*
> - * OK, we tried to call the file hook for mmap(), but an error
> - * arose. The mapping is in an inconsistent state and we must not invoke
> - * any further hooks on it.
> + * 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
> + * operations.
> + *
> + * In the former case the VMA is in an inconsistent state and we mustn't
> + * invoke any further hooks on it, in the latter case the hook actually
> + * wanted no further hooks to be invoked, so fix both by setting dummy
> + * VMA ops.
> */
> - vma->vm_ops = &vma_dummy_vm_ops;
> + if (unlikely(err || !vma->vm_ops))
> + vma->vm_ops = &vma_dummy_vm_ops;
>
> return err;
> }
> diff --git a/mm/vma.c b/mm/vma.c
> index 35e7a64855fa..4b8d430d9619 100644
> --- a/mm/vma.c
> +++ b/mm/vma.c
> @@ -2621,6 +2621,19 @@ static int __mmap_new_file_vma(struct mmap_state *map,
> return 0;
> }
>
> +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)
I was wondering whether we should call this "map_is_private_anon", due to
MAP_ANON|MAP_SHARED. But looking at __mmap_new_vma(), the existing "is_anon" is
also limited to MAP_ANON|MAP_PRIVATE.
> +{
> + if (!map_is_private(map))
> + return false;
> +
> + return !map->file || file_is_dev_zero(map->file);
> +}
> +
> /*
> * __mmap_new_vma() - Allocate a new VMA for the region, as merging was not
> * possible.
> @@ -2634,8 +2647,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;
> @@ -2651,7 +2663,7 @@ static int __mmap_new_vma(struct mmap_state *map,
> struct vm_area_struct **vmap,
>
> vma_iter_config(vmi, map->addr, map->end);
>
> - if (is_anon)
> + if (is_anon && !map->file)
> vma_set_anonymous(vma);
>
> vma_set_range(vma, map->addr, map->end, map->pgoff, map->anon_pgoff);
> @@ -2669,6 +2681,10 @@ static int __mmap_new_vma(struct mmap_state *map,
> struct vm_area_struct **vmap,
> else if (!is_anon)
> error = shmem_zero_setup(vma);
>
> + /* Temporary MAP_PRIVATE-/dev/zero workaround. */
> + if (is_anon && map->file)
> + vma_set_anonymous(vma);
> +
> if (error)
> goto free_iter_vma;
>
> @@ -2777,6 +2793,10 @@ static int call_mmap_prepare(struct mmap_state *map,
> if (err)
> return err;
>
> + /* Hooks cannot mark themselves anonymous. */
I guess this comment will be stale soon (after #4 where you drop the
set_anonymous part).
Should it be
"vm_ops are strictly required with mmap_prepare"
or sth like that?
> + if (!desc->vm_ops)
> + return -EINVAL;
> +
> err = call_action_prepare(map, desc);
> if (err)
> return err;
> @@ -2799,10 +2819,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;
> }
>
> @@ -2882,7 +2899,7 @@ static unsigned long __mmap_region(struct file *file,
> unsigned long addr,
> allocated_new = true;
> }
>
> - if (have_mmap_prepare)
> + if (have_mmap_prepare && !map_is_anon(&map))
> set_vma_user_defined_fields(vma, &map);
Ah, we have mmap_zero_prepare() for handling the shmem_zero_setup_desc(). I was
just about to ask whether we can just get rid of this here.
But, hold on, do we now even need that? Could core-mm now take care of that as
well, and we could just remove mmap_zero_prepare() entirely?
That is, we'd make shmem_zero_setup() in __mmap_new_vma() take care of this?
Then we might not even need shmem_zero_setup_desc() anymore.
Maybe harder than it sounds at first.
--
Cheers,
David