Skip to contentSkip to conversation

fs/proc/kcore: don't read offline sections, logically offline pages and hwpoisoned pages

PATCH Superseded David Hildenbrand 5 participants 37 messages Apr 29, 2021 – May 6, 2021

Subsystems (10)

v1 of 3 Superseded Latest is v3 →

lore
Share & tools
lore
#0 lore raw

cover fs/proc/kcore: don't read offline sections, logically offline pages and hwpoisoned pages

This is (obviously) for v5.13++; no need to rush with review, but I decided to send it around right away.

Looking for places where the kernel might unconditionally read PageOffline() pages, I stumbled over /proc/kcore; turns out /proc/kcore needs some more love to not touch some other pages we really don't want to read -- i.e., hwpoisoned.

Examples for PageOffline() pages are pages inflated in a balloon, memory unplugged via virtio-mem, and partially-present sections in memory added by the Hyper-V balloon.

When reading pages inflated in a balloon, we essentially produce unnecessary load in the hypervisor; holes in partially present sections in case of Hyper-V are not accessible and already were a problem for /proc/vmcore, fixed in makedumpfile by detecting PageOffline() pages. In the future, virtio-mem might disallow reading unplugged memory -- marked as PageOffline() -- in some environments, resulting in undefined behavior when accessed; therefore, I'm trying to identify and rework all these (corner) cases.

With this series, there is really only access via /dev/mem, /proc/vmcore and kdb left after I ripped out /dev/kmem. kdb is an advanced corner-case use case -- we won't care for now if someone explicitly tries to do nasty things by reading from/writing to physical addresses we better not touch. /dev/mem is a use case we won't support for virtio-mem, at least for now, so we'll simply disallow mapping any virtio-mem memory via /dev/mem next. /proc/vmcore is really only a problem when dumping the old kernel via something that's not makedumpfile (read: basically never), however, we'll try sanitizing that as well in the second kernel in the future.

Tested via kcore_dump:
https://github.com/schlafwandler/kcore_dump

Cc Andrew Morton <[email protected]>
Cc "Michael S. Tsirkin" <[email protected]>
Cc Jason Wang <[email protected]>
Cc Alexey Dobriyan <[email protected]>
Cc Mike Rapoport <[email protected]>
Cc "Matthew Wilcox (Oracle)" <[email protected]>
Cc Oscar Salvador <[email protected]>
Cc Michal Hocko <[email protected]>
Cc Roman Gushchin <[email protected]>
Cc Alex Shi <[email protected]>
Cc Steven Price <[email protected]>
Cc Mike Kravetz <[email protected]>
Cc Aili Yao <[email protected]>
Cc Jiri Bohac <[email protected]>
Cc "K. Y. Srinivasan" <[email protected]>
Cc Haiyang Zhang <[email protected]>
Cc Stephen Hemminger <[email protected]>
Cc Wei Liu <[email protected]>
Cc Naoya Horiguchi <[email protected]>

David Hildenbrand (7):
fs/proc/kcore: drop KCORE_REMAP and KCORE_OTHER fs/proc/kcore: pfn_is_ram check only applies to KCORE_RAM
mm: rename and move page_is_poisoned()
fs/proc/kcore: don't read offline sections, logically offline pages
and hwpoisoned pages
mm: introduce page_offline_(begin|end|freeze|unfreeze) to synchronize
setting PageOffline()
virtio-mem: use page_offline_(start|end) when setting PageOffline() fs/proc/kcore: use page_offline_(freeze|unfreeze)

 drivers/virtio/virtio_mem.c |  2 ++
 fs/proc/kcore.c             | 69 ++++++++++++++++++++++++++++++-------
 include/linux/kcore.h       |  3 --
 include/linux/page-flags.h  | 12 +++++++
 mm/gup.c                    |  6 +++-
 mm/internal.h               | 20 -----------
 mm/util.c                   | 40 +++++++++++++++++++++
 7 files changed, 115 insertions(+), 37 deletions(-)

base-commit: 9f4ad9e425a1d3b6a34617b8ea226d56a119a717

Signature · 1 line
2.30.2
#1 lore raw

PATCH 1/7 fs/proc/kcore: drop KCORE_REMAP and KCORE_OTHER

Commit db779ef67ffe ("proc/kcore: Remove unused kclist_add_remap()") removed the last user of KCORE_REMAP.

Commit 595dd46ebfc1 ("vfs/proc/kcore, x86/mm/kcore: Fix SMAP fault when dumping vsyscall user page") removed the last user of KCORE_OTHER.

Let's drop both types. While at it, also drop vaddr in "struct kcore_list", used by KCORE_REMAP only.

---
 fs/proc/kcore.c       | 7 ++-----
 include/linux/kcore.h | 3 ---
 2 files changed, 2 insertions(+), 8 deletions(-)
fs/proc/kcore.c +2 -5
index 4d2e64e9016c..09f77d3c6e15 100644
@@ -380,11 +380,8 @@ read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos)
 			phdr->p_type = PT_LOAD;
 			phdr->p_flags = PF_R | PF_W | PF_X;
 			phdr->p_offset = kc_vaddr_to_offset(m->addr) + data_offset;
-			if (m->type == KCORE_REMAP)
-				phdr->p_vaddr = (size_t)m->vaddr;
-			else
-				phdr->p_vaddr = (size_t)m->addr;
-			if (m->type == KCORE_RAM || m->type == KCORE_REMAP)
+			phdr->p_vaddr = (size_t)m->addr;
+			if (m->type == KCORE_RAM)
 				phdr->p_paddr = __pa(m->addr);
 			else if (m->type == KCORE_TEXT)
 				phdr->p_paddr = __pa_symbol(m->addr);
include/linux/kcore.h +0 -3
index da676cdbd727..86c0f1d18998 100644
@@ -11,14 +11,11 @@ enum kcore_type {
 	KCORE_RAM,
 	KCORE_VMEMMAP,
 	KCORE_USER,
-	KCORE_OTHER,
-	KCORE_REMAP,
 };
 
 struct kcore_list {
 	struct list_head list;
 	unsigned long addr;
-	unsigned long vaddr;
 	size_t size;
 	int type;
 };
Signature · 1 line
2.30.2
reviewer Reviewed-by on v1 1/7
#2 lore raw

Re: [PATCH v1 1/7] fs/proc/kcore: drop KCORE_REMAP and KCORE_OTHER

Quoted 10 lines · David Hildenbrand

Commit db779ef67ffe ("proc/kcore: Remove unused kclist_add_remap()") removed the last user of KCORE_REMAP.

Commit 595dd46ebfc1 ("vfs/proc/kcore, x86/mm/kcore: Fix SMAP fault when dumping vsyscall user page") removed the last user of KCORE_OTHER.

Let's drop both types. While at it, also drop vaddr in "struct kcore_list", used by KCORE_REMAP only.

Quoted 45 lines
---
 fs/proc/kcore.c       | 7 ++-----
 include/linux/kcore.h | 3 ---
 2 files changed, 2 insertions(+), 8 deletions(-)
fs/proc/kcore.c +2 -5
index 4d2e64e9016c..09f77d3c6e15 100644
@@ -380,11 +380,8 @@ read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos)
 			phdr->p_type = PT_LOAD;
 			phdr->p_flags = PF_R | PF_W | PF_X;
 			phdr->p_offset = kc_vaddr_to_offset(m->addr) + data_offset;
-			if (m->type == KCORE_REMAP)
-				phdr->p_vaddr = (size_t)m->vaddr;
-			else
-				phdr->p_vaddr = (size_t)m->addr;
-			if (m->type == KCORE_RAM || m->type == KCORE_REMAP)
+			phdr->p_vaddr = (size_t)m->addr;
+			if (m->type == KCORE_RAM)
 				phdr->p_paddr = __pa(m->addr);
 			else if (m->type == KCORE_TEXT)
 				phdr->p_paddr = __pa_symbol(m->addr);
include/linux/kcore.h +0 -3
index da676cdbd727..86c0f1d18998 100644
@@ -11,14 +11,11 @@ enum kcore_type {
 	KCORE_RAM,
 	KCORE_VMEMMAP,
 	KCORE_USER,
-	KCORE_OTHER,
-	KCORE_REMAP,
 };
 
 struct kcore_list {
 	struct list_head list;
 	unsigned long addr;
-	unsigned long vaddr;
 	size_t size;
 	int type;
 };
Signature · 1 line
2.30.2
Signature · 2 lines
Sincerely yours,
Mike.
#3 lore raw

PATCH 2/7 fs/proc/kcore: pfn_is_ram check only applies to KCORE_RAM

In reply to David Hildenbrand

Let's resturcture the code, using switch-case, and checking pfn_is_ram() only when we are dealing with KCORE_RAM.

---
 fs/proc/kcore.c | 35 +++++++++++++++++++++++++++--------
 1 file changed, 27 insertions(+), 8 deletions(-)
fs/proc/kcore.c +27 -8
index 09f77d3c6e15..ed6fbb3bd50c 100644
@@ -483,25 +483,36 @@ read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos)
 				goto out;
 			}
 			m = NULL;	/* skip the list anchor */
