Skip to contentSkip to conversation

mm: migrate: Fix reference check race between __find_get_block() and migration

PATCH Applied Mel Gorman 1 participant 1 message Jul 18, 2019

Subsystems (2)
lore
Share & tools
lore

Mel Gorman

author
#0 lore raw

PATCH 1/1 mm: migrate: Fix reference check race between __find_get_block() and migration

From: Jan Kara <[email protected]>

buffer_migrate_page_norefs() can race with bh users in the following way:

CPU1 CPU2
buffer_migrate_page_norefs()
buffer_migrate_lock_buffers()
checks bh refs
spin_unlock(&mapping->private_lock)

                                        __find_get_block()
                                          spin_lock(&mapping->private_lock)
                                          grab bh ref
                                          spin_unlock(&mapping->private_lock)

move page do bh work

This can result in various issues like lost updates to buffers (i.e. metadata corruption) or use after free issues for the old page.

This patch closes the race by holding mapping->private_lock while the mapping is being moved to a new page. Ordinarily, a reference can be taken outside of the private_lock using the per-cpu BH LRU but the references are checked and the LRU invalidated if necessary. The private_lock is held once the references are known so the buffer lookup slow path will spin on the private_lock. Between the page lock and private_lock, it should be impossible for other references to be acquired and updates to happen during the migration.

A user had reported data corruption issues on a distribution kernel with a similar page migration implementation as mainline. The data corruption could not be reproduced with this patch applied. A small number of migration-intensive tests were run and no performance problems were noted.

[[email protected]: Changelog, removed tracing]
Fixes 89cb0888ca14 "mm: migrate: provide buffer_migrate_page_norefs()"
---
 mm/migrate.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)
mm/migrate.c +3 -1
index e9594bc0d406..a59e4aed6d2e 100644
@@ -771,12 +771,12 @@ static int __buffer_migrate_page(struct address_space *mapping,
 			}
 			bh = bh->b_this_page;
 		} while (bh != head);
-		spin_unlock(&mapping->private_lock);
 		if (busy) {
 			if (invalidated) {
 				rc = -EAGAIN;
 				goto unlock_buffers;
 			}
+			spin_unlock(&mapping->private_lock);
 			invalidate_bh_lrus();
 			invalidated = true;
 			goto recheck_buffers;
@@ -809,6 +809,8 @@ static int __buffer_migrate_page(struct address_space *mapping,
 
 	rc = MIGRATEPAGE_SUCCESS;
 unlock_buffers:
+	if (check_refs)
+		spin_unlock(&mapping->private_lock);
 	bh = head;
 	do {
 		unlock_buffer(bh);
Signature · 2 lines
Mel Gorman
SUSE Labs