x86: Use __builtin_object_size() to validate the buffer size for copy_from_user()
authorArjan van de Ven <arjan@infradead.org>
Sat, 26 Sep 2009 12:33:01 +0000 (14:33 +0200)
committerIngo Molnar <mingo@elte.hu>
Sat, 26 Sep 2009 14:25:41 +0000 (16:25 +0200)
gcc (4.x) supports the __builtin_object_size() builtin, which
reports the size of an object that a pointer point to, when known
at compile time. If the buffer size is not known at compile time, a
constant -1 is returned.

This patch uses this feature to add a sanity check to
copy_from_user(); if the target buffer is known to be smaller than
the copy size, the copy is aborted and a WARNing is emitted in
memory debug mode.

These extra checks compile away when the object size is not known,
or if both the buffer size and the copy length are constants.

Signed-off-by: Arjan van de Ven <arjan@linux.intel.com>
LKML-Reference: <20090926143301.2c396b94@infradead.org>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
arch/x86/include/asm/uaccess_32.h
arch/x86/include/asm/uaccess_64.h
arch/x86/kernel/x8664_ksyms_64.c
arch/x86/lib/copy_user_64.S
arch/x86/lib/usercopy_32.c
include/linux/compiler-gcc4.h
include/linux/compiler.h

index 632fb44b4cb547fe126830a76945d8b9fc6727bd..582d6aef7417e8de12af450afc278e182d5093ef 100644 (file)
@@ -187,9 +187,26 @@ __copy_from_user_inatomic_nocache(void *to, const void __user *from,
 
 unsigned long __must_check copy_to_user(void __user *to,
                                        const void *from, unsigned long n);
-unsigned long __must_check copy_from_user(void *to,
+unsigned long __must_check _copy_from_user(void *to,
                                          const void __user *from,
                                          unsigned long n);
+
+static inline unsigned long __must_check copy_from_user(void *to,
+                                         const void __user *from,
+                                         unsigned long n)
+{
+       int sz = __compiletime_object_size(to);
+       int ret = -EFAULT;
+
+       if (likely(sz == -1 || sz >= n))
+               ret = _copy_from_user(to, from, n);
+#ifdef CONFIG_DEBUG_VM
+       else
+               WARN(1, "Buffer overflow detected!\n");
+#endif
+       return ret;
+}
+
 long __must_check strncpy_from_user(char *dst, const char __user *src,
                                    long count);
 long __must_check __strncpy_from_user(char *dst,
index db24b215fc50668bcf3f9da0f6757016aab7956e..ce6fec7ce38d403bd986651c19999bdb266f9fd5 100644 (file)
@@ -21,10 +21,27 @@ copy_user_generic(void *to, const void *from, unsigned len);
 __must_check unsigned long
 copy_to_user(void __user *to, const void *from, unsigned len);
 __must_check unsigned long
-copy_from_user(void *to, const void __user *from, unsigned len);
+_copy_from_user(void *to, const void __user *from, unsigned len);
 __must_check unsigned long
 copy_in_user(void __user *to, const void __user *from, unsigned len);
 
+static inline unsigned long __must_check copy_from_user(void *to,
+                                         const void __user *from,
+                                         unsigned long n)
+{
+       int sz = __compiletime_object_size(to);
+       int ret = -EFAULT;
+
+       if (likely(sz == -1 || sz >= n))
+               ret = _copy_from_user(to, from, n);
+#ifdef CONFIG_DEBUG_VM
+       else
+               WARN(1, "Buffer overflow detected!\n");
+#endif
+       return ret;
+}
+
+
 static __always_inline __must_check
 int __copy_from_user(void *dst, const void __user *src, unsigned size)
 {
index 3909e3ba5ce3b40d152f004c0348108cbb036d11..a0cdd8cc1d675052d9496c28de20b741d136e6ad 100644 (file)
@@ -30,7 +30,7 @@ EXPORT_SYMBOL(__put_user_8);
 
 EXPORT_SYMBOL(copy_user_generic);
 EXPORT_SYMBOL(__copy_user_nocache);
-EXPORT_SYMBOL(copy_from_user);
+EXPORT_SYMBOL(_copy_from_user);
 EXPORT_SYMBOL(copy_to_user);
 EXPORT_SYMBOL(__copy_from_user_inatomic);
 
index 6ba0f7bb85eadaeebffde0d428dd8af7f932d2c9..4be3c415b3e984e9507113849a238faf63200cce 100644 (file)
@@ -78,7 +78,7 @@ ENTRY(copy_to_user)
 ENDPROC(copy_to_user)
 
 /* Standard copy_from_user with segment limit checking */
-ENTRY(copy_from_user)
+ENTRY(_copy_from_user)
        CFI_STARTPROC
        GET_THREAD_INFO(%rax)
        movq %rsi,%rcx
@@ -88,7 +88,7 @@ ENTRY(copy_from_user)
        jae bad_from_user
        ALTERNATIVE_JUMP X86_FEATURE_REP_GOOD,copy_user_generic_unrolled,copy_user_generic_string
        CFI_ENDPROC
-ENDPROC(copy_from_user)
+ENDPROC(_copy_from_user)
 
 ENTRY(copy_user_generic)
        CFI_STARTPROC
index 1f118d462acc242990eb7a796b6832d8d90bc442..8498684e45b0d0864b490a2673daffdc0db01241 100644 (file)
@@ -874,7 +874,7 @@ EXPORT_SYMBOL(copy_to_user);
  * data to the requested size using zero bytes.
  */
 unsigned long
-copy_from_user(void *to, const void __user *from, unsigned long n)
+_copy_from_user(void *to, const void __user *from, unsigned long n)
 {
        if (access_ok(VERIFY_READ, from, n))
                n = __copy_from_user(to, from, n);
@@ -882,4 +882,4 @@ copy_from_user(void *to, const void __user *from, unsigned long n)
                memset(to, 0, n);
        return n;
 }
-EXPORT_SYMBOL(copy_from_user);
+EXPORT_SYMBOL(_copy_from_user);
index 450fa597c94d22dce8cf28d6387d04145c046f97..a3aef5d55dba58e9d8567387b884db85e106de8a 100644 (file)
@@ -37,3 +37,5 @@
 #define __cold                 __attribute__((__cold__))
 
 #endif
+
+#define __compiletime_object_size(obj) __builtin_object_size(obj, 0)
index 04fb5135b4e16eea28e3e847d2ed36c01c78a88a..8e54108688f94eb7d98ce3c45356e96dc7b08049 100644 (file)
@@ -266,6 +266,10 @@ void ftrace_likely_update(struct ftrace_branch_data *f, int val, int expect);
 # define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))
 #endif
 
+/* Compile time object size, -1 for unknown */
+#ifndef __compiletime_object_size
+# define __compiletime_object_size(obj) -1
+#endif
 /*
  * Prevent the compiler from merging or refetching accesses.  The compiler
  * is also forbidden from reordering successive instances of ACCESS_ONCE(),