ARM: 9358/2: Implement PAN for LPAE by TTBR0 page table walks disablement
authorLinus Walleij <linus.walleij@linaro.org>
Mon, 25 Mar 2024 07:31:13 +0000 (08:31 +0100)
committerRussell King (Oracle) <rmk+kernel@armlinux.org.uk>
Thu, 18 Apr 2024 11:10:46 +0000 (12:10 +0100)
With LPAE enabled, privileged no-access cannot be enforced using CPU
domains as such feature is not available. This patch implements PAN
by disabling TTBR0 page table walks while in kernel mode.

The ARM architecture allows page table walks to be split between TTBR0
and TTBR1. With LPAE enabled, the split is defined by a combination of
TTBCR T0SZ and T1SZ bits. Currently, an LPAE-enabled kernel uses TTBR0
for user addresses and TTBR1 for kernel addresses with the VMSPLIT_2G
and VMSPLIT_3G configurations. The main advantage for the 3:1 split is
that TTBR1 is reduced to 2 levels, so potentially faster TLB refill
(though usually the first level entries are already cached in the TLB).

The PAN support on LPAE-enabled kernels uses TTBR0 when running in user
space or in kernel space during user access routines (TTBCR T0SZ and
T1SZ are both 0). When running user accesses are disabled in kernel
mode, TTBR0 page table walks are disabled by setting TTBCR.EPD0. TTBR1
is used for kernel accesses (including loadable modules; anything
covered by swapper_pg_dir) by reducing the TTBCR.T0SZ to the minimum
(2^(32-7) = 32MB). To avoid user accesses potentially hitting stale TLB
entries, the ASID is switched to 0 (reserved) by setting TTBCR.A1 and
using the ASID value in TTBR1. The difference from a non-PAN kernel is
that with the 3:1 memory split, TTBR1 always uses 3 levels of page
tables.

As part of the change we are using preprocessor elif definied() clauses
so balance these clauses by converting relevant precedingt ifdef
clauses to if defined() clauses.

Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
Reviewed-by: Kees Cook <keescook@chromium.org>
Tested-by: Florian Fainelli <florian.fainelli@broadcom.com>
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
arch/arm/Kconfig
arch/arm/include/asm/assembler.h
arch/arm/include/asm/pgtable-3level-hwdef.h
arch/arm/include/asm/ptrace.h
arch/arm/include/asm/uaccess-asm.h
arch/arm/include/asm/uaccess.h
arch/arm/kernel/asm-offsets.c
arch/arm/kernel/suspend.c
arch/arm/lib/csumpartialcopyuser.S
arch/arm/mm/fault.c

index b14aed3a17abb6732e0b87d3ba1b4edc260e7f0e..1394a8491210f10b2047adf67270edf91680228b 100644 (file)
@@ -1233,9 +1233,9 @@ config HIGHPTE
          consumed by page tables.  Setting this option will allow
          user-space 2nd level page tables to reside in high memory.
 
-config CPU_SW_DOMAIN_PAN
-       bool "Enable use of CPU domains to implement privileged no-access"
-       depends on MMU && !ARM_LPAE
+config ARM_PAN
+       bool "Enable privileged no-access"
+       depends on MMU
        default y
        help
          Increase kernel security by ensuring that normal kernel accesses
@@ -1244,10 +1244,26 @@ config CPU_SW_DOMAIN_PAN
          by ensuring that magic values (such as LIST_POISON) will always
          fault when dereferenced.
 
+         The implementation uses CPU domains when !CONFIG_ARM_LPAE and
+         disabling of TTBR0 page table walks with CONFIG_ARM_LPAE.
+
+config CPU_SW_DOMAIN_PAN
+       def_bool y
+       depends on ARM_PAN && !ARM_LPAE
+       help
+         Enable use of CPU domains to implement privileged no-access.
+
          CPUs with low-vector mappings use a best-efforts implementation.
          Their lower 1MB needs to remain accessible for the vectors, but
          the remainder of userspace will become appropriately inaccessible.
 
