Skip to content

Commit 7af5b90

Browse files
linuswRussell King (Oracle)
authored and
Russell King (Oracle)
committed
ARM: 9358/2: Implement PAN for LPAE by TTBR0 page table walks disablement
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>
1 parent de7f60f commit 7af5b90

File tree

10 files changed

+155
-6
lines changed

10 files changed

+155
-6
lines changed

arch/arm/Kconfig

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1233,9 +1233,9 @@ config HIGHPTE
12331233
consumed by page tables. Setting this option will allow
12341234
user-space 2nd level page tables to reside in high memory.
12351235

1236-
config CPU_SW_DOMAIN_PAN
1237-
bool "Enable use of CPU domains to implement privileged no-access"
1238-
depends on MMU && !ARM_LPAE
1236+
config ARM_PAN
1237+
bool "Enable privileged no-access"
1238+
depends on MMU
12391239
default y
12401240
help
12411241
Increase kernel security by ensuring that normal kernel accesses
@@ -1244,10 +1244,26 @@ config CPU_SW_DOMAIN_PAN
12441244
by ensuring that magic values (such as LIST_POISON) will always
12451245
fault when dereferenced.
12461246

1247+
The implementation uses CPU domains when !CONFIG_ARM_LPAE and
1248+
disabling of TTBR0 page table walks with CONFIG_ARM_LPAE.
1249+
1250+
config CPU_SW_DOMAIN_PAN
1251+
def_bool y
1252+
depends on ARM_PAN && !ARM_LPAE
1253+
help
1254+
Enable use of CPU domains to implement privileged no-access.
1255+
12471256
CPUs with low-vector mappings use a best-efforts implementation.
12481257
Their lower 1MB needs to remain accessible for the vectors, but
12491258
the remainder of userspace will become appropriately inaccessible.
12501259

1260+
config CPU_TTBR0_PAN
1261+
def_bool y
1262+
depends on ARM_PAN && ARM_LPAE
1263+
help
1264+
Enable privileged no-access by disabling TTBR0 page table walks when
1265+
running in kernel mode.
1266+
12511267
config HW_PERF_EVENTS
12521268
def_bool y
12531269
depends on ARM_PMU

arch/arm/include/asm/assembler.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <asm/opcodes-virt.h>
2222
#include <asm/asm-offsets.h>
2323
#include <asm/page.h>
24+
#include <asm/pgtable.h>
2425
#include <asm/thread_info.h>
2526
#include <asm/uaccess-asm.h>
2627

arch/arm/include/asm/pgtable-3level-hwdef.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
#define PHYS_MASK_SHIFT (40)
7575
#define PHYS_MASK ((1ULL << PHYS_MASK_SHIFT) - 1)
7676

77+
#ifndef CONFIG_CPU_TTBR0_PAN
7778
/*
7879
* TTBR0/TTBR1 split (PAGE_OFFSET):
7980
* 0x40000000: T0SZ = 2, T1SZ = 0 (not used)
@@ -93,6 +94,14 @@
9394
#endif
9495

9596
#define TTBR1_SIZE (((PAGE_OFFSET >> 30) - 1) << 16)
97+
#else
98+
/*
99+
* With CONFIG_CPU_TTBR0_PAN enabled, TTBR1 is only used during uaccess
100+
* disabled regions when TTBR0 is disabled.
101+
*/
102+
#define TTBR1_OFFSET 0 /* pointing to swapper_pg_dir */
103+
#define TTBR1_SIZE 0 /* TTBR1 size controlled via TTBCR.T0SZ */
104+
#endif
96105