-		} else if (!pfn_is_ram(__pa(start) >> PAGE_SHIFT)) {
-			if (clear_user(buffer, tsz)) {
-				ret = -EFAULT;
-				goto out;
-			}
-		} else if (m->type == KCORE_VMALLOC) {
+			goto skip;
+		}
+
+		switch (m->type) {
+		case KCORE_VMALLOC:
 			vread(buf, (char *)start, tsz);
 			/* we have to zero-fill user buffer even if no read */
 			if (copy_to_user(buffer, buf, tsz)) {
 				ret = -EFAULT;
 				goto out;
 			}
-		} else if (m->type == KCORE_USER) {
+			break;
+		case KCORE_USER:
 			/* User page is handled prior to normal kernel page: */
 			if (copy_to_user(buffer, (char *)start, tsz)) {
 				ret = -EFAULT;
 				goto out;
 			}
-		} else {
+			break;
+		case KCORE_RAM:
+			if (!pfn_is_ram(__pa(start) >> PAGE_SHIFT)) {
+				if (clear_user(buffer, tsz)) {
+					ret = -EFAULT;
+					goto out;
+				}
+				break;
+			}
+			fallthrough;
+		case KCORE_VMEMMAP:
+		case KCORE_TEXT:
 			if (kern_addr_valid(start)) {
 				/*
 				 * Using bounce buffer to bypass the
@@ -525,7 +536,15 @@ read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos)
 					goto out;
 				}
 			}
+			break;
+		default:
+			pr_warn_once("Unhandled KCORE type: %d\n", m->type);
+			if (clear_user(buffer, tsz)) {
+				ret = -EFAULT;
+				goto out;
+			}
 		}
+skip:
 		buflen -= tsz;
 		*fpos += tsz;
 		buffer += tsz;
Signature · 1 line
2.30.2
reviewer Reviewed-by on v1 2/7
#4 lore raw

Re: [PATCH v1 2/7] fs/proc/kcore: pfn_is_ram check only applies to KCORE_RAM

Quoted 4 lines · David Hildenbrand

Let's resturcture the code, using switch-case, and checking pfn_is_ram() only when we are dealing with KCORE_RAM.

Quoted 72 lines
---
 fs/proc/kcore.c | 35 +++++++++++++++++++++++++++--------
 1 file changed, 27 insertions(+), 8 deletions(-)
fs/proc/kcore.c +27 -8
index 09f77d3c6e15..ed6fbb3bd50c 100644
@@ -483,25 +483,36 @@ read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos)
 				goto out;
 			}
 			m = NULL;	/* skip the list anchor */
-		} else if (!pfn_is_ram(__pa(start) >> PAGE_SHIFT)) {
-			if (clear_user(buffer, tsz)) {
-				ret = -EFAULT;
-				goto out;
-			}
-		} else if (m->type == KCORE_VMALLOC) {
+			goto skip;
+		}
+
+		switch (m->type) {
+		case KCORE_VMALLOC:
 			vread(buf, (char *)start, tsz);
 			/* we have to zero-fill user buffer even if no read */
 			if (copy_to_user(buffer, buf, tsz)) {
 				ret = -EFAULT;
 				goto out;
 			}
-		} else if (m->type == KCORE_USER) {
+			break;
+		case KCORE_USER:
 			/* User page is handled prior to normal kernel page: */
 			if (copy_to_user(buffer, (char *)start, tsz)) {
 				ret = -EFAULT;
 				goto out;
 			}
-		} else {
+			break;
+		case KCORE_RAM:
+			if (!pfn_is_ram(__pa(start) >> PAGE_SHIFT)) {
+				if (clear_user(buffer, tsz)) {
+					ret = -EFAULT;
+					goto out;
+				}
+				break;
+			}
+			fallthrough;
+		case KCORE_VMEMMAP:
+		case KCORE_TEXT:
 			if (kern_addr_valid(start)) {
 				/*
 				 * Using bounce buffer to bypass the
@@ -525,7 +536,15 @@ read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos)
 					goto out;
 				}
 			}
+			break;
+		default:
+			pr_warn_once("Unhandled KCORE type: %d\n", m->type);
+			if (clear_user(buffer, tsz)) {
+				ret = -EFAULT;
+				goto out;
+			}
 		}
+skip:
 		buflen -= tsz;
 		*fpos += tsz;
 		buffer += tsz;
Signature · 1 line
2.30.2
Signature · 2 lines
Sincerely yours,
Mike.
#5 lore raw

PATCH 3/7 mm: rename and move page_is_poisoned()

In reply to David Hildenbrand

Commit d3378e86d182 ("mm/gup: check page posion status for coredump.") introduced page_is_poisoned(), however, v5 [1] of the patch used "page_is_hwpoison()" and something went wrong while upstreaming. Rename the function and move it to page-flags.h, from where it can be used in other -- kcore -- context.

Move the comment to the place where it belongs and simplify.

[1] https://lkml.kernel.org/r/20210322193318.377c9ce9@alex-virtual-machine

---
 include/linux/page-flags.h |  7 +++++++
 mm/gup.c                   |  6 +++++-
 mm/internal.h              | 20 --------------------
 3 files changed, 12 insertions(+), 21 deletions(-)
include/linux/page-flags.h +7 -0
index 04a34c08e0a6..b8c56672a588 100644
@@ -694,6 +694,13 @@ PAGEFLAG_FALSE(DoubleMap)
 	TESTSCFLAG_FALSE(DoubleMap)
 #endif
 
+static inline bool is_page_hwpoison(struct page *page)
+{
+	if (PageHWPoison(page))
+		return true;
+	return PageHuge(page) && PageHWPoison(compound_head(page));
+}
+
 /*
  * For pages that are never mapped to userspace (and aren't PageSlab),
  * page_type may be used.  Because it is initialised to -1, we invert the
mm/gup.c +5 -1
index ef7d2da9f03f..000f3303e7f2 100644
@@ -1536,7 +1536,11 @@ struct page *get_dump_page(unsigned long addr)
 	if (locked)
 		mmap_read_unlock(mm);
 
-	if (ret == 1 && is_page_poisoned(page))
+	/*
+	 * We might have hwpoisoned pages still mapped into user space. Don't
+	 * read these pages when creating a coredump, access could be fatal.
+	 */
+	if (ret == 1 && is_page_hwpoison(page))
 		return NULL;
 
 	return (ret == 1) ? page : NULL;
mm/internal.h +0 -20
index cb3c5e0a7799..1432feec62df 100644
@@ -97,26 +97,6 @@ static inline void set_page_refcounted(struct page *page)
 	set_page_count(page, 1);
 }
 
-/*
- * When kernel touch the user page, the user page may be have been marked
- * poison but still mapped in user space, if without this page, the kernel
- * can guarantee the data integrity and operation success, the kernel is
- * better to check the posion status and avoid touching it, be good not to
- * panic, coredump for process fatal signal is a sample case matching this
- * scenario. Or if kernel can't guarantee the data integrity, it's better
- * not to call this function, let kernel touch the poison page and get to
- * panic.
- */
-static inline bool is_page_poisoned(struct page *page)
-{
-	if (PageHWPoison(page))
-		return true;
-	else if (PageHuge(page) && PageHWPoison(compound_head(page)))
-		return true;
-
-	return false;
-}
-
 extern unsigned long highest_memmap_pfn;
 
 /*
Signature · 1 line
2.30.2
reviewer Reviewed-by on v1 3/7
#6 lore raw

Re: [PATCH v1 3/7] mm: rename and move page_is_poisoned()

Quoted 11 lines · David Hildenbrand

Commit d3378e86d182 ("mm/gup: check page posion status for coredump.") introduced page_is_poisoned(), however, v5 [1] of the patch used "page_is_hwpoison()" and something went wrong while upstreaming. Rename the function and move it to page-flags.h, from where it can be used in other -- kcore -- context.

Move the comment to the place where it belongs and simplify.

[1] https://lkml.kernel.org/r/20210322193318.377c9ce9@alex-virtual-machine

Quoted 5 lines
---
 include/linux/page-flags.h |  7 +++++++
 mm/gup.c                   |  6 +++++-
 mm/internal.h              | 20 --------------------
 3 files changed, 12 insertions(+), 21 deletions(-)

Nice :)

Quoted 70 lines
include/linux/page-flags.h +7 -0
index 04a34c08e0a6..b8c56672a588 100644
@@ -694,6 +694,13 @@ PAGEFLAG_FALSE(DoubleMap)
 	TESTSCFLAG_FALSE(DoubleMap)
 #endif
 
+static inline bool is_page_hwpoison(struct page *page)
+{
+	if (PageHWPoison(page))
+		return true;
+	return PageHuge(page) && PageHWPoison(compound_head(page));
+}
+
 /*
  * For pages that are never mapped to userspace (and aren't PageSlab),
  * page_type may be used.  Because it is initialised to -1, we invert the
mm/gup.c +5 -1
index ef7d2da9f03f..000f3303e7f2 100644
@@ -1536,7 +1536,11 @@ struct page *get_dump_page(unsigned long addr)
 	if (locked)
 		mmap_read_unlock(mm);
 
-	if (ret == 1 && is_page_poisoned(page))
+	/*
+	 * We might have hwpoisoned pages still mapped into user space. Don't
+	 * read these pages when creating a coredump, access could be fatal.
+	 */
+	if (ret == 1 && is_page_hwpoison(page))
 		return NULL;
 
 	return (ret == 1) ? page : NULL;
mm/internal.h +0 -20
index cb3c5e0a7799..1432feec62df 100644
@@ -97,26 +97,6 @@ static inline void set_page_refcounted(struct page *page)
 	set_page_count(page, 1);
 }
 
-/*
- * When kernel touch the user page, the user page may be have been marked
- * poison but still mapped in user space, if without this page, the kernel
- * can guarantee the data integrity and operation success, the kernel is
- * better to check the posion status and avoid touching it, be good not to
- * panic, coredump for process fatal signal is a sample case matching this
- * scenario. Or if kernel can't guarantee the data integrity, it's better
- * not to call this function, let kernel touch the poison page and get to
- * panic.
- */
-static inline bool is_page_poisoned(struct page *page)
-{
-	if (PageHWPoison(page))
-		return true;
-	else if (PageHuge(page) && PageHWPoison(compound_head(page)))
-		return true;
-
-	return false;
-}
-
 extern unsigned long highest_memmap_pfn;
 
 /*
Signature · 1 line
2.30.2
Signature · 2 lines
Sincerely yours,
Mike.
reviewer
#7 lore raw

Re: [PATCH v1 3/7] mm: rename and move page_is_poisoned()

In reply to David Hildenbrand
Quoted 11 lines · David Hildenbrand

Commit d3378e86d182 ("mm/gup: check page posion status for coredump.") introduced page_is_poisoned(), however, v5 [1] of the patch used "page_is_hwpoison()" and something went wrong while upstreaming. Rename the function and move it to page-flags.h, from where it can be used in other -- kcore -- context.

Move the comment to the place where it belongs and simplify.

[1] https://lkml.kernel.org/r/20210322193318.377c9ce9@alex-virtual-machine

I do agree that being explicit about hwpoison is much better. Poisoned page can be also an unitialized one and I believe this is the reason why you are bringing that up.

But you've made me look at d3378e86d182 and I am wondering whether this is really a valid patch. First of all it can leak a reference count AFAICS. Moreover it doesn't really fix anything because the page can be marked hwpoison right after the check is done. I do not think the race is feasible to be closed. So shouldn't we rather revert it?

Signature · 2 lines
Michal Hocko
SUSE Labs
#8 lore raw

Re: [PATCH v1 3/7] mm: rename and move page_is_poisoned()

Quoted 9 lines · Michal Hocko
Quoted 8 lines · David Hildenbrand

Commit d3378e86d182 ("mm/gup: check page posion status for coredump.") introduced page_is_poisoned(), however, v5 [1] of the patch used "page_is_hwpoison()" and something went wrong while upstreaming. Rename the function and move it to page-flags.h, from where it can be used in other -- kcore -- context.

Move the comment to the place where it belongs and simplify.

Quoted 7 lines
Quoted 3 lines

[1] https://lkml.kernel.org/r/20210322193318.377c9ce9@alex-virtual-machine

I do agree that being explicit about hwpoison is much better. Poisoned page can be also an unitialized one and I believe this is the reason why you are bringing that up.

I'm bringing it up because I want to reuse that function as state above :)

Quoted 6 lines

But you've made me look at d3378e86d182 and I am wondering whether this is really a valid patch. First of all it can leak a reference count AFAICS. Moreover it doesn't really fix anything because the page can be marked hwpoison right after the check is done. I do not think the race is feasible to be closed. So shouldn't we rather revert it?

I am not sure if we really care about races here that much here? I mean, essentially we are racing with HW breaking asynchronously. Just because we would be synchronizing with SetPageHWPoison() wouldn't mean we can stop HW from breaking.

Long story short, this should be good enough for the cases we actually can handle? What am I missing?

Signature · 3 lines
Thanks,

David / dhildenb
reviewer
#9 lore raw

Re: [PATCH v1 3/7] mm: rename and move page_is_poisoned()

Quoted 27 lines · David Hildenbrand
Quoted 9 lines · Michal Hocko
Quoted 8 lines · David Hildenbrand

Commit d3378e86d182 ("mm/gup: check page posion status for coredump.") introduced page_is_poisoned(), however, v5 [1] of the patch used "page_is_hwpoison()" and something went wrong while upstreaming. Rename the function and move it to page-flags.h, from where it can be used in other -- kcore -- context.

Move the comment to the place where it belongs and simplify.

Quoted 7 lines
Quoted 3 lines

[1] https://lkml.kernel.org/r/20210322193318.377c9ce9@alex-virtual-machine

I do agree that being explicit about hwpoison is much better. Poisoned page can be also an unitialized one and I believe this is the reason why you are bringing that up.

I'm bringing it up because I want to reuse that function as state above :)

Quoted 6 lines

But you've made me look at d3378e86d182 and I am wondering whether this is really a valid patch. First of all it can leak a reference count AFAICS. Moreover it doesn't really fix anything because the page can be marked hwpoison right after the check is done. I do not think the race is feasible to be closed. So shouldn't we rather revert it?

Quoted 4 lines

I am not sure if we really care about races here that much here? I mean, essentially we are racing with HW breaking asynchronously. Just because we would be synchronizing with SetPageHWPoison() wouldn't mean we can stop HW from breaking.

Right

Quoted 2 lines

Long story short, this should be good enough for the cases we actually can handle? What am I missing?

I am not sure I follow. My point is that I fail to see any added value of the check as it doesn't prevent the race (it fundamentally cannot as the page can be poisoned at any time) but the failure path doesn't put_page which is incorrect even for hwpoison pages.

Signature · 2 lines
Michal Hocko
SUSE Labs
#10 lore raw

Re: [PATCH v1 3/7] mm: rename and move page_is_poisoned()

Quoted 7 lines
Quoted 2 lines

Long story short, this should be good enough for the cases we actually can handle? What am I missing?

I am not sure I follow. My point is that I fail to see any added value of the check as it doesn't prevent the race (it fundamentally cannot as the page can be poisoned at any time) but the failure path doesn't put_page which is incorrect even for hwpoison pages.

Oh, I think you are right. If we have a page and return NULL we would leak a reference.

Actually, we discussed in that thread handling this entirely differently, which resulted in a v7 [1]; however Andrew moved forward with this (outdated?) patch, maybe that was just a mistake?

Yes, I agree we should revert that patch for now.

Regarding the race comment: AFAIU e.g., [2], it's not really a problem with a race, but rather some corner case issue that can happen if we fail in memory_failure().

[1] https://lkml.kernel.org/r/20210406104123.451ee3c3@alex-virtual-machine
[2] https://lkml.kernel.org/r/[email protected]

Signature · 3 lines
Thanks,

David / dhildenb
reviewer
#11 lore raw

Re: [PATCH v1 3/7] mm: rename and move page_is_poisoned()

Quoted 8 lines · David Hildenbrand
Quoted 7 lines
Quoted 2 lines

Long story short, this should be good enough for the cases we actually can handle? What am I missing?

I am not sure I follow. My point is that I fail to see any added value of the check as it doesn't prevent the race (it fundamentally cannot as the page can be poisoned at any time) but the failure path doesn't put_page which is incorrect even for hwpoison pages.

Quoted 8 lines

Oh, I think you are right. If we have a page and return NULL we would leak a reference.

Actually, we discussed in that thread handling this entirely differently, which resulted in a v7 [1]; however Andrew moved forward with this (outdated?) patch, maybe that was just a mistake?

Yes, I agree we should revert that patch for now.

OK, Let me send the revert to Andrew.

Signature · 2 lines
Michal Hocko
SUSE Labs
#12 lore raw

Re: [PATCH v1 3/7] mm: rename and move page_is_poisoned()

Quoted 12 lines · On Wed, 5 May 2021 15:45:47 +0200 Michal Hocko <[email protected]> wrote:
Quoted 11 lines · David Hildenbrand
Quoted 7 lines
Quoted 2 lines

Long story short, this should be good enough for the cases we actually can handle? What am I missing?

I am not sure I follow. My point is that I fail to see any added value of the check as it doesn't prevent the race (it fundamentally cannot as the page can be poisoned at any time) but the failure path doesn't put_page which is incorrect even for hwpoison pages.

Oh, I think you are right. If we have a page and return NULL we would leak a reference.

Quoted 8 lines
Quoted 5 lines

Actually, we discussed in that thread handling this entirely differently, which resulted in a v7 [1]; however Andrew moved forward with this (outdated?) patch, maybe that was just a mistake?

Yes, I agree we should revert that patch for now.

OK, Let me send the revert to Andrew.

Got this!
Anyway, I will try to post a new patch for this issue based on the previous patch v7.

Thanks!
Aili Yao

#13 lore raw

Re: [PATCH v1 3/7] mm: rename and move page_is_poisoned()

In reply to Michal Hocko
Quoted 35 lines · On Wed, 5 May 2021 15:27:39 +0200 Michal Hocko <[email protected]> wrote:
Quoted 27 lines · David Hildenbrand
Quoted 9 lines · Michal Hocko
Quoted 8 lines · David Hildenbrand

Commit d3378e86d182 ("mm/gup: check page posion status for coredump.") introduced page_is_poisoned(), however, v5 [1] of the patch used "page_is_hwpoison()" and something went wrong while upstreaming. Rename the function and move it to page-flags.h, from where it can be used in other -- kcore -- context.

Move the comment to the place where it belongs and simplify.

Quoted 7 lines
Quoted 3 lines

[1] https://lkml.kernel.org/r/20210322193318.377c9ce9@alex-virtual-machine

I do agree that being explicit about hwpoison is much better. Poisoned page can be also an unitialized one and I believe this is the reason why you are bringing that up.

I'm bringing it up because I want to reuse that function as state above :)

Quoted 6 lines

But you've made me look at d3378e86d182 and I am wondering whether this is really a valid patch. First of all it can leak a reference count AFAICS. Moreover it doesn't really fix anything because the page can be marked hwpoison right after the check is done. I do not think the race is feasible to be closed. So shouldn't we rather revert it?

Quoted 4 lines

I am not sure if we really care about races here that much here? I mean, essentially we are racing with HW breaking asynchronously. Just because we would be synchronizing with SetPageHWPoison() wouldn't mean we can stop HW from breaking.

Right

Quoted 7 lines
Quoted 2 lines

Long story short, this should be good enough for the cases we actually can handle? What am I missing?

I am not sure I follow. My point is that I fail to see any added value of the check as it doesn't prevent the race (it fundamentally cannot as the page can be poisoned at any time) but the failure path doesn't put_page which is incorrect even for hwpoison pages.

Sorry, I have something to say:

I have noticed the ref count leak in the previous topic ,but I don't think it's a really matter. For memory recovery case for user pages, we will keep one reference to the poison page so the error page will not be freed to buddy allocator. which can be checked in memory_faulure() function.

For the case here, the reference count for error page may be greater than one as it's not unmmapped successfully and may shared. we take a reference for that page will not result some really mattering issue.

The only break I can think for this leak is that we will fail to unpoison the error page for test purpose.

Thanks!
Aili Yao

reviewer
#14 lore raw

Re: [PATCH v1 3/7] mm: rename and move page_is_poisoned()

Quoted 46 lines · Aili Yao
Quoted 35 lines · On Wed, 5 May 2021 15:27:39 +0200 Michal Hocko <[email protected]> wrote:
Quoted 27 lines · David Hildenbrand
Quoted 9 lines · Michal Hocko
Quoted 8 lines · David Hildenbrand

Commit d3378e86d182 ("mm/gup: check page posion status for coredump.") introduced page_is_poisoned(), however, v5 [1] of the patch used "page_is_hwpoison()" and something went wrong while upstreaming. Rename the function and move it to page-flags.h, from where it can be used in other -- kcore -- context.

Move the comment to the place where it belongs and simplify.

Quoted 7 lines
Quoted 3 lines

[1] https://lkml.kernel.org/r/20210322193318.377c9ce9@alex-virtual-machine

I do agree that being explicit about hwpoison is much better. Poisoned page can be also an unitialized one and I believe this is the reason why you are bringing that up.

I'm bringing it up because I want to reuse that function as state above :)

Quoted 6 lines

But you've made me look at d3378e86d182 and I am wondering whether this is really a valid patch. First of all it can leak a reference count AFAICS. Moreover it doesn't really fix anything because the page can be marked hwpoison right after the check is done. I do not think the race is feasible to be closed. So shouldn't we rather revert it?

Quoted 4 lines

I am not sure if we really care about races here that much here? I mean, essentially we are racing with HW breaking asynchronously. Just because we would be synchronizing with SetPageHWPoison() wouldn't mean we can stop HW from breaking.

Right

Quoted 7 lines
Quoted 2 lines

Long story short, this should be good enough for the cases we actually can handle? What am I missing?

I am not sure I follow. My point is that I fail to see any added value of the check as it doesn't prevent the race (it fundamentally cannot as the page can be poisoned at any time) but the failure path doesn't put_page which is incorrect even for hwpoison pages.

Quoted 6 lines

Sorry, I have something to say:

I have noticed the ref count leak in the previous topic ,but I don't think it's a really matter. For memory recovery case for user pages, we will keep one reference to the poison page so the error page will not be freed to buddy allocator. which can be checked in memory_faulure() function.

So what would happen if those pages are hwpoisoned from userspace rather than by HW. And repeatedly so?

Signature · 2 lines
Michal Hocko
SUSE Labs
#15 lore raw

Re: [PATCH v1 3/7] mm: rename and move page_is_poisoned()

Quoted 49 lines · On Thu, 6 May 2021 09:06:14 +0200 Michal Hocko <[email protected]> wrote:
Quoted 48 lines · Aili Yao
Quoted 35 lines · On Wed, 5 May 2021 15:27:39 +0200 Michal Hocko <[email protected]> wrote:
Quoted 27 lines · David Hildenbrand
Quoted 9 lines · Michal Hocko
Quoted 8 lines · David Hildenbrand

Commit d3378e86d182 ("mm/gup: check page posion status for coredump.") introduced page_is_poisoned(), however, v5 [1] of the patch used "page_is_hwpoison()" and something went wrong while upstreaming. Rename the function and move it to page-flags.h, from where it can be used in other -- kcore -- context.

Move the comment to the place where it belongs and simplify.

Quoted 7 lines
Quoted 3 lines

[1] https://lkml.kernel.org/r/20210322193318.377c9ce9@alex-virtual-machine

I do agree that being explicit about hwpoison is much better. Poisoned page can be also an unitialized one and I believe this is the reason why you are bringing that up.

I'm bringing it up because I want to reuse that function as state above :)

Quoted 6 lines

But you've made me look at d3378e86d182 and I am wondering whether this is really a valid patch. First of all it can leak a reference count AFAICS. Moreover it doesn't really fix anything because the page can be marked hwpoison right after the check is done. I do not think the race is feasible to be closed. So shouldn't we rather revert it?

Quoted 4 lines

I am not sure if we really care about races here that much here? I mean, essentially we are racing with HW breaking asynchronously. Just because we would be synchronizing with SetPageHWPoison() wouldn't mean we can stop HW from breaking.

Right

Quoted 7 lines
Quoted 2 lines

Long story short, this should be good enough for the cases we actually can handle? What am I missing?

I am not sure I follow. My point is that I fail to see any added value of the check as it doesn't prevent the race (it fundamentally cannot as the page can be poisoned at any time) but the failure path doesn't put_page which is incorrect even for hwpoison pages.

Sorry, I have something to say:

Quoted 7 lines
Quoted 4 lines

I have noticed the ref count leak in the previous topic ,but I don't think it's a really matter. For memory recovery case for user pages, we will keep one reference to the poison page so the error page will not be freed to buddy allocator. which can be checked in memory_faulure() function.

So what would happen if those pages are hwpoisoned from userspace rather than by HW. And repeatedly so?

Sorry, I may be not totally understand what you mean.

Do you mean hard page offline from mcelog?
If yes, I think it's not for one real UC error but for CE storms.
when we access this page in kernel, the access may success even it was marked hwpoison.

Thanks!
Aili Yao

reviewer
#16 lore raw

Re: [PATCH v1 3/7] mm: rename and move page_is_poisoned()

Quoted 6 lines · Aili Yao
Quoted 3 lines · On Thu, 6 May 2021 09:06:14 +0200 Michal Hocko <[email protected]> wrote:
Quoted 2 lines · Aili Yao

On Wed, 5 May 2021 15:27:39 +0200
Michal Hocko <[email protected]> wrote:

[...]

Quoted 12 lines
Quoted 12 lines
Quoted 11 lines
Quoted 4 lines

I am not sure I follow. My point is that I fail to see any added value of the check as it doesn't prevent the race (it fundamentally cannot as the page can be poisoned at any time) but the failure path doesn't put_page which is incorrect even for hwpoison pages.

Sorry, I have something to say:

I have noticed the ref count leak in the previous topic ,but I don't think it's a really matter. For memory recovery case for user pages, we will keep one reference to the poison page so the error page will not be freed to buddy allocator. which can be checked in memory_faulure() function.

Quoted 6 lines
Quoted 2 lines

So what would happen if those pages are hwpoisoned from userspace rather than by HW. And repeatedly so?

Sorry, I may be not totally understand what you mean.

Do you mean hard page offline from mcelog?

No I mean soft hwpoison from userspace (e.g. by MADV_HWPOISON but there are other interfaces AFAIK).

And just to be explicit. All those interfaces are root only (CAP_SYS_ADMIN) so I am not really worried about any malitious abuse of the reference leak. I am mostly concerned that this is obviously broken without a good reason. The most trivial fix would have been to put_page in the return path but as I've mentioned in other email thread the fix really needs a deeper thought and consider other things.

Hope that clarifies this some more.

Signature · 2 lines
Michal Hocko
SUSE Labs
#17 lore raw

Re: [PATCH v1 3/7] mm: rename and move page_is_poisoned()

Quoted 30 lines · On Thu, 6 May 2021 09:55:51 +0200 Michal Hocko <[email protected]> wrote:
Quoted 6 lines · Aili Yao
Quoted 3 lines · On Thu, 6 May 2021 09:06:14 +0200 Michal Hocko <[email protected]> wrote:
Quoted 2 lines · Aili Yao

On Wed, 5 May 2021 15:27:39 +0200
Michal Hocko <[email protected]> wrote:

[...]

Quoted 12 lines
Quoted 12 lines
Quoted 11 lines
Quoted 4 lines

I am not sure I follow. My point is that I fail to see any added value of the check as it doesn't prevent the race (it fundamentally cannot as the page can be poisoned at any time) but the failure path doesn't put_page which is incorrect even for hwpoison pages.

Sorry, I have something to say:

I have noticed the ref count leak in the previous topic ,but I don't think it's a really matter. For memory recovery case for user pages, we will keep one reference to the poison page so the error page will not be freed to buddy allocator. which can be checked in memory_faulure() function.

Quoted 6 lines
Quoted 2 lines

So what would happen if those pages are hwpoisoned from userspace rather than by HW. And repeatedly so?

Sorry, I may be not totally understand what you mean.

Do you mean hard page offline from mcelog?

No I mean soft hwpoison from userspace (e.g. by MADV_HWPOISON but there are other interfaces AFAIK).

Quoted 8 lines

And just to be explicit. All those interfaces are root only (CAP_SYS_ADMIN) so I am not really worried about any malitious abuse of the reference leak. I am mostly concerned that this is obviously broken without a good reason. The most trivial fix would have been to put_page in the return path but as I've mentioned in other email thread the fix really needs a deeper thought and consider other things.

Hope that clarifies this some more.

Thanks, got it!
Yes, there are some test scenarios that should be covered.

But for test, the default SIGBUS handlers is usually replaced, and the test process may not hit the coredump code.

Anyway, there is a ref leak in the normal enviorments and better to be fixed.

Thanks!
Aili Yao

#18 lore raw

PATCH 4/7 fs/proc/kcore: don't read offline sections, logically offline pages and hwpoisoned pages

In reply to David Hildenbrand

Let's avoid reading:

1) Offline memory sections: the content of offline memory sections is stale as the memory is effectively unused by the kernel. On s390x with standby memory, offline memory sections (belonging to offline storage increments) are not accessible. With virtio-mem and the hyper-v balloon, we can have unavailable memory chunks that should not be accessed inside offline memory sections. Last but not least, offline memory sections might contain hwpoisoned pages which we can no longer identify because the memmap is stale.

2) PG_offline pages: logically offline pages that are documented as "The content of these pages is effectively stale. Such pages should not be touched (read/write/dump/save) except by their owner.". Examples include pages inflated in a balloon or unavailble memory ranges inside hotplugged memory sections with virtio-mem or the hyper-v balloon.

3) PG_hwpoison pages: Reading pages marked as hwpoisoned can be fatal. As documented: "Accessing is not safe since it may cause another machine check. Don't touch!"

Reading /proc/kcore now performs similar checks as when reading /proc/vmcore for kdump via makedumpfile: problematic pages are exclude. It's also similar to hibernation code, however, we don't skip hwpoisoned pages when processing pages in kernel/power/snapshot.c:saveable_page() yet.

Note 1: we can race against memory offlining code, especially memory going offline and getting unplugged: however, we will properly tear down the identity mapping and handle faults gracefully when accessing this memory from kcore code.

Note 2: we can race against drivers setting PageOffline() and turning memory inaccessible in the hypervisor. We'll handle this in a follow-up patch.

---
 fs/proc/kcore.c | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)
fs/proc/kcore.c +13 -1
index ed6fbb3bd50c..92ff1e4436cb 100644
@@ -465,6 +465,9 @@ read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos)
 
 	m = NULL;
 	while (buflen) {
+		struct page *page;
+		unsigned long pfn;
+
 		/*
 		 * If this is the first iteration or the address is not within
 		 * the previous entry, search for a matching entry.
@@ -503,7 +506,16 @@ read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos)
 			}
 			break;
 		case KCORE_RAM:
-			if (!pfn_is_ram(__pa(start) >> PAGE_SHIFT)) {
+			pfn = __pa(start) >> PAGE_SHIFT;
+			page = pfn_to_online_page(pfn);
+
+			/*
+			 * Don't read offline sections, logically offline pages
+			 * (e.g., inflated in a balloon), hwpoisoned pages,
+			 * and explicitly excluded physical ranges.
+			 */
+			if (!page || PageOffline(page) ||
+			    is_page_hwpoison(page) || !pfn_is_ram(pfn)) {
 				if (clear_user(buffer, tsz)) {
 					ret = -EFAULT;
 					goto out;
Signature · 1 line
2.30.2
reviewer Reviewed-by on v1 4/7
#19 lore raw
Quoted 32 lines · David Hildenbrand

Let's avoid reading:

1) Offline memory sections: the content of offline memory sections is stale as the memory is effectively unused by the kernel. On s390x with standby memory, offline memory sections (belonging to offline storage increments) are not accessible. With virtio-mem and the hyper-v balloon, we can have unavailable memory chunks that should not be accessed inside offline memory sections. Last but not least, offline memory sections might contain hwpoisoned pages which we can no longer identify because the memmap is stale.

2) PG_offline pages: logically offline pages that are documented as "The content of these pages is effectively stale. Such pages should not be touched (read/write/dump/save) except by their owner.". Examples include pages inflated in a balloon or unavailble memory ranges inside hotplugged memory sections with virtio-mem or the hyper-v balloon.

3) PG_hwpoison pages: Reading pages marked as hwpoisoned can be fatal. As documented: "Accessing is not safe since it may cause another machine check. Don't touch!"

