| From 319561669a59d8e9206ab311ae5433ef92fd79d1 Mon Sep 17 00:00:00 2001 |
| From: Alistair Popple <apopple@nvidia.com> |
| Date: Thu, 21 Apr 2022 16:36:10 -0700 |
| Subject: mm/mmu_notifier.c: fix race in mmu_interval_notifier_remove() |
| MIME-Version: 1.0 |
| Content-Type: text/plain; charset=UTF-8 |
| Content-Transfer-Encoding: 8bit |
| |
| From: Alistair Popple <apopple@nvidia.com> |
| |
| commit 319561669a59d8e9206ab311ae5433ef92fd79d1 upstream. |
| |
| In some cases it is possible for mmu_interval_notifier_remove() to race |
| with mn_tree_inv_end() allowing it to return while the notifier data |
| structure is still in use. Consider the following sequence: |
| |
| CPU0 - mn_tree_inv_end() CPU1 - mmu_interval_notifier_remove() |
| ----------------------------------- ------------------------------------ |
| spin_lock(subscriptions->lock); |
| seq = subscriptions->invalidate_seq; |
| spin_lock(subscriptions->lock); spin_unlock(subscriptions->lock); |
| subscriptions->invalidate_seq++; |
| wait_event(invalidate_seq != seq); |
| return; |
| interval_tree_remove(interval_sub); kfree(interval_sub); |
| spin_unlock(subscriptions->lock); |
| wake_up_all(); |
| |
| As the wait_event() condition is true it will return immediately. This |
| can lead to use-after-free type errors if the caller frees the data |
| structure containing the interval notifier subscription while it is |
| still on a deferred list. Fix this by taking the appropriate lock when |
| reading invalidate_seq to ensure proper synchronisation. |
| |
| I observed this whilst running stress testing during some development. |
| You do have to be pretty unlucky, but it leads to the usual problems of |
| use-after-free (memory corruption, kernel crash, difficult to diagnose |
| WARN_ON, etc). |
| |
| Link: https://lkml.kernel.org/r/20220420043734.476348-1-apopple@nvidia.com |
| Fixes: 99cb252f5e68 ("mm/mmu_notifier: add an interval tree notifier") |
| Signed-off-by: Alistair Popple <apopple@nvidia.com> |
| Signed-off-by: Jason Gunthorpe <jgg@nvidia.com> |
| Cc: Christian Kรถnig <christian.koenig@amd.com> |
| Cc: John Hubbard <jhubbard@nvidia.com> |
| Cc: Ralph Campbell <rcampbell@nvidia.com> |
| Cc: <stable@vger.kernel.org> |
| Signed-off-by: Andrew Morton <akpm@linux-foundation.org> |
| Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| mm/mmu_notifier.c | 14 +++++++++++++- |
| 1 file changed, 13 insertions(+), 1 deletion(-) |
| |
| --- a/mm/mmu_notifier.c |
| +++ b/mm/mmu_notifier.c |
| @@ -1043,6 +1043,18 @@ int mmu_interval_notifier_insert_locked( |
| } |
| EXPORT_SYMBOL_GPL(mmu_interval_notifier_insert_locked); |
| |
| +static bool |
| +mmu_interval_seq_released(struct mmu_notifier_subscriptions *subscriptions, |
| + unsigned long seq) |
| +{ |
| + bool ret; |
| + |
| + spin_lock(&subscriptions->lock); |
| + ret = subscriptions->invalidate_seq != seq; |
| + spin_unlock(&subscriptions->lock); |
| + return ret; |
| +} |
| + |
| /** |
| * mmu_interval_notifier_remove - Remove a interval notifier |
| * @interval_sub: Interval subscription to unregister |
| @@ -1090,7 +1102,7 @@ void mmu_interval_notifier_remove(struct |
| lock_map_release(&__mmu_notifier_invalidate_range_start_map); |
| if (seq) |
| wait_event(subscriptions->wq, |
| - READ_ONCE(subscriptions->invalidate_seq) != seq); |
| + mmu_interval_seq_released(subscriptions, seq)); |
| |
| /* pairs with mmgrab in mmu_interval_notifier_insert() */ |
| mmdrop(mm); |