diff options
author | Kirill A. Shutemov <kirill.shutemov@linux.intel.com> | 2018-07-26 19:37:35 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2018-07-26 22:38:03 -0400 |
commit | bfd40eaff5abb9f62c8ef94ca13ed0d94a560f10 (patch) | |
tree | 21093daa88e9da939477f6957a4ccc2edb06f972 | |
parent | 2c4541e24c55e2847bede93e33d749280edd429a (diff) |
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+ #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
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/20180724121139.62570-4-kirill.shutemov@linux.intel.com
Signed-off-by: Kirill A. Shutemov <kirill.shutemov@linux.intel.com>
Reported-by: syzbot+3f84280d52be9b7083cc@syzkaller.appspotmail.com
Acked-by: Linus Torvalds <torvalds@linux-foundation.org>
Reviewed-by: Andrew Morton <akpm@linux-foundation.org>
Cc: Dmitry Vyukov <dvyukov@google.com>
Cc: Oleg Nesterov <oleg@redhat.com>
Cc: Andrea Arcangeli <aarcange@redhat.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r-- | drivers/char/mem.c | 1 | ||||
-rw-r--r-- | fs/exec.c | 1 | ||||
-rw-r--r-- | include/linux/mm.h | 8 | ||||
-rw-r--r-- | mm/mmap.c | 3 | ||||
-rw-r--r-- | mm/nommu.c | 2 |
5 files changed, 15 insertions, 0 deletions
diff --git a/drivers/char/mem.c b/drivers/char/mem.c index ffeb60d3434c..df66a9dd0aae 100644 --- a/drivers/char/mem.c +++ b/drivers/char/mem.c | |||
@@ -708,6 +708,7 @@ static int mmap_zero(struct file *file, struct vm_area_struct *vma) | |||
708 | #endif | 708 | #endif |
709 | if (vma->vm_flags & VM_SHARED) | 709 | if (vma->vm_flags & VM_SHARED) |
710 | return shmem_zero_setup(vma); | 710 | return shmem_zero_setup(vma); |
711 | vma_set_anonymous(vma); | ||
711 | return 0; | 712 | return 0; |
712 | } | 713 | } |
713 | 714 | ||
@@ -293,6 +293,7 @@ static int __bprm_mm_init(struct linux_binprm *bprm) | |||
293 | bprm->vma = vma = vm_area_alloc(mm); | 293 | bprm->vma = vma = vm_area_alloc(mm); |
294 | if (!vma) | 294 | if (!vma) |
295 | return -ENOMEM; | 295 | return -ENOMEM; |
296 | vma_set_anonymous(vma); | ||
296 | 297 | ||
297 | if (down_write_killable(&mm->mmap_sem)) { | 298 | if (down_write_killable(&mm->mmap_sem)) { |
298 | err = -EINTR; | 299 | err = -EINTR; |
diff --git a/include/linux/mm.h b/include/linux/mm.h index 31540f166987..7ba6d356d18f 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h | |||
@@ -454,10 +454,18 @@ struct vm_operations_struct { | |||
454 | 454 | ||
455 | static inline void vma_init(struct vm_area_struct *vma, struct mm_struct *mm) | 455 | static inline void vma_init(struct vm_area_struct *vma, struct mm_struct *mm) |
456 | { | 456 | { |
457 | static const struct vm_operations_struct dummy_vm_ops = {}; | ||
458 | |||
457 | vma->vm_mm = mm; | 459 | vma->vm_mm = mm; |
460 | vma->vm_ops = &dummy_vm_ops; | ||
458 | INIT_LIST_HEAD(&vma->anon_vma_chain); | 461 | INIT_LIST_HEAD(&vma->anon_vma_chain); |
459 | } | 462 | } |
460 | 463 | ||
464 | static inline void vma_set_anonymous(struct vm_area_struct *vma) | ||
465 | { | ||
466 | vma->vm_ops = NULL; | ||
467 | } | ||
468 | |||
461 | struct mmu_gather; | 469 | struct mmu_gather; |
462 | struct inode; | 470 | struct inode; |
463 | 471 | ||
@@ -1778,6 +1778,8 @@ unsigned long mmap_region(struct file *file, unsigned long addr, | |||
1778 | error = shmem_zero_setup(vma); | 1778 | error = shmem_zero_setup(vma); |
1779 | if (error) | 1779 | if (error) |
1780 | goto free_vma; | 1780 | goto free_vma; |
1781 | } else { | ||
1782 | vma_set_anonymous(vma); | ||
1781 | } | 1783 | } |
1782 | 1784 | ||
1783 | vma_link(mm, vma, prev, rb_link, rb_parent); | 1785 | vma_link(mm, vma, prev, rb_link, rb_parent); |
@@ -2983,6 +2985,7 @@ static int do_brk_flags(unsigned long addr, unsigned long len, unsigned long fla | |||
2983 | return -ENOMEM; | 2985 | return -ENOMEM; |
2984 | } | 2986 | } |
2985 | 2987 | ||
2988 | vma_set_anonymous(vma); | ||
2986 | vma->vm_start = addr; | 2989 | vma->vm_start = addr; |
2987 | vma->vm_end = addr + len; | 2990 | vma->vm_end = addr + len; |
2988 | vma->vm_pgoff = pgoff; | 2991 | vma->vm_pgoff = pgoff; |
diff --git a/mm/nommu.c b/mm/nommu.c index 1d22fdbf7d7c..9fc9e43335b6 100644 --- a/mm/nommu.c +++ b/mm/nommu.c | |||
@@ -1145,6 +1145,8 @@ static int do_mmap_private(struct vm_area_struct *vma, | |||
1145 | if (ret < len) | 1145 | if (ret < len) |
1146 | memset(base + ret, 0, len - ret); | 1146 | memset(base + ret, 0, len - ret); |
1147 | 1147 | ||
1148 | } else { | ||
1149 | vma_set_anonymous(vma); | ||
1148 | } | 1150 | } |
1149 | 1151 | ||
1150 | return 0; | 1152 | return 0; |