Reading /proc/kcore now performs similar checks as when reading /proc/vmcore for kdump via makedumpfile: problematic pages are exclude. It's also similar to hibernation code, however, we don't skip hwpoisoned pages when processing pages in kernel/power/snapshot.c:saveable_page() yet.

Note 1: we can race against memory offlining code, especially memory going offline and getting unplugged: however, we will properly tear down the identity mapping and handle faults gracefully when accessing this memory from kcore code.

Quoted 5 lines

Note 2: we can race against drivers setting PageOffline() and turning memory inaccessible in the hypervisor. We'll handle this in a follow-up patch.

Quoted 39 lines
---
 fs/proc/kcore.c | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)
fs/proc/kcore.c +13 -1
index ed6fbb3bd50c..92ff1e4436cb 100644
@@ -465,6 +465,9 @@ read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos)
 
 	m = NULL;
 	while (buflen) {
+		struct page *page;
+		unsigned long pfn;
+
 		/*
 		 * If this is the first iteration or the address is not within
 		 * the previous entry, search for a matching entry.
@@ -503,7 +506,16 @@ read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos)
 			}
 			break;
 		case KCORE_RAM:
-			if (!pfn_is_ram(__pa(start) >> PAGE_SHIFT)) {
+			pfn = __pa(start) >> PAGE_SHIFT;
+			page = pfn_to_online_page(pfn);
+
+			/*
+			 * Don't read offline sections, logically offline pages
+			 * (e.g., inflated in a balloon), hwpoisoned pages,
+			 * and explicitly excluded physical ranges.
+			 */
+			if (!page || PageOffline(page) ||
+			    is_page_hwpoison(page) || !pfn_is_ram(pfn)) {
 				if (clear_user(buffer, tsz)) {
 					ret = -EFAULT;
 					goto out;
Signature · 1 line
2.30.2
Signature · 2 lines
Sincerely yours,
Mike.
#20 lore raw

PATCH 5/7 mm: introduce page_offline_(begin|end|freeze|unfreeze) to synchronize setting PageOffline()

In reply to David Hildenbrand

A driver might set a page logically offline -- PageOffline() -- and turn the page inaccessible in the hypervisor; after that, access to page content can be fatal. One example is virtio-mem; while unplugged memory -- marked as PageOffline() can currently be read in the hypervisor, this will no longer be the case in the future; for example, when having a virtio-mem device backed by huge pages in the hypervisor.

Some special PFN walkers -- i.e., /proc/kcore -- read content of random pages after checking PageOffline(); however, these PFN walkers can race with drivers that set PageOffline().

Let's introduce page_offline_(begin|end|freeze|unfreeze) for synchronizing.

page_offline_freeze()/page_offline_unfreeze() allows for a subsystem to synchronize with such drivers, achieving that a page cannot be set PageOffline() while frozen.

page_offline_begin()/page_offline_end() is used by drivers that care about such races when setting a page PageOffline().

For simplicity, use a rwsem for now; neither drivers nor users are performance sensitive.

---
 include/linux/page-flags.h |  5 +++++
 mm/util.c                  | 38 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 43 insertions(+)
include/linux/page-flags.h +5 -0
index b8c56672a588..e3d00c72f459 100644
@@ -767,6 +767,11 @@ PAGE_TYPE_OPS(Buddy, buddy)
  */
 PAGE_TYPE_OPS(Offline, offline)
 
+extern void page_offline_freeze(void);
+extern void page_offline_unfreeze(void);
+extern void page_offline_begin(void);
+extern void page_offline_end(void);
+
 /*
  * Marks pages in use as page tables.
  */
mm/util.c +38 -0
index 54870226cea6..95395d4e4209 100644
@@ -1013,3 +1013,41 @@ void mem_dump_obj(void *object)
 	}
 	pr_cont(" non-slab/vmalloc memory.\n");
 }
+
+/*
+ * A driver might set a page logically offline -- PageOffline() -- and
+ * turn the page inaccessible in the hypervisor; after that, access to page
+ * content can be fatal.
+ *
+ * Some special PFN walkers -- i.e., /proc/kcore -- read content of random
+ * pages after checking PageOffline(); however, these PFN walkers can race
+ * with drivers that set PageOffline().
+ *
+ * page_offline_freeze()/page_offline_unfreeze() allows for a subsystem to
+ * synchronize with such drivers, achieving that a page cannot be set
+ * PageOffline() while frozen.
+ *
+ * page_offline_begin()/page_offline_end() is used by drivers that care about
+ * such races when setting a page PageOffline().
+ */
+static DECLARE_RWSEM(page_offline_rwsem);
+
+void page_offline_freeze(void)
+{
+	down_read(&page_offline_rwsem);
+}
+
+void page_offline_unfreeze(void)
+{
+	up_read(&page_offline_rwsem);
+}
+
+void page_offline_begin(void)
+{
+	down_write(&page_offline_rwsem);
+}
+
+void page_offline_end(void)
+{
+	up_write(&page_offline_rwsem);
+}
Signature · 1 line
2.30.2
reviewer
#21 lore raw

Re: [PATCH v1 5/7] mm: introduce page_offline_(begin|end|freeze|unfreeze) to synchronize setting PageOffline()

Quoted 7 lines · David Hildenbrand

A driver might set a page logically offline -- PageOffline() -- and turn the page inaccessible in the hypervisor; after that, access to page content can be fatal. One example is virtio-mem; while unplugged memory -- marked as PageOffline() can currently be read in the hypervisor, this will no longer be the case in the future; for example, when having a virtio-mem device backed by huge pages in the hypervisor.

Quoted 5 lines

Some special PFN walkers -- i.e., /proc/kcore -- read content of random pages after checking PageOffline(); however, these PFN walkers can race with drivers that set PageOffline().

Let's introduce page_offline_(begin|end|freeze|unfreeze) for

Bikeshed: freeze|thaw?

Quoted 83 lines

synchronizing.

page_offline_freeze()/page_offline_unfreeze() allows for a subsystem to synchronize with such drivers, achieving that a page cannot be set PageOffline() while frozen.

page_offline_begin()/page_offline_end() is used by drivers that care about such races when setting a page PageOffline().

For simplicity, use a rwsem for now; neither drivers nor users are performance sensitive.

---
 include/linux/page-flags.h |  5 +++++
 mm/util.c                  | 38 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 43 insertions(+)
include/linux/page-flags.h +5 -0
index b8c56672a588..e3d00c72f459 100644
@@ -767,6 +767,11 @@ PAGE_TYPE_OPS(Buddy, buddy)
  */
 PAGE_TYPE_OPS(Offline, offline)
 
+extern void page_offline_freeze(void);
+extern void page_offline_unfreeze(void);
+extern void page_offline_begin(void);
+extern void page_offline_end(void);
+
 /*
  * Marks pages in use as page tables.
  */
mm/util.c +38 -0
index 54870226cea6..95395d4e4209 100644
@@ -1013,3 +1013,41 @@ void mem_dump_obj(void *object)
 	}
 	pr_cont(" non-slab/vmalloc memory.\n");
 }
