Skip to content

Commit 86a6a68

Browse files
committed
arm64: start using 'asm goto' for get_user() when available
This generates noticeably better code with compilers that support it, since we don't need to test the error register etc, the exception just jumps to the error handling directly. Note that this also marks SW_TTBR0_PAN incompatible with KCSAN support, since KCSAN wants to save and restore the user access state. KCSAN and SW_TTBR0_PAN were probably always incompatible, but it became obvious only when implementing the unsafe user access functions. At that point the default empty user_access_save/restore() functions weren't provided by the default fallback functions. Signed-off-by: Linus Torvalds <[email protected]>
1 parent 6ba59ff commit 86a6a68

File tree

3 files changed

+104
-30
lines changed

3 files changed

+104
-30
lines changed

arch/arm64/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1649,6 +1649,7 @@ config RODATA_FULL_DEFAULT_ENABLED
16491649

16501650
config ARM64_SW_TTBR0_PAN
16511651
bool "Emulate Privileged Access Never using TTBR0_EL1 switching"
1652+
depends on !KCSAN
16521653
help
16531654
Enabling this option prevents the kernel from accessing
16541655
user-space memory directly by pointing TTBR0_EL1 to a reserved

arch/arm64/include/asm/uaccess.h

Lines changed: 98 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -184,29 +184,40 @@ static inline void __user *__uaccess_mask_ptr(const void __user *ptr)
184184
* The "__xxx_error" versions set the third argument to -EFAULT if an error
185185
* occurs, and leave it unchanged on success.
186186
*/
187-
#define __get_mem_asm(load, reg, x, addr, err, type) \
187+
#ifdef CONFIG_CC_HAS_ASM_GOTO_OUTPUT
188+
#define __get_mem_asm(load, reg, x, addr, label, type) \
189+
asm_goto_output( \
190+
"1: " load " " reg "0, [%1]\n" \
191+
_ASM_EXTABLE_##type##ACCESS_ERR(1b, %l2, %w0) \
192+
: "=r" (x) \
193+
: "r" (addr) : : label)
194+
#else
195+
#define __get_mem_asm(load, reg, x, addr, label, type) do { \
196+
int __gma_err = 0; \
188197
asm volatile( \
189198
"1: " load " " reg "1, [%2]\n" \
190199
"2:\n" \
191200
_ASM_EXTABLE_##type##ACCESS_ERR_ZERO(1b, 2b, %w0, %w1) \
192-
: "+r" (err), "=r" (x) \
193-
: "r" (addr))
201+
: "+r" (__gma_err), "=r" (x) \
202+
: "r" (addr)); \
203+
if (__gma_err) goto label; } while (0)
204+
#endif
194205

