Skip to content

Commit 97478b5

Browse files
kirylsfrothwell
authored andcommitted
mm: Fix vma_is_anonymous() false-positives
vma_is_anonymous() relies on ->vm_ops being NULL to detect anonymous VMA. This is unreliable as ->mmap may not set ->vm_ops. False-positive vma_is_anonymous() may lead to crashes: next ffff8801ce5e7040 prev ffff8801d20eca50 mm ffff88019c1e13c0 prot 27 anon_vma ffff88019680cdd8 vm_ops 0000000000000000 pgoff 0 file ffff8801b2ec2d00 private_data 0000000000000000 flags: 0xff(read|write|exec|shared|mayread|maywrite|mayexec|mayshare) ------------[ cut here ]------------ kernel BUG at mm/memory.c:1422! invalid opcode: 0000 [#1] SMP KASAN CPU: 0 PID: 18486 Comm: syz-executor3 Not tainted 4.18.0-rc3+ torvalds#136 Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 01/01/2011 RIP: 0010:zap_pmd_range mm/memory.c:1421 [inline] RIP: 0010:zap_pud_range mm/memory.c:1466 [inline] RIP: 0010:zap_p4d_range mm/memory.c:1487 [inline] RIP: 0010:unmap_page_range+0x1c18/0x2220 mm/memory.c:1508 Code: ff 31 ff 4c 89 e6 42 c6 04 33 f8 e8 92 dd d0 ff 4d 85 e4 0f 85 4a eb ff ff e8 54 dc d0 ff 48 8b bd 10 fc ff ff e8 82 95 fe ff <0f> 0b e8 41 dc d0 ff 0f 0b 4c 89 ad 18 fc ff ff c7 85 7c fb ff ff RSP: 0018:ffff8801b0587330 EFLAGS: 00010286 RAX: 000000000000013c RBX: 1ffff100360b0e9c RCX: ffffc90002620000 RDX: 0000000000000000 RSI: ffffffff81631851 RDI: 0000000000000001 RBP: ffff8801b05877c8 R08: ffff880199d40300 R09: ffffed003b5c4fc0 R10: ffffed003b5c4fc0 R11: ffff8801dae27e07 R12: 0000000000000000 R13: ffff88019c1e13c0 R14: dffffc0000000000 R15: 0000000020e01000 FS: 00007fca32251700(0000) GS:ffff8801dae00000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007f04c540d000 CR3: 00000001ac1f0000 CR4: 00000000001426f0 DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000 DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400 Call Trace: unmap_single_vma+0x1a0/0x310 mm/memory.c:1553 zap_page_range_single+0x3cc/0x580 mm/memory.c:1644 unmap_mapping_range_vma mm/memory.c:2792 [inline] unmap_mapping_range_tree mm/memory.c:2813 [inline] unmap_mapping_pages+0x3a7/0x5b0 mm/memory.c:2845 unmap_mapping_range+0x48/0x60 mm/memory.c:2880 truncate_pagecache+0x54/0x90 mm/truncate.c:800 truncate_setsize+0x70/0xb0 mm/truncate.c:826 simple_setattr+0xe9/0x110 fs/libfs.c:409 notify_change+0xf13/0x10f0 fs/attr.c:335 do_truncate+0x1ac/0x2b0 fs/open.c:63 do_sys_ftruncate+0x492/0x560 fs/open.c:205 __do_sys_ftruncate fs/open.c:215 [inline] __se_sys_ftruncate fs/open.c:213 [inline] __x64_sys_ftruncate+0x59/0x80 fs/open.c:213 do_syscall_64+0x1b9/0x820 arch/x86/entry/common.c:290 entry_SYSCALL_64_after_hwframe+0x49/0xbe Reproducer: #include <stdio.h> #include <stddef.h> #include <stdint.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <unistd.h> #include <fcntl.h> #define KCOV_INIT_TRACE _IOR('c', 1, unsigned long) #define KCOV_ENABLE _IO('c', 100) #define KCOV_DISABLE _IO('c', 101) #define COVER_SIZE (1024<<10) #define KCOV_TRACE_PC 0 #define KCOV_TRACE_CMP 1 int main(int argc, char **argv) { int fd; unsigned long *cover; system("mount -t debugfs none /sys/kernel/debug"); fd = open("/sys/kernel/debug/kcov", O_RDWR); ioctl(fd, KCOV_INIT_TRACE, COVER_SIZE); cover = mmap(NULL, COVER_SIZE * sizeof(unsigned long), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); munmap(cover, COVER_SIZE * sizeof(unsigned long)); cover = mmap(NULL, COVER_SIZE * sizeof(unsigned long), PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); memset(cover, 0, COVER_SIZE * sizeof(unsigned long)); ftruncate(fd, 3UL << 20); return 0; } This can be fixed by assigning anonymous VMAs own vm_ops and not relying on it being NULL. If ->mmap() failed to set ->vm_ops, mmap_region() will set it to dummy_vm_ops. This way we will have non-NULL ->vm_ops for all VMAs. Link: http://lkml.kernel.org/r/[email protected] Signed-off-by: Kirill A. Shutemov <[email protected]> Reported-by: [email protected] Reviewed-by: Andrew Morton <[email protected]> Cc: Dmitry Vyukov <[email protected]> Cc: Oleg Nesterov <[email protected]> Cc: Andrea Arcangeli <[email protected]> Cc: <[email protected]> Signed-off-by: Andrew Morton <[email protected]> Signed-off-by: Stephen Rothwell <[email protected]>
1 parent 9f15bde commit 97478b5

File tree

7 files changed

+23
-3
lines changed

7 files changed

+23
-3
lines changed

drivers/char/mem.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,7 @@ static int mmap_zero(struct file *file, struct vm_area_struct *vma)
708708
#endif
709709
if (vma->vm_flags & VM_SHARED)
710710
return shmem_zero_setup(vma);
711+
vma->vm_ops = &anon_vm_ops;
711712
return 0;
712713
}
713714

