ARM: uaccess: provide uaccess_save_and_enable() and uaccess_restore()
authorRussell King <rmk+kernel@arm.linux.org.uk>
Wed, 19 Aug 2015 10:02:28 +0000 (11:02 +0100)
committerRussell King <rmk+kernel@arm.linux.org.uk>
Tue, 25 Aug 2015 15:14:43 +0000 (16:14 +0100)
Provide uaccess_save_and_enable() and uaccess_restore() to permit
control of userspace visibility to the kernel, and hook these into
the appropriate places in the kernel where we need to access
userspace.

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
arch/arm/include/asm/futex.h
arch/arm/include/asm/uaccess.h
arch/arm/kernel/armksyms.c
arch/arm/lib/clear_user.S
arch/arm/lib/copy_from_user.S
arch/arm/lib/copy_to_user.S
arch/arm/lib/uaccess_with_memcpy.c

index 5eed82809d82b7aa9c74670fd9c9624bbd930803..6795368ad0238068c55fa4c8b56e0d67d9b9a5cf 100644 (file)
 #ifdef CONFIG_SMP
 
 #define __futex_atomic_op(insn, ret, oldval, tmp, uaddr, oparg)        \
+({                                                             \
+       unsigned int __ua_flags;                                \
        smp_mb();                                               \
        prefetchw(uaddr);                                       \
+       __ua_flags = uaccess_save_and_enable();                 \
        __asm__ __volatile__(                                   \
        "1:     ldrex   %1, [%3]\n"                             \
        "       " insn "\n"                                     \
        __futex_atomic_ex_table("%5")                           \
        : "=&r" (ret), "=&r" (oldval), "=&r" (tmp)              \
        : "r" (uaddr), "r" (oparg), "Ir" (-EFAULT)              \
-       : "cc", "memory")
+       : "cc", "memory");                                      \
+       uaccess_restore(__ua_flags);                            \
+})
 
 static inline int
 futex_atomic_cmpxchg_inatomic(u32 *uval, u32 __user *uaddr,
                              u32 oldval, u32 newval)
 {
+       unsigned int __ua_flags;
        int ret;
        u32 val;
 
@@ -49,6 +55,7 @@ futex_atomic_cmpxchg_inatomic(u32 *uval, u32 __user *uaddr,
        smp_mb();
        /* Prefetching cannot fault */
        prefetchw(uaddr);
+       __ua_flags = uaccess_save_and_enable();
        __asm__ __volatile__("@futex_atomic_cmpxchg_inatomic\n"
        "1:     ldrex   %1, [%4]\n"
        "       teq     %1, %2\n"
@@ -61,6 +68,7 @@ futex_atomic_cmpxchg_inatomic(u32 *uval, u32 __user *uaddr,
        : "=&r" (ret), "=&r" (val)
        : "r" (oldval), "r" (newval), "r" (uaddr), "Ir" (-EFAULT)
        : "cc", "memory");
+       uaccess_restore(__ua_flags);
        smp_mb();
 
        *uval = val;
@@ -73,6 +81,8 @@ futex_atomic_cmpxchg_inatomic(u32 *uval, u32 __user *uaddr,
 #include <asm/domain.h>
 
 #define __futex_atomic_op(insn, ret, oldval, tmp, uaddr, oparg)        \
+({                                                             \
+       unsigned int __ua_flags = uaccess_save_and_enable();    \
        __asm__ __volatile__(                                   \
        "1:     " TUSER(ldr) "  %1, [%3]\n"                     \
        "       " insn "\n"                                     \
@@ -81,12 +91,15 @@ futex_atomic_cmpxchg_inatomic(u32 *uval, u32 __user *uaddr,
        __futex_atomic_ex_table("%5")                           \
        : "=&r" (ret), "=&r" (oldval), "=&r" (tmp)              \
        : "r" (uaddr), "r" (oparg), "Ir" (-EFAULT)              \
-       : "cc", "memory")
+       : "cc", "memory");                                      \
+       uaccess_restore(__ua_flags);                            \
+})
 
 static inline int
 futex_atomic_cmpxchg_inatomic(u32 *uval, u32 __user *uaddr,
                              u32 oldval, u32 newval)
 {
+       unsigned int __ua_flags;
        int ret = 0;
        u32 val;
 
@@ -94,6 +107,7 @@ futex_atomic_cmpxchg_inatomic(u32 *uval, u32 __user *uaddr,
                return -EFAULT;
 
        preempt_disable();
+       __ua_flags = uaccess_save_and_enable();
        __asm__ __volatile__("@futex_atomic_cmpxchg_inatomic\n"
        "1:     " TUSER(ldr) "  %1, [%4]\n"
        "       teq     %1, %2\n"
@@ -103,6 +117,7 @@ futex_atomic_cmpxchg_inatomic(u32 *uval, u32 __user *uaddr,
        : "+r" (ret), "=&r" (val)
        : "r" (oldval), "r" (newval), "r" (uaddr), "Ir" (-EFAULT)
        : "cc", "memory");
+       uaccess_restore(__ua_flags);
 
        *uval = val;
        preempt_enable();
index 74b17d09ef7aa54cb98d22ccf5f068b4c39971be..82880132f941a17c6586b065ce5dfc1a2bf03309 100644 (file)
@@ -49,6 +49,21 @@ struct exception_table_entry
 
 extern int fixup_exception(struct pt_regs *regs);
 
+/*
+ * These two functions allow hooking accesses to userspace to increase
+ * system integrity by ensuring that the kernel can not inadvertantly
+ * perform such accesses (eg, via list poison values) which could then
+ * be exploited for priviledge escalation.
+ */
+static inline unsigned int uaccess_save_and_enable(void)
+{
+       return 0;
+}
+
+static inline void uaccess_restore(unsigned int flags)
+{
+}
+
 /*
  * These two are intentionally not defined anywhere - if the kernel
  * code generates any references to them, that's a bug.
@@ -165,6 +180,7 @@ extern int __get_user_64t_4(void *);
                register typeof(x) __r2 asm("r2");                      \
                register unsigned long __l asm("r1") = __limit;         \
                register int __e asm("r0");                             \
+               unsigned int __ua_flags = uaccess_save_and_enable();    \
                switch (sizeof(*(__p))) {                               \
                case 1:                                                 \
                        if (sizeof((x)) >= 8)                           \
@@ -192,6 +208,7 @@ extern int __get_user_64t_4(void *);
                        break;                                          \
                default: __e = __get_user_bad(); break;                 \
                }                                                       \
+               uaccess_restore(__ua_flags);                            \
                x = (typeof(*(p))) __r2;                                \
                __e;                                                    \
        })
@@ -224,6 +241,7 @@ extern int __put_user_8(void *, unsigned long long);
                register const typeof(*(p)) __user *__p asm("r0") = __tmp_p; \
                register unsigned long __l asm("r1") = __limit;         \
                register int __e asm("r0");                             \
+               unsigned int __ua_flags = uaccess_save_and_enable();    \
                switch (sizeof(*(__p))) {                               \
                case 1:                                                 \
                        __put_user_x(__r2, __p, __e, __l, 1);           \
@@ -239,6 +257,7 @@ extern int __put_user_8(void *, unsigned long long);
                        break;                                          \
                default: __e = __put_user_bad(); break;                 \
                }                                                       \
+               uaccess_restore(__ua_flags);                            \
                __e;                                                    \
        })
 
@@ -300,14 +319,17 @@ static inline void set_fs(mm_segment_t fs)
 do {                                                                   \
        unsigned long __gu_addr = (unsigned long)(ptr);                 \
        unsigned long __gu_val;                                         \
+       unsigned int __ua_flags;                                        \
        __chk_user_ptr(ptr);                                            \
        might_fault();                                                  \
+       __ua_flags = uaccess_save_and_enable();                         \
        switch (sizeof(*(ptr))) {                                       \
        case 1: __get_user_asm_byte(__gu_val, __gu_addr, err);  break;  \
        case 2: __get_user_asm_half(__gu_val, __gu_addr, err);  break;  \
        case 4: __get_user_asm_word(__gu_val, __gu_addr, err);  break;  \
        default: (__gu_val) = __get_user_bad();                         \
        }                                                               \
+       uaccess_restore(__ua_flags);                                    \
        (x) = (__typeof__(*(ptr)))__gu_val;                             \
 } while (0)
 
@@ -381,9 +403,11 @@ do {                                                                       \
 #define __put_user_err(x, ptr, err)                                    \
 do {                                                                   \
        unsigned long __pu_addr = (unsigned long)(ptr);                 \
+       unsigned int __ua_flags;                                        \
        __typeof__(*(ptr)) __pu_val = (x);                              \
        __chk_user_ptr(ptr);                                            \
        might_fault();                                                  \
+       __ua_flags = uaccess_save_and_enable();                         \
        switch (sizeof(*(ptr))) {                                       \
        case 1: __put_user_asm_byte(__pu_val, __pu_addr, err);  break;  \
        case 2: __put_user_asm_half(__pu_val, __pu_addr, err);  break;  \
@@ -391,6 +415,7 @@ do {                                                                        \
        case 8: __put_user_asm_dword(__pu_val, __pu_addr, err); break;  \
        default: __put_user_bad();                                      \
        }                                                               \
+       uaccess_restore(__ua_flags);                                    \
 } while (0)
 
 #define __put_user_asm_byte(x, __pu_addr, err)                 \
@@ -474,11 +499,46 @@ do {                                                                      \
 
 
 #ifdef CONFIG_MMU
-extern unsigned long __must_check __copy_from_user(void *to, const void __user *from, unsigned long n);
-extern unsigned long __must_check __copy_to_user(void __user *to, const void *from, unsigned long n);
-extern unsigned long __must_check __copy_to_user_std(void __user *to, const void *from, unsigned long n);
-extern unsigned long __must_check __clear_user(void __user *addr, unsigned long n);
-extern unsigned long __must_check __clear_user_std(void __user *addr, unsigned long n);
+extern unsigned long __must_check
+arm_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)
+{
+       unsigned int __ua_flags = uaccess_save_and_enable();
+       n = arm_copy_from_user(to, from, n);
+       uaccess_restore(__ua_flags);
+       return n;
+}
+
+extern unsigned long __must_check
+arm_copy_to_user(void __user *to, const void *from, unsigned long n);
+extern unsigned long __must_check
+__copy_to_user_std(void __user *to, const void *from, unsigned long n);
+
+static inline unsigned long __must_check
+__copy_to_user(void __user *to, const void *from, unsigned long n)
+{
+       unsigned int __ua_flags = uaccess_save_and_enable();
+       n = arm_copy_to_user(to, from, n);
+       uaccess_restore(__ua_flags);
+       return n;
+}
+
+extern unsigned long __must_check
+arm_clear_user(void __user *addr, unsigned long n);
+extern unsigned long __must_check
+__clear_user_std(void __user *addr, unsigned long n);
+
+static inline unsigned long __must_check
+__clear_user(void __user *addr, unsigned long n)
+{
+       unsigned int __ua_flags = uaccess_save_and_enable();
+       n = arm_clear_user(addr, n);
+       uaccess_restore(__ua_flags);
+       return n;
+}
+
 #else
 #define __copy_from_user(to, from, n)  (memcpy(to, (void __force *)from, n), 0)
 #define __copy_to_user(to, from, n)    (memcpy((void __force *)to, from, n), 0)
@@ -511,6 +571,7 @@ static inline unsigned long __must_check clear_user(void __user *to, unsigned lo
        return n;
 }
 
+/* These are from lib/ code, and use __get_user() and friends */
 extern long strncpy_from_user(char *dest, const char __user *src, long count);
 
 extern __must_check long strlen_user(const char __user *str);
index a88671cfe1ffb1e1ee43b06a7c8c6fd704f38580..a35d72d30b56a0eae4d76fbeb3d56a588c3c5844 100644 (file)
@@ -91,9 +91,9 @@ EXPORT_SYMBOL(__memzero);
 #ifdef CONFIG_MMU
 EXPORT_SYMBOL(copy_page);
 
-EXPORT_SYMBOL(__copy_from_user);
-EXPORT_SYMBOL(__copy_to_user);
-EXPORT_SYMBOL(__clear_user);
+EXPORT_SYMBOL(arm_copy_from_user);
+EXPORT_SYMBOL(arm_copy_to_user);
+EXPORT_SYMBOL(arm_clear_user);
 
 EXPORT_SYMBOL(__get_user_1);
 EXPORT_SYMBOL(__get_user_2);
index 1710fd7db2d57d35ed342417336980f987db5985..970d6c0437743cda6a78620e1439eccb91398da2 100644 (file)
 
                .text
 
-/* Prototype: int __clear_user(void *addr, size_t sz)
+/* Prototype: unsigned long arm_clear_user(void *addr, size_t sz)
  * Purpose  : clear some user memory
  * Params   : addr - user memory address to clear
  *          : sz   - number of bytes to clear
  * Returns  : number of bytes NOT cleared
  */
 ENTRY(__clear_user_std)
-WEAK(__clear_user)
+WEAK(arm_clear_user)
                stmfd   sp!, {r1, lr}
                mov     r2, #0
                cmp     r1, #4
@@ -44,7 +44,7 @@ WEAK(__clear_user)
 USER(          strnebt r2, [r0])
                mov     r0, #0
                ldmfd   sp!, {r1, pc}
-ENDPROC(__clear_user)
+ENDPROC(arm_clear_user)
 ENDPROC(__clear_user_std)
 
                .pushsection .text.fixup,"ax"
index 7a235b9952be04e3ed8acd8892d5ca4d63ee27ff..1512bebfbf1b18ad317648891385a24e93d1f35f 100644 (file)
@@ -17,7 +17,7 @@
 /*
  * Prototype:
  *
- *     size_t __copy_from_user(void *to, const void *from, size_t n)
+ *     size_t arm_copy_from_user(void *to, const void *from, size_t n)
  *
  * Purpose:
  *
 
        .text
 
-ENTRY(__copy_from_user)
+ENTRY(arm_copy_from_user)
 
 #include "copy_template.S"
 
-ENDPROC(__copy_from_user)
+ENDPROC(arm_copy_from_user)
 
        .pushsection .fixup,"ax"
        .align 0
index 9648b0675a3efc81dd412fa5b3be820c1a3d6242..caf5019d8161e2f1914a797a4c6800844a27d570 100644 (file)
@@ -17,7 +17,7 @@
 /*
  * Prototype:
  *
- *     size_t __copy_to_user(void *to, const void *from, size_t n)
+ *     size_t arm_copy_to_user(void *to, const void *from, size_t n)
  *
  * Purpose:
  *
        .text
 
 ENTRY(__copy_to_user_std)
-WEAK(__copy_to_user)
+WEAK(arm_copy_to_user)
 
 #include "copy_template.S"
 
-ENDPROC(__copy_to_user)
+ENDPROC(arm_copy_to_user)
 ENDPROC(__copy_to_user_std)
 
        .pushsection .text.fixup,"ax"
index 3e58d710013c3ad9b377fc76e6dad58f377e88a7..77f020e75ccd2bdd1a616c34f70a0e46c845e76f 100644 (file)
@@ -136,7 +136,7 @@ out:
 }
 
 unsigned long
-__copy_to_user(void __user *to, const void *from, unsigned long n)
+arm_copy_to_user(void __user *to, const void *from, unsigned long n)
 {
        /*
         * This test is stubbed out of the main function above to keep
@@ -190,7 +190,7 @@ out:
        return n;
 }
 
-unsigned long __clear_user(void __user *addr, unsigned long n)
+unsigned long arm_clear_user(void __user *addr, unsigned long n)
 {
        /* See rational for this in __copy_to_user() above. */
        if (n < 64)