Skip to content

Commit 4b65a5d

Browse files
committed
arm64: Introduce uaccess_{disable,enable} functionality based on TTBR0_EL1
This patch adds the uaccess macros/functions to disable access to user space by setting TTBR0_EL1 to a reserved zeroed page. Since the value written to TTBR0_EL1 must be a physical address, for simplicity this patch introduces a reserved_ttbr0 page at a constant offset from swapper_pg_dir. The uaccess_disable code uses the ttbr1_el1 value adjusted by the reserved_ttbr0 offset. Enabling access to user is done by restoring TTBR0_EL1 with the value from the struct thread_info ttbr0 variable. Interrupts must be disabled during the uaccess_ttbr0_enable code to ensure the atomicity of the thread_info.ttbr0 read and TTBR0_EL1 write. This patch also moves the get_thread_info asm macro from entry.S to assembler.h for reuse in the uaccess_ttbr0_* macros. Cc: Will Deacon <[email protected]> Cc: James Morse <[email protected]> Cc: Kees Cook <[email protected]> Cc: Mark Rutland <[email protected]> Signed-off-by: Catalin Marinas <[email protected]>
1 parent f33bcf0 commit 4b65a5d

File tree

10 files changed

+146
-13
lines changed

10 files changed

+146
-13
lines changed

arch/arm64/include/asm/assembler.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,15 @@
4141
msr daifclr, #2
4242
.endm
4343

44+
.macro save_and_disable_irq, flags
45+
mrs \flags, daif
46+
msr daifset, #2
47+
.endm
48+
49+
.macro restore_irq, flags
50+
msr daif, \flags
51+
.endm
52+
4453
/*
4554
* Enable and disable debug exceptions.
4655
*/
@@ -406,6 +415,13 @@ alternative_endif
406415
movk \reg, :abs_g0_nc:\val
407416
.endm
408417

418+
/*
419+
* Return the current thread_info.
420+
*/
421+
.macro get_thread_info, rd
422+
mrs \rd, sp_el0
423+
.endm
424+
409425
/*
410426
* Errata workaround post TTBR0_EL1 update.
411427
*/

arch/arm64/include/asm/cpufeature.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,12 @@ static inline bool system_supports_fpsimd(void)
241241
return !cpus_have_const_cap(ARM64_HAS_NO_FPSIMD);
242242
}
243243

244+
static inline bool system_uses_ttbr0_pan(void)
245+
{
246+
return IS_ENABLED(CONFIG_ARM64_SW_TTBR0_PAN) &&
247+
!cpus_have_cap(ARM64_HAS_PAN);
248+
}
249+
244250
#endif /* __ASSEMBLY__ */
245251

246252
#endif

arch/arm64/include/asm/kernel-pgtable.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#ifndef __ASM_KERNEL_PGTABLE_H
2020
#define __ASM_KERNEL_PGTABLE_H
2121

22+
#include <asm/pgtable.h>
2223
#include <asm/sparsemem.h>
2324

2425
/*
@@ -54,6 +55,12 @@
5455
#define SWAPPER_DIR_SIZE (SWAPPER_PGTABLE_LEVELS * PAGE_SIZE)
5556
#define IDMAP_DIR_SIZE (IDMAP_PGTABLE_LEVELS * PAGE_SIZE)
5657

58+
#ifdef CONFIG_ARM64_SW_TTBR0_PAN
59+
#define RESERVED_TTBR0_SIZE (PAGE_SIZE)
60+
#else
61+
#define RESERVED_TTBR0_SIZE (0)
62+
#endif
63+
5764
/* Initial memory map size */
5865
#if ARM64_SWAPPER_USES_SECTION_MAPS
5966
#define SWAPPER_BLOCK_SHIFT SECTION_SHIFT

arch/arm64/include/asm/thread_info.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ typedef unsigned long mm_segment_t;
4747
struct thread_info {
4848
unsigned long flags; /* low level flags */
4949
mm_segment_t addr_limit; /* address limit */
50+
#ifdef CONFIG_ARM64_SW_TTBR0_PAN
51+
u64 ttbr0; /* saved TTBR0_EL1 */
52+
#endif
5053
int preempt_count; /* 0 => preemptable, <0 => bug */
5154
};
5255

arch/arm64/include/asm/uaccess.h

Lines changed: 102 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#define __ASM_UACCESS_H
2020

2121
#include <asm/alternative.h>
22+
#include <asm/kernel-pgtable.h>
2223
#include <asm/sysreg.h>
2324

