From: Craig Bergstrom Date: Wed, 15 Nov 2017 22:29:51 +0000 (-0700) Subject: x86/mm: Limit mmap() of /dev/mem to valid physical addresses X-Git-Tag: v4.1.12-124.31.3~672 X-Git-Url: https://www.infradead.org/git/?a=commitdiff_plain;h=856a704baff47d18ca95b216fcea516da0c45cfb;p=users%2Fjedix%2Flinux-maple.git x86/mm: Limit mmap() of /dev/mem to valid physical addresses One thing /dev/mem access APIs should verify is that there's no way that excessively large pfn's can leak into the high bits of the page table entry. In particular, if people can use "very large physical page addresses" through /dev/mem to set the bits past bit 58 - SOFTW4 and permission key bits and NX bit, that could *really* confuse the kernel. We had an earlier attempt: ce56a86e2ade ("x86/mm: Limit mmap() of /dev/mem to valid physical addresses") ... which turned out to be too restrictive (breaking mem=... bootups for example) and had to be reverted in: 90edaac62729 ("Revert "x86/mm: Limit mmap() of /dev/mem to valid physical addresses"") This v2 attempt modifies the original patch and makes sure that mmap(/dev/mem) limits the pfns so that it at least fits in the actual pteval_t architecturally: - Make sure mmap_mem() actually validates that the offset fits in phys_addr_t ( This may be indirectly true due to some other check, but it's not entirely obvious. ) - Change valid_mmap_phys_addr_range() to just use phys_addr_valid() on the top byte ( Top byte is sufficient, because mmap_mem() has already checked that it cannot wrap. ) - Add a few comments about what the valid_phys_addr_range() vs. valid_mmap_phys_addr_range() difference is. Signed-off-by: Craig Bergstrom [ Fixed the checks and added comments. ] Signed-off-by: Linus Torvalds [ Collected the discussion and patches into a commit. ] Cc: Boris Ostrovsky Cc: Fengguang Wu Cc: Greg Kroah-Hartman Cc: Hans Verkuil Cc: Mauro Carvalho Chehab Cc: Peter Zijlstra Cc: Sander Eikelenboom Cc: Sean Young Cc: Thomas Gleixner Link: http://lkml.kernel.org/r/CA+55aFyEcOMb657vWSmrM13OxmHxC-XxeBmNis=DwVvpJUOogQ@mail.gmail.com Signed-off-by: Ingo Molnar Orabug: 28220674 CVE: CVE-2018-3620 (cherry picked from commit be62a32044061cb4a3b70a10598e093f1319102e) Signed-off-by: Mihai Carabas Reviewed-by: Darren Kenny Reviewed-by: Boris Ostrovsky Conflicts: drivers/char/mem.c Contextual: we have another structure. Just ditch that check. --- diff --git a/arch/x86/include/asm/io.h b/arch/x86/include/asm/io.h index 81942efae8a2..4f8cc169b38c 100644 --- a/arch/x86/include/asm/io.h +++ b/arch/x86/include/asm/io.h @@ -101,6 +101,10 @@ build_mmio_write(writeq, "q", unsigned long, "r", :"memory") #endif +#define ARCH_HAS_VALID_PHYS_ADDR_RANGE +extern int valid_phys_addr_range(phys_addr_t addr, size_t size); +extern int valid_mmap_phys_addr_range(unsigned long pfn, size_t size); + /** * virt_to_phys - map virtual addresses to physical * @address: address to remap diff --git a/arch/x86/mm/mmap.c b/arch/x86/mm/mmap.c index c57eb534aa00..cb87336f44e1 100644 --- a/arch/x86/mm/mmap.c +++ b/arch/x86/mm/mmap.c @@ -31,6 +31,8 @@ #include #include +#include "physaddr.h" + struct va_alignment __read_mostly va_align = { .flags = -1, }; @@ -167,3 +169,17 @@ bool mmap_address_hint_valid(unsigned long addr, unsigned long len) return (addr > TASK_SIZE_MAX) == (addr + len > TASK_SIZE_MAX); } + +/* Can we access it for direct reading/writing? Must be RAM: */ +int valid_phys_addr_range(phys_addr_t addr, size_t count) +{ + return addr + count <= __pa(high_memory); +} + +/* Can we access it through mmap? Must be a valid physical address: */ +int valid_mmap_phys_addr_range(unsigned long pfn, size_t count) +{ + phys_addr_t addr = (phys_addr_t)pfn << PAGE_SHIFT; + + return phys_addr_valid(addr + count - 1); +} diff --git a/drivers/char/mem.c b/drivers/char/mem.c index 5ffb67599431..b0f151c4f212 100644 --- a/drivers/char/mem.c +++ b/drivers/char/mem.c @@ -344,6 +344,11 @@ static const struct vm_operations_struct mmap_mem_ops = { static int mmap_mem(struct file *file, struct vm_area_struct *vma) { size_t size = vma->vm_end - vma->vm_start; + phys_addr_t offset = (phys_addr_t)vma->vm_pgoff << PAGE_SHIFT; + + /* Does it even fit in phys_addr_t? */ + if (offset >> PAGE_SHIFT != vma->vm_pgoff) + return -EINVAL; if (!valid_mmap_phys_addr_range(vma->vm_pgoff, size)) return -EINVAL;