fs/exec.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@ static int __bprm_mm_init(struct linux_binprm *bprm)
307307
* configured yet.
308308
*/
309309
BUILD_BUG_ON(VM_STACK_FLAGS & VM_STACK_INCOMPLETE_SETUP);
310+
vma->vm_ops = &anon_vm_ops;
310311
vma->vm_end = STACK_TOP_MAX;
311312
vma->vm_start = vma->vm_end - PAGE_SIZE;
312313
vma->vm_flags = VM_SOFTDIRTY | VM_STACK_FLAGS | VM_STACK_INCOMPLETE_SETUP;

fs/hugetlbfs/inode.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,7 @@ static long hugetlbfs_fallocate(struct file *file, int mode, loff_t offset,
597597
memset(&pseudo_vma, 0, sizeof(struct vm_area_struct));
598598
pseudo_vma.vm_flags = (VM_HUGETLB | VM_MAYSHARE | VM_SHARED);
599599
pseudo_vma.vm_file = file;
600+
pseudo_vma.vm_ops = &dummy_vm_ops;
600601

601602
for (index = start; index < end; index++) {
602603
/*

include/linux/mm.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1536,9 +1536,12 @@ int clear_page_dirty_for_io(struct page *page);
15361536

15371537
int get_cmdline(struct task_struct *task, char *buffer, int buflen);
15381538

1539+
extern const struct vm_operations_struct anon_vm_ops;
1540+
extern const struct vm_operations_struct dummy_vm_ops;
1541+
15391542
static inline bool vma_is_anonymous(struct vm_area_struct *vma)
15401543
{
1541-
return !vma->vm_ops;
1544+
return vma->vm_ops == &anon_vm_ops;
15421545
}
15431546

15441547
#ifdef CONFIG_SHMEM

mm/khugepaged.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,7 @@ int khugepaged_enter_vma_merge(struct vm_area_struct *vma,
440440
* page fault if needed.
441441
*/
442442
return 0;
443-
if (vma->vm_ops || (vm_flags & VM_NO_KHUGEPAGED))
443+
if (!vma_is_anonymous(vma) || (vm_flags & VM_NO_KHUGEPAGED))
444444
/* khugepaged not yet working on file or special mappings */
445445
return 0;
446446
hstart = (vma->vm_start + ~HPAGE_PMD_MASK) & HPAGE_PMD_MASK;
@@ -831,7 +831,7 @@ static bool hugepage_vma_check(struct vm_area_struct *vma)
831831
return IS_ALIGNED((vma->vm_start >> PAGE_SHIFT) - vma->vm_pgoff,
832832
HPAGE_PMD_NR);
833833
}
834-
if (!vma->anon_vma || vma->vm_ops)
834+
if (!vma->anon_vma || !vma_is_anonymous(vma))
835835
return false;
836836
if (is_vma_temporary_stack(vma))
837837
return false;

mm/mmap.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ int mmap_rnd_compat_bits __read_mostly = CONFIG_ARCH_MMAP_RND_COMPAT_BITS;
7171
static bool ignore_rlimit_data;
7272
core_param(ignore_rlimit_data, ignore_rlimit_data, bool, 0644);
7373

74+
const struct vm_operations_struct anon_vm_ops = {};
75+
const struct vm_operations_struct dummy_vm_ops = {};
76+
7477
static void unmap_region(struct mm_struct *mm,
7578
struct vm_area_struct *vma, struct vm_area_struct *prev,
7679
unsigned long start, unsigned long end);
@@ -561,6 +564,8 @@ static unsigned long count_vma_pages_range(struct mm_struct *mm,
561564
void __vma_link_rb(struct mm_struct *mm, struct vm_area_struct *vma,
562565
struct rb_node **rb_link, struct rb_node *rb_parent)
563566
{
567+
WARN_ONCE(!vma->vm_ops, "missing vma->vm_ops");
568+
564569
/* Update tracking information for the gap following the new vma. */
565570
if (vma->vm_next)
566571
vma_gap_update(vma->vm_next);
@@ -1774,12 +1779,19 @@ unsigned long mmap_region(struct file *file, unsigned long addr,
17741779
*/
17751780
WARN_ON_ONCE(addr != vma->vm_start);
17761781

1782+
/* All mappings must have ->vm_ops set */
1783+
if (!vma->vm_ops)
1784+
vma->vm_ops = &dummy_vm_ops;
1785+
17771786
addr = vma->vm_start;
17781787
vm_flags = vma->vm_flags;
17791788
} else if (vm_flags & VM_SHARED) {
17801789
error = shmem_zero_setup(vma);
17811790
if (error)
17821791
goto free_vma;
1792+
} else {
1793+
/* vma_is_anonymous() relies on this. */
1794+
vma->vm_ops = &anon_vm_ops;
17831795
}
17841796

17851797
vma_link(mm, vma, prev, rb_link, rb_parent);
@@ -2992,6 +3004,7 @@ static int do_brk_flags(unsigned long addr, unsigned long len, unsigned long fla
29923004

29933005
INIT_LIST_HEAD(&vma->anon_vma_chain);
29943006
vma->vm_mm = mm;
3007+
vma->vm_ops = &anon_vm_ops;
29953008
vma->vm_start = addr;
29963009
vma->vm_end = addr + len;
29973010
vma->vm_pgoff = pgoff;

mm/shmem.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1424,6 +1424,7 @@ static void shmem_pseudo_vma_init(struct vm_area_struct *vma,
14241424
/* Bias interleave by inode number to distribute better across nodes */
14251425
vma->vm_pgoff = index + info->vfs_inode.i_ino;
14261426
vma->vm_policy = mpol_shared_policy_lookup(&info->policy, index);
1427+
vma->vm_ops = &dummy_vm_ops;
14271428
}
14281429

14291430
static void shmem_pseudo_vma_destroy(struct vm_area_struct *vma)

0 commit comments

Comments
 (0)