+config CPU_TTBR0_PAN
+       def_bool y
+       depends on ARM_PAN && ARM_LPAE
+       help
+         Enable privileged no-access by disabling TTBR0 page table walks when
+         running in kernel mode.
+
 config HW_PERF_EVENTS
        def_bool y
        depends on ARM_PMU
index aebe2c8f6a686cc591f4dc8c19582e7701251051..d33c1e24e00b3d317a697c1412e9b8c5f11a1e43 100644 (file)
@@ -21,6 +21,7 @@
 #include <asm/opcodes-virt.h>
 #include <asm/asm-offsets.h>
 #include <asm/page.h>
+#include <asm/pgtable.h>
 #include <asm/thread_info.h>
 #include <asm/uaccess-asm.h>
 
index 19da7753a0b8bdae1c78c1142969d16a7c380539..323ad811732e5dad7c0fc1d5ab5a314fab5cc012 100644 (file)
@@ -74,6 +74,7 @@
 #define PHYS_MASK_SHIFT                (40)
 #define PHYS_MASK              ((1ULL << PHYS_MASK_SHIFT) - 1)
 
+#ifndef CONFIG_CPU_TTBR0_PAN
 /*
  * TTBR0/TTBR1 split (PAGE_OFFSET):
  *   0x40000000: T0SZ = 2, T1SZ = 0 (not used)
 #endif
 
 #define TTBR1_SIZE     (((PAGE_OFFSET >> 30) - 1) << 16)
+#else
+/*
+ * With CONFIG_CPU_TTBR0_PAN enabled, TTBR1 is only used during uaccess
+ * disabled regions when TTBR0 is disabled.
+ */
+#define TTBR1_OFFSET   0                       /* pointing to swapper_pg_dir */
+#define TTBR1_SIZE     0                       /* TTBR1 size controlled via TTBCR.T0SZ */
+#endif
 
 /*
  * TTBCR register bits.
index 14a38cc67e0bc966891af77d23a8897873ea46a3..6eb311fb2da06fa393f8be7caec8d997637a88b2 100644 (file)
@@ -20,6 +20,7 @@ struct pt_regs {
 struct svc_pt_regs {
        struct pt_regs regs;
        u32 dacr;
+       u32 ttbcr;
 };
 
 #define to_svc_pt_regs(r) container_of(r, struct svc_pt_regs, regs)
index ea42ba25920f69968293c33f37d297462a0a391f..4bccd895d954e58774d0ac42da61ea35e96de0f6 100644 (file)
@@ -39,7 +39,7 @@
 #endif
        .endm
 
-#ifdef CONFIG_CPU_SW_DOMAIN_PAN
+#if defined(CONFIG_CPU_SW_DOMAIN_PAN)
 
        .macro  uaccess_disable, tmp, isb=1
        /*
        .endif
        .endm
 
+#elif defined(CONFIG_CPU_TTBR0_PAN)
+
+       .macro  uaccess_disable, tmp, isb=1
+       /*
+        * Disable TTBR0 page table walks (EDP0 = 1), use the reserved ASID
+        * from TTBR1 (A1 = 1) and enable TTBR1 page table walks for kernel
+        * addresses by reducing TTBR0 range to 32MB (T0SZ = 7).
+        */
+       mrc     p15, 0, \tmp, c2, c0, 2         @ read TTBCR
+       orr     \tmp, \tmp, #TTBCR_EPD0 | TTBCR_T0SZ_MASK
+       orr     \tmp, \tmp, #TTBCR_A1
+       mcr     p15, 0, \tmp, c2, c0, 2         @ write TTBCR
+       .if     \isb
+       instr_sync
+       .endif
+       .endm
+
+       .macro  uaccess_enable, tmp, isb=1
+       /*
+        * Enable TTBR0 page table walks (T0SZ = 0, EDP0 = 0) and ASID from
+        * TTBR0 (A1 = 0).
+        */
+       mrc     p15, 0, \tmp, c2, c0, 2         @ read TTBCR
+       bic     \tmp, \tmp, #TTBCR_EPD0 | TTBCR_T0SZ_MASK
+       bic     \tmp, \tmp, #TTBCR_A1
+       mcr     p15, 0, \tmp, c2, c0, 2         @ write TTBCR
+       .if     \isb
+       instr_sync
+       .endif
+       .endm
+
 #else
 
        .macro  uaccess_disable, tmp, isb=1
 #define DACR(x...)     x
 #else
 #define DACR(x...)
