diff options
author | Ralph Campbell <rcampbell@nvidia.com> | 2018-10-30 18:04:14 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2018-10-31 11:54:11 -0400 |
commit | 86a2d59841ab0b147ffc1b7b3041af87927cf312 (patch) | |
tree | d10b1917669b769bfa3192228b05feec7f473e5f | |
parent | aab8d0520e6e7c2a61f71195e6ce7007a4843afb (diff) |
mm/hmm: fix race between hmm_mirror_unregister() and mmu_notifier callback
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>
-rw-r--r-- | mm/hmm.c | 36 |
1 files changed, 21 insertions, 15 deletions
@@ -91,16 +91,6 @@ static struct hmm *hmm_register(struct mm_struct *mm) | |||
91 | spin_lock_init(&hmm->lock); | 91 | spin_lock_init(&hmm->lock); |
92 | hmm->mm = mm; | 92 | hmm->mm = mm; |
93 | 93 | ||
94 | /* | ||
95 | * We should only get here if hold the mmap_sem in write mode ie on | ||
96 | * registration of first mirror through hmm_mirror_register() | ||
97 | */ | ||
98 | hmm->mmu_notifier.ops = &hmm_mmu_notifier_ops; | ||
99 | if (__mmu_notifier_register(&hmm->mmu_notifier, mm)) { | ||
100 | kfree(hmm); | ||
101 | return NULL; | ||
102 | } | ||
103 | |||
104 | spin_lock(&mm->page_table_lock); | 94 | spin_lock(&mm->page_table_lock); |
105 | if (!mm->hmm) | 95 | if (!mm->hmm) |
106 | mm->hmm = hmm; | 96 | mm->hmm = hmm; |
@@ -108,12 +98,27 @@ static struct hmm *hmm_register(struct mm_struct *mm) | |||
108 | cleanup = true; | 98 | cleanup = true; |
109 | spin_unlock(&mm->page_table_lock); | 99 | spin_unlock(&mm->page_table_lock); |
110 | 100 | ||
111 | if (cleanup) { | 101 | if (cleanup) |
112 | mmu_notifier_unregister(&hmm->mmu_notifier, mm); | 102 | goto error; |
113 | kfree(hmm); | 103 | |
114 | } | 104 | /* |
105 | * We should only get here if hold the mmap_sem in write mode ie on | ||
106 | * registration of first mirror through hmm_mirror_register() | ||
107 | */ | ||
108 | hmm->mmu_notifier.ops = &hmm_mmu_notifier_ops; | ||
109 | if (__mmu_notifier_register(&hmm->mmu_notifier, mm)) | ||
110 | goto error_mm; | ||
115 | 111 | ||
116 | return mm->hmm; | 112 | return mm->hmm; |
113 | |||
114 | error_mm: | ||
115 | spin_lock(&mm->page_table_lock); | ||
116 | if (mm->hmm == hmm) | ||
117 | mm->hmm = NULL; | ||
118 | spin_unlock(&mm->page_table_lock); | ||
119 | error: | ||
120 | kfree(hmm); | ||
121 | return NULL; | ||
117 | } | 122 | } |
118 | 123 | ||
119 | void hmm_mm_destroy(struct mm_struct *mm) | 124 | void hmm_mm_destroy(struct mm_struct *mm) |
@@ -278,12 +283,13 @@ void hmm_mirror_unregister(struct hmm_mirror *mirror) | |||
278 | if (!should_unregister || mm == NULL) | 283 | if (!should_unregister || mm == NULL) |
279 | return; | 284 | return; |
280 | 285 | ||
286 | mmu_notifier_unregister_no_release(&hmm->mmu_notifier, mm); | ||
287 | |||
281 | spin_lock(&mm->page_table_lock); | 288 | spin_lock(&mm->page_table_lock); |
282 | if (mm->hmm == hmm) | 289 | if (mm->hmm == hmm) |
283 | mm->hmm = NULL; | 290 | mm->hmm = NULL; |
284 | spin_unlock(&mm->page_table_lock); | 291 | spin_unlock(&mm->page_table_lock); |
285 | 292 | ||
286 | mmu_notifier_unregister_no_release(&hmm->mmu_notifier, mm); | ||
287 | kfree(hmm); | 293 | kfree(hmm); |
288 | } | 294 | } |
289 | EXPORT_SYMBOL(hmm_mirror_unregister); | 295 | EXPORT_SYMBOL(hmm_mirror_unregister); |