+
+/*
+ * A driver might set a page logically offline -- PageOffline() -- and
+ * turn the page inaccessible in the hypervisor; after that, access to page
+ * content can be fatal.
+ *
+ * Some special PFN walkers -- i.e., /proc/kcore -- read content of random
+ * pages after checking PageOffline(); however, these PFN walkers can race
+ * with drivers that set PageOffline().
+ *
+ * page_offline_freeze()/page_offline_unfreeze() allows for a subsystem to
+ * synchronize with such drivers, achieving that a page cannot be set
+ * PageOffline() while frozen.
+ *
+ * page_offline_begin()/page_offline_end() is used by drivers that care about
+ * such races when setting a page PageOffline().
+ */
+static DECLARE_RWSEM(page_offline_rwsem);
+
+void page_offline_freeze(void)
+{
+	down_read(&page_offline_rwsem);
+}
+
+void page_offline_unfreeze(void)
+{
+	up_read(&page_offline_rwsem);
+}
+
+void page_offline_begin(void)
+{
+	down_write(&page_offline_rwsem);
+}
+
+void page_offline_end(void)
+{
+	up_write(&page_offline_rwsem);
+}
Signature · 1 line
2.30.2
Signature · 2 lines
Sincerely yours,
Mike.
#22 lore raw

