Skip to content

Commit 3eb6660

Browse files
KAGA-KOKOPeter Zijlstra
authored andcommitted
uaccess: Provide ASM GOTO safe wrappers for unsafe_*_user()
ASM GOTO is miscompiled by GCC when it is used inside a auto cleanup scope: bool foo(u32 __user *p, u32 val) { scoped_guard(pagefault) unsafe_put_user(val, p, efault); return true; efault: return false; } e80: e8 00 00 00 00 call e85 <foo+0x5> e85: 65 48 8b 05 00 00 00 00 mov %gs:0x0(%rip),%rax e8d: 83 80 04 14 00 00 01 addl $0x1,0x1404(%rax) // pf_disable++ e94: 89 37 mov %esi,(%rdi) e96: 83 a8 04 14 00 00 01 subl $0x1,0x1404(%rax) // pf_disable-- e9d: b8 01 00 00 00 mov $0x1,%eax // success ea2: e9 00 00 00 00 jmp ea7 <foo+0x27> // ret ea7: 31 c0 xor %eax,%eax // fail ea9: e9 00 00 00 00 jmp eae <foo+0x2e> // ret which is broken as it leaks the pagefault disable counter on failure. Clang at least fails the build. Linus suggested to add a local label into the macro scope and let that jump to the actual caller supplied error label. __label__ local_label; \ arch_unsafe_get_user(x, ptr, local_label); \ if (0) { \ local_label: \ goto label; \ That works for both GCC and clang. clang: c80: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1) c85: 65 48 8b 0c 25 00 00 00 00 mov %gs:0x0,%rcx c8e: ff 81 04 14 00 00 incl 0x1404(%rcx) // pf_disable++ c94: 31 c0 xor %eax,%eax // set retval to false c96: 89 37 mov %esi,(%rdi) // write c98: b0 01 mov $0x1,%al // set retval to true c9a: ff 89 04 14 00 00 decl 0x1404(%rcx) // pf_disable-- ca0: 2e e9 00 00 00 00 cs jmp ca6 <foo+0x26> // ret The exception table entry points correctly to c9a GCC: f70: e8 00 00 00 00 call f75 <baz+0x5> f75: 65 48 8b 05 00 00 00 00 mov %gs:0x0(%rip),%rax f7d: 83 80 04 14 00 00 01 addl $0x1,0x1404(%rax) // pf_disable++ f84: 8b 17 mov (%rdi),%edx f86: 89 16 mov %edx,(%rsi) f88: 83 a8 04 14 00 00 01 subl $0x1,0x1404(%rax) // pf_disable-- f8f: b8 01 00 00 00 mov $0x1,%eax // success f94: e9 00 00 00 00 jmp f99 <baz+0x29> // ret f99: 83 a8 04 14 00 00 01 subl $0x1,0x1404(%rax) // pf_disable-- fa0: 31 c0 xor %eax,%eax // fail fa2: e9 00 00 00 00 jmp fa7 <baz+0x37> // ret The exception table entry points correctly to f99 So both compilers optimize out the extra goto and emit correct and efficient code. Provide a generic wrapper to do that to avoid modifying all the affected architecture specific implementation with that workaround. The only change required for architectures is to rename unsafe_*_user() to arch_unsafe_*_user(). That's done in subsequent changes. Suggested-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org> Reviewed-by: Mathieu Desnoyers <mathieu.desnoyers@efficios.com> Link: https://patch.msgid.link/877bweujtn.ffs@tglx
1 parent 44c5b67 commit 3eb6660

1 file changed

Lines changed: 68 additions & 4 deletions

File tree

include/linux/uaccess.h

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -518,7 +518,34 @@ long strncpy_from_user_nofault(char *dst, const void __user *unsafe_addr,
518518
long count);
519519
long strnlen_user_nofault(const void __user *unsafe_addr, long count);
520520

521-
#ifndef __get_kernel_nofault
521+
#ifdef arch_get_kernel_nofault
522+
/*
523+
* Wrap the architecture implementation so that @label can be outside of a
524+
* cleanup() scope. A regular C goto works correctly, but ASM goto does
525+
* not. Clang rejects such an attempt, but GCC silently emits buggy code.
526+
*/
527+
#define __get_kernel_nofault(dst, src, type, label) \
528+
do { \
529+
__label__ local_label; \
530+
arch_get_kernel_nofault(dst, src, type, local_label); \
531+
if (0) { \
532+
local_label: \
533+
goto label; \
534+
} \
535+
} while (0)
536+
537+
#define __put_kernel_nofault(dst, src, type, label) \
538+
do { \
539+
__label__ local_label; \
540+
arch_put_kernel_nofault(dst, src, type, local_label); \
541+
if (0) { \
542+
local_label: \
543+
goto label; \
544+
} \
545+
} while (0)
546+
547+
#elif !defined(__get_kernel_nofault) /* arch_get_kernel_nofault */
548+
522549
#define __get_kernel_nofault(dst, src, type, label) \
523550
do { \
524551
type __user *p = (type __force __user *)(src); \
@@ -535,7 +562,8 @@ do { \
535562
if (__put_user(data, p)) \
536563
goto label; \
537564
} while (0)
538-
#endif
565+
566+
#endif /* !__get_kernel_nofault */
539567

540568
/**
541569
* get_kernel_nofault(): safely attempt to read from a location
@@ -549,7 +577,42 @@ do { \
549577
copy_from_kernel_nofault(&(val), __gk_ptr, sizeof(val));\
550578
})
551579

552-
#ifndef user_access_begin
580+
#ifdef user_access_begin
581+
582+
#ifdef arch_unsafe_get_user
583+
/*
584+
* Wrap the architecture implementation so that @label can be outside of a
585+
* cleanup() scope. A regular C goto works correctly, but ASM goto does
586+
* not. Clang rejects such an attempt, but GCC silently emits buggy code.
587+
*
588+
* Some architectures use internal local labels already, but this extra
589+
* indirection here is harmless because the compiler optimizes it out
590+
* completely in any case. This construct just ensures that the ASM GOTO
591+
* target is always in the local scope. The C goto 'label' works correctly
592+
* when leaving a cleanup() scope.
593+
*/
594+
#define unsafe_get_user(x, ptr, label) \
595+
do { \
596+
__label__ local_label; \
597+
arch_unsafe_get_user(x, ptr, local_label); \
598+
if (0) { \
599+
local_label: \
600+
goto label; \
601+
} \
602+
} while (0)
603+
604+
#define unsafe_put_user(x, ptr, label) \
605+
do { \
606+
__label__ local_label; \
607+
arch_unsafe_put_user(x, ptr, local_label); \
608+
if (0) { \
609+
local_label: \
610+
goto label; \
611+
} \
612+
} while (0)
613+
#endif /* arch_unsafe_get_user */
614+
615+
#else /* user_access_begin */
553616
#define user_access_begin(ptr,len) access_ok(ptr, len)
554617
#define user_access_end() do { } while (0)
555618
#define unsafe_op_wrap(op, err) do { if (unlikely(op)) goto err; } while (0)
@@ -559,7 +622,8 @@ do { \
559622
#define unsafe_copy_from_user(d,s,l,e) unsafe_op_wrap(__copy_from_user(d,s,l),e)
560623
static inline unsigned long user_access_save(void) { return 0UL; }
561624
static inline void user_access_restore(unsigned long flags) { }
562-
#endif
625+
#endif /* !user_access_begin */
626+
563627
#ifndef user_write_access_begin
564628
#define user_write_access_begin user_access_begin
565629
#define user_write_access_end user_access_end

0 commit comments

Comments
 (0)