mm/hmm: fix race between hmm_mirror_unregister() and mmu_notifier callback
authorRalph Campbell <rcampbell@nvidia.com>
Tue, 30 Oct 2018 22:04:14 +0000 (15:04 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Wed, 31 Oct 2018 15:54:11 +0000 (08:54 -0700)
In hmm_mirror_unregister(), mm->hmm is set to NULL and then
mmu_notifier_unregister_no_release() is called.  That creates a small
window where mmu_notifier can call mmu_notifier_ops with mm->hmm equal to
NULL.  Fix this by first unregistering mmu notifier callbacks and then
setting mm->hmm to NULL.

Similarly in hmm_register(), set mm->hmm before registering mmu_notifier
callbacks so callback functions always see mm->hmm set.

Link: http://lkml.kernel.org/r/20181019160442.18723-4-jglisse@redhat.com
Signed-off-by: Ralph Campbell <rcampbell@nvidia.com>
Signed-off-by: Jérôme Glisse <jglisse@redhat.com>
Reviewed-by: John Hubbard <jhubbard@nvidia.com>
Reviewed-by: Jérôme Glisse <jglisse@redhat.com>
Reviewed-by: Balbir Singh <bsingharora@gmail.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
mm/hmm.c

index de9840f60100836774260105b487195de817d637..63ca3b5be306c49de6f9a2b8c6c551b19eb2f767 100644 (file)
--- a/mm/hmm.c
+++ b/mm/hmm.c
@@ -91,16 +91,6 @@ static struct hmm *hmm_register(struct mm_struct *mm)
        spin_lock_init(&hmm->lock);
        hmm->mm = mm;
 
-       /*
-        * We should only get here if hold the mmap_sem in write mode ie on
-        * registration of first mirror through hmm_mirror_register()
-        */
-       hmm->mmu_notifier.ops = &hmm_mmu_notifier_ops;
-       if (__mmu_notifier_register(&hmm->mmu_notifier, mm)) {
-               kfree(hmm);
-               return NULL;
-       }
-
        spin_lock(&mm->page_table_lock);
        if (!mm->hmm)
                mm->hmm = hmm;
@@ -108,12 +98,27 @@ static struct hmm *hmm_register(struct mm_struct *mm)
                cleanup = true;
        spin_unlock(&mm->page_table_lock);
 
-       if (cleanup) {
-               mmu_notifier_unregister(&hmm->mmu_notifier, mm);
-               kfree(hmm);
-       }
+       if (cleanup)
+               goto error;
+
+       /*
+        * We should only get here if hold the mmap_sem in write mode ie on
+        * registration of first mirror through hmm_mirror_register()
+        */
+       hmm->mmu_notifier.ops = &hmm_mmu_notifier_ops;
+       if (__mmu_notifier_register(&hmm->mmu_notifier, mm))
+               goto error_mm;
 
        return mm->hmm;
+
+error_mm:
+       spin_lock(&mm->page_table_lock);
+       if (mm->hmm == hmm)
+               mm->hmm = NULL;
+       spin_unlock(&mm->page_table_lock);
+error:
+       kfree(hmm);
+       return NULL;
 }
 
 void hmm_mm_destroy(struct mm_struct *mm)
@@ -278,12 +283,13 @@ void hmm_mirror_unregister(struct hmm_mirror *mirror)
        if (!should_unregister || mm == NULL)
                return;
 
+       mmu_notifier_unregister_no_release(&hmm->mmu_notifier, mm);
+
        spin_lock(&mm->page_table_lock);
        if (mm->hmm == hmm)
                mm->hmm = NULL;
        spin_unlock(&mm->page_table_lock);
 
-       mmu_notifier_unregister_no_release(&hmm->mmu_notifier, mm);
        kfree(hmm);
 }
 EXPORT_SYMBOL(hmm_mirror_unregister);