Skip to contentSkip to conversation

RFC: READ/WRITE_ONCE vma/mm cleanups

PATCH In review Andrea Arcangeli 5 participants 9 messages Mar 1, 2019 – Mar 5, 2019

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

cover RFC: READ/WRITE_ONCE vma/mm cleanups

Hello,

This was a well known issue for more than a decade, but until a few months ago we relied on the compiler to stick to atomic accesses and updates while walking and updating pagetables.

However now the 64bit native_set_pte finally uses WRITE_ONCE and gup_pmd_range uses READ_ONCE as well.

This convert more racy VM places to avoid depending on the expected compiler behavior to achieve kernel runtime correctness.

It mostly guarantees gcc to do atomic updates at 64bit granularity (practically not needed) and it also prevents gcc to emit code that risks getting confused if the memory unexpectedly changes under it (unlikely to ever be needed).

The list of vm_start/end/pgoff to update isn't complete, I covered the most obvious places, but before wasting too much time at doing a full audit I thought it was safer to post it and get some comment. More updates can be posted incrementally anyway.

Andrea Arcangeli (2):
coredump: use READ_ONCE to read mm->flags
mm: use READ/WRITE_ONCE to access anonymous vmas
vm_start/vm_end/vm_pgoff

 fs/coredump.c |  2 +-
 mm/gup.c      | 23 +++++++++++++----------
 mm/internal.h |  3 ++-
 mm/memory.c   |  2 +-
 mm/mmap.c     | 16 ++++++++--------
 mm/rmap.c     |  3 ++-
 mm/vmacache.c |  3 ++-
 7 files changed, 29 insertions(+), 23 deletions(-)
#1 lore raw

PATCH 1/2 coredump: use READ_ONCE to read mm->flags

mm->flags can still change freely under the coredump using atomic bitops in proc_coredump_filter_write(). So read the mm->flags with READ_ONCE for correctness.

---
 fs/coredump.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
fs/coredump.c +1 -1
index e42e17e55bfd..cc175d52090a 100644
@@ -560,7 +560,7 @@ void do_coredump(const kernel_siginfo_t *siginfo)
 		 * inconsistency of bit flags, since this flag is not protected
 		 * by any locks.
 		 */
-		.mm_flags = mm->flags,
+		.mm_flags = READ_ONCE(mm->flags),
 	};
 
 	audit_core_dumps(siginfo->si_signo);
#2 lore raw

PATCH 2/2 mm: use READ/WRITE_ONCE to access anonymous vmas vm_start/vm_end/vm_pgoff

In reply to Andrea Arcangeli

This converts the updates under mmap_sem for reading, rmap lock for writing and PT lock to vm_start/end/pgoff of anonymous vmas to use WRITE_ONCE().

This also converts some of the accesses under mmap_sem for reading that are concurrent with the aforementioned WRITE_ONCE()s to use READ_ONCE().

---
 mm/gup.c      | 23 +++++++++++++----------
 mm/internal.h |  3 ++-
 mm/memory.c   |  2 +-
 mm/mmap.c     | 16 ++++++++--------
 mm/rmap.c     |  3 ++-
 mm/vmacache.c |  3 ++-
 6 files changed, 28 insertions(+), 22 deletions(-)
