Skip to content

Commit bb666b7

Browse files
lorenzo-stoakesakpm00
authored andcommitted
mm: add mmap_prepare() compatibility layer for nested file systems
Nested file systems, that is those which invoke call_mmap() within their own f_op->mmap() handlers, may encounter underlying file systems which provide the f_op->mmap_prepare() hook introduced by commit c84bf6d ("mm: introduce new .mmap_prepare() file callback"). We have a chicken-and-egg scenario here - until all file systems are converted to using .mmap_prepare(), we cannot convert these nested handlers, as we can't call f_op->mmap from an .mmap_prepare() hook. So we have to do it the other way round - invoke the .mmap_prepare() hook from an .mmap() one. in order to do so, we need to convert VMA state into a struct vm_area_desc descriptor, invoking the underlying file system's f_op->mmap_prepare() callback passing a pointer to this, and then setting VMA state accordingly and safely. This patch achieves this via the compat_vma_mmap_prepare() function, which we invoke from call_mmap() if f_op->mmap_prepare() is specified in the passed in file pointer. We place the fundamental logic into mm/vma.h where VMA manipulation belongs. We also update the VMA userland tests to accommodate the changes. The compat_vma_mmap_prepare() function and its associated machinery is temporary, and will be removed once the conversion of file systems is complete. We carefully place this code so it can be used with CONFIG_MMU and also with cutting edge nommu silicon. [[email protected]: export compat_vma_mmap_prepare tp fix build] [[email protected]: remove unused declarations] Link: https://lkml.kernel.org/r/[email protected] Link: https://lkml.kernel.org/r/[email protected] Fixes: c84bf6d ("mm: introduce new .mmap_prepare() file callback"). Signed-off-by: Lorenzo Stoakes <[email protected]> Reported-by: Jann Horn <[email protected]> Closes: https://lore.kernel.org/linux-mm/CAG48ez04yOEVx1ekzOChARDDBZzAKwet8PEoPM4Ln3_rk91AzQ@mail.gmail.com/ Reviewed-by: Pedro Falcato <[email protected]> Reviewed-by: Vlastimil Babka <[email protected]> Cc: Al Viro <[email protected]> Cc: Christian Brauner <[email protected]> Cc: Jan Kara <[email protected]> Cc: Jann Horn <[email protected]> Cc: Liam Howlett <[email protected]> Signed-off-by: Andrew Morton <[email protected]>
1 parent 66ac1a4 commit bb666b7

File tree

5 files changed

+107
-3
lines changed

5 files changed

+107
-3
lines changed

include/linux/fs.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2274,10 +2274,12 @@ static inline bool file_has_valid_mmap_hooks(struct file *file)
22742274
return true;
22752275
}
22762276

2277+
int compat_vma_mmap_prepare(struct file *file, struct vm_area_struct *vma);
2278+
22772279
static inline int call_mmap(struct file *file, struct vm_area_struct *vma)
22782280
{
2279-
if (WARN_ON_ONCE(file->f_op->mmap_prepare))
2280-
return -EINVAL;
2281+
if (file->f_op->mmap_prepare)
2282+
return compat_vma_mmap_prepare(file, vma);
22812283

22822284
return file->f_op->mmap(file, vma);
22832285
}

mm/util.c

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1131,3 +1131,43 @@ void flush_dcache_folio(struct folio *folio)
11311131
}
11321132
EXPORT_SYMBOL(flush_dcache_folio);
11331133
#endif
1134+
1135+
/**
1136+
* compat_vma_mmap_prepare() - Apply the file's .mmap_prepare() hook to an
1137+
* existing VMA
1138+
* @file: The file which possesss an f_op->mmap_prepare() hook
1139+
* @vma: The VMA to apply the .mmap_prepare() hook to.
1140+
*
1141+
* Ordinarily, .mmap_prepare() is invoked directly upon mmap(). However, certain
1142+
* 'wrapper' file systems invoke a nested mmap hook of an underlying file.
1143+
*
1144+
* Until all filesystems are converted to use .mmap_prepare(), we must be
1145+
* conservative and continue to invoke these 'wrapper' filesystems using the
1146+
* deprecated .mmap() hook.
1147+
*
1148+
* However we have a problem if the underlying file system possesses an
1149+
* .mmap_prepare() hook, as we are in a different context when we invoke the
1150+
* .mmap() hook, already having a VMA to deal with.
1151+
*
1152+
* compat_vma_mmap_prepare() is a compatibility function that takes VMA state,
1153+
* establishes a struct vm_area_desc descriptor, passes to the underlying
1154+
* .mmap_prepare() hook and applies any changes performed by it.
1155+
*
1156+
* Once the conversion of filesystems is complete this function will no longer
1157+
* be required and will be removed.
1158+
*
1159+
* Returns: 0 on success or error.
1160+
*/
1161+
int compat_vma_mmap_prepare(struct file *file, struct vm_area_struct *vma)
1162+
{
1163+
struct vm_area_desc desc;
1164+
int err;
1165+
1166+
err = file->f_op->mmap_prepare(vma_to_desc(vma, &desc));
1167+
if (err)
1168+
return err;
1169+
set_vma_from_desc(vma, &desc);
1170+
1171+
return 0;
1172+
}
1173+
EXPORT_SYMBOL(compat_vma_mmap_prepare);

