Home Home > GIT Browse
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichal Hocko <mhocko@suse.com>2018-11-02 17:53:44 +0100
committerMichal Hocko <mhocko@suse.com>2018-11-02 17:53:44 +0100
commit432fc1b26bb60386ad14af07f21e36d65eab55af (patch)
tree893fd6d59fb20ff04b6bbe898f9faa42c01af8ce
parent4594c2433fe2103b3b13093ffb08b961b26dcff5 (diff)
mremap: properly flush TLB before releasing the page
(bnc#1113769, CVE-2018-18281).
-rw-r--r--patches.fixes/mremap-properly-flush-TLB-before-releasing-the-page.patch103
-rw-r--r--series.conf2
2 files changed, 105 insertions, 0 deletions
diff --git a/patches.fixes/mremap-properly-flush-TLB-before-releasing-the-page.patch b/patches.fixes/mremap-properly-flush-TLB-before-releasing-the-page.patch
new file mode 100644
index 0000000000..d0e0b1df05
--- /dev/null
+++ b/patches.fixes/mremap-properly-flush-TLB-before-releasing-the-page.patch
@@ -0,0 +1,103 @@
+From eb66ae030829605d61fbef1909ce310e29f78821 Mon Sep 17 00:00:00 2001
+From: Linus Torvalds <torvalds@linux-foundation.org>
+Date: Fri, 12 Oct 2018 15:22:59 -0700
+Subject: [PATCH] mremap: properly flush TLB before releasing the page
+Git-commit: eb66ae030829605d61fbef1909ce310e29f78821
+Patch-mainline: v4.19
+References: bnc#1113769, CVE-2018-18281
+
+Jann Horn points out that our TLB flushing was subtly wrong for the
+mremap() case. What makes mremap() special is that we don't follow the
+usual "add page to list of pages to be freed, then flush tlb, and then
+free pages". No, mremap() obviously just _moves_ the page from one page
+table location to another.
+
+That matters, because mremap() thus doesn't directly control the
+lifetime of the moved page with a freelist: instead, the lifetime of the
+page is controlled by the page table locking, that serializes access to
+the entry.
+
+As a result, we need to flush the TLB not just before releasing the lock
+for the source location (to avoid any concurrent accesses to the entry),
+but also before we release the destination page table lock (to avoid the
+TLB being flushed after somebody else has already done something to that
+page).
+
+This also makes the whole "need_flush" logic unnecessary, since we now
+always end up flushing the TLB for every valid entry.
+
+Reported-and-tested-by: Jann Horn <jannh@google.com>
+Acked-by: Will Deacon <will.deacon@arm.com>
+Tested-by: Ingo Molnar <mingo@kernel.org>
+Acked-by: Peter Zijlstra (Intel) <peterz@infradead.org>
+Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+Signed-off-by: Michal Hocko <mhocko@suse.com>
+
+---
+ mm/huge_memory.c | 1 +
+ mm/mremap.c | 12 ++++--------
+ 2 files changed, 5 insertions(+), 8 deletions(-)
+
+--- a/mm/huge_memory.c
++++ b/mm/huge_memory.c
+@@ -1092,6 +1092,7 @@ int move_huge_pmd(struct vm_area_struct
+ pmd = pmdp_get_and_clear(mm, old_addr, old_pmd);
+ VM_BUG_ON(!pmd_none(*new_pmd));
+ set_pmd_at(mm, new_addr, new_pmd, pmd);
++ flush_tlb_range(vma, old_addr, old_addr + PMD_SIZE);
+ spin_unlock(&mm->page_table_lock);
+ ret = 1;
+ }
+--- a/mm/mremap.c
++++ b/mm/mremap.c
+@@ -77,6 +77,7 @@ static void move_ptes(struct vm_area_str
+ struct mm_struct *mm = vma->vm_mm;
+ pte_t *old_pte, *new_pte, pte;
+ spinlock_t *old_ptl, *new_ptl;
++ unsigned long len = old_end - old_addr;
+
+ if (vma->vm_file) {
+ /*
+@@ -110,6 +111,7 @@ static void move_ptes(struct vm_area_str
+ }
+
+ arch_leave_lazy_mmu_mode();
++ flush_tlb_range(vma, old_end - len, old_end);
+ if (new_ptl != old_ptl)
+ spin_unlock(new_ptl);
+ pte_unmap(new_pte - 1);
+@@ -126,7 +128,6 @@ unsigned long move_page_tables(struct vm
+ {
+ unsigned long extent, next, old_end;
+ pmd_t *old_pmd, *new_pmd;
+- bool need_flush = false;
+
+ old_end = old_addr + len;
+ flush_cache_range(vma, old_addr, old_end);
+@@ -152,12 +153,10 @@ unsigned long move_page_tables(struct vm
+ err = move_huge_pmd(vma, new_vma, old_addr,
+ new_addr, old_end,
+ old_pmd, new_pmd);
+- if (err > 0) {
+- need_flush = true;
++ if (err > 0)
+ continue;
+- } else if (!err) {
++ else if (!err)
+ split_huge_page_pmd(vma->vm_mm, old_pmd);
+- }
+ VM_BUG_ON(pmd_trans_huge(*old_pmd));
+ }
+ if (pmd_none(*new_pmd) && __pte_alloc(new_vma->vm_mm, new_vma,
+@@ -170,10 +169,7 @@ unsigned long move_page_tables(struct vm
+ extent = LATENCY_LIMIT;
+ move_ptes(vma, old_pmd, old_addr, old_addr + extent,
+ new_vma, new_pmd, new_addr);
+- need_flush = true;
+ }
+- if (likely(need_flush))
+- flush_tlb_range(vma, old_end-len, old_addr);
+
+ mmu_notifier_invalidate_range_end(vma->vm_mm, old_end-len, old_end);
+
diff --git a/series.conf b/series.conf
index 4ce25f8a52..cbb7a8c8c8 100644
--- a/series.conf
+++ b/series.conf
@@ -833,6 +833,8 @@
patches.suse/mm-mremap-avoid-sending-one-ipi-per-page.patch
patches.suse/mm-thp-mremap-support-and-tlb-optimization.patch
+ patches.fixes/mremap-properly-flush-TLB-before-releasing-the-page.patch
+
# Minor fixes from 3.0-3.8 not included in -stable
patches.fixes/mm-swap-token-makes-global-variables-to-function-local.patch
patches.fixes/mm-thp-minor-lock-simplification-in-__khugepaged_exit.patch