+#endif
+
+#ifdef CONFIG_CPU_TTBR0_PAN
+#define PAN(x...)      x
+#else
+#define PAN(x...)
 #endif
 
        /*
        .macro  uaccess_entry, tsk, tmp0, tmp1, tmp2, disable
  DACR( mrc     p15, 0, \tmp0, c3, c0, 0)
  DACR( str     \tmp0, [sp, #SVC_DACR])
+ PAN(  mrc     p15, 0, \tmp0, c2, c0, 2)
+ PAN(  str     \tmp0, [sp, #SVC_TTBCR])
        .if \disable && IS_ENABLED(CONFIG_CPU_SW_DOMAIN_PAN)
        /* kernel=client, user=no access */
        mov     \tmp2, #DACR_UACCESS_DISABLE
        .macro  uaccess_exit, tsk, tmp0, tmp1
  DACR( ldr     \tmp0, [sp, #SVC_DACR])
  DACR( mcr     p15, 0, \tmp0, c3, c0, 0)
+ PAN(  ldr     \tmp0, [sp, #SVC_TTBCR])
+ PAN(  mcr     p15, 0, \tmp0, c2, c0, 2)
        .endm
 
 #undef DACR
+#undef PAN
 
 #endif /* __ASM_UACCESS_ASM_H__ */
index 2278769f115653777dc8d876cda13e0f8865b5d9..25d21d7d6e3efee0c41693c099976baf04aff74d 100644 (file)
@@ -14,6 +14,8 @@
 #include <asm/domain.h>
 #include <asm/unaligned.h>
 #include <asm/unified.h>
+#include <asm/pgtable.h>
+#include <asm/proc-fns.h>
 #include <asm/compiler.h>
 
 #include <asm/extable.h>
@@ -24,7 +26,7 @@
  * perform such accesses (eg, via list poison values) which could then
  * be exploited for priviledge escalation.
  */
-#ifdef CONFIG_CPU_SW_DOMAIN_PAN
+#if defined(CONFIG_CPU_SW_DOMAIN_PAN)
 
 static __always_inline unsigned int uaccess_save_and_enable(void)
 {
@@ -43,6 +45,28 @@ static __always_inline void uaccess_restore(unsigned int flags)
        set_domain(flags);
 }
 
+#elif defined(CONFIG_CPU_TTBR0_PAN)
+
+static inline unsigned int uaccess_save_and_enable(void)
+{
+       unsigned int old_ttbcr = cpu_get_ttbcr();
+
+       /*
+        * Enable TTBR0 page table walks (T0SZ = 0, EDP0 = 0) and ASID from
+        * TTBR0 (A1 = 0).
+        */
+       cpu_set_ttbcr(old_ttbcr & ~(TTBCR_A1 | TTBCR_EPD0 | TTBCR_T0SZ_MASK));
+       isb();
+
+       return old_ttbcr;
+}
+
+static inline void uaccess_restore(unsigned int flags)
+{
+       cpu_set_ttbcr(flags);
+       isb();
+}
+
 #else
 
 static inline unsigned int uaccess_save_and_enable(void)
index 4915662842ff1df4041ed504a456db581c063570..4853875740d0fe61c6bbc32ddd9a16fa8d1fb530 100644 (file)
@@ -85,6 +85,7 @@ int main(void)
   DEFINE(S_OLD_R0,             offsetof(struct pt_regs, ARM_ORIG_r0));
   DEFINE(PT_REGS_SIZE,         sizeof(struct pt_regs));
   DEFINE(SVC_DACR,             offsetof(struct svc_pt_regs, dacr));
+  DEFINE(SVC_TTBCR,            offsetof(struct svc_pt_regs, ttbcr));
   DEFINE(SVC_REGS_SIZE,                sizeof(struct svc_pt_regs));
   BLANK();
   DEFINE(SIGFRAME_RC3_OFFSET,  offsetof(struct sigframe, retcode[3]));
index c3ec3861dd0792a5ad18ac3875ffcfedefa2b8ec..58a6441b58c49a129866513f5c86836eb41cf359 100644 (file)
@@ -12,6 +12,7 @@
 #include <asm/smp_plat.h>
 #include <asm/suspend.h>
 #include <asm/tlbflush.h>
+#include <asm/uaccess.h>
 
 extern int __cpu_suspend(unsigned long, int (*)(unsigned long), u32 cpuid);
 extern void cpu_resume_mmu(void);
@@ -26,6 +27,13 @@ int cpu_suspend(unsigned long arg, int (*fn)(unsigned long))
        if (!idmap_pgd)
                return -EINVAL;
 
+       /*
+        * Needed for the MMU disabling/enabing code to be able to run from
+        * TTBR0 addresses.
+        */
+       if (IS_ENABLED(CONFIG_CPU_TTBR0_PAN))
+               uaccess_save_and_enable();
+
        /*
         * Function graph tracer state gets incosistent when the kernel
         * calls functions that never return (aka suspend finishers) hence
index 6928781e6beebcf3aab564ef0a52461e3ad43e92..c289bde0474349c3158df92b57eacce9fd040407 100644 (file)
@@ -13,7 +13,8 @@
 
                .text
 
-#ifdef CONFIG_CPU_SW_DOMAIN_PAN
+#if defined(CONFIG_CPU_SW_DOMAIN_PAN)
+
                .macro  save_regs
                mrc     p15, 0, ip, c3, c0, 0
                stmfd   sp!, {r1, r2, r4 - r8, ip, lr}
                mcr     p15, 0, ip, c3, c0, 0
                ret     lr
                .endm
+
+#elif defined(CONFIG_CPU_TTBR0_PAN)
+
+               .macro  save_regs
+               mrc     p15, 0, ip, c2, c0, 2           @ read TTBCR
+               stmfd   sp!, {r1, r2, r4 - r8, ip, lr}
+               uaccess_enable ip
+               .endm
+
+               .macro  load_regs
+               ldmfd   sp!, {r1, r2, r4 - r8, ip, lr}
+               mcr     p15, 0, ip, c2, c0, 2           @ restore TTBCR
+               ret     lr
+               .endm
+
 #else
+
                .macro  save_regs
                stmfd   sp!, {r1, r2, r4 - r8, lr}
                .endm
@@ -33,6 +50,7 @@
                .macro  load_regs
                ldmfd   sp!, {r1, r2, r4 - r8, pc}
                .endm
+
 #endif
 
                .macro  load1b, reg1
index 439dc6a26bb982ba7b085ca924ba4fc862ac61cc..dfa9554ef331a99b8ebb1a5397276cdc20802ac4 100644 (file)
@@ -242,6 +242,27 @@ static inline bool is_permission_fault(unsigned int fsr)
        return false;
 }
 
+#ifdef CONFIG_CPU_TTBR0_PAN
+static inline bool ttbr0_usermode_access_allowed(struct pt_regs *regs)
+{
+       struct svc_pt_regs *svcregs;
+
+       /* If we are in user mode: permission granted */
+       if (user_mode(regs))
+               return true;
+
+       /* uaccess state saved above pt_regs on SVC exception entry */
+       svcregs = to_svc_pt_regs(regs);
+
+       return !(svcregs->ttbcr & TTBCR_EPD0);
+}
+#else
+static inline bool ttbr0_usermode_access_allowed(struct pt_regs *regs)
+{
+       return true;
+}
+#endif
+
 static int __kprobes
 do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
 {
@@ -285,6 +306,14 @@ do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
 
        perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS, 1, regs, addr);
 
+       /*
+        * Privileged access aborts with CONFIG_CPU_TTBR0_PAN enabled are
+        * routed via the translation fault mechanism. Check whether uaccess
+        * is disabled while in kernel mode.
+        */
+       if (!ttbr0_usermode_access_allowed(regs))
+               goto no_context;
+
        if (!(flags & FAULT_FLAG_USER))
                goto lock_mmap;