Skip to content

Commit

Permalink
unsafe_[get|put]_user: change interface to use a error target label
Browse files Browse the repository at this point in the history
When I initially added the unsafe_[get|put]_user() helpers in commit
5b24a7a ("Add 'unsafe' user access functions for batched
accesses"), I made the mistake of modeling the interface on our
traditional __[get|put]_user() functions, which return zero on success,
or -EFAULT on failure.

That interface is fairly easy to use, but it's actually fairly nasty for
good code generation, since it essentially forces the caller to check
the error value for each access.

In particular, since the error handling is already internally
implemented with an exception handler, and we already use "asm goto" for
various other things, we could fairly easily make the error cases just
jump directly to an error label instead, and avoid the need for explicit
checking after each operation.

So switch the interface to pass in an error label, rather than checking
the error value in the caller.  Best do it now before we start growing
more users (the signal handling code in particular would be a good place
to use the new interface).

So rather than

	if (unsafe_get_user(x, ptr))
		... handle error ..

the interface is now

	unsafe_get_user(x, ptr, label);

where an error during the user mode fetch will now just cause a jump to
'label' in the caller.

Right now the actual _implementation_ of this all still ends up being a
"if (err) goto label", and does not take advantage of any exception
label tricks, but for "unsafe_put_user()" in particular it should be
fairly straightforward to convert to using the exception table model.

Note that "unsafe_get_user()" is much harder to convert to a clever
exception table model, because current versions of gcc do not allow the
use of "asm goto" (for the exception) with output values (for the actual
value to be fetched).  But that is hopefully not a limitation in the
long term.

[ Also note that it might be a good idea to switch unsafe_get_user() to
  actually _return_ the value it fetches from user space, but this
  commit only changes the error handling semantics ]

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
  • Loading branch information
torvalds committed Aug 8, 2016
1 parent 574673c commit 1bd4403
Show file tree
Hide file tree
Showing 4 changed files with 17 additions and 18 deletions.
16 changes: 8 additions & 8 deletions arch/x86/include/asm/uaccess.h
Expand Up @@ -812,21 +812,21 @@ copy_to_user(void __user *to, const void *from, unsigned long n)
#define user_access_begin() __uaccess_begin()
#define user_access_end() __uaccess_end()

#define unsafe_put_user(x, ptr) \
({ \
#define unsafe_put_user(x, ptr, err_label) \
do { \
int __pu_err; \
__put_user_size((x), (ptr), sizeof(*(ptr)), __pu_err, -EFAULT); \
__builtin_expect(__pu_err, 0); \
})
if (unlikely(__pu_err)) goto err_label; \
} while (0)

#define unsafe_get_user(x, ptr) \
({ \
#define unsafe_get_user(x, ptr, err_label) \
do { \
int __gu_err; \
unsigned long __gu_val; \
__get_user_size(__gu_val, (ptr), sizeof(*(ptr)), __gu_err, -EFAULT); \
(x) = (__force __typeof__(*(ptr)))__gu_val; \
__builtin_expect(__gu_err, 0); \
})
if (unlikely(__gu_err)) goto err_label; \
} while (0)

#endif /* _ASM_X86_UACCESS_H */

4 changes: 2 additions & 2 deletions include/linux/uaccess.h
Expand Up @@ -114,8 +114,8 @@ extern long strncpy_from_unsafe(char *dst, const void *unsafe_addr, long count);
#ifndef user_access_begin
#define user_access_begin() do { } while (0)
#define user_access_end() do { } while (0)
#define unsafe_get_user(x, ptr) __get_user(x, ptr)
#define unsafe_put_user(x, ptr) __put_user(x, ptr)
#define unsafe_get_user(x, ptr, err) do { if (unlikely(__get_user(x, ptr))) goto err; } while (0)
#define unsafe_put_user(x, ptr, err) do { if (unlikely(__put_user(x, ptr))) goto err; } while (0)
#endif

#endif /* __LINUX_UACCESS_H__ */
8 changes: 4 additions & 4 deletions lib/strncpy_from_user.c
Expand Up @@ -40,8 +40,8 @@ static inline long do_strncpy_from_user(char *dst, const char __user *src, long
unsigned long c, data;

/* Fall back to byte-at-a-time if we get a page fault */
if (unlikely(unsafe_get_user(c,(unsigned long __user *)(src+res))))
break;
unsafe_get_user(c, (unsigned long __user *)(src+res), byte_at_a_time);

*(unsigned long *)(dst+res) = c;
if (has_zero(c, &data, &constants)) {
data = prep_zero_mask(c, data, &constants);
Expand All @@ -56,8 +56,7 @@ static inline long do_strncpy_from_user(char *dst, const char __user *src, long
while (max) {
char c;

if (unlikely(unsafe_get_user(c,src+res)))
return -EFAULT;
unsafe_get_user(c,src+res, efault);
dst[res] = c;
if (!c)
return res;
Expand All @@ -76,6 +75,7 @@ static inline long do_strncpy_from_user(char *dst, const char __user *src, long
* Nope: we hit the address space limit, and we still had more
* characters the caller would have wanted. That's an EFAULT.
*/
efault:
return -EFAULT;
}

Expand Down
7 changes: 3 additions & 4 deletions lib/strnlen_user.c
Expand Up @@ -45,8 +45,7 @@ static inline long do_strnlen_user(const char __user *src, unsigned long count,
src -= align;
max += align;

if (unlikely(unsafe_get_user(c,(unsigned long __user *)src)))
return 0;
unsafe_get_user(c, (unsigned long __user *)src, efault);
c |= aligned_byte_mask(align);

for (;;) {
Expand All @@ -61,8 +60,7 @@ static inline long do_strnlen_user(const char __user *src, unsigned long count,
if (unlikely(max <= sizeof(unsigned long)))
break;
max -= sizeof(unsigned long);
if (unlikely(unsafe_get_user(c,(unsigned long __user *)(src+res))))
return 0;
unsafe_get_user(c, (unsigned long __user *)(src+res), efault);
}
res -= align;

Expand All @@ -77,6 +75,7 @@ static inline long do_strnlen_user(const char __user *src, unsigned long count,
* Nope: we hit the address space limit, and we still had more
* characters the caller would have wanted. That's 0.
*/
efault:
return 0;
}

Expand Down

0 comments on commit 1bd4403

Please sign in to comment.