mm/gup.c +13 -10
index 75029649baca..5cac5c462b40 100644
@@ -699,7 +699,7 @@ static long __get_user_pages(struct task_struct *tsk, struct mm_struct *mm,
 		unsigned int page_increm;
 
 		/* first iteration or cross vma bound */
-		if (!vma || start >= vma->vm_end) {
+		if (!vma || start >= READ_ONCE(vma->vm_end)) {
 			vma = find_extend_vma(mm, start);
 			if (!vma && in_gate_area(mm, start)) {
 				ret = get_gate_page(mm, start & PAGE_MASK,
@@ -850,7 +850,7 @@ int fixup_user_fault(struct task_struct *tsk, struct mm_struct *mm,
 
 retry:
 	vma = find_extend_vma(mm, address);
-	if (!vma || address < vma->vm_start)
+	if (!vma || address < READ_ONCE(vma->vm_start))
 		return -EFAULT;
 
 	if (!vma_permits_fault(vma, fault_flags))
@@ -1218,8 +1218,8 @@ long populate_vma_page_range(struct vm_area_struct *vma,
 
 	VM_BUG_ON(start & ~PAGE_MASK);
 	VM_BUG_ON(end   & ~PAGE_MASK);
-	VM_BUG_ON_VMA(start < vma->vm_start, vma);
-	VM_BUG_ON_VMA(end   > vma->vm_end, vma);
+	VM_BUG_ON_VMA(start < READ_ONCE(vma->vm_start), vma);
+	VM_BUG_ON_VMA(end   > READ_ONCE(vma->vm_end), vma);
 	VM_BUG_ON_MM(!rwsem_is_locked(&mm->mmap_sem), mm);
 
 	gup_flags = FOLL_TOUCH | FOLL_POPULATE | FOLL_MLOCK;
@@ -1258,7 +1258,7 @@ long populate_vma_page_range(struct vm_area_struct *vma,
 int __mm_populate(unsigned long start, unsigned long len, int ignore_errors)
 {
 	struct mm_struct *mm = current->mm;
-	unsigned long end, nstart, nend;
+	unsigned long end, nstart, nend, vma_start, vma_end;
 	struct vm_area_struct *vma = NULL;
 	int locked = 0;
 	long ret = 0;
@@ -1274,19 +1274,22 @@ int __mm_populate(unsigned long start, unsigned long len, int ignore_errors)
 			locked = 1;
 			down_read(&mm->mmap_sem);
 			vma = find_vma(mm, nstart);
-		} else if (nstart >= vma->vm_end)
+		} else if (nstart >= vma_end)
 			vma = vma->vm_next;
-		if (!vma || vma->vm_start >= end)
+		if (!vma)
 			break;
+		vma_start = READ_ONCE(vma->vm_start);
+		if (vma_start >= end)
+			break;
+		vma_end = READ_ONCE(vma->vm_end);
 		/*
 		 * Set [nstart; nend) to intersection of desired address
 		 * range with the first VMA. Also, skip undesirable VMA types.
 		 */
-		nend = min(end, vma->vm_end);
+		nend = min(end, vma_end);
 		if (vma->vm_flags & (VM_IO | VM_PFNMAP))
 			continue;
-		if (nstart < vma->vm_start)
-			nstart = vma->vm_start;
+		nstart = max(nstart, vma_start);
 		/*
 		 * Now fault in a range of pages. populate_vma_page_range()
 		 * double checks the vma flags, so that it won't mlock pages
mm/internal.h +2 -1
index f4a7bb02decf..839dbcf3c7ed 100644
@@ -337,7 +337,8 @@ static inline unsigned long
 __vma_address(struct page *page, struct vm_area_struct *vma)
 {
 	pgoff_t pgoff = page_to_pgoff(page);
-	return vma->vm_start + ((pgoff - vma->vm_pgoff) << PAGE_SHIFT);
+	return READ_ONCE(vma->vm_start) +
+		((pgoff - READ_ONCE(vma->vm_pgoff)) << PAGE_SHIFT);
 }
 
 static inline unsigned long
mm/memory.c +1 -1
index 896d8aa08c0a..b76b659a026d 100644
@@ -4257,7 +4257,7 @@ int __access_remote_vm(struct task_struct *tsk, struct mm_struct *mm,
 			 * we can access using slightly different code.
 			 */
 			vma = find_vma(mm, addr);
-			if (!vma || vma->vm_start > addr)
+			if (!vma || READ_ONCE(vma->vm_start) > addr)
 				break;
 			if (vma->vm_ops && vma->vm_ops->access)
 				ret = vma->vm_ops->access(vma, addr, buf,
mm/mmap.c +8 -8
index f901065c4c64..9b84617c11c6 100644
@@ -2240,9 +2240,9 @@ struct vm_area_struct *find_vma(struct mm_struct *mm, unsigned long addr)
 
 		tmp = rb_entry(rb_node, struct vm_area_struct, vm_rb);
 
-		if (tmp->vm_end > addr) {
+		if (READ_ONCE(tmp->vm_end) > addr) {
 			vma = tmp;
-			if (tmp->vm_start <= addr)
+			if (READ_ONCE(tmp->vm_start) <= addr)
 				break;
 			rb_node = rb_node->rb_left;
 		} else
@@ -2399,7 +2399,7 @@ int expand_upwards(struct vm_area_struct *vma, unsigned long address)
 					mm->locked_vm += grow;
 				vm_stat_account(mm, vma->vm_flags, grow);
 				anon_vma_interval_tree_pre_update_vma(vma);
-				vma->vm_end = address;
+				WRITE_ONCE(vma->vm_end, address);
 				anon_vma_interval_tree_post_update_vma(vma);
 				if (vma->vm_next)
 					vma_gap_update(vma->vm_next);
@@ -2480,8 +2480,8 @@ int expand_downwards(struct vm_area_struct *vma,
 					mm->locked_vm += grow;
 				vm_stat_account(mm, vma->vm_flags, grow);
 				anon_vma_interval_tree_pre_update_vma(vma);
-				vma->vm_start = address;
-				vma->vm_pgoff -= grow;
+				WRITE_ONCE(vma->vm_start, address);
+				WRITE_ONCE(vma->vm_pgoff, vma->vm_pgoff - grow);
 				anon_vma_interval_tree_post_update_vma(vma);
 				vma_gap_update(vma);
 				spin_unlock(&mm->page_table_lock);
@@ -2530,7 +2530,7 @@ find_extend_vma(struct mm_struct *mm, unsigned long addr)
 	if (!prev || expand_stack(prev, addr))
 		return NULL;
 	if (prev->vm_flags & VM_LOCKED)
-		populate_vma_page_range(prev, addr, prev->vm_end, NULL);
+		populate_vma_page_range(prev, addr, READ_ONCE(prev->vm_end), NULL);
 	return prev;
 }
 #else
@@ -2549,11 +2549,11 @@ find_extend_vma(struct mm_struct *mm, unsigned long addr)
 	vma = find_vma(mm, addr);
 	if (!vma)
 		return NULL;
-	if (vma->vm_start <= addr)
+	start = READ_ONCE(vma->vm_start);
+	if (start <= addr)
 		return vma;
 	if (!(vma->vm_flags & VM_GROWSDOWN))
 		return NULL;
-	start = vma->vm_start;
 	if (expand_stack(vma, addr))
 		return NULL;
 	if (vma->vm_flags & VM_LOCKED)
mm/rmap.c +2 -1
index 0454ecc29537..d8d06bb87381 100644
@@ -702,7 +702,8 @@ unsigned long page_address_in_vma(struct page *page, struct vm_area_struct *vma)
 	} else
 		return -EFAULT;
 	address = __vma_address(page, vma);
-	if (unlikely(address < vma->vm_start || address >= vma->vm_end))
+	if (unlikely(address < READ_ONCE(vma->vm_start) ||
+		     address >= READ_ONCE(vma->vm_end)))
 		return -EFAULT;
 	return address;
 }
mm/vmacache.c +2 -1
index cdc32a3b02fa..655554c85bdb 100644
@@ -77,7 +77,8 @@ struct vm_area_struct *vmacache_find(struct mm_struct *mm, unsigned long addr)
 			if (WARN_ON_ONCE(vma->vm_mm != mm))
 				break;
 #endif
-			if (vma->vm_start <= addr && vma->vm_end > addr) {
+			if (READ_ONCE(vma->vm_start) <= addr &&
+			    READ_ONCE(vma->vm_end) > addr) {
 				count_vm_vmacache_event(VMACACHE_FIND_HITS);
 				return vma;
 			}
#3 lore raw
In reply to Andrea Arcangeli
Quoted 17 lines · Andrea Arcangeli

Hello,

This was a well known issue for more than a decade, but until a few months ago we relied on the compiler to stick to atomic accesses and updates while walking and updating pagetables.

However now the 64bit native_set_pte finally uses WRITE_ONCE and gup_pmd_range uses READ_ONCE as well.

This convert more racy VM places to avoid depending on the expected compiler behavior to achieve kernel runtime correctness.

It mostly guarantees gcc to do atomic updates at 64bit granularity (practically not needed) and it also prevents gcc to emit code that risks getting confused if the memory unexpectedly changes under it (unlikely to ever be needed).

Quoted 4 lines

The list of vm_start/end/pgoff to update isn't complete, I covered the most obvious places, but before wasting too much time at doing a full audit I thought it was safer to post it and get some comment. More updates can be posted incrementally anyway.

The intention is described well to my eyes.

Do I understand correctly, that it's attempt to get away with modifying vma's fields under down_read(mmap_sem)?

I'm not fan of this.

It can help with producing stable value for the one field, but it doesn't help if more than one thing changed under you. Like if both vm_start and vm_end modifed under you, it can lead to inconsistency. Like vm_end < vm_start.

Signature · 1 line
 Kirill A. Shutemov
#4 lore raw
Quoted 23 lines · Kirill A. Shutemov
Quoted 17 lines · Andrea Arcangeli

Hello,

This was a well known issue for more than a decade, but until a few months ago we relied on the compiler to stick to atomic accesses and updates while walking and updating pagetables.

However now the 64bit native_set_pte finally uses WRITE_ONCE and gup_pmd_range uses READ_ONCE as well.

This convert more racy VM places to avoid depending on the expected compiler behavior to achieve kernel runtime correctness.

It mostly guarantees gcc to do atomic updates at 64bit granularity (practically not needed) and it also prevents gcc to emit code that risks getting confused if the memory unexpectedly changes under it (unlikely to ever be needed).

Quoted 4 lines

The list of vm_start/end/pgoff to update isn't complete, I covered the most obvious places, but before wasting too much time at doing a full audit I thought it was safer to post it and get some comment. More updates can be posted incrementally anyway.

Quoted 4 lines

The intention is described well to my eyes.

Do I understand correctly, that it's attempt to get away with modifying vma's fields under down_read(mmap_sem)?

If that's the intention, then IMHO it's not that well described. It talks about "racy VM places" but e.g. the __mm_populate() changes are for code protected by down_read(). So what's going on here?

Quoted 7 lines

I'm not fan of this.

It can help with producing stable value for the one field, but it doesn't help if more than one thing changed under you. Like if both vm_start and vm_end modifed under you, it can lead to inconsistency. Like vm_end < vm_start.

#5 lore raw

Hello Kirill and Vlastimil,

Quoted 24 lines · Vlastimil Babka
Quoted 23 lines · Kirill A. Shutemov
Quoted 21 lines · Andrea Arcangeli

Hello,

This was a well known issue for more than a decade, but until a few months ago we relied on the compiler to stick to atomic accesses and updates while walking and updating pagetables.

However now the 64bit native_set_pte finally uses WRITE_ONCE and gup_pmd_range uses READ_ONCE as well.

This convert more racy VM places to avoid depending on the expected compiler behavior to achieve kernel runtime correctness.

It mostly guarantees gcc to do atomic updates at 64bit granularity (practically not needed) and it also prevents gcc to emit code that risks getting confused if the memory unexpectedly changes under it (unlikely to ever be needed).

The list of vm_start/end/pgoff to update isn't complete, I covered the most obvious places, but before wasting too much time at doing a full audit I thought it was safer to post it and get some comment. More updates can be posted incrementally anyway.

Quoted 4 lines
Quoted 4 lines

The intention is described well to my eyes.

Do I understand correctly, that it's attempt to get away with modifying vma's fields under down_read(mmap_sem)?

The issue is that we already get away with it, but we do it without READ/WRITE_ONCE. The patch should changes nothing, it should only reduce the dependency on the compiler to do what we expect.

Quoted 3 lines

If that's the intention, then IMHO it's not that well described. It talks about "racy VM places" but e.g. the __mm_populate() changes are for code protected by down_read(). So what's going on here?

expand_stack can move anonymous vma vm_end up or vm_start/pgoff down, while we hold the mmap_sem for writing. See the location of the three WRITE_ONCE in the patch.

So whenever we deal with a vma that we don't know if it's filebacked (filebacked vmas cannot growsup/down) and that we don't know if it has VM_GROWSDOWN/UP set, we shall use READ_ONCE to access vm_start/end/pgoff. This is the only thing the patch is about, and it should make no runtime difference at all, but then the WRITE_ONCE in native_set_pte also should make no runtime difference just like the READ_ONCE in gup_pmd_range should make no runtime difference. I mean we don't trust the compiler with gup_fast but then we trust it with expand_stack vs find_vma.

#6 lore raw
Quoted 35 lines · Andrea Arcangeli
Quoted 24 lines · Vlastimil Babka
Quoted 23 lines · Kirill A. Shutemov
Quoted 21 lines · Andrea Arcangeli

Hello,

This was a well known issue for more than a decade, but until a few months ago we relied on the compiler to stick to atomic accesses and updates while walking and updating pagetables.

However now the 64bit native_set_pte finally uses WRITE_ONCE and gup_pmd_range uses READ_ONCE as well.

This convert more racy VM places to avoid depending on the expected compiler behavior to achieve kernel runtime correctness.

It mostly guarantees gcc to do atomic updates at 64bit granularity (practically not needed) and it also prevents gcc to emit code that risks getting confused if the memory unexpectedly changes under it (unlikely to ever be needed).

The list of vm_start/end/pgoff to update isn't complete, I covered the most obvious places, but before wasting too much time at doing a full audit I thought it was safer to post it and get some comment. More updates can be posted incrementally anyway.

Quoted 4 lines
Quoted 4 lines

The intention is described well to my eyes.

Do I understand correctly, that it's attempt to get away with modifying vma's fields under down_read(mmap_sem)?

The issue is that we already get away with it, but we do it without READ/WRITE_ONCE. The patch should changes nothing, it should only reduce the dependency on the compiler to do what we expect.

Quoted 7 lines
Quoted 3 lines

If that's the intention, then IMHO it's not that well described. It talks about "racy VM places" but e.g. the __mm_populate() changes are for code protected by down_read(). So what's going on here?

expand_stack can move anonymous vma vm_end up or vm_start/pgoff down, while we hold the mmap_sem for writing. See the location of the three WRITE_ONCE in the patch.

You mean for reading, right? Yes, with expand_stack being held for read such members were never really serialized by the mmap_sem and thus we should not be computing stale values.

Thanks.

#7 lore raw
In reply to Andrea Arcangeli
Quoted 27 lines · Andrea Arcangeli

Hello Kirill and Vlastimil,

Quoted 24 lines · Vlastimil Babka
Quoted 23 lines · Kirill A. Shutemov
Quoted 21 lines · Andrea Arcangeli

Hello,

This was a well known issue for more than a decade, but until a few months ago we relied on the compiler to stick to atomic accesses and updates while walking and updating pagetables.

However now the 64bit native_set_pte finally uses WRITE_ONCE and gup_pmd_range uses READ_ONCE as well.

This convert more racy VM places to avoid depending on the expected compiler behavior to achieve kernel runtime correctness.

It mostly guarantees gcc to do atomic updates at 64bit granularity (practically not needed) and it also prevents gcc to emit code that risks getting confused if the memory unexpectedly changes under it (unlikely to ever be needed).

The list of vm_start/end/pgoff to update isn't complete, I covered the most obvious places, but before wasting too much time at doing a full audit I thought it was safer to post it and get some comment. More updates can be posted incrementally anyway.

Quoted 8 lines
Quoted 4 lines
Quoted 4 lines

The intention is described well to my eyes.

Do I understand correctly, that it's attempt to get away with modifying vma's fields under down_read(mmap_sem)?

The issue is that we already get away with it, but we do it without READ/WRITE_ONCE. The patch should changes nothing, it should only reduce the dependency on the compiler to do what we expect.

Yes, it is pre-existing problem. And yes, complier may screw this up. The patch may reduce dependency on the compiler, but it doesn't mean it reduces chance of race.

Consider your changes into __mm_populate() and populate_vma_page_range(). You put READ_ONCE() in both functions. But populate_vma_page_range() gets called from __mm_populate(). Before your change compiler may optimize the code and load from the memory once for a field. With your changes complier will issue two loads.

It *increases* chances of the race, not reduces them.

The current locking scheme doesn't allow modifying VMA field without down_write(mmap_sem).

We do have hacks[1] that try to bypass the limitation, but AFAIK we never had a solid explanation why this should work. Sparkling READ_ONCE() doesn't help with this, but makes it appears legitimate.

[1] I believe we also touch vm_flags without proper locking to set/clear VM_LOCKED.

Signature · 1 line
 Kirill A. Shutemov
reviewer
#8 lore raw
Quoted 47 lines · Kirill A. Shutemov
Quoted 27 lines · Andrea Arcangeli

Hello Kirill and Vlastimil,

Quoted 24 lines · Vlastimil Babka
Quoted 23 lines · Kirill A. Shutemov
Quoted 21 lines · Andrea Arcangeli

Hello,

This was a well known issue for more than a decade, but until a few months ago we relied on the compiler to stick to atomic accesses and updates while walking and updating pagetables.

However now the 64bit native_set_pte finally uses WRITE_ONCE and gup_pmd_range uses READ_ONCE as well.

This convert more racy VM places to avoid depending on the expected compiler behavior to achieve kernel runtime correctness.

It mostly guarantees gcc to do atomic updates at 64bit granularity (practically not needed) and it also prevents gcc to emit code that risks getting confused if the memory unexpectedly changes under it (unlikely to ever be needed).

The list of vm_start/end/pgoff to update isn't complete, I covered the most obvious places, but before wasting too much time at doing a full audit I thought it was safer to post it and get some comment. More updates can be posted incrementally anyway.

Quoted 8 lines
Quoted 4 lines
Quoted 4 lines

The intention is described well to my eyes.

Do I understand correctly, that it's attempt to get away with modifying vma's fields under down_read(mmap_sem)?

The issue is that we already get away with it, but we do it without READ/WRITE_ONCE. The patch should changes nothing, it should only reduce the dependency on the compiler to do what we expect.

Yes, it is pre-existing problem. And yes, complier may screw this up. The patch may reduce dependency on the compiler, but it doesn't mean it reduces chance of race.

Consider your changes into __mm_populate() and populate_vma_page_range(). You put READ_ONCE() in both functions. But populate_vma_page_range() gets called from __mm_populate(). Before your change compiler may optimize the code and load from the memory once for a field. With your changes complier will issue two loads.

Quoted 8 lines

It *increases* chances of the race, not reduces them.

The current locking scheme doesn't allow modifying VMA field without down_write(mmap_sem).

We do have hacks[1] that try to bypass the limitation, but AFAIK we never had a solid explanation why this should work. Sparkling READ_ONCE() doesn't help with this, but makes it appears legitimate.

I do agree with Kirill here. Sprinkling {READ,WRITE}_ONCE around just doesn't solve anything. I am pretty sure that people will not think about it and we will end up in a similar half covered situation in few years again. I would rather remove all those hacks and use a saner locking scheme instead.

Quoted 2 lines

[1] I believe we also touch vm_flags without proper locking to set/clear VM_LOCKED.

Signature · 2 lines
Michal Hocko
SUSE Labs