97106
/*
98107
* TTBCR register bits.

arch/arm/include/asm/ptrace.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ struct pt_regs {
2020
struct svc_pt_regs {
2121
struct pt_regs regs;
2222
u32 dacr;
23+
u32 ttbcr;
2324
};
2425

2526
#define to_svc_pt_regs(r) container_of(r, struct svc_pt_regs, regs)

arch/arm/include/asm/uaccess-asm.h

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
#endif
4040
.endm
4141

42-
#ifdef CONFIG_CPU_SW_DOMAIN_PAN
42+
#if defined(CONFIG_CPU_SW_DOMAIN_PAN)
4343

4444
.macro uaccess_disable, tmp, isb=1
4545
/*
@@ -65,6 +65,37 @@
6565
.endif
6666
.endm
6767

68+
#elif defined(CONFIG_CPU_TTBR0_PAN)
69+
70+
.macro uaccess_disable, tmp, isb=1
71+
/*
72+
* Disable TTBR0 page table walks (EDP0 = 1), use the reserved ASID
73+
* from TTBR1 (A1 = 1) and enable TTBR1 page table walks for kernel
74+
* addresses by reducing TTBR0 range to 32MB (T0SZ = 7).
75+
*/
76+
mrc p15, 0, \tmp, c2, c0, 2 @ read TTBCR
77+
orr \tmp, \tmp, #TTBCR_EPD0 | TTBCR_T0SZ_MASK
78+
orr \tmp, \tmp, #TTBCR_A1
79+
mcr p15, 0, \tmp, c2, c0, 2 @ write TTBCR
80+
.if \isb
81+
instr_sync
82+
.endif
83+
.endm
84+
85+
.macro uaccess_enable, tmp, isb=1
86+
/*
87+
* Enable TTBR0 page table walks (T0SZ = 0, EDP0 = 0) and ASID from
88+
* TTBR0 (A1 = 0).
89+
*/
90+
mrc p15, 0, \tmp, c2, c0, 2 @ read TTBCR
91+
bic \tmp, \tmp, #TTBCR_EPD0 | TTBCR_T0SZ_MASK
92+
bic \tmp, \tmp, #TTBCR_A1
93+
mcr p15, 0, \tmp, c2, c0, 2 @ write TTBCR
94+
.if \isb
95+
instr_sync
96+
.endif
97+
.endm
98+
6899
#else
69100

70101
.macro uaccess_disable, tmp, isb=1
@@ -79,6 +110,12 @@
79110
#define DACR(x...) x
80111
#else
81112
#define DACR(x...)
113+
#endif
114+
115+
#ifdef CONFIG_CPU_TTBR0_PAN
116+
#define PAN(x...) x
117+
#else
118+
#define PAN(x...)
82119
#endif
83120