2425
#ifndef __ASSEMBLY__
@@ -125,16 +126,71 @@ static inline void set_fs(mm_segment_t fs)
125126
/*
126127
* User access enabling/disabling.
127128
*/
129+
#ifdef CONFIG_ARM64_SW_TTBR0_PAN
130+
static inline void __uaccess_ttbr0_disable(void)
131+
{
132+
unsigned long ttbr;
133+
134+
/* reserved_ttbr0 placed at the end of swapper_pg_dir */
135+
ttbr = read_sysreg(ttbr1_el1) + SWAPPER_DIR_SIZE;
136+
write_sysreg(ttbr, ttbr0_el1);
137+
isb();
138+
}
139+
140+
static inline void __uaccess_ttbr0_enable(void)
141+
{
142+
unsigned long flags;
143+
144+
/*
145+
* Disable interrupts to avoid preemption between reading the 'ttbr0'
146+
* variable and the MSR. A context switch could trigger an ASID
147+
* roll-over and an update of 'ttbr0'.
148+
*/
149+
local_irq_save(flags);
150+
write_sysreg(current_thread_info()->ttbr0, ttbr0_el1);
151+
isb();
152+
local_irq_restore(flags);
153+
}
154+
155+
static inline bool uaccess_ttbr0_disable(void)
156+
{
157+
if (!system_uses_ttbr0_pan())
158+
return false;
159+
__uaccess_ttbr0_disable();
160+
return true;
161+
}
162+
163+
static inline bool uaccess_ttbr0_enable(void)
164+
{
165+
if (!system_uses_ttbr0_pan())
166+
return false;
167+
__uaccess_ttbr0_enable();
168+
return true;
169+
}
170+
#else
171+
static inline bool uaccess_ttbr0_disable(void)
172+
{
173+
return false;
174+
}
175+
176+
static inline bool uaccess_ttbr0_enable(void)
177+
{
178+
return false;
179+
}
180+
#endif
181+
128182
#define __uaccess_disable(alt) \
129183
do { \
130-
asm(ALTERNATIVE("nop", SET_PSTATE_PAN(1), alt, \
131-
CONFIG_ARM64_PAN)); \
184+
if (!uaccess_ttbr0_disable()) \
185+
asm(ALTERNATIVE("nop", SET_PSTATE_PAN(1), alt, \
186+
CONFIG_ARM64_PAN)); \
132187
} while (0)
133188

134189
#define __uaccess_enable(alt) \
135190
do { \
136-
asm(ALTERNATIVE("nop", SET_PSTATE_PAN(0), alt, \
137-
CONFIG_ARM64_PAN)); \
191+
if (uaccess_ttbr0_enable()) \
192+
asm(ALTERNATIVE("nop", SET_PSTATE_PAN(0), alt, \
193+
CONFIG_ARM64_PAN)); \
138194
} while (0)
139195

140196
static inline void uaccess_disable(void)
@@ -373,16 +429,56 @@ extern __must_check long strnlen_user(const char __user *str, long n);
373429
#include <asm/assembler.h>
374430