195-
#define __raw_get_mem(ldr, x, ptr, err, type) \
206+
#define __raw_get_mem(ldr, x, ptr, label, type) \
196207
do { \
197208
unsigned long __gu_val; \
198209
switch (sizeof(*(ptr))) { \
199210
case 1: \
200-
__get_mem_asm(ldr "b", "%w", __gu_val, (ptr), (err), type); \
211+
__get_mem_asm(ldr "b", "%w", __gu_val, (ptr), label, type); \
201212
break; \
202213
case 2: \
203-
__get_mem_asm(ldr "h", "%w", __gu_val, (ptr), (err), type); \
214+
__get_mem_asm(ldr "h", "%w", __gu_val, (ptr), label, type); \
204215
break; \
205216
case 4: \
206-
__get_mem_asm(ldr, "%w", __gu_val, (ptr), (err), type); \
217+
__get_mem_asm(ldr, "%w", __gu_val, (ptr), label, type); \
207218
break; \
208219
case 8: \
209-
__get_mem_asm(ldr, "%x", __gu_val, (ptr), (err), type); \
220+
__get_mem_asm(ldr, "%x", __gu_val, (ptr), label, type); \
210221
break; \
211222
default: \
212223
BUILD_BUG(); \
@@ -219,27 +230,34 @@ do { \
219230
* uaccess_ttbr0_disable(). As `x` and `ptr` could contain blocking functions,
220231
* we must evaluate these outside of the critical section.
221232
*/
222-
#define __raw_get_user(x, ptr, err) \
233+
#define __raw_get_user(x, ptr, label) \
223234
do { \
224235
__typeof__(*(ptr)) __user *__rgu_ptr = (ptr); \
225236
__typeof__(x) __rgu_val; \
226237
__chk_user_ptr(ptr); \
227-
\
228-
uaccess_ttbr0_enable(); \
229-
__raw_get_mem("ldtr", __rgu_val, __rgu_ptr, err, U); \
230-
uaccess_ttbr0_disable(); \
231-
\
232-
(x) = __rgu_val; \
238+
do { \
239+
__label__ __rgu_failed; \
240+
uaccess_ttbr0_enable(); \
241+
__raw_get_mem("ldtr", __rgu_val, __rgu_ptr, __rgu_failed, U); \
242+
uaccess_ttbr0_disable(); \
243+
(x) = __rgu_val; \
244+
break; \
245+
__rgu_failed: \
246+
uaccess_ttbr0_disable(); \
247+
goto label; \
248+
} while (0); \
233249
} while (0)
234250

235251
#define __get_user_error(x, ptr, err) \
236252
do { \
253+
__label__ __gu_failed; \
237254
__typeof__(*(ptr)) __user *__p = (ptr); \
238255
might_fault(); \
239256
if (access_ok(__p, sizeof(*__p))) { \
240257
__p = uaccess_mask_ptr(__p); \
241-
__raw_get_user((x), __p, (err)); \
258+
__raw_get_user((x), __p, __gu_failed); \
242259
} else { \
260+
__gu_failed: \
243261
(x) = (__force __typeof__(x))0; (err) = -EFAULT; \
244262
} \
245263
} while (0)
@@ -262,15 +280,18 @@ do { \
262280
do { \
263281
__typeof__(dst) __gkn_dst = (dst); \
264282
__typeof__(src) __gkn_src = (src); \
265-
int __gkn_err = 0; \
266-
\
267-
__mte_enable_tco_async(); \
268-
__raw_get_mem("ldr", *((type *)(__gkn_dst)), \
269-
(__force type *)(__gkn_src), __gkn_err, K); \
270-
__mte_disable_tco_async(); \
283+
do { \
284+
__label__ __gkn_label; \
271285
\
272-
if (unlikely(__gkn_err)) \
286+
__mte_enable_tco_async(); \
287+
__raw_get_mem("ldr", *((type *)(__gkn_dst)), \
288+
(__force type *)(__gkn_src), __gkn_label, K); \
289+
__mte_disable_tco_async(); \
290+
break; \
291+
__gkn_label: \
292+
__mte_disable_tco_async(); \
273293
goto err_label; \
294+
} while (0); \
274295
} while (0)
275296

276297
#define __put_mem_asm(store, reg, x, addr, err, type) \
@@ -381,6 +402,60 @@ extern unsigned long __must_check __arch_copy_to_user(void __user *to, const voi
381402
__actu_ret; \
382403
})
383404

405+
static __must_check __always_inline bool user_access_begin(const void __user *ptr, size_t len)
406+
{
407+
if (unlikely(!access_ok(ptr,len)))
408+
return 0;
409+
uaccess_ttbr0_enable();
410+
return 1;
411+
}
412+
#define user_access_begin(a,b) user_access_begin(a,b)
413+
#define user_access_end() uaccess_ttbr0_disable()
414+
415+
/*
416+
* The arm64 inline asms should learn abut asm goto, and we should
417+
* teach user_access_begin() about address masking.
418+
*/
419+
#define unsafe_put_user(x, ptr, label) do { \
420+
int __upu_err = 0; \
421+
__raw_put_mem("sttr", x, uaccess_mask_ptr(ptr), __upu_err, U); \
422+
if (__upu_err) goto label; \
423+
} while (0)
424+
425+
#define unsafe_get_user(x, ptr, label) \
426+
__raw_get_mem("ldtr", x, uaccess_mask_ptr(ptr), label, U)
427+
428+
/*
429+
* KCSAN uses these to save and restore ttbr state.
430+
* We do not support KCSAN with ARM64_SW_TTBR0_PAN, so
431+
* they are no-ops.
432+
*/
433+
static inline unsigned long user_access_save(void) { return 0; }
434+
static inline void user_access_restore(unsigned long enabled) { }
435+
436+
/*
437+
* We want the unsafe accessors to always be inlined and use
438+
* the error labels - thus the macro games.
439+
*/
440+
#define unsafe_copy_loop(dst, src, len, type, label) \
441+
while (len >= sizeof(type)) { \
442+
unsafe_put_user(*(type *)(src),(type __user *)(dst),label); \
443+
dst += sizeof(type); \
444+
src += sizeof(type); \
445+
len -= sizeof(type); \
446+
}
447+
448+
#define unsafe_copy_to_user(_dst,_src,_len,label) \
449+
do { \
450+
char __user *__ucu_dst = (_dst); \
451+
const char *__ucu_src = (_src); \
452+
size_t __ucu_len = (_len); \
453+
unsafe_copy_loop(__ucu_dst, __ucu_src, __ucu_len, u64, label); \
454+
unsafe_copy_loop(__ucu_dst, __ucu_src, __ucu_len, u32, label); \
455+
unsafe_copy_loop(__ucu_dst, __ucu_src, __ucu_len, u16, label); \
456+
unsafe_copy_loop(__ucu_dst, __ucu_src, __ucu_len, u8, label); \
457+
} while (0)
458+
384459
#define INLINE_COPY_TO_USER
385460
#define INLINE_COPY_FROM_USER
386461

arch/arm64/kernel/mte.c

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -582,25 +582,23 @@ subsys_initcall(register_mte_tcf_preferred_sysctl);
582582
size_t mte_probe_user_range(const char __user *uaddr, size_t size)
583583
{
584584
const char __user *end = uaddr + size;
585-
int err = 0;
586585
char val;
587586

588-
__raw_get_user(val, uaddr, err);
589-
if (err)
590-
return size;
587+
__raw_get_user(val, uaddr, efault);
591588

592589
uaddr = PTR_ALIGN(uaddr, MTE_GRANULE_SIZE);
593590
while (uaddr < end) {
594591
/*
595592
* A read is sufficient for mte, the caller should have probed
596593
* for the pte write permission if required.
597594
*/
598-
__raw_get_user(val, uaddr, err);
599-
if (err)
600-
return end - uaddr;
595+
__raw_get_user(val, uaddr, efault);
601596
uaddr += MTE_GRANULE_SIZE;
602597
}
603598
(void)val;
604599

605600
return 0;
601+
602+
efault:
603+
return end - uaddr;
606604
}

0 commit comments

Comments
 (0)