x86: Replace assembly access_ok() with a C variant
authorLinus Torvalds <torvalds@linux-foundation.org>
Fri, 27 Dec 2013 23:30:58 +0000 (15:30 -0800)
committerH. Peter Anvin <hpa@zytor.com>
Sat, 28 Dec 2013 00:58:17 +0000 (16:58 -0800)
It turns out that the assembly variant doesn't actually produce that
good code, presumably partly because it creates a long dependency
chain with no scheduling, and partly because we cannot get a flags
result out of gcc (which could be fixed with asm goto, but it turns
out not to be worth it.)

The C code allows gcc to schedule and generate multiple (easily
predictable) branches, and as a side benefit we can really optimize
the case where the size is constant.

Link: http://lkml.kernel.org/r/CA%2B55aFzPBdbfKovMT8Edr4SmE2_=%2BOKJFac9XW2awegogTkVTA@mail.gmail.com
Signed-off-by: H. Peter Anvin <hpa@zytor.com>
arch/x86/include/asm/uaccess.h

index 8ec57c0..84ecf1d 100644 (file)
 /*
  * Test whether a block of memory is a valid user space address.
  * Returns 0 if the range is valid, nonzero otherwise.
- *
- * This is equivalent to the following test:
- * (u33)addr + (u33)size > (u33)current->addr_limit.seg (u65 for x86_64)
- *
- * This needs 33-bit (65-bit for x86_64) arithmetic. We have a carry...
  */
+static inline int __chk_range_not_ok(unsigned long addr, unsigned long size, unsigned long limit)
+{
+       /*
+        * If we have used "sizeof()" for the size,
+        * we know it won't overflow the limit (but
+        * it might overflow the 'addr', so it's
+        * important to subtract the size from the
+        * limit, not add it to the address).
+        */
+       if (__builtin_constant_p(size))
+               return addr > limit - size;
+
+       /* Arbitrary sizes? Be careful about overflow */
+       addr += size;
+       return (addr < size) || (addr > limit);
+}
 
 #define __range_not_ok(addr, size, limit)                              \
 ({                                                                     \
-       unsigned long flag, roksum;                                     \
        __chk_user_ptr(addr);                                           \
-       asm("add %3,%1 ; sbb %0,%0 ; cmp %1,%4 ; sbb $0,%0"             \
-           : "=&r" (flag), "=r" (roksum)                               \
-           : "1" (addr), "g" ((long)(size)),                           \
-             "rm" (limit));                                            \
-       flag;                                                           \
+       __chk_range_not_ok((unsigned long __force)(addr), size, limit); \
 })
 
 /**