84121
/*
@@ -94,6 +131,8 @@
94131
.macro uaccess_entry, tsk, tmp0, tmp1, tmp2, disable
95132
DACR( mrc p15, 0, \tmp0, c3, c0, 0)
96133
DACR( str \tmp0, [sp, #SVC_DACR])
134+
PAN( mrc p15, 0, \tmp0, c2, c0, 2)
135+
PAN( str \tmp0, [sp, #SVC_TTBCR])
97136
.if \disable && IS_ENABLED(CONFIG_CPU_SW_DOMAIN_PAN)
98137
/* kernel=client, user=no access */
99138
mov \tmp2, #DACR_UACCESS_DISABLE
@@ -112,8 +151,11 @@
112151
.macro uaccess_exit, tsk, tmp0, tmp1
113152
DACR( ldr \tmp0, [sp, #SVC_DACR])
114153
DACR( mcr p15, 0, \tmp0, c3, c0, 0)
154+
PAN( ldr \tmp0, [sp, #SVC_TTBCR])
155+
PAN( mcr p15, 0, \tmp0, c2, c0, 2)
115156
.endm
116157

117158
#undef DACR
159+
#undef PAN
118160

119161
#endif /* __ASM_UACCESS_ASM_H__ */

arch/arm/include/asm/uaccess.h

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
#include <asm/domain.h>
1515
#include <asm/unaligned.h>
1616
#include <asm/unified.h>
17+
#include <asm/pgtable.h>
18+
#include <asm/proc-fns.h>
1719
#include <asm/compiler.h>
1820

1921
#include <asm/extable.h>
@@ -24,7 +26,7 @@
2426
* perform such accesses (eg, via list poison values) which could then
2527
* be exploited for priviledge escalation.
2628
*/
27-
#ifdef CONFIG_CPU_SW_DOMAIN_PAN
29+
#if defined(CONFIG_CPU_SW_DOMAIN_PAN)
2830

2931
static __always_inline unsigned int uaccess_save_and_enable(void)
3032
{
@@ -43,6 +45,28 @@ static __always_inline void uaccess_restore(unsigned int flags)
4345
set_domain(flags);
4446
}
4547

48+
#elif defined(CONFIG_CPU_TTBR0_PAN)
49+
50+
static inline unsigned int uaccess_save_and_enable(void)
51+
{
52+
unsigned int old_ttbcr = cpu_get_ttbcr();
53+
54+
/*
55+
* Enable TTBR0 page table walks (T0SZ = 0, EDP0 = 0) and ASID from
56+
* TTBR0 (A1 = 0).
57+
*/
58+
cpu_set_ttbcr(old_ttbcr & ~(TTBCR_A1 | TTBCR_EPD0 | TTBCR_T0SZ_MASK));
59+
isb();
60+
61+
return old_ttbcr;
62+
}
63+
64+
static inline void uaccess_restore(unsigned int flags)
65+
{
66+
cpu_set_ttbcr(flags);
67+
isb();
68+
}
69+
4670
#else
4771

4872
static inline unsigned int uaccess_save_and_enable(void)

arch/arm/kernel/asm-offsets.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ int main(void)
8585
DEFINE(S_OLD_R0, offsetof(struct pt_regs, ARM_ORIG_r0));
8686
DEFINE(PT_REGS_SIZE, sizeof(struct pt_regs));
8787
DEFINE(SVC_DACR, offsetof(struct svc_pt_regs, dacr));
88+
DEFINE(SVC_TTBCR, offsetof(struct svc_pt_regs, ttbcr));
8889
DEFINE(SVC_REGS_SIZE, sizeof(struct svc_pt_regs));
8990
BLANK();
9091
DEFINE(SIGFRAME_RC3_OFFSET, offsetof(struct sigframe, retcode[3]));

arch/arm/kernel/suspend.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <asm/smp_plat.h>
1313
#include <asm/suspend.h>
1414
#include <asm/tlbflush.h>
15+
#include <asm/uaccess.h>
1516

1617
extern int __cpu_suspend(unsigned long, int (*)(unsigned long), u32 cpuid);
1718
extern void cpu_resume_mmu(void);
@@ -26,6 +27,13 @@ int cpu_suspend(unsigned long arg, int (*fn)(unsigned long))
2627
if (!idmap_pgd)
2728
return -EINVAL;
2829

30+
/*
31+
* Needed for the MMU disabling/enabing code to be able to run from
32+
* TTBR0 addresses.
33+
*/
34+
if (IS_ENABLED(CONFIG_CPU_TTBR0_PAN))
35+
uaccess_save_and_enable();
36+
2937
/*
3038
* Function graph tracer state gets incosistent when the kernel
3139
* calls functions that never return (aka suspend finishers) hence

arch/arm/lib/csumpartialcopyuser.S

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313

1414
.text
1515

16-
#ifdef CONFIG_CPU_SW_DOMAIN_PAN
16+
#if defined(CONFIG_CPU_SW_DOMAIN_PAN)
17+
1718
.macro save_regs
1819
mrc p15, 0, ip, c3, c0, 0
1920
stmfd sp!, {r1, r2, r4 - r8, ip, lr}
@@ -25,14 +26,31 @@
2526
mcr p15, 0, ip, c3, c0, 0
2627
ret lr
2728
.endm
29+
30+
#elif defined(CONFIG_CPU_TTBR0_PAN)
31+
32+
.macro save_regs
33+
mrc p15, 0, ip, c2, c0, 2 @ read TTBCR
34+
stmfd sp!, {r1, r2, r4 - r8, ip, lr}
35+
uaccess_enable ip
36+
.endm
37+
38+
.macro load_regs
39+
ldmfd sp!, {r1, r2, r4 - r8, ip, lr}
40+
mcr p15, 0, ip, c2, c0, 2 @ restore TTBCR
41+
ret lr
42+
.endm
43+
2844
#else
45+
2946
.macro save_regs
3047
stmfd sp!, {r1, r2, r4 - r8, lr}
3148
.endm
3249

3350
.macro load_regs
3451
ldmfd sp!, {r1, r2, r4 - r8, pc}
3552
.endm
53+
3654
#endif
3755

3856
.macro load1b, reg1

arch/arm/mm/fault.c

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,27 @@ static inline bool is_permission_fault(unsigned int fsr)
242242
return false;
243243
}
244244

245+
#ifdef CONFIG_CPU_TTBR0_PAN
246+
static inline bool ttbr0_usermode_access_allowed(struct pt_regs *regs)
247+
{
248+
struct svc_pt_regs *svcregs;
249+
250+
/* If we are in user mode: permission granted */
251+
if (user_mode(regs))
252+
return true;
253+
254+
/* uaccess state saved above pt_regs on SVC exception entry */
255+
svcregs = to_svc_pt_regs(regs);
256+
257+
return !(svcregs->ttbcr & TTBCR_EPD0);
258+
}
259+
#else
260+
static inline bool ttbr0_usermode_access_allowed(struct pt_regs *regs)
261+
{
262+
return true;
263+
}
264+
#endif
265+
245266
static int __kprobes
246267
do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
247268
{
@@ -285,6 +306,14 @@ do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
285306

286307
perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS, 1, regs, addr);
287308

309+
/*
310+
* Privileged access aborts with CONFIG_CPU_TTBR0_PAN enabled are
311+
* routed via the translation fault mechanism. Check whether uaccess
312+
* is disabled while in kernel mode.
313+
*/
314+
if (!ttbr0_usermode_access_allowed(regs))
315+
goto no_context;
316+
288317
if (!(flags & FAULT_FLAG_USER))
289318
goto lock_mmap;
290319

0 commit comments

Comments
 (0)