From: Vincenzo Frascino <vincenzo.frasc...@arm.com>

Add the implementation of the in-kernel fault handler.

When a tag fault happens on a kernel address:
* MTE is disabled on the current CPU,
* the execution continues.

When a tag fault happens on a user address:
* the kernel executes do_bad_area() and panics.

The tag fault handler for kernel addresses is currently empty and will be
filled in by a future commit.

Signed-off-by: Vincenzo Frascino <vincenzo.frasc...@arm.com>
Co-developed-by: Andrey Konovalov <andreyk...@google.com>
Signed-off-by: Andrey Konovalov <andreyk...@google.com>
Reviewed-by: Catalin Marinas <catalin.mari...@arm.com>
---
Change-Id: I9b8aa79567f7c45f4d6a1290efcf34567e620717
---
 arch/arm64/include/asm/uaccess.h | 23 ++++++++++++++++
 arch/arm64/mm/fault.c            | 45 ++++++++++++++++++++++++++++++++
 2 files changed, 68 insertions(+)

diff --git a/arch/arm64/include/asm/uaccess.h b/arch/arm64/include/asm/uaccess.h
index 385a189f7d39..d841a560fae7 100644
--- a/arch/arm64/include/asm/uaccess.h
+++ b/arch/arm64/include/asm/uaccess.h
@@ -200,13 +200,36 @@ do {                                                      
                \
                                CONFIG_ARM64_PAN));                     \
 } while (0)
 
+/*
+ * The Tag Check Flag (TCF) mode for MTE is per EL, hence TCF0
+ * affects EL0 and TCF affects EL1 irrespective of which TTBR is
+ * used.
+ * The kernel accesses TTBR0 usually with LDTR/STTR instructions
+ * when UAO is available, so these would act as EL0 accesses using
+ * TCF0.
+ * However futex.h code uses exclusives which would be executed as
+ * EL1, this can potentially cause a tag check fault even if the
+ * user disables TCF0.
+ *
+ * To address the problem we set the PSTATE.TCO bit in uaccess_enable()
+ * and reset it in uaccess_disable().
+ *
+ * The Tag check override (TCO) bit disables temporarily the tag checking
+ * preventing the issue.
+ */
 static inline void uaccess_disable(void)
 {
+       asm volatile(ALTERNATIVE("nop", SET_PSTATE_TCO(0),
+                                ARM64_MTE, CONFIG_KASAN_HW_TAGS));
+
        __uaccess_disable(ARM64_HAS_PAN);
 }
 
 static inline void uaccess_enable(void)
 {
+       asm volatile(ALTERNATIVE("nop", SET_PSTATE_TCO(1),
+                                ARM64_MTE, CONFIG_KASAN_HW_TAGS));
+
        __uaccess_enable(ARM64_HAS_PAN);
 }
 
diff --git a/arch/arm64/mm/fault.c b/arch/arm64/mm/fault.c
index 183d1e6dd9e0..1e4b9353c68a 100644
--- a/arch/arm64/mm/fault.c
+++ b/arch/arm64/mm/fault.c
@@ -34,6 +34,7 @@
 #include <asm/debug-monitors.h>
 #include <asm/esr.h>
 #include <asm/kprobes.h>
+#include <asm/mte.h>
 #include <asm/processor.h>
 #include <asm/sysreg.h>
 #include <asm/system_misc.h>
@@ -297,6 +298,44 @@ static void die_kernel_fault(const char *msg, unsigned 
long addr,
        do_exit(SIGKILL);
 }
 
+static void report_tag_fault(unsigned long addr, unsigned int esr,
+                            struct pt_regs *regs)
+{
+}
+
+static void do_tag_recovery(unsigned long addr, unsigned int esr,
+                          struct pt_regs *regs)
+{
+       static bool reported;
+
+       if (!READ_ONCE(reported)) {
+               report_tag_fault(addr, esr, regs);
+               WRITE_ONCE(reported, true);
+       }
+
+       /*
+        * Disable MTE Tag Checking on the local CPU for the current EL.
+        * It will be done lazily on the other CPUs when they will hit a
+        * tag fault.
+        */
+       sysreg_clear_set(sctlr_el1, SCTLR_ELx_TCF_MASK, SCTLR_ELx_TCF_NONE);
+       isb();
+}
+
+static bool is_el1_mte_sync_tag_check_fault(unsigned int esr)
+{
+       unsigned int ec = ESR_ELx_EC(esr);
+       unsigned int fsc = esr & ESR_ELx_FSC;
+
+       if (ec != ESR_ELx_EC_DABT_CUR)
+               return false;
+
+       if (fsc == ESR_ELx_FSC_MTE)
+               return true;
+
+       return false;
+}
+
 static void __do_kernel_fault(unsigned long addr, unsigned int esr,
                              struct pt_regs *regs)
 {
@@ -313,6 +352,12 @@ static void __do_kernel_fault(unsigned long addr, unsigned 
int esr,
            "Ignoring spurious kernel translation fault at virtual address 
%016lx\n", addr))
                return;
 
+       if (is_el1_mte_sync_tag_check_fault(esr)) {
+               do_tag_recovery(addr, esr, regs);
+
+               return;
+       }
+
        if (is_el1_permission_fault(addr, esr, regs)) {
                if (esr & ESR_ELx_WNR)
                        msg = "write to read-only memory";
-- 
2.29.2.454.gaff20da3a2-goog

Reply via email to