mm/vma.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3113,7 +3113,6 @@ int __vm_munmap(unsigned long start, size_t len, bool unlock)
31133113
return ret;
31143114
}
31153115

3116-
31173116
/* Insert vm structure into process list sorted by address
31183117
* and into the inode's i_mmap tree. If vm_file is non-NULL
31193118
* then i_mmap_rwsem is taken here.

mm/vma.h

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,53 @@ static inline int vma_iter_store_gfp(struct vma_iterator *vmi,
222222
return 0;
223223
}
224224

225+
226+
/*
227+
* Temporary helper functions for file systems which wrap an invocation of
228+
* f_op->mmap() but which might have an underlying file system which implements
229+
* f_op->mmap_prepare().
230+
*/
231+
232+
static inline struct vm_area_desc *vma_to_desc(struct vm_area_struct *vma,
233+
struct vm_area_desc *desc)
234+
{
235+
desc->mm = vma->vm_mm;
236+
desc->start = vma->vm_start;
237+
desc->end = vma->vm_end;
238+
239+
desc->pgoff = vma->vm_pgoff;
240+
desc->file = vma->vm_file;
241+
desc->vm_flags = vma->vm_flags;
242+
desc->page_prot = vma->vm_page_prot;
243+
244+
desc->vm_ops = NULL;
245+
desc->private_data = NULL;
246+
247+
return desc;
248+
}
249+
250+
static inline void set_vma_from_desc(struct vm_area_struct *vma,
251+
struct vm_area_desc *desc)
252+
{
253+
/*
254+
* Since we're invoking .mmap_prepare() despite having a partially
255+
* established VMA, we must take care to handle setting fields
256+
* correctly.
257+
*/
258+
259+
/* Mutable fields. Populated with initial state. */
260+
vma->vm_pgoff = desc->pgoff;
261+
if (vma->vm_file != desc->file)
262+
vma_set_file(vma, desc->file);
263+
if (vma->vm_flags != desc->vm_flags)
264+
vm_flags_set(vma, desc->vm_flags);
265+
vma->vm_page_prot = desc->page_prot;
266+
267+
/* User-defined fields. */
268+
vma->vm_ops = desc->vm_ops;
269+
vma->vm_private_data = desc->private_data;
270+
}
271+
225272
int
226273
do_vmi_align_munmap(struct vma_iterator *vmi, struct vm_area_struct *vma,
227274
struct mm_struct *mm, unsigned long start,

tools/testing/vma/vma_internal.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,14 @@ typedef __bitwise unsigned int vm_fault_t;
159159

160160
#define ASSERT_EXCLUSIVE_WRITER(x)
161161

162+
/**
163+
* swap - swap values of @a and @b
164+
* @a: first value
165+
* @b: second value
166+
*/
167+
#define swap(a, b) \
168+
do { typeof(a) __tmp = (a); (a) = (b); (b) = __tmp; } while (0)
169+
162170
struct kref {
163171
refcount_t refcount;
164172
};
@@ -1468,4 +1476,12 @@ static inline void fixup_hugetlb_reservations(struct vm_area_struct *vma)
14681476
(void)vma;
14691477
}
14701478

1479+
static inline void vma_set_file(struct vm_area_struct *vma, struct file *file)
1480+
{
1481+
/* Changing an anonymous vma with this is illegal */
1482+
get_file(file);
1483+
swap(vma->vm_file, file);
1484+
fput(file);
1485+
}
1486+
14711487
#endif /* __MM_VMA_INTERNAL_H */

0 commit comments

Comments
 (0)