summaryrefslogtreecommitdiffstats
path: root/mm/migrate.c
diff options
context:
space:
mode:
authorJan Kara <jack@suse.cz>2019-08-03 00:48:47 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2019-08-03 10:02:00 -0400
commitebdf4de5642fb6580b0763158b6b4b791c4d6a4d (patch)
tree6f8280d4164053302950918e05ee67ca5eff6ca8 /mm/migrate.c
parentfa1e512fac717f34e7c12d7a384c46e90a647392 (diff)
mm: migrate: fix reference check race between __find_get_block() and migration
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. [mgorman@techsingularity.net: Changelog, removed tracing] Link: http://lkml.kernel.org/r/20190718090238.GF24383@techsingularity.net Fixes: 89cb0888ca14 "mm: migrate: provide buffer_migrate_page_norefs()" Signed-off-by: Jan Kara <jack@suse.cz> Signed-off-by: Mel Gorman <mgorman@techsingularity.net> Cc: <stable@vger.kernel.org> [5.0+] Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'mm/migrate.c')
-rw-r--r--mm/migrate.c4
1 files changed, 3 insertions, 1 deletions
diff --git a/mm/migrate.c b/mm/migrate.c
index 8992741f10aa..515718392b24 100644
--- a/mm/migrate.c
+++ b/mm/migrate.c
@@ -767,12 +767,12 @@ recheck_buffers:
767 } 767 }
768 bh = bh->b_this_page; 768 bh = bh->b_this_page;
769 } while (bh != head); 769 } while (bh != head);
770 spin_unlock(&mapping->private_lock);
771 if (busy) { 770 if (busy) {
772 if (invalidated) { 771 if (invalidated) {
773 rc = -EAGAIN; 772 rc = -EAGAIN;
774 goto unlock_buffers; 773 goto unlock_buffers;
775 } 774 }
775 spin_unlock(&mapping->private_lock);
776 invalidate_bh_lrus(); 776 invalidate_bh_lrus();
777 invalidated = true; 777 invalidated = true;
778 goto recheck_buffers; 778 goto recheck_buffers;
@@ -805,6 +805,8 @@ recheck_buffers:
805 805
806 rc = MIGRATEPAGE_SUCCESS; 806 rc = MIGRATEPAGE_SUCCESS;
807unlock_buffers: 807unlock_buffers:
808 if (check_refs)
809 spin_unlock(&mapping->private_lock);
808 bh = head; 810 bh = head;
809 do { 811 do {
810 unlock_buffer(bh); 812 unlock_buffer(bh);