375431
/*
376-
* User access enabling/disabling macros. These are no-ops when UAO is
377-
* present.
432+
* User access enabling/disabling macros.
433+
*/
434+
#ifdef CONFIG_ARM64_SW_TTBR0_PAN
435+
.macro __uaccess_ttbr0_disable, tmp1
436+
mrs \tmp1, ttbr1_el1 // swapper_pg_dir
437+
add \tmp1, \tmp1, #SWAPPER_DIR_SIZE // reserved_ttbr0 at the end of swapper_pg_dir
438+
msr ttbr0_el1, \tmp1 // set reserved TTBR0_EL1
439+
isb
440+
.endm
441+
442+
.macro __uaccess_ttbr0_enable, tmp1
443+
get_thread_info \tmp1
444+
ldr \tmp1, [\tmp1, #TSK_TI_TTBR0] // load saved TTBR0_EL1
445+
msr ttbr0_el1, \tmp1 // set the non-PAN TTBR0_EL1
446+
isb
447+
.endm
448+
449+
.macro uaccess_ttbr0_disable, tmp1
450+
alternative_if_not ARM64_HAS_PAN
451+
__uaccess_ttbr0_disable \tmp1
452+
alternative_else_nop_endif
453+
.endm
454+
455+
.macro uaccess_ttbr0_enable, tmp1, tmp2
456+
alternative_if_not ARM64_HAS_PAN
457+
save_and_disable_irq \tmp2 // avoid preemption
458+
__uaccess_ttbr0_enable \tmp1
459+
restore_irq \tmp2
460+
alternative_else_nop_endif
461+
.endm
462+
#else
463+
.macro uaccess_ttbr0_disable, tmp1
464+
.endm
465+
466+
.macro uaccess_ttbr0_enable, tmp1, tmp2
467+
.endm
468+
#endif
469+
470+
/*
471+
* These macros are no-ops when UAO is present.
378472
*/
379473
.macro uaccess_disable_not_uao, tmp1
474+
uaccess_ttbr0_disable \tmp1
380475
alternative_if ARM64_ALT_PAN_NOT_UAO
381476
SET_PSTATE_PAN(1)
382477
alternative_else_nop_endif
383478
.endm
384479

385480
.macro uaccess_enable_not_uao, tmp1, tmp2
481+
uaccess_ttbr0_enable \tmp1, \tmp2
386482
alternative_if ARM64_ALT_PAN_NOT_UAO
387483
SET_PSTATE_PAN(0)
388484
alternative_else_nop_endif

arch/arm64/kernel/asm-offsets.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ int main(void)
3939
DEFINE(TSK_TI_FLAGS, offsetof(struct task_struct, thread_info.flags));
4040
DEFINE(TSK_TI_PREEMPT, offsetof(struct task_struct, thread_info.preempt_count));
4141
DEFINE(TSK_TI_ADDR_LIMIT, offsetof(struct task_struct, thread_info.addr_limit));
42+
#ifdef CONFIG_ARM64_SW_TTBR0_PAN
43+
DEFINE(TSK_TI_TTBR0, offsetof(struct task_struct, thread_info.ttbr0));
44+
#endif
4245
DEFINE(TSK_STACK, offsetof(struct task_struct, stack));
4346
BLANK();
4447
DEFINE(THREAD_CPU_CONTEXT, offsetof(struct task_struct, thread.cpu_context));

arch/arm64/kernel/cpufeature.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ unsigned int compat_elf_hwcap2 __read_mostly;
4747
#endif
4848

4949
DECLARE_BITMAP(cpu_hwcaps, ARM64_NCAPS);
50+
EXPORT_SYMBOL(cpu_hwcaps);
5051

5152
DEFINE_STATIC_KEY_ARRAY_FALSE(cpu_hwcap_keys, ARM64_NCAPS);
5253
EXPORT_SYMBOL(cpu_hwcap_keys);

arch/arm64/kernel/entry.S

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -183,10 +183,6 @@ alternative_else_nop_endif
183183
eret // return to kernel
184184
.endm
185185

186-
.macro get_thread_info, rd
187-
mrs \rd, sp_el0
188-
.endm
189-
190186
.macro irq_stack_entry
191187
mov x19, sp // preserve the original sp
192188

arch/arm64/kernel/head.S

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -326,14 +326,14 @@ __create_page_tables:
326326
* dirty cache lines being evicted.
327327
*/
328328
adrp x0, idmap_pg_dir
329-
adrp x1, swapper_pg_dir + SWAPPER_DIR_SIZE
329+
adrp x1, swapper_pg_dir + SWAPPER_DIR_SIZE + RESERVED_TTBR0_SIZE
330330
bl __inval_cache_range
331331

332332
/*
333333
* Clear the idmap and swapper page tables.
334334
*/
335335
adrp x0, idmap_pg_dir
336-
adrp x6, swapper_pg_dir + SWAPPER_DIR_SIZE
336+
adrp x6, swapper_pg_dir + SWAPPER_DIR_SIZE + RESERVED_TTBR0_SIZE
337337
1: stp xzr, xzr, [x0], #16
338338
stp xzr, xzr, [x0], #16
339339
stp xzr, xzr, [x0], #16
@@ -412,7 +412,7 @@ __create_page_tables:
412412
* tables again to remove any speculatively loaded cache lines.
413413
*/
414414
adrp x0, idmap_pg_dir
415-
adrp x1, swapper_pg_dir + SWAPPER_DIR_SIZE
415+
adrp x1, swapper_pg_dir + SWAPPER_DIR_SIZE + RESERVED_TTBR0_SIZE
416416
dmb sy
417417
bl __inval_cache_range
418418

arch/arm64/kernel/vmlinux.lds.S

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,11 @@ SECTIONS
216216
swapper_pg_dir = .;
217217
. += SWAPPER_DIR_SIZE;
218218

219+
#ifdef CONFIG_ARM64_SW_TTBR0_PAN
220+
reserved_ttbr0 = .;
221+
. += RESERVED_TTBR0_SIZE;
222+
#endif
223+
219224
_end = .;
220225

221226
STABS_DEBUG

0 commit comments

Comments
 (0)