From: Yosry Ahmed <yosryahmed@xxxxxxxxxx> During suspend-like operations (suspend, hibernate, kexec w/ preserve_context), the processor state (including CR3) is usually saved and restored later. In the kexec case, this only happens when KEXEC_PRESERVE_CONTEXT is used to jump back to the original kernel. In relocate_kernel(), some registers including CR3 are stored in VA_CONTROL_PAGE. If preserve_context is set (passed into relocate_kernel() in RCX), after running the new kernel the code under 'virtual_mapped' restores these registers. This is similar to what happens in suspend and hibernate. Note that even when KEXEC_PRESERVE_CONTEXT is not set, relocate_kernel() still accesses CR3. It mainly reads and writes it to flush the TLB. This could be problematic and cause improper ASI enters (see below), but it is assumed to be safe because the kernel will essentially reboot in this case anyway. Saving and restoring CR3 in this fashion can cause a problem if the suspend/hibernate/kexec is performed within an ASI domain. A restricted CR3 will be saved, and later restored after ASI had potentially already exited (e.g. from an NMI after CR3 is stored). This will cause an _improper_ ASI enter, where code starts executing in a restricted address space, yet ASI metadata (especially curr_asi) says otherwise. Exit ASI early in all these paths by registering a syscore_suspend() callback. syscore_suspend() is called in all the above paths (for kexec, only with KEXEC_PRESERVE_CONTEXT) after IRQs are finally disabled before the operation. This is not currently strictly required but is convenient because when ASI gains the ability to persist across context switching, there will be additional synchronization requirements simplified by this. Note: If the CR3 accesses in relocate_kernel() when KEXEC_PRESERVE_CONTEXT is not set are concerning, they could be handled by registering a syscore_shutdown() callback to exit ASI. syscore_shutdown() is called in the kexec path where KEXEC_PRESERVE_CONTEXT is not set starting commit 7bb943806ff6 ("kexec: do syscore_shutdown() in kernel_kexec"). Signed-off-by: Yosry Ahmed <yosryahmed@xxxxxxxxxx> Signed-off-by: Brendan Jackman <jackmanb@xxxxxxxxxx> --- arch/x86/mm/asi.c | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/arch/x86/mm/asi.c b/arch/x86/mm/asi.c index a9f9bfbf85eb47d16ef8d0bfbc7713f07052d3ed..c5073af1a82ded1c6fc467cd7a5d29a39d676bb4 100644 --- a/arch/x86/mm/asi.c +++ b/arch/x86/mm/asi.c @@ -6,6 +6,7 @@ #include <linux/init.h> #include <linux/pgtable.h> +#include <linux/syscore_ops.h> #include <asm/cmdline.h> #include <asm/cpufeature.h> @@ -243,6 +244,32 @@ static int asi_map_percpu(struct asi *asi, void *percpu_addr, size_t len) return 0; } +#ifdef CONFIG_PM_SLEEP +static int asi_suspend(void) +{ + /* + * Must be called after IRQs are disabled and rescheduling is no longer + * possible (so that we cannot re-enter ASI before suspending. + */ + lockdep_assert_irqs_disabled(); + + /* + * Suspend operations sometimes save CR3 as part of the saved state, + * which is restored later (e.g. do_suspend_lowlevel() in the suspend + * path, swsusp_arch_suspend() in the hibernate path, relocate_kernel() + * in the kexec path). Saving a restricted CR3 and restoring it later + * could leave to improperly entering ASI. Exit ASI before such + * operations. + */ + asi_exit(); + return 0; +} + +static struct syscore_ops asi_syscore_ops = { + .suspend = asi_suspend, +}; +#endif /* CONFIG_PM_SLEEP */ + static int __init asi_global_init(void) { int err; @@ -306,6 +333,10 @@ static int __init asi_global_init(void) asi_clone_pgd(asi_global_nonsensitive_pgd, init_mm.pgd, VMEMMAP_START + (1UL << PGDIR_SHIFT)); +#ifdef CONFIG_PM_SLEEP + register_syscore_ops(&asi_syscore_ops); +#endif + return 0; } subsys_initcall(asi_global_init) -- 2.47.1.613.gc27f4b7a9f-goog