Re: [PATCH v1 5/7] mm: introduce page_offline_(begin|end|freeze|unfreeze) to synchronize setting PageOffline()

Quoted 8 lines · Mike Rapoport
Quoted 7 lines · David Hildenbrand

A driver might set a page logically offline -- PageOffline() -- and turn the page inaccessible in the hypervisor; after that, access to page content can be fatal. One example is virtio-mem; while unplugged memory -- marked as PageOffline() can currently be read in the hypervisor, this will no longer be the case in the future; for example, when having a virtio-mem device backed by huge pages in the hypervisor.

Quoted 8 lines
Quoted 5 lines

Some special PFN walkers -- i.e., /proc/kcore -- read content of random pages after checking PageOffline(); however, these PFN walkers can race with drivers that set PageOffline().

Let's introduce page_offline_(begin|end|freeze|unfreeze) for

Bikeshed: freeze|thaw?

Sure :)

Signature · 3 lines
Thanks,

David / dhildenb
reviewer Acked-by on v1 5/7
#23 lore raw

Re: [PATCH v1 5/7] mm: introduce page_offline_(begin|end|freeze|unfreeze) to synchronize setting PageOffline()

In reply to David Hildenbrand
Quoted 18 lines · David Hildenbrand

A driver might set a page logically offline -- PageOffline() -- and turn the page inaccessible in the hypervisor; after that, access to page content can be fatal. One example is virtio-mem; while unplugged memory -- marked as PageOffline() can currently be read in the hypervisor, this will no longer be the case in the future; for example, when having a virtio-mem device backed by huge pages in the hypervisor.

Some special PFN walkers -- i.e., /proc/kcore -- read content of random pages after checking PageOffline(); however, these PFN walkers can race with drivers that set PageOffline().

Let's introduce page_offline_(begin|end|freeze|unfreeze) for synchronizing.

page_offline_freeze()/page_offline_unfreeze() allows for a subsystem to synchronize with such drivers, achieving that a page cannot be set PageOffline() while frozen.

Quoted 5 lines

page_offline_begin()/page_offline_end() is used by drivers that care about such races when setting a page PageOffline().

For simplicity, use a rwsem for now; neither drivers nor users are performance sensitive.

Please add a note to the PageOffline documentation as well. While are adding the api close enough an explicit note there wouldn't hurt.

Quoted 1 line

As to the patch itself, I am slightly worried that other pfn walkers might be less tolerant to the locking than the proc ones. On the other hand most users shouldn't really care as they do not tend to touch the memory content and PageOffline check without any synchronization should be sufficient for those. Let's try this out and see where we get...

Quoted 70 lines
---
 include/linux/page-flags.h |  5 +++++
 mm/util.c                  | 38 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 43 insertions(+)
include/linux/page-flags.h +5 -0
index b8c56672a588..e3d00c72f459 100644
@@ -767,6 +767,11 @@ PAGE_TYPE_OPS(Buddy, buddy)
  */
 PAGE_TYPE_OPS(Offline, offline)
 
+extern void page_offline_freeze(void);
+extern void page_offline_unfreeze(void);
+extern void page_offline_begin(void);
+extern void page_offline_end(void);
+
 /*
  * Marks pages in use as page tables.
  */
mm/util.c +38 -0
index 54870226cea6..95395d4e4209 100644
@@ -1013,3 +1013,41 @@ void mem_dump_obj(void *object)
 	}
 	pr_cont(" non-slab/vmalloc memory.\n");
 }
+
+/*
+ * A driver might set a page logically offline -- PageOffline() -- and
+ * turn the page inaccessible in the hypervisor; after that, access to page
+ * content can be fatal.
+ *
+ * Some special PFN walkers -- i.e., /proc/kcore -- read content of random
+ * pages after checking PageOffline(); however, these PFN walkers can race
+ * with drivers that set PageOffline().
+ *
+ * page_offline_freeze()/page_offline_unfreeze() allows for a subsystem to
+ * synchronize with such drivers, achieving that a page cannot be set
+ * PageOffline() while frozen.
+ *
+ * page_offline_begin()/page_offline_end() is used by drivers that care about
+ * such races when setting a page PageOffline().
+ */
+static DECLARE_RWSEM(page_offline_rwsem);
+
+void page_offline_freeze(void)
+{
+	down_read(&page_offline_rwsem);
+}
+
+void page_offline_unfreeze(void)
+{
+	up_read(&page_offline_rwsem);
+}
+
+void page_offline_begin(void)
+{
+	down_write(&page_offline_rwsem);
+}
+
+void page_offline_end(void)
+{
+	up_write(&page_offline_rwsem);
+}
Signature · 1 line
2.30.2
Signature · 2 lines
Michal Hocko
SUSE Labs
#24 lore raw

Re: [PATCH v1 5/7] mm: introduce page_offline_(begin|end|freeze|unfreeze) to synchronize setting PageOffline()

Quoted 19 lines · Michal Hocko
Quoted 18 lines · David Hildenbrand

A driver might set a page logically offline -- PageOffline() -- and turn the page inaccessible in the hypervisor; after that, access to page content can be fatal. One example is virtio-mem; while unplugged memory -- marked as PageOffline() can currently be read in the hypervisor, this will no longer be the case in the future; for example, when having a virtio-mem device backed by huge pages in the hypervisor.

Some special PFN walkers -- i.e., /proc/kcore -- read content of random pages after checking PageOffline(); however, these PFN walkers can race with drivers that set PageOffline().

Let's introduce page_offline_(begin|end|freeze|unfreeze) for synchronizing.

page_offline_freeze()/page_offline_unfreeze() allows for a subsystem to synchronize with such drivers, achieving that a page cannot be set PageOffline() while frozen.

Quoted 8 lines
Quoted 5 lines

page_offline_begin()/page_offline_end() is used by drivers that care about such races when setting a page PageOffline().

For simplicity, use a rwsem for now; neither drivers nor users are performance sensitive.

Please add a note to the PageOffline documentation as well. While are adding the api close enough an explicit note there wouldn't hurt.

Will do.

Quoted 8 lines
Quoted 2 lines

As to the patch itself, I am slightly worried that other pfn walkers might be less tolerant to the locking than the proc ones. On the other hand most users shouldn't really care as they do not tend to touch the memory content and PageOffline check without any synchronization should be sufficient for those. Let's try this out and see where we get...

My thinking. Users that actually read random page content (as discussed in the cover letter) are

1. Hibernation
2. Dumping (/proc/kcore, /proc/vmcore)
3. Physical memory access bypassing the kernel via /dev/mem
4. Live debug tools (kgdb)

Other PFN walkers really shouldn't (and don't) access random page content.

Thanks!

Signature · 3 lines
Thanks,

David / dhildenb
reviewer
#25 lore raw

Re: [PATCH v1 5/7] mm: introduce page_offline_(begin|end|freeze|unfreeze) to synchronize setting PageOffline()

Quoted 40 lines · David Hildenbrand
Quoted 19 lines · Michal Hocko
Quoted 18 lines · David Hildenbrand

A driver might set a page logically offline -- PageOffline() -- and turn the page inaccessible in the hypervisor; after that, access to page content can be fatal. One example is virtio-mem; while unplugged memory -- marked as PageOffline() can currently be read in the hypervisor, this will no longer be the case in the future; for example, when having a virtio-mem device backed by huge pages in the hypervisor.

Some special PFN walkers -- i.e., /proc/kcore -- read content of random pages after checking PageOffline(); however, these PFN walkers can race with drivers that set PageOffline().

Let's introduce page_offline_(begin|end|freeze|unfreeze) for synchronizing.

page_offline_freeze()/page_offline_unfreeze() allows for a subsystem to synchronize with such drivers, achieving that a page cannot be set PageOffline() while frozen.

Quoted 8 lines
Quoted 5 lines

page_offline_begin()/page_offline_end() is used by drivers that care about such races when setting a page PageOffline().

For simplicity, use a rwsem for now; neither drivers nor users are performance sensitive.

Please add a note to the PageOffline documentation as well. While are adding the api close enough an explicit note there wouldn't hurt.

Will do.

Quoted 8 lines
Quoted 2 lines

As to the patch itself, I am slightly worried that other pfn walkers might be less tolerant to the locking than the proc ones. On the other hand most users shouldn't really care as they do not tend to touch the memory content and PageOffline check without any synchronization should be sufficient for those. Let's try this out and see where we get...

Quoted 7 lines

My thinking. Users that actually read random page content (as discussed in the cover letter) are

1. Hibernation
2. Dumping (/proc/kcore, /proc/vmcore)
3. Physical memory access bypassing the kernel via /dev/mem
4. Live debug tools (kgdb)

I think you can add

5. Very old drivers

Quoted 10 lines

Other PFN walkers really shouldn't (and don't) access random page content.

Thanks!

Signature · 3 lines
Thanks,

David / dhildenb
Signature · 2 lines
Sincerely yours,
Mike.
#26 lore raw

PATCH 6/7 virtio-mem: use page_offline_(start|end) when setting PageOffline()

In reply to David Hildenbrand

Let's properly use page_offline_(start|end) to synchronize setting PageOffline(), so we won't have valid page access to unplugged memory regions from /proc/kcore.

---
 drivers/virtio/virtio_mem.c | 2 ++
 mm/util.c                   | 2 ++
 2 files changed, 4 insertions(+)
