mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-09-05 18:39:59 +00:00
x86/mm, mm/hwpoison: Don't unconditionally unmap kernel 1:1 pages
commitfd0e786d9d
upstream. In the following commit:ce0fa3e56a
("x86/mm, mm/hwpoison: Clear PRESENT bit for kernel 1:1 mappings of poison pages") ... we added code to memory_failure() to unmap the page from the kernel 1:1 virtual address space to avoid speculative access to the page logging additional errors. But memory_failure() may not always succeed in taking the page offline, especially if the page belongs to the kernel. This can happen if there are too many corrected errors on a page and either mcelog(8) or drivers/ras/cec.c asks to take a page offline. Since we remove the 1:1 mapping early in memory_failure(), we can end up with the page unmapped, but still in use. On the next access the kernel crashes :-( There are also various debug paths that call memory_failure() to simulate occurrence of an error. Since there is no actual error in memory, we don't need to map out the page for those cases. Revert most of the previous attempt and keep the solution local to arch/x86/kernel/cpu/mcheck/mce.c. Unmap the page only when: 1) there is a real error 2) memory_failure() succeeds. All of this only applies to 64-bit systems. 32-bit kernel doesn't map all of memory into kernel space. It isn't worth adding the code to unmap the piece that is mapped because nobody would run a 32-bit kernel on a machine that has recoverable machine checks. Signed-off-by: Tony Luck <tony.luck@intel.com> Cc: Andrew Morton <akpm@linux-foundation.org> Cc: Andy Lutomirski <luto@kernel.org> Cc: Borislav Petkov <bp@suse.de> Cc: Brian Gerst <brgerst@gmail.com> Cc: Dave <dave.hansen@intel.com> Cc: Denys Vlasenko <dvlasenk@redhat.com> Cc: Josh Poimboeuf <jpoimboe@redhat.com> Cc: Linus Torvalds <torvalds@linux-foundation.org> Cc: Naoya Horiguchi <n-horiguchi@ah.jp.nec.com> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Robert (Persistent Memory) <elliott@hpe.com> Cc: Thomas Gleixner <tglx@linutronix.de> Cc: linux-mm@kvack.org Cc: stable@vger.kernel.org #v4.14 Fixes:ce0fa3e56a
("x86/mm, mm/hwpoison: Clear PRESENT bit for kernel 1:1 mappings of poison pages") Signed-off-by: Ingo Molnar <mingo@kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
e4ea7c1222
commit
26f8c38bb4
5 changed files with 26 additions and 18 deletions
|
@ -52,10 +52,6 @@ static inline void clear_page(void *page)
|
||||||
|
|
||||||
void copy_page(void *to, void *from);
|
void copy_page(void *to, void *from);
|
||||||
|
|
||||||
#ifdef CONFIG_X86_MCE
|
|
||||||
#define arch_unmap_kpfn arch_unmap_kpfn
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif /* !__ASSEMBLY__ */
|
#endif /* !__ASSEMBLY__ */
|
||||||
|
|
||||||
#ifdef CONFIG_X86_VSYSCALL_EMULATION
|
#ifdef CONFIG_X86_VSYSCALL_EMULATION
|
||||||
|
|
|
@ -115,4 +115,19 @@ static inline void mce_unregister_injector_chain(struct notifier_block *nb) { }
|
||||||
|
|
||||||
extern struct mca_config mca_cfg;
|
extern struct mca_config mca_cfg;
|
||||||
|
|
||||||
|
#ifndef CONFIG_X86_64
|
||||||
|
/*
|
||||||
|
* On 32-bit systems it would be difficult to safely unmap a poison page
|
||||||
|
* from the kernel 1:1 map because there are no non-canonical addresses that
|
||||||
|
* we can use to refer to the address without risking a speculative access.
|
||||||
|
* However, this isn't much of an issue because:
|
||||||
|
* 1) Few unmappable pages are in the 1:1 map. Most are in HIGHMEM which
|
||||||
|
* are only mapped into the kernel as needed
|
||||||
|
* 2) Few people would run a 32-bit kernel on a machine that supports
|
||||||
|
* recoverable errors because they have too much memory to boot 32-bit.
|
||||||
|
*/
|
||||||
|
static inline void mce_unmap_kpfn(unsigned long pfn) {}
|
||||||
|
#define mce_unmap_kpfn mce_unmap_kpfn
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif /* __X86_MCE_INTERNAL_H__ */
|
#endif /* __X86_MCE_INTERNAL_H__ */
|
||||||
|
|
|
@ -106,6 +106,10 @@ static struct irq_work mce_irq_work;
|
||||||
|
|
||||||
static void (*quirk_no_way_out)(int bank, struct mce *m, struct pt_regs *regs);
|
static void (*quirk_no_way_out)(int bank, struct mce *m, struct pt_regs *regs);
|
||||||
|
|
||||||
|
#ifndef mce_unmap_kpfn
|
||||||
|
static void mce_unmap_kpfn(unsigned long pfn);
|
||||||
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* CPU/chipset specific EDAC code can register a notifier call here to print
|
* CPU/chipset specific EDAC code can register a notifier call here to print
|
||||||
* MCE errors in a human-readable form.
|
* MCE errors in a human-readable form.
|
||||||
|
@ -582,7 +586,8 @@ static int srao_decode_notifier(struct notifier_block *nb, unsigned long val,
|
||||||
|
|
||||||
if (mce_usable_address(mce) && (mce->severity == MCE_AO_SEVERITY)) {
|
if (mce_usable_address(mce) && (mce->severity == MCE_AO_SEVERITY)) {
|
||||||
pfn = mce->addr >> PAGE_SHIFT;
|
pfn = mce->addr >> PAGE_SHIFT;
|
||||||
memory_failure(pfn, MCE_VECTOR, 0);
|
if (memory_failure(pfn, MCE_VECTOR, 0))
|
||||||
|
mce_unmap_kpfn(pfn);
|
||||||
}
|
}
|
||||||
|
|
||||||
return NOTIFY_OK;
|
return NOTIFY_OK;
|
||||||
|
@ -1049,12 +1054,13 @@ static int do_memory_failure(struct mce *m)
|
||||||
ret = memory_failure(m->addr >> PAGE_SHIFT, MCE_VECTOR, flags);
|
ret = memory_failure(m->addr >> PAGE_SHIFT, MCE_VECTOR, flags);
|
||||||
if (ret)
|
if (ret)
|
||||||
pr_err("Memory error not recovered");
|
pr_err("Memory error not recovered");
|
||||||
|
else
|
||||||
|
mce_unmap_kpfn(m->addr >> PAGE_SHIFT);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(arch_unmap_kpfn) && defined(CONFIG_MEMORY_FAILURE)
|
#ifndef mce_unmap_kpfn
|
||||||
|
static void mce_unmap_kpfn(unsigned long pfn)
|
||||||
void arch_unmap_kpfn(unsigned long pfn)
|
|
||||||
{
|
{
|
||||||
unsigned long decoy_addr;
|
unsigned long decoy_addr;
|
||||||
|
|
||||||
|
@ -1065,7 +1071,7 @@ void arch_unmap_kpfn(unsigned long pfn)
|
||||||
* We would like to just call:
|
* We would like to just call:
|
||||||
* set_memory_np((unsigned long)pfn_to_kaddr(pfn), 1);
|
* set_memory_np((unsigned long)pfn_to_kaddr(pfn), 1);
|
||||||
* but doing that would radically increase the odds of a
|
* but doing that would radically increase the odds of a
|
||||||
* speculative access to the posion page because we'd have
|
* speculative access to the poison page because we'd have
|
||||||
* the virtual address of the kernel 1:1 mapping sitting
|
* the virtual address of the kernel 1:1 mapping sitting
|
||||||
* around in registers.
|
* around in registers.
|
||||||
* Instead we get tricky. We create a non-canonical address
|
* Instead we get tricky. We create a non-canonical address
|
||||||
|
@ -1090,7 +1096,6 @@ void arch_unmap_kpfn(unsigned long pfn)
|
||||||
|
|
||||||
if (set_memory_np(decoy_addr, 1))
|
if (set_memory_np(decoy_addr, 1))
|
||||||
pr_warn("Could not invalidate pfn=0x%lx from 1:1 map\n", pfn);
|
pr_warn("Could not invalidate pfn=0x%lx from 1:1 map\n", pfn);
|
||||||
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -127,10 +127,4 @@ static __always_inline enum lru_list page_lru(struct page *page)
|
||||||
|
|
||||||
#define lru_to_page(head) (list_entry((head)->prev, struct page, lru))
|
#define lru_to_page(head) (list_entry((head)->prev, struct page, lru))
|
||||||
|
|
||||||
#ifdef arch_unmap_kpfn
|
|
||||||
extern void arch_unmap_kpfn(unsigned long pfn);
|
|
||||||
#else
|
|
||||||
static __always_inline void arch_unmap_kpfn(unsigned long pfn) { }
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1146,8 +1146,6 @@ int memory_failure(unsigned long pfn, int trapno, int flags)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
arch_unmap_kpfn(pfn);
|
|
||||||
|
|
||||||
orig_head = hpage = compound_head(p);
|
orig_head = hpage = compound_head(p);
|
||||||
num_poisoned_pages_inc();
|
num_poisoned_pages_inc();
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue