[PATCHv3 07/17] arm64: sdei: explicitly simulate PAN/UAO entry

Mark Rutland
2020-10-26T13:36:40Z

SDEI enters the kernel with a non-architectural exception which does not
manipulate PSTATE bits (e.g. PAN, UAO) in the same way as architectural
exceptions. We currently fix this up with a combination of
__uaccess_enable_hw_pan() and force_uaccess_access_begin(), but this has
a few problems:

* When set_fs() is removed, force_uaccess_begin() will have no HW
side-effects, and UAO will need to be reset elsewhere.

* Kernels built without support for PAN or UAO will not reset these bits
upon SDEI entry, and may inherit the values used by a VM, leading to
unexpected behaviour.

* Kernels built *with* support for PAN or UAO, when run on systems with
mismatched support across CPUs, will not reset these bits upon SDEI
entry, and may inherit the values used by a VM, leading to unexpected
behaviour.

To deal with all of these, let's always explicitly reset the PAN and UAO
bits when an SDEI event is delivered to the kernel. As above, we must do
so even when the kernel has chosen to not use PAN/UAO, or was not built
with support for PAN/UAO generally.

The existing system_uses_ttbr0_pan() is redefined in terms of
system_uses_hw_pan() both for clarity and as a minor optimization when
HW PAN is not selected.

Signed-off-by: Mark Rutland <mark.rutland@arm.com>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Christoph Hellwig <hch@lst.de>
Cc: James Morse <james.morse@arm.com>
Cc: Will Deacon <will@kernel.org>
---
arch/arm64/include/asm/cpufeature.h | 22 +++++++++++++++++++++-
arch/arm64/kernel/sdei.c | 19 ++++++++++++++-----
2 files changed, 35 insertions(+), 6 deletions(-)

diff --git a/arch/arm64/include/asm/cpufeature.h b/arch/arm64/include/asm/cpufeature.h
index f7e7144af174c..8f83582d370ec 100644
--- a/arch/arm64/include/asm/cpufeature.h
+++ b/arch/arm64/include/asm/cpufeature.h
@@ -645,10 +645,16 @@ static __always_inline bool system_supports_fpsimd(void)
return !cpus_have_const_cap(ARM64_HAS_NO_FPSIMD);
}

+static inline bool system_uses_hw_pan(void)
+{
+ return IS_ENABLED(CONFIG_ARM64_PAN) &&
+ cpus_have_const_cap(ARM64_HAS_PAN);
+}
+
static inline bool system_uses_ttbr0_pan(void)
{
return IS_ENABLED(CONFIG_ARM64_SW_TTBR0_PAN) &&
- !cpus_have_const_cap(ARM64_HAS_PAN);
+ !system_uses_hw_pan();
}

static __always_inline bool system_supports_sve(void)
@@ -740,6 +746,20 @@ static inline bool cpu_has_hw_af(void)
ID_AA64MMFR1_HADBS_SHIFT);
}

+static inline bool cpu_has_pan(void)
+{
+ u64 mmfr1 = read_cpuid(ID_AA64MMFR1_EL1);
+ return cpuid_feature_extract_unsigned_field(mmfr1,
+ ID_AA64MMFR1_PAN_SHIFT);
+}
+
+static inline bool cpu_has_uao(void)
+{
+ u64 mmfr2 = read_cpuid(ID_AA64MMFR2_EL1);
+ return cpuid_feature_extract_unsigned_field(mmfr2,
+ ID_AA64MMFR2_UAO_SHIFT);
+}
+
#ifdef CONFIG_ARM64_AMU_EXTN
/* Check whether the cpu supports the Activity Monitors Unit (AMU) */
extern bool cpu_has_amu_feat(int cpu);
diff --git a/arch/arm64/kernel/sdei.c b/arch/arm64/kernel/sdei.c
index 4a5f24602aa0c..908d7be70eac0 100644
--- a/arch/arm64/kernel/sdei.c
+++ b/arch/arm64/kernel/sdei.c
@@ -216,6 +216,16 @@ static __kprobes unsigned long _sdei_handler(struct pt_regs *regs,
return vbar + 0x480;
}

+static void __kprobes notrace __sdei_pstate_entry(void)
+{
+ if (system_uses_hw_pan())
+ set_pstate_pan(1);
+ else if (cpu_has_pan())
+ set_pstate_pan(0);
+
+ if (cpu_has_uao())
+ set_pstate_uao(0);
+}

asmlinkage __kprobes notrace unsigned long
__sdei_handler(struct pt_regs *regs, struct sdei_registered_event *arg)
@@ -224,12 +234,11 @@ __sdei_handler(struct pt_regs *regs, struct sdei_registered_event *arg)
mm_segment_t orig_addr_limit;

/*
- * We didn't take an exception to get here, so the HW hasn't set PAN or
- * cleared UAO, and the exception entry code hasn't reset addr_limit.
- * Set PAN, then use force_uaccess_begin() to clear UAO and reset
- * addr_limit.
+ * We didn't take an exception to get here, so the HW hasn't
+ * set/cleared bits in PSTATE that we may rely on. Intialize PAN/UAO,
+ * then use force_uaccess_begin() to reset addr_limit.
*/
- __uaccess_enable_hw_pan();
+ __sdei_pstate_entry();
orig_addr_limit = force_uaccess_begin();

nmi_enter();