aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2016-08-08 16:02:01 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2016-08-08 16:02:01 -0400
commit1bd4403d86a1c06cb6cc9ac87664a0c9d3413d51 (patch)
treef1a8f1572b452abcf9237c28859dac4c9bd31fa7
parent574673c231a5fad1560249cc3a598907acb36cf9 (diff)
unsafe_[get|put]_user: change interface to use a error target label
When I initially added the unsafe_[get|put]_user() helpers in commit 5b24a7a2aa20 ("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>
-rw-r--r--arch/x86/include/asm/uaccess.h16
-rw-r--r--include/linux/uaccess.h4
-rw-r--r--lib/strncpy_from_user.c8
-rw-r--r--lib/strnlen_user.c7
4 files changed, 17 insertions, 18 deletions
diff --git a/arch/x86/include/asm/uaccess.h b/arch/x86/include/asm/uaccess.h
index c03bfb68c503..52f230094c51 100644
--- a/arch/x86/include/asm/uaccess.h
+++ b/arch/x86/include/asm/uaccess.h
@@ -812,21 +812,21 @@ copy_to_user(void __user *to, const void *from, unsigned long n)
812#define user_access_begin() __uaccess_begin() 812#define user_access_begin() __uaccess_begin()
813#define user_access_end() __uaccess_end() 813#define user_access_end() __uaccess_end()
814 814
815#define unsafe_put_user(x, ptr) \ 815#define unsafe_put_user(x, ptr, err_label) \
816({ \ 816do { \
817 int __pu_err; \ 817 int __pu_err; \
818 __put_user_size((x), (ptr), sizeof(*(ptr)), __pu_err, -EFAULT); \ 818 __put_user_size((x), (ptr), sizeof(*(ptr)), __pu_err, -EFAULT); \
819 __builtin_expect(__pu_err, 0); \ 819 if (unlikely(__pu_err)) goto err_label; \
820}) 820} while (0)
821 821
822#define unsafe_get_user(x, ptr) \ 822#define unsafe_get_user(x, ptr, err_label) \
823({ \ 823do { \
824 int __gu_err; \ 824 int __gu_err; \
825 unsigned long __gu_val; \ 825 unsigned long __gu_val; \
826 __get_user_size(__gu_val, (ptr), sizeof(*(ptr)), __gu_err, -EFAULT); \ 826 __get_user_size(__gu_val, (ptr), sizeof(*(ptr)), __gu_err, -EFAULT); \
827 (x) = (__force __typeof__(*(ptr)))__gu_val; \ 827 (x) = (__force __typeof__(*(ptr)))__gu_val; \
828 __builtin_expect(__gu_err, 0); \ 828 if (unlikely(__gu_err)) goto err_label; \
829}) 829} while (0)
830 830
831#endif /* _ASM_X86_UACCESS_H */ 831#endif /* _ASM_X86_UACCESS_H */
832 832
diff --git a/include/linux/uaccess.h b/include/linux/uaccess.h
index 349557825428..f30c187ed785 100644
--- a/include/linux/uaccess.h
+++ b/include/linux/uaccess.h
@@ -114,8 +114,8 @@ extern long strncpy_from_unsafe(char *dst, const void *unsafe_addr, long count);
114#ifndef user_access_begin 114#ifndef user_access_begin
115#define user_access_begin() do { } while (0) 115#define user_access_begin() do { } while (0)
116#define user_access_end() do { } while (0) 116#define user_access_end() do { } while (0)
117#define unsafe_get_user(x, ptr) __get_user(x, ptr) 117#define unsafe_get_user(x, ptr, err) do { if (unlikely(__get_user(x, ptr))) goto err; } while (0)
118#define unsafe_put_user(x, ptr) __put_user(x, ptr) 118#define unsafe_put_user(x, ptr, err) do { if (unlikely(__put_user(x, ptr))) goto err; } while (0)
119#endif 119#endif
120 120
121#endif /* __LINUX_UACCESS_H__ */ 121#endif /* __LINUX_UACCESS_H__ */
diff --git a/lib/strncpy_from_user.c b/lib/strncpy_from_user.c
index 33f655ef48cd..9c5fe8110413 100644
--- a/lib/strncpy_from_user.c
+++ b/lib/strncpy_from_user.c
@@ -40,8 +40,8 @@ static inline long do_strncpy_from_user(char *dst, const char __user *src, long
40 unsigned long c, data; 40 unsigned long c, data;
41 41
42 /* Fall back to byte-at-a-time if we get a page fault */ 42 /* Fall back to byte-at-a-time if we get a page fault */
43 if (unlikely(unsafe_get_user(c,(unsigned long __user *)(src+res)))) 43 unsafe_get_user(c, (unsigned long __user *)(src+res), byte_at_a_time);
44 break; 44
45 *(unsigned long *)(dst+res) = c; 45 *(unsigned long *)(dst+res) = c;
46 if (has_zero(c, &data, &constants)) { 46 if (has_zero(c, &data, &constants)) {
47 data = prep_zero_mask(c, data, &constants); 47 data = prep_zero_mask(c, data, &constants);
@@ -56,8 +56,7 @@ byte_at_a_time:
56 while (max) { 56 while (max) {
57 char c; 57 char c;
58 58
59 if (unlikely(unsafe_get_user(c,src+res))) 59 unsafe_get_user(c,src+res, efault);
60 return -EFAULT;
61 dst[res] = c; 60 dst[res] = c;
62 if (!c) 61 if (!c)
63 return res; 62 return res;
@@ -76,6 +75,7 @@ byte_at_a_time:
76 * Nope: we hit the address space limit, and we still had more 75 * Nope: we hit the address space limit, and we still had more
77 * characters the caller would have wanted. That's an EFAULT. 76 * characters the caller would have wanted. That's an EFAULT.
78 */ 77 */
78efault:
79 return -EFAULT; 79 return -EFAULT;
80} 80}
81 81
diff --git a/lib/strnlen_user.c b/lib/strnlen_user.c
index 2625943625d7..8e105ed4df12 100644
--- a/lib/strnlen_user.c
+++ b/lib/strnlen_user.c
@@ -45,8 +45,7 @@ static inline long do_strnlen_user(const char __user *src, unsigned long count,
45 src -= align; 45 src -= align;
46 max += align; 46 max += align;
47 47
48 if (unlikely(unsafe_get_user(c,(unsigned long __user *)src))) 48 unsafe_get_user(c, (unsigned long __user *)src, efault);
49 return 0;
50 c |= aligned_byte_mask(align); 49 c |= aligned_byte_mask(align);
51 50
52 for (;;) { 51 for (;;) {
@@ -61,8 +60,7 @@ static inline long do_strnlen_user(const char __user *src, unsigned long count,
61 if (unlikely(max <= sizeof(unsigned long))) 60 if (unlikely(max <= sizeof(unsigned long)))
62 break; 61 break;
63 max -= sizeof(unsigned long); 62 max -= sizeof(unsigned long);
64 if (unlikely(unsafe_get_user(c,(unsigned long __user *)(src+res)))) 63 unsafe_get_user(c, (unsigned long __user *)(src+res), efault);
65 return 0;
66 } 64 }
67 res -= align; 65 res -= align;
68 66
@@ -77,6 +75,7 @@ static inline long do_strnlen_user(const char __user *src, unsigned long count,
77 * Nope: we hit the address space limit, and we still had more 75 * Nope: we hit the address space limit, and we still had more
78 * characters the caller would have wanted. That's 0. 76 * characters the caller would have wanted. That's 0.
79 */ 77 */
78efault:
80 return 0; 79 return 0;
81} 80}
82 81