From: Jérôme Glisse <[email protected]>

This code was lost in translation at one point. This properly call
mmu_notifier_unregister_no_release() once last user is gone. This
fix the zombie mm_struct as without this patch we do not drop the
refcount we have on it.

Changed since v1:
  - close race window between a last mirror unregistering and a new
    mirror registering, which could have lead to use after free()
    kind of bug

Signed-off-by: Jérôme Glisse <[email protected]>
Cc: Evgeny Baskakov <[email protected]>
Cc: Ralph Campbell <[email protected]>
Cc: Mark Hairgrove <[email protected]>
Cc: John Hubbard <[email protected]>
---
 mm/hmm.c | 35 +++++++++++++++++++++++++++++++++--
 1 file changed, 33 insertions(+), 2 deletions(-)

diff --git a/mm/hmm.c b/mm/hmm.c
index 6088fa6ed137..f75aa8df6e97 100644
--- a/mm/hmm.c
+++ b/mm/hmm.c
@@ -222,13 +222,24 @@ int hmm_mirror_register(struct hmm_mirror *mirror, struct 
mm_struct *mm)
        if (!mm || !mirror || !mirror->ops)
                return -EINVAL;
 
+again:
        mirror->hmm = hmm_register(mm);
        if (!mirror->hmm)
                return -ENOMEM;
 
        down_write(&mirror->hmm->mirrors_sem);
-       list_add(&mirror->list, &mirror->hmm->mirrors);
-       up_write(&mirror->hmm->mirrors_sem);
+       if (mirror->hmm->mm == NULL) {
+               /*
+                * A racing hmm_mirror_unregister() is about to destroy the hmm
+                * struct. Try again to allocate a new one.
+                */
+               up_write(&mirror->hmm->mirrors_sem);
+               mirror->hmm = NULL;
+               goto again;
+       } else {
+               list_add(&mirror->list, &mirror->hmm->mirrors);
+               up_write(&mirror->hmm->mirrors_sem);
+       }
 
        return 0;
 }
@@ -244,10 +255,30 @@ EXPORT_SYMBOL(hmm_mirror_register);
 void hmm_mirror_unregister(struct hmm_mirror *mirror)
 {
        struct hmm *hmm = mirror->hmm;
+       bool should_unregister = false;
+       struct mm_struct *mm;
+
+       if (list_empty(&mirror->list))
+               return;
 
        down_write(&hmm->mirrors_sem);
        list_del_init(&mirror->list);
+       should_unregister = list_empty(&hmm->mirrors);
+       mm = hmm->mm;
+       hmm->mm = NULL;
        up_write(&hmm->mirrors_sem);
+
+       if (!should_unregister || mm == NULL)
+               return;
+
+       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);
 
-- 
2.14.3

Reply via email to