drivers/virtio/virtio_mem.c +2 -0
index 10ec60d81e84..dc2a2e2b2ff8 100644
@@ -1065,6 +1065,7 @@ static int virtio_mem_memory_notifier_cb(struct notifier_block *nb,
 static void virtio_mem_set_fake_offline(unsigned long pfn,
 					unsigned long nr_pages, bool onlined)
 {
+	page_offline_begin();
 	for (; nr_pages--; pfn++) {
 		struct page *page = pfn_to_page(pfn);
 
@@ -1075,6 +1076,7 @@ static void virtio_mem_set_fake_offline(unsigned long pfn,
 			ClearPageReserved(page);
 		}
 	}
+	page_offline_end();
 }
 
 /*
mm/util.c +2 -0
index 95395d4e4209..d0e357bd65e6 100644
@@ -1046,8 +1046,10 @@ void page_offline_begin(void)
 {
 	down_write(&page_offline_rwsem);
 }
+EXPORT_SYMBOL(page_offline_begin);
 
 void page_offline_end(void)
 {
 	up_write(&page_offline_rwsem);
 }
+EXPORT_SYMBOL(page_offline_end);
Signature · 1 line
2.30.2
reviewer
#27 lore raw

Re: [PATCH v1 6/7] virtio-mem: use page_offline_(start|end) when setting PageOffline()

Quoted 23 lines · David Hildenbrand

Let's properly use page_offline_(start|end) to synchronize setting PageOffline(), so we won't have valid page access to unplugged memory regions from /proc/kcore.

---
 drivers/virtio/virtio_mem.c | 2 ++
 mm/util.c                   | 2 ++
 2 files changed, 4 insertions(+)
drivers/virtio/virtio_mem.c +1 -0
index 10ec60d81e84..dc2a2e2b2ff8 100644
@@ -1065,6 +1065,7 @@ static int virtio_mem_memory_notifier_cb(struct notifier_block *nb,
 static void virtio_mem_set_fake_offline(unsigned long pfn,
 					unsigned long nr_pages, bool onlined)
 {
+	page_offline_begin();
 	for (; nr_pages--; pfn++) {
 		struct page *page = pfn_to_page(pfn);
 

drivers/virtio/virtio_mem.c static void virtio_mem_set_fake_offline(unsigned long pfn, · line 1079

Quoted 4 lines
drivers/virtio/virtio_mem.c +1 -0
@@ -1075,6 +1076,7 @@ static void virtio_mem_set_fake_offline(unsigned long pfn,
 			ClearPageReserved(page);
 		}
 	}
+	page_offline_end();

I'm not really familiar with ballooning and memory hotplug, but is it the only place that needs page_offline_{begin,end} ?

mm/util.c void page_offline_begin(void) · line 1049

Quoted 12 lines
drivers/virtio/virtio_mem.c +0 -0
 }
 
 /*
mm/util.c +1 -0
index 95395d4e4209..d0e357bd65e6 100644
@@ -1046,8 +1046,10 @@ void page_offline_begin(void)
 {
 	down_write(&page_offline_rwsem);
 }
+EXPORT_SYMBOL(page_offline_begin);

Should have been a part of the previous patch.

mm/util.c void page_offline_begin(void) · line 1055

Quoted 5 lines
mm/util.c +1 -0
 void page_offline_end(void)
 {
 	up_write(&page_offline_rwsem);
 }
+EXPORT_SYMBOL(page_offline_end);

Ditto

Quoted 3 lines
Signature · 1 line
2.30.2
Signature · 2 lines
Sincerely yours,
Mike.
#28 lore raw

Re: [PATCH v1 6/7] virtio-mem: use page_offline_(start|end) when setting PageOffline()

Quoted 23 lines · Mike Rapoport
Quoted 22 lines · David Hildenbrand

Let's properly use page_offline_(start|end) to synchronize setting PageOffline(), so we won't have valid page access to unplugged memory regions from /proc/kcore.

---
  drivers/virtio/virtio_mem.c | 2 ++
  mm/util.c                   | 2 ++
  2 files changed, 4 insertions(+)
drivers/virtio/virtio_mem.c +1 -0
index 10ec60d81e84..dc2a2e2b2ff8 100644
@@ -1065,6 +1065,7 @@ static int virtio_mem_memory_notifier_cb(struct notifier_block *nb,
  static void virtio_mem_set_fake_offline(unsigned long pfn,
  					unsigned long nr_pages, bool onlined)
  {
+	page_offline_begin();
  	for (; nr_pages--; pfn++) {
  		struct page *page = pfn_to_page(pfn);
 
Quoted 8 lines

drivers/virtio/virtio_mem.c static void virtio_mem_set_fake_offline(unsigned long pfn, · line 1079

Quoted 5 lines
drivers/virtio/virtio_mem.c +1 -0
@@ -1075,6 +1076,7 @@ static void virtio_mem_set_fake_offline(unsigned long pfn,
  			ClearPageReserved(page);
  		}
  	}
+	page_offline_end();

I'm not really familiar with ballooning and memory hotplug, but is it the only place that needs page_offline_{begin,end} ?

Existing balloon implementations that I am aware of (Hyper-V, XEN, virtio-balloon, vmware-balloon) usually allow reading inflated memory; doing so might result in unnecessary overhead in the hypervisor, so we really want to avoid it -- but it's strictly not forbidden and has been working forever. So we barely care about races: if there would be a rare race, we'd still be able to read that memory.

For virtio-mem, it'll be different in the future when using shmem, huge pages, !anonymous private mappings, ... as backing storage for a VM; there will be a virtio spec extension to document that virtio-mem changes that indicate the new behavior won't allow reading unplugged memory and doing so will result in undefined behavior.

Signature · 3 lines
Thanks,

David / dhildenb
maintainer Acked-by on v1 6/7
#29 lore raw

Re: [PATCH v1 6/7] virtio-mem: use page_offline_(start|end) when setting PageOffline()

In reply to David Hildenbrand
Quoted 5 lines · David Hildenbrand

Let's properly use page_offline_(start|end) to synchronize setting PageOffline(), so we won't have valid page access to unplugged memory regions from /proc/kcore.

the patch looks good to me as such

Feel free to merge with rest of patcgset - it seems to mostly live in the fs/mm space.

IF you respin, maybe add the explanation you sent in response to Mike's comments in the commit log.

Quoted 42 lines
---
 drivers/virtio/virtio_mem.c | 2 ++
 mm/util.c                   | 2 ++
 2 files changed, 4 insertions(+)
drivers/virtio/virtio_mem.c +2 -0
index 10ec60d81e84..dc2a2e2b2ff8 100644
@@ -1065,6 +1065,7 @@ static int virtio_mem_memory_notifier_cb(struct notifier_block *nb,
 static void virtio_mem_set_fake_offline(unsigned long pfn,
 					unsigned long nr_pages, bool onlined)
 {
+	page_offline_begin();
 	for (; nr_pages--; pfn++) {
 		struct page *page = pfn_to_page(pfn);
 
@@ -1075,6 +1076,7 @@ static void virtio_mem_set_fake_offline(unsigned long pfn,
 			ClearPageReserved(page);
 		}
 	}
+	page_offline_end();
 }
 
 /*
mm/util.c +2 -0
index 95395d4e4209..d0e357bd65e6 100644
@@ -1046,8 +1046,10 @@ void page_offline_begin(void)
 {
 	down_write(&page_offline_rwsem);
 }
+EXPORT_SYMBOL(page_offline_begin);
 
 void page_offline_end(void)
 {
 	up_write(&page_offline_rwsem);
 }
+EXPORT_SYMBOL(page_offline_end);
Signature · 1 line
2.30.2
#30 lore raw

PATCH 7/7 fs/proc/kcore: use page_offline_(freeze|unfreeze)

In reply to David Hildenbrand

Let's properly synchronize with drivers that set PageOffline(). Unfreeze every now and then, so drivers that want to set PageOffline() can make progress.

---
 fs/proc/kcore.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)
fs/proc/kcore.c +15 -0
index 92ff1e4436cb..3d7531f47389 100644
@@ -311,6 +311,7 @@ static void append_kcore_note(char *notes, size_t *i, const char *name,
 static ssize_t
 read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos)
 {
+	size_t page_offline_frozen = 0;
 	char *buf = file->private_data;
 	size_t phdrs_offset, notes_offset, data_offset;
 	size_t phdrs_len, notes_len;
@@ -509,6 +510,18 @@ read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos)
 			pfn = __pa(start) >> PAGE_SHIFT;
 			page = pfn_to_online_page(pfn);
 
+			/*
+			 * Don't race against drivers that set PageOffline()
+			 * and expect no further page access.
+			 */
+			if (page_offline_frozen == MAX_ORDER_NR_PAGES) {
+				page_offline_unfreeze();
+				page_offline_frozen = 0;
+				cond_resched();
+			}
+			if (!page_offline_frozen++)
+				page_offline_freeze();
+
 			/*
 			 * Don't read offline sections, logically offline pages
 			 * (e.g., inflated in a balloon), hwpoisoned pages,
@@ -565,6 +578,8 @@ read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos)
 	}
 
 out:
+	if (page_offline_frozen)
+		page_offline_unfreeze();
 	up_read(&kclist_lock);
 	if (ret)
 		return ret;
Signature · 1 line
2.30.2
reviewer
#31 lore raw

Re: [PATCH v1 7/7] fs/proc/kcore: use page_offline_(freeze|unfreeze)

Quoted 22 lines · David Hildenbrand

Let's properly synchronize with drivers that set PageOffline(). Unfreeze every now and then, so drivers that want to set PageOffline() can make progress.

---
 fs/proc/kcore.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)
fs/proc/kcore.c +1 -0
index 92ff1e4436cb..3d7531f47389 100644
@@ -311,6 +311,7 @@ static void append_kcore_note(char *notes, size_t *i, const char *name,
 static ssize_t
 read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos)
 {
+	size_t page_offline_frozen = 0;
 	char *buf = file->private_data;
 	size_t phdrs_offset, notes_offset, data_offset;
 	size_t phdrs_len, notes_len;

fs/proc/kcore.c read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos) · line 511

Quoted 2 lines
fs/proc/kcore.c +0 -0
@@ -509,6 +510,18 @@ read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos)
 			pfn = __pa(start) >> PAGE_SHIFT;
 			page = pfn_to_online_page(pfn);

Can't this race with page offlining for the first time we get here?

fs/proc/kcore.c read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos) · line 524

Quoted 12 lines
fs/proc/kcore.c +12 -0
+			/*
+			 * Don't race against drivers that set PageOffline()
+			 * and expect no further page access.
+			 */
+			if (page_offline_frozen == MAX_ORDER_NR_PAGES) {
+				page_offline_unfreeze();
+				page_offline_frozen = 0;
+				cond_resched();
+			}
+			if (!page_offline_frozen++)
+				page_offline_freeze();
+

Don't we need to freeze before doing pfn_to_online_page()?

Quoted 15 lines
fs/proc/kcore.c +2 -0
 			/*
 			 * Don't read offline sections, logically offline pages
 			 * (e.g., inflated in a balloon), hwpoisoned pages,
@@ -565,6 +578,8 @@ read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos)
 	}
 
 out:
+	if (page_offline_frozen)
+		page_offline_unfreeze();
 	up_read(&kclist_lock);
 	if (ret)
 		return ret;
Signature · 1 line
2.30.2
Signature · 2 lines
Sincerely yours,
Mike.
#32 lore raw

Re: [PATCH v1 7/7] fs/proc/kcore: use page_offline_(freeze|unfreeze)

Quoted 19 lines · Mike Rapoport
Quoted 18 lines · David Hildenbrand

Let's properly synchronize with drivers that set PageOffline(). Unfreeze every now and then, so drivers that want to set PageOffline() can make progress.

---
  fs/proc/kcore.c | 15 +++++++++++++++
  1 file changed, 15 insertions(+)
fs/proc/kcore.c +1 -0
index 92ff1e4436cb..3d7531f47389 100644
@@ -311,6 +311,7 @@ static void append_kcore_note(char *notes, size_t *i, const char *name,
  static ssize_t
  read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos)
  {
+	size_t page_offline_frozen = 0;
Quoted 8 lines

fs/proc/kcore.c read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos) · line 511

Quoted 6 lines
fs/proc/kcore.c +0 -0
  	char *buf = file->private_data;
  	size_t phdrs_offset, notes_offset, data_offset;
  	size_t phdrs_len, notes_len;
@@ -509,6 +510,18 @@ read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos)
  			pfn = __pa(start) >> PAGE_SHIFT;
  			page = pfn_to_online_page(pfn);

Can't this race with page offlining for the first time we get here?

To clarify, we have three types of offline pages in the kernel ...

a) Pages part of an offline memory section; the memap is stale and not trustworthy. pfn_to_online_page() checks that. We *can* protect against memory offlining using get_online_mems()/put_online_mems(), but usually avoid doing so as the race window is very small (and a problem all over the kernel we basically never hit) and locking is rather expensive. In the future, we might switch to rcu to handle that more efficiently and avoiding these possible races.

b) PageOffline(): logically offline pages contained in an online memory section with a sane memmap. virtio-mem calls these pages "fake offline"; something like a "temporary" memory hole. The new mechanism I propose will be used to handle synchronization as races can be more severe, e.g., when reading actual page content here.

c) Soft offline pages: hwpoisoned pages that are not actually harmful yet, but could become harmful in the future. So we better try to remove the page from the page allcoator and try to migrate away existing users.

So page_offline_* handle "b) PageOffline()" only. There is a tiny race between pfn_to_online_page(pfn) and looking at the memmap as we have in many cases already throughout the kernel, to be tackled in the future.

(A better name for PageOffline() might make sense; PageSoftOffline() would be catchy but interferes with c). PageLogicallyOffline() is ugly; PageFakeOffline() might do)

Quoted 15 lines

fs/proc/kcore.c read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos) · line 524

Quoted 13 lines
fs/proc/kcore.c +12 -0
+			/*
+			 * Don't race against drivers that set PageOffline()
+			 * and expect no further page access.
+			 */
+			if (page_offline_frozen == MAX_ORDER_NR_PAGES) {
+				page_offline_unfreeze();
+				page_offline_frozen = 0;
+				cond_resched();
+			}
+			if (!page_offline_frozen++)
+				page_offline_freeze();
+

Don't we need to freeze before doing pfn_to_online_page()?

See my explanation above. Thanks!

Signature · 3 lines
Thanks,

David / dhildenb
reviewer
#33 lore raw

Re: [PATCH v1 7/7] fs/proc/kcore: use page_offline_(freeze|unfreeze)

Quoted 46 lines · David Hildenbrand
Quoted 19 lines · Mike Rapoport
Quoted 18 lines · David Hildenbrand

Let's properly synchronize with drivers that set PageOffline(). Unfreeze every now and then, so drivers that want to set PageOffline() can make progress.

---
  fs/proc/kcore.c | 15 +++++++++++++++
  1 file changed, 15 insertions(+)
fs/proc/kcore.c +1 -0
index 92ff1e4436cb..3d7531f47389 100644
@@ -311,6 +311,7 @@ static void append_kcore_note(char *notes, size_t *i, const char *name,
  static ssize_t
  read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos)
  {
+	size_t page_offline_frozen = 0;
Quoted 8 lines

fs/proc/kcore.c read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos) · line 511

Quoted 6 lines
fs/proc/kcore.c +0 -0
  	char *buf = file->private_data;
  	size_t phdrs_offset, notes_offset, data_offset;
  	size_t phdrs_len, notes_len;
@@ -509,6 +510,18 @@ read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos)
  			pfn = __pa(start) >> PAGE_SHIFT;
  			page = pfn_to_online_page(pfn);

Can't this race with page offlining for the first time we get here?

To clarify, we have three types of offline pages in the kernel ...

a) Pages part of an offline memory section; the memap is stale and not trustworthy. pfn_to_online_page() checks that. We *can* protect against memory offlining using get_online_mems()/put_online_mems(), but usually avoid doing so as the race window is very small (and a problem all over the kernel we basically never hit) and locking is rather expensive. In the future, we might switch to rcu to handle that more efficiently and avoiding these possible races.

b) PageOffline(): logically offline pages contained in an online memory section with a sane memmap. virtio-mem calls these pages "fake offline"; something like a "temporary" memory hole. The new mechanism I propose will be used to handle synchronization as races can be more severe, e.g., when reading actual page content here.

Quoted 8 lines

c) Soft offline pages: hwpoisoned pages that are not actually harmful yet, but could become harmful in the future. So we better try to remove the page from the page allcoator and try to migrate away existing users.

So page_offline_* handle "b) PageOffline()" only. There is a tiny race between pfn_to_online_page(pfn) and looking at the memmap as we have in many cases already throughout the kernel, to be tackled in the future.

Right, but here you anyway add locking, so why exclude the first iteration?

Quoted 4 lines

(A better name for PageOffline() might make sense; PageSoftOffline() would be catchy but interferes with c). PageLogicallyOffline() is ugly; PageFakeOffline() might do)

Quoted 12 lines
Quoted 4 lines
Quoted 4 lines
fs/proc/kcore.c +4 -0
+			/*
+			 * Don't race against drivers that set PageOffline()
+			 * and expect no further page access.
+			 */
Quoted 8 lines

fs/proc/kcore.c read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos) · line 524

Quoted 8 lines
fs/proc/kcore.c +8 -0
+			if (page_offline_frozen == MAX_ORDER_NR_PAGES) {
+				page_offline_unfreeze();
+				page_offline_frozen = 0;
+				cond_resched();
+			}
+			if (!page_offline_frozen++)
+				page_offline_freeze();
+

