Skip to contentSkip to conversation

hwpoison checks for /dev/mem etc.

PATCH New Wu Fengguang 1 participant 5 messages Jan 30, 2010

Subsystems (6)
lore
Share & tools
lore
#0 lore raw

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

#1 lore raw

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.

CC Greg KH <[email protected]>
CC Andi Kleen <[email protected]>
CC Benjamin Herrenschmidt <[email protected]>
CC Christoph Lameter <[email protected]>
CC Ingo Molnar <[email protected]>
CC Tejun Heo <[email protected]>
CC Nick Piggin <[email protected]>
CC KAMEZAWA Hiroyuki <[email protected]>
---
 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)
#2 lore raw

PATCH 2/4 hwpoison: prevent /dev/mem from accessing hwpoison pages

In reply to Wu Fengguang

Return 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 * fi
 
 	while (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 * f
 	while (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_struct
 static 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))
#3 lore raw

PATCH 3/4 hwpoison: prevent /dev/kcore from accessing hwpoison pages

In reply to Wu Fengguang

Silently 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.

CC Ingo Molnar <[email protected]>
CC Andi Kleen <[email protected]>
CC Pekka Enberg <[email protected]>
---
 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(unsig
 int 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;
 }
 
 /*
#4 lore raw

PATCH 4/4 hwpoison: avoid "still referenced by -1 users" warning

In reply to Wu Fengguang

Get 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);