hwpoison checks for /dev/mem etc.
Subsystems (6)
cover hwpoison checks for /dev/mem etc.
Andrew and Andi,
This -mm patchset adds hwpoison check for
- /dev/mem
- /dev/kmem
- /proc/kcore
and a trivial hwpoison fix.
It tries to do minimal change, by removing the previously debated vread/vwrite simplification.
Thanks,
Fengguang
PATCH 1/4 hwpoison: prevent /dev/kmem from accessing hwpoison pages
When /dev/kmem read()/write() encounters hwpoison page, stop it and return the amount of work done till now, or return -EIO if nothing have been copied.
For simplicity, hwpoison pages accessed by vmalloc address are siliently skipped, instead of returning -EIO.
--- drivers/char/mem.c | 18 ++++++++++++++---- mm/vmalloc.c | 4 ++-- 2 files changed, 16 insertions(+), 6 deletions(-)
drivers/char/mem.c +14 -4
@@ -426,6 +426,9 @@ static ssize_t read_kmem(struct file *fi*/ kbuf =xlate_dev_kmem_ptr ((char *)p); +if (unlikely (virt_addr_valid (kbuf) && +PageHWPoison (virt_to_page (kbuf)))) +return -EIO ;if (copy_to_user (buf, kbuf, sz))return -EFAULT ; buf += sz;
@@ -471,6 +474,7 @@ do_write_kmem(unsigned long p, const cha {ssize_t written, sz;unsigned long copied; +int err =0 ; written =0 ;#ifdef __ARCH_HAS_NO_PAGE_ZERO_MAPPED
@@ -497,13 +501,19 @@ do_write_kmem(unsigned long p, const cha*/ ptr =xlate_dev_kmem_ptr ((char *)p); +if (unlikely (virt_addr_valid (ptr) && +PageHWPoison (virt_to_page (ptr)))) { + err = -EIO ; +break ; + } + copied =copy_from_user (ptr, buf, sz);if (copied) { written += sz - copied; -if (written) -break ; -return -EFAULT ; + err = -EFAULT ; +break ; } + buf += sz; p += sz; count -= sz;
@@ -511,7 +521,7 @@ do_write_kmem(unsigned long p, const cha } *ppos += written; -return written; +return written ? written : err; }
mm/vmalloc.c +2 -2
@@ -1669,7 +1669,7 @@ static int aligned_vread(char *buf, char* interface, rarely used. Instead of that, we'll use * kmap() and get small overhead in this access function. */ -if (p) { +if (p && !PageHWPoison (p)) {/* * we can expect USER0 is not used (see vread/vwrite's * function description)
@@ -1708,7 +1708,7 @@ static int aligned_vwrite(char *buf, cha* interface, rarely used. Instead of that, we'll use * kmap() and get small overhead in this access function. */ -if (p) { +if (p && !PageHWPoison (p)) {/* * we can expect USER0 is not used (see vread/vwrite's * function description)
PATCH 2/4 hwpoison: prevent /dev/mem from accessing hwpoison pages
In reply to Wu FengguangReturn EIO when user space tries to read/write/mmap hwpoison pages via the /dev/mem interface.
The approach: rename range_is_allowed() to devmem_check_pfn_range(), and add PageHWPoison() test in it. This function will be called for the whole mmap() range, or page by page for read()/write(). So it would fail the mmap() request as a whole, and return partial results for read()/write().
--- drivers/char/mem.c | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-)
drivers/char/mem.c +21 -18
@@ -89,31 +89,28 @@ static inline int valid_mmap_phys_addr_r }#endif -#ifdef CONFIG_STRICT_DEVMEM -static inline int range_is_allowed (unsigned long pfn,unsigned long size) +static int devmem_check_pfn_range (unsigned long pfn,unsigned long bytes) {u64 from = ((u64 )pfn) <<PAGE_SHIFT ; -u64 to = from + size; +u64 to = from + bytes;u64 cursor = from;while (cursor < to) { +#ifdef CONFIG_STRICT_DEVMEM if (!devmem_is_allowed (pfn)) {printk (KERN_INFO "Program %s tried to access /dev/mem between %Lx->%Lx.\n" , current->comm, from, to); -return 0 ; +return -EPERM ; } +#endif +if (pfn_valid (pfn) &&PageHWPoison (pfn_to_page (pfn))) +return -EIO ; cursor +=PAGE_SIZE ; pfn++; } -return 1 ; -} -#else -static inline int range_is_allowed (unsigned long pfn,unsigned long size) -{ -return 1 ; +return 0 ; } -#endif void __attribute__((weak))unxlate_dev_mem_ptr (unsigned long phys,void *addr) {
@@ -150,11 +147,13 @@ static ssize_t read_mem(struct file * fiwhile (count >0 ) {unsigned long remaining; +int err; sz =size_inside_page (p, count); -if (!range_is_allowed (p >>PAGE_SHIFT , count)) -return -EPERM ; + err =devmem_check_pfn_range (p >>PAGE_SHIFT , count); +if (err) +return err;/* * On ia64 if a page has been mapped somewhere as
@@ -184,9 +183,10 @@ static ssize_t write_mem(struct file * f size_t count, loff_t *ppos) { unsigned long p = *ppos; -ssize_t written, sz;unsigned long copied; +ssize_t written, sz;void *ptr; +int err;if (!valid_phys_addr_range (p, count))return -EFAULT ;
@@ -208,8 +208,9 @@ static ssize_t write_mem(struct file * fwhile (count >0 ) { sz =size_inside_page (p, count); -if (!range_is_allowed (p >>PAGE_SHIFT , sz)) -return -EPERM ; + err =devmem_check_pfn_range (p >>PAGE_SHIFT , sz); +if (err) +return err;/* * On ia64 if a page has been mapped somewhere as
@@ -297,6 +298,7 @@ static const struct vm_operations_structstatic int mmap_mem (struct file * file,struct vm_area_struct * vma) {size_t size = vma->vm_end - vma->vm_start; +int err;if (!valid_mmap_phys_addr_range (vma->vm_pgoff, size))return -EINVAL ;
@@ -304,8 +306,9 @@ static int mmap_mem(struct file * file,if (!private_mapping_ok (vma))return -ENOSYS ; -if (!range_is_allowed (vma->vm_pgoff, size)) -return -EPERM ; + err =devmem_check_pfn_range (vma->vm_pgoff, size); +if (err) +return err;if (!phys_mem_access_prot_allowed (file, vma->vm_pgoff, size, &vma->vm_page_prot))
PATCH 3/4 hwpoison: prevent /dev/kcore from accessing hwpoison pages
In reply to Wu FengguangSilently fill buffer with zeros when encounter hwpoison pages (accessing the hwpoison page content is deadly).
This patch does not cover X86_32 - which has a dumb kern_addr_valid(). It is unlikely anyone run a 32bit kernel will care about the hwpoison feature - its usable memory is limited.
--- arch/x86/mm/init_64.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-)
arch/x86/mm/init_64.c +13 -3
@@ -825,6 +825,7 @@ int __init reserve_bootmem_generic(unsigint kern_addr_valid (unsigned long addr) {unsigned long above = ((long )addr) >> __VIRTUAL_MASK_SHIFT; +unsigned long pfn;pgd_t *pgd;pud_t *pud;pmd_t *pmd;
@@ -845,14 +846,23 @@ int kern_addr_valid(unsigned long addr)if (pmd_none (*pmd))return 0 ; -if (pmd_large (*pmd)) -return pfn_valid (pmd_pfn (*pmd)); +if (pmd_large (*pmd)) { + pfn =pmd_pfn (*pmd); + pfn +=pte_index (addr); + goto check_pfn; + } pte =pte_offset_kernel (pmd, addr);if (pte_none (*pte))return 0 ; -return pfn_valid (pte_pfn (*pte)); + pfn =pte_pfn (*pte); +check_pfn: +if (!pfn_valid (pfn)) +return 0 ; +if (PageHWPoison (pfn_to_page (pfn))) +return 0 ; +return 1 ; }/*
PATCH 4/4 hwpoison: avoid "still referenced by -1 users" warning
In reply to Wu FengguangGet rid of the amusing last line, emitted for slab/reserved kernel pages:
[ 328.396842] MCE 0x1ff00: Unknown page state [ 328.399058] MCE 0x1ff00: dirty unknown page state page recovery: Failed [ 328.402465] MCE 0x1ff00: unknown page state page still referenced by -1 users
--- mm/memory-failure.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
mm/memory-failure.c +1 -1
@@ -803,7 +803,7 @@ static int page_action(struct page_state count =page_count (p) -1 ;if (ps->action == me_swapcache_dirty && result ==DELAYED ) count--; -if (count !=0 ) { +if (count >0 ) {printk (KERN_ERR "MCE %#lx: %s page still referenced by %d users\n" , pfn, ps->msg, count);