BTW, did you consider something like

	if (page_offline_frozen++ % MAX_ORDER_NR_PAGES == 0) {
		page_offline_unfreeze();
		cond_resched();
		page_offline_freeze();
	}

We don't seem to care about page_offline_frozen overflows here, do we?

Signature · 2 lines
Sincerely yours,
Mike.
#34 lore raw

Re: [PATCH v1 7/7] fs/proc/kcore: use page_offline_(freeze|unfreeze)

Quoted 52 lines · Mike Rapoport
Quoted 51 lines · David Hildenbrand
Quoted 19 lines · Mike Rapoport
Quoted 18 lines · David Hildenbrand

Let's properly synchronize with drivers that set PageOffline(). Unfreeze every now and then, so drivers that want to set PageOffline() can make progress.

---
   fs/proc/kcore.c | 15 +++++++++++++++
   1 file changed, 15 insertions(+)
fs/proc/kcore.c +1 -0
index 92ff1e4436cb..3d7531f47389 100644
@@ -311,6 +311,7 @@ static void append_kcore_note(char *notes, size_t *i, const char *name,
   static ssize_t
   read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos)
   {
+	size_t page_offline_frozen = 0;
Quoted 8 lines

fs/proc/kcore.c read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos) · line 511

Quoted 6 lines
fs/proc/kcore.c +0 -0
   	char *buf = file->private_data;
   	size_t phdrs_offset, notes_offset, data_offset;
   	size_t phdrs_len, notes_len;
@@ -509,6 +510,18 @@ read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos)
   			pfn = __pa(start) >> PAGE_SHIFT;
   			page = pfn_to_online_page(pfn);

Can't this race with page offlining for the first time we get here?

To clarify, we have three types of offline pages in the kernel ...

a) Pages part of an offline memory section; the memap is stale and not trustworthy. pfn_to_online_page() checks that. We *can* protect against memory offlining using get_online_mems()/put_online_mems(), but usually avoid doing so as the race window is very small (and a problem all over the kernel we basically never hit) and locking is rather expensive. In the future, we might switch to rcu to handle that more efficiently and avoiding these possible races.

b) PageOffline(): logically offline pages contained in an online memory section with a sane memmap. virtio-mem calls these pages "fake offline"; something like a "temporary" memory hole. The new mechanism I propose will be used to handle synchronization as races can be more severe, e.g., when reading actual page content here.

c) Soft offline pages: hwpoisoned pages that are not actually harmful yet, but could become harmful in the future. So we better try to remove the page from the page allcoator and try to migrate away existing users.

Quoted 5 lines
Quoted 3 lines

So page_offline_* handle "b) PageOffline()" only. There is a tiny race between pfn_to_online_page(pfn) and looking at the memmap as we have in many cases already throughout the kernel, to be tackled in the future.

Right, but here you anyway add locking, so why exclude the first iteration?

What we're protecting is PageOffline() below. If I didn't mess up, we should always be calling page_offline_freeze() before calling PageOffline(). Or am I missing something?

Quoted 2 lines

BTW, did you consider something like

Yes, I played with something like that. We'd have to handle the first page_offline_freeze() freeze differently, though, and that's where things got a bit ugly in my attempts.

Quoted 8 lines
	if (page_offline_frozen++ % MAX_ORDER_NR_PAGES == 0) {
		page_offline_unfreeze();
		cond_resched();
		page_offline_freeze();
	}

We don't seem to care about page_offline_frozen overflows here, do we?

No, the buffer size is also size_t and gets incremented on a per-byte basis. The variant I have right now looked the cleanest to me. Happy to hear simpler alternatives.

Signature · 3 lines
Thanks,

David / dhildenb
reviewer
#35 lore raw

Re: [PATCH v1 7/7] fs/proc/kcore: use page_offline_(freeze|unfreeze)

Quoted 57 lines · David Hildenbrand
Quoted 56 lines · Mike Rapoport
Quoted 54 lines · David Hildenbrand
Quoted 19 lines · Mike Rapoport
Quoted 18 lines · David Hildenbrand

Let's properly synchronize with drivers that set PageOffline(). Unfreeze every now and then, so drivers that want to set PageOffline() can make progress.

---
   fs/proc/kcore.c | 15 +++++++++++++++
   1 file changed, 15 insertions(+)
fs/proc/kcore.c +1 -0
index 92ff1e4436cb..3d7531f47389 100644
@@ -311,6 +311,7 @@ static void append_kcore_note(char *notes, size_t *i, const char *name,
   static ssize_t
   read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos)
   {
+	size_t page_offline_frozen = 0;
Quoted 8 lines

fs/proc/kcore.c read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos) · line 511

Quoted 6 lines
fs/proc/kcore.c +0 -0
   	char *buf = file->private_data;
   	size_t phdrs_offset, notes_offset, data_offset;
   	size_t phdrs_len, notes_len;
@@ -509,6 +510,18 @@ read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos)
   			pfn = __pa(start) >> PAGE_SHIFT;
   			page = pfn_to_online_page(pfn);

Can't this race with page offlining for the first time we get here?

To clarify, we have three types of offline pages in the kernel ...

a) Pages part of an offline memory section; the memap is stale and not trustworthy. pfn_to_online_page() checks that. We *can* protect against memory offlining using get_online_mems()/put_online_mems(), but usually avoid doing so as the race window is very small (and a problem all over the kernel we basically never hit) and locking is rather expensive. In the future, we might switch to rcu to handle that more efficiently and avoiding these possible races.

b) PageOffline(): logically offline pages contained in an online memory section with a sane memmap. virtio-mem calls these pages "fake offline"; something like a "temporary" memory hole. The new mechanism I propose will be used to handle synchronization as races can be more severe, e.g., when reading actual page content here.

c) Soft offline pages: hwpoisoned pages that are not actually harmful yet, but could become harmful in the future. So we better try to remove the page from the page allcoator and try to migrate away existing users.

So page_offline_* handle "b) PageOffline()" only. There is a tiny race between pfn_to_online_page(pfn) and looking at the memmap as we have in many cases already throughout the kernel, to be tackled in the future.

Quoted 5 lines
Quoted 1 line

Right, but here you anyway add locking, so why exclude the first iteration?

What we're protecting is PageOffline() below. If I didn't mess up, we should always be calling page_offline_freeze() before calling PageOffline(). Or am I missing something?

Somehow I was under impression we are protecting both pfn_to_online_page() and PageOffline().

Quoted 13 lines
Quoted 1 line

BTW, did you consider something like

Yes, I played with something like that. We'd have to handle the first page_offline_freeze() freeze differently, though, and that's where things got a bit ugly in my attempts.

Quoted 7 lines
	if (page_offline_frozen++ % MAX_ORDER_NR_PAGES == 0) {
		page_offline_unfreeze();
		cond_resched();
		page_offline_freeze();
	}
Quoted 5 lines
Quoted 1 line

We don't seem to care about page_offline_frozen overflows here, do we?

No, the buffer size is also size_t and gets incremented on a per-byte basis. The variant I have right now looked the cleanest to me. Happy to hear simpler alternatives.

Well, locking for the first time before the while() loop and doing resched-relock outside switch() would be definitely nicer, and it makes the last unlock unconditional.

The cost of prevention of memory offline during reads of !KCORE_RAM parts does not seem that significant to me, but I may be missing something.

Signature · 2 lines
Sincerely yours,
Mike.
#36 lore raw

Re: [PATCH v1 7/7] fs/proc/kcore: use page_offline_(freeze|unfreeze)

Quoted 86 lines · Mike Rapoport
Quoted 57 lines · David Hildenbrand
Quoted 56 lines · Mike Rapoport
Quoted 54 lines · David Hildenbrand
Quoted 19 lines · Mike Rapoport
Quoted 18 lines · David Hildenbrand

Let's properly synchronize with drivers that set PageOffline(). Unfreeze every now and then, so drivers that want to set PageOffline() can make progress.

---
    fs/proc/kcore.c | 15 +++++++++++++++
    1 file changed, 15 insertions(+)
fs/proc/kcore.c +1 -0
index 92ff1e4436cb..3d7531f47389 100644
@@ -311,6 +311,7 @@ static void append_kcore_note(char *notes, size_t *i, const char *name,
    static ssize_t
    read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos)
    {
+	size_t page_offline_frozen = 0;
Quoted 8 lines

fs/proc/kcore.c read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos) · line 511

Quoted 6 lines
fs/proc/kcore.c +0 -0
    	char *buf = file->private_data;
    	size_t phdrs_offset, notes_offset, data_offset;
    	size_t phdrs_len, notes_len;
@@ -509,6 +510,18 @@ read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos)
    			pfn = __pa(start) >> PAGE_SHIFT;
    			page = pfn_to_online_page(pfn);

Can't this race with page offlining for the first time we get here?

To clarify, we have three types of offline pages in the kernel ...

a) Pages part of an offline memory section; the memap is stale and not trustworthy. pfn_to_online_page() checks that. We *can* protect against memory offlining using get_online_mems()/put_online_mems(), but usually avoid doing so as the race window is very small (and a problem all over the kernel we basically never hit) and locking is rather expensive. In the future, we might switch to rcu to handle that more efficiently and avoiding these possible races.

b) PageOffline(): logically offline pages contained in an online memory section with a sane memmap. virtio-mem calls these pages "fake offline"; something like a "temporary" memory hole. The new mechanism I propose will be used to handle synchronization as races can be more severe, e.g., when reading actual page content here.

c) Soft offline pages: hwpoisoned pages that are not actually harmful yet, but could become harmful in the future. So we better try to remove the page from the page allcoator and try to migrate away existing users.

So page_offline_* handle "b) PageOffline()" only. There is a tiny race between pfn_to_online_page(pfn) and looking at the memmap as we have in many cases already throughout the kernel, to be tackled in the future.

Quoted 5 lines
Quoted 1 line

Right, but here you anyway add locking, so why exclude the first iteration?

What we're protecting is PageOffline() below. If I didn't mess up, we should always be calling page_offline_freeze() before calling PageOffline(). Or am I missing something?

Somehow I was under impression we are protecting both pfn_to_online_page() and PageOffline().

Quoted 13 lines
Quoted 1 line

BTW, did you consider something like

Yes, I played with something like that. We'd have to handle the first page_offline_freeze() freeze differently, though, and that's where things got a bit ugly in my attempts.

Quoted 7 lines
	if (page_offline_frozen++ % MAX_ORDER_NR_PAGES == 0) {
		page_offline_unfreeze();
		cond_resched();
		page_offline_freeze();
	}
Quoted 5 lines
Quoted 1 line

We don't seem to care about page_offline_frozen overflows here, do we?

No, the buffer size is also size_t and gets incremented on a per-byte basis. The variant I have right now looked the cleanest to me. Happy to hear simpler alternatives.

Quoted 6 lines

Well, locking for the first time before the while() loop and doing resched-relock outside switch() would be definitely nicer, and it makes the last unlock unconditional.

The cost of prevention of memory offline during reads of !KCORE_RAM parts does not seem that significant to me, but I may be missing something.

Also true, I'll have a look if I can just simplify that.

Signature · 3 lines
Thanks,

David / dhildenb