[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20250712192202.707192-4-gatlin.newhouse@gmail.com>
Date: Sat, 12 Jul 2025 19:21:48 +0000
From: Gatlin Newhouse <gatlin.newhouse@...il.com>
To: linux-hardening@...r.kernel.org
Cc: Gatlin Newhouse <gatlin.newhouse@...il.com>
Subject: [RFC v1 03/17] x86: asm: support caching in do_get_user_call()
Adds various caching functions with different sizes alongside a macro to
select the smallest possible caching function to enable caching of user
calls to protect against time-of-check to time-of-use bugs.
---
arch/x86/include/asm/uaccess.h | 211 ++++++++++++++++++++++++++++--
arch/x86/include/asm/uaccess_64.h | 54 ++++++++
2 files changed, 254 insertions(+), 11 deletions(-)
diff --git a/arch/x86/include/asm/uaccess.h b/arch/x86/include/asm/uaccess.h
index 3a7755c1a441..9096aaec5482 100644
--- a/arch/x86/include/asm/uaccess.h
+++ b/arch/x86/include/asm/uaccess.h
@@ -73,27 +73,215 @@ extern int __get_user_bad(void);
* Clang/LLVM cares about the size of the register, but still wants
* the base register for something that ends up being a pair.
*/
+
+#ifdef CONFIG_SAFEFETCH
+#include <linux/safefetch.h>
+#include <linux/safefetch_static_keys.h>
+
+extern int df_get_user1(unsigned long long user_src, unsigned char user_val,
+ unsigned long long kern_dst);
+extern int df_get_user2(unsigned long long user_src, unsigned short user_val,
+ unsigned long long kern_dst);
+extern int df_get_user4(unsigned long long user_src, unsigned int user_val,
+ unsigned long long kern_dst);
+extern int df_get_user8(unsigned long long user_src, unsigned long user_val,
+ unsigned long long kern_dst);
+extern int df_get_useru8(unsigned long long user_src, unsigned long user_val,
+ unsigned long long kern_dst);
+
+// This macro returns the smallest possible get_user function based on value x
+#define __dfgetuserfunc(x) \
+ __dfgetuserfuncfits(x, char, df_get_user1, \
+ __dfgetuserfuncfits(x, short, df_get_user2, \
+ __dfgetuserfuncfits(x, int, df_get_user4, \
+ __dfgetuserfuncfits(x, long, df_get_user8, \
+ df_get_useru8))))
+
+// This macro will deduce the best double fetch get_user protection function,
+// based on the register content
+#define __dfgetuserfuncfits(x, type, func, not) \
+ __builtin_choose_expr(sizeof(x) <= sizeof(type), func, not)
+
+
+//#define GET_USER_CALL_CHECK(x) (likely(!x) && !IS_WHITELISTED(current))
+#define GET_USER_CALL_CHECK(x) likely(!x)
+
+
+// fn = get_user function name template
+// x = destination
+// ptr = source
+#define do_get_user_call(fn, x, ptr) \
+({ \
+ /* __ret_gu = the return value from the copy from user function */ \
+ int __ret_gu; \
+ /* register = compiler hint to store it into a register instead of RAM
+ * __inttype = func that gets the smallest variable type that fits the source
+ * __val_gu = intermediate storage of user obtained variable
+ * Obtain a register with a size equal to *ptr and store the user data pointer inside it
+ */ \
+ register __inttype(*(ptr)) __val_gu asm("%"_ASM_DX); \
+ /* Sparse integrity check, checks is a ptr is in fact a pointer to user space */ \
+ __chk_user_ptr(ptr); \
+ /* asm := assembly instruction
+ * volatile := no optimizations
+ *
+ * Assembler template:
+ * "call" := issue a call assembly instruction
+ * "__" #fn "_%P4" := stringbuilder that creates the right __get_user_X function name
+ * based on size of ptr
+ * %P4 := Take fourth variable value as literal string
+ *
+ * Output operands:
+ * "=a" (__ret_gu) := overwrite (=) the address register (a) __ret_gu
+ * "=r" (__val_gu) := overwrite (=) the general register (r) __val_gu
+ * ASM_CALL_CONSTRAINT := Constraint that forces the right execution order of inline asm
+ *
+ * Input operands:
+ * "0" (ptr) := first argument, the user space source address
+ * "i" (sizeof(*(ptr))) := second argument, the size of user space data that must be copied
+ *
+ * This function calls one of the __get_user_X functions based on the size of the ptr data
+ * This copies the data from user space into the temporary variable __val_gu
+ * The result of this operation is stored in the variable __ret_gu
+ */ \
+ asm volatile("call __" #fn "_%P4" \
+ : "=a" (__ret_gu), "=r" (__val_gu), \
+ ASM_CALL_CONSTRAINT \
+ : "0" (ptr), "i" (sizeof(*(ptr)))); \
+ instrument_get_user(__val_gu); \
+ /* Casts the variable inside __val_gu to the correct type and stores it inside
+ * the kernel destination 'x'
+ */ \
+ (x) = (__force __typeof__(*(ptr))) __val_gu; \
+ IF_SAFEFETCH_STATIC_BRANCH_UNLIKELY_WRAPPER(safefetch_copy_from_user_key) { \
+ if (GET_USER_CALL_CHECK(__ret_gu)) { \
+ __ret_gu = __dfgetuserfunc(*(ptr))((unsigned long long)(ptr), __val_gu, (unsigned long long)(&x)) ; \
+ } \
+ } \
+ /* Integrity check that expects a 0 as value for __ret_gu (call successful) */ \
+ __builtin_expect(__ret_gu, 0); \
+})
+
+// fn = get_user function name template
+// x = destination
+// ptr = source
+#define do_get_user_call_no_dfcache(fn, x, ptr) \
+({ \
+ /* __ret_gu = the return value from the copy from user function */ \
+ int __ret_gu; \
+ /* register = compiler hint to store it into a register instead of RAM \
+ * __inttype = func that gets the smallest variable type that fits the source \
+ * __val_gu = intermediate storage of user obtained variable \
+ * Obtain a register with a size equal to *ptr and store the user data pointer inside it \
+ */ \
+ register __inttype(*(ptr)) __val_gu asm("%"_ASM_DX); \
+ /* Sparse integrity check, checks is a ptr is in fact a pointer to user space */ \
+ __chk_user_ptr(ptr); \
+ /* asm := assembly instruction
+ * volatile := no optimizations
+ *
+ * Assembler template:
+ * "call" := issue a call assembly instruction
+ * "__" #fn "_%P4" := stringbuilder that creates the right __get_user_X function name
+ * based on size of ptr
+ * %P4 := Take fourth variable value as literal string
+ *
+ * Output operands:
+ * "=a" (__ret_gu) := overwrite (=) the address register (a) __ret_gu
+ * "=r" (__val_gu) := overwrite (=) the general register (r) __val_gu
+ * ASM_CALL_CONSTRAINT := Constraint that forces the right execution order of inline asm
+ *
+ * Input operands:
+ * "0" (ptr) := first argument, the user space source address
+ * "i" (sizeof(*(ptr))) := second argument, the size of user space data that must be copied
+ *
+ * This function calls one of the __get_user_X functions based on the size of the ptr data
+ * This copies the data from user space into the temporary variable __val_gu
+ * The result of this operation is stored in the variable __ret_gu
+ */ \
+ asm volatile("call __" #fn "_%P4" \
+ : "=a" (__ret_gu), "=r" (__val_gu), \
+ ASM_CALL_CONSTRAINT \
+ : "0" (ptr), "i" (sizeof(*(ptr)))); \
+ /* Casts the variable inside __val_gu to the correct type and stores it inside
+ * the kernel destination 'x'
+ */ \
+ instrument_get_user(__val_gu); \
+ (x) = (__force __typeof__(*(ptr))) __val_gu; \
+ /* Integrity check that expects a 0 as value for __ret_gu (call successful) */ \
+ __builtin_expect(__ret_gu, 0); \
+})
+
+#define get_user_no_dfcache(x, ptr) ({ might_fault(); do_get_user_call_no_dfcache(get_user, x, ptr); })
+
+#define __get_user_no_dfcache(x, ptr) do_get_user_call_no_dfcache(get_user_nocheck, x, ptr)
+
+#define unsafe_op_wrap(op, err) do { if (unlikely(op)) goto err; } while (0)
+#define unsafe_get_user_no_dfcache(x, p, e) unsafe_op_wrap(__get_user_no_dfcache(x, p), e)
+
+#else
+
+
+// fn = get_user function name template
+// x = destination
+// ptr = source
#define do_get_user_call(fn,x,ptr) \
({ \
+ /* __ret_gu = the return value from the copy from user function */ \
int __ret_gu; \
+ /* register = compiler hint to store it into a register instead of RAM \
+ * __inttype = func that gets the smallest variable type that fits the source \
+ * __val_gu = intermediate storage of user obtained variable \
+ * Obtain a register with a size equal to *ptr and store the user data pointer inside it \
+ */ \
register __inttype(*(ptr)) __val_gu asm("%"_ASM_DX); \
+ /* Sparse integrity check, checks is a ptr is in fact a pointer to user space */ \
__chk_user_ptr(ptr); \
+ /* asm := assembly instruction
+ * volatile := no optimizations
+ *
+ * Assembler template:
+ * "call" := issue a call assembly instruction
+ * "__" #fn "_%c[size]" := stringbuilder that creates the right __get_user_X function name
+ * based on size of ptr
+ * %c[size] := Take fourth variable value as literal string with
+ * named argument size per 8c860ed
+ *
+ * Output operands:
+ * "=a" (__ret_gu) := overwrite (=) the address register (a) __ret_gu
+ * "=r" (__val_gu) := overwrite (=) the general register (r) __val_gu
+ * ASM_CALL_CONSTRAINT := Constraint that forces the right execution order of inline asm
+ *
+ * Input operands:
+ * "0" (ptr) := first argument, the user space source address
+ * "i" (sizeof(*(ptr))) := second argument, the size of user space data that must be copied
+ *
+ * This function calls one of the __get_user_X functions based on the size of the ptr data
+ * This copies the data from user space into the temporary variable __val_gu
+ * The result of this operation is stored in the variable __ret_gu
+ */ \
asm volatile("call __" #fn "_%c[size]" \
: "=a" (__ret_gu), "=r" (__val_gu), \
ASM_CALL_CONSTRAINT \
: "0" (ptr), [size] "i" (sizeof(*(ptr)))); \
instrument_get_user(__val_gu); \
+ /* Casts the variable inside __val_gu to the correct type and stores it inside
+ * the kernel destination 'x'
+ */ \
(x) = (__force __typeof__(*(ptr))) __val_gu; \
+ /* Integrity check that expects a 0 as value for __ret_gu (call successful) */ \
__builtin_expect(__ret_gu, 0); \
})
-/**
+#endif
+
+/*
* get_user - Get a simple variable from user space.
- * @x: Variable to store result.
+ * @x: Variable to store result.
* @ptr: Source address, in user space.
*
* Context: User context only. This function may sleep if pagefaults are
- * enabled.
+ * enabled.
*
* This macro copies a single simple variable from user space to kernel
* space. It supports simple types like char and int, but not larger
@@ -107,13 +295,15 @@ extern int __get_user_bad(void);
*/
#define get_user(x,ptr) ({ might_fault(); do_get_user_call(get_user,x,ptr); })
-/**
+
+
+/*
* __get_user - Get a simple variable from user space, with less checking.
- * @x: Variable to store result.
+ * @x: Variable to store result.
* @ptr: Source address, in user space.
*
* Context: User context only. This function may sleep if pagefaults are
- * enabled.
+ * enabled.
*
* This macro copies a single simple variable from user space to kernel
* space. It supports simple types like char and int, but not larger
@@ -130,7 +320,6 @@ extern int __get_user_bad(void);
*/
#define __get_user(x,ptr) do_get_user_call(get_user_nocheck,x,ptr)
-
#ifdef CONFIG_X86_32
#define __put_user_goto_u64(x, addr, label) \
asm goto("\n" \
@@ -190,11 +379,11 @@ extern void __put_user_nocheck_8(void);
/**
* put_user - Write a simple value into user space.
- * @x: Value to copy to user space.
+ * @x: Value to copy to user space.
* @ptr: Destination address, in user space.
*
* Context: User context only. This function may sleep if pagefaults are
- * enabled.
+ * enabled.
*
* This macro copies a single simple value from kernel space to user
* space. It supports simple types like char and int, but not larger
@@ -209,11 +398,11 @@ extern void __put_user_nocheck_8(void);
/**
* __put_user - Write a simple value into user space, with less checking.
- * @x: Value to copy to user space.
+ * @x: Value to copy to user space.
* @ptr: Destination address, in user space.
*
* Context: User context only. This function may sleep if pagefaults are
- * enabled.
+ * enabled.
*
* This macro copies a single simple value from kernel space to user
* space. It supports simple types like char and int, but not larger
diff --git a/arch/x86/include/asm/uaccess_64.h b/arch/x86/include/asm/uaccess_64.h
index c8a5ae35c871..b588c5248c6d 100644
--- a/arch/x86/include/asm/uaccess_64.h
+++ b/arch/x86/include/asm/uaccess_64.h
@@ -135,11 +135,65 @@ copy_user_generic(void *to, const void *from, unsigned long len)
return len;
}
+#ifdef CONFIG_SAFEFETCH
+#include <linux/safefetch.h>
+
+#ifdef SAFEFETCH_STATIC_KEYS
+#include <linux/safefetch_static_keys.h>
+static __always_inline __must_check unsigned long
+raw_copy_from_user(void *dst, const void __user *src, unsigned long size)
+{
+ if (static_branch_unlikely(&safefetch_copy_from_user_key)) {
+ // Insert user data into protection mechanism and then into the kernel destination
+ return df_copy_from_user((unsigned long long)src, (unsigned long long)dst, size);
+ } else {
+ return copy_user_generic(dst, (__force void *)src, size);
+ }
+}
+#else
static __always_inline __must_check unsigned long
raw_copy_from_user(void *dst, const void __user *src, unsigned long size)
+{
+ // Insert user data into protection mechanism and then into the kernel destination
+ return df_copy_from_user((unsigned long long)src, (unsigned long long)dst, size);
+}
+#endif
+
+#ifdef SAFEFETCH_PIN_BUDDY_PAGES
+#ifdef SAFEFETCH_STATIC_KEYS
+#include <linux/safefetch_static_keys.h>
+static __always_inline __must_check unsigned long
+raw_copy_from_user_pinning(void *dst, const void __user *src, unsigned long size)
+{
+ if (static_branch_unlikely(&safefetch_copy_from_user_key)) {
+ // Insert user data into protection mechanism and then into the kernel destination
+ return df_copy_from_user_pinning((unsigned long long)src, (unsigned long long)dst, size);
+ } else {
+ return copy_user_generic(dst, (__force void *)src, size);
+ }
+}
+#else
+static __always_inline __must_check unsigned long
+raw_copy_from_user_pinning(void *dst, const void __user *src, unsigned long size)
+{
+ // Insert user data into protection mechanism and then into the kernel destination
+ return df_copy_from_user_pinning((unsigned long long)src, (unsigned long long)dst, size);
+}
+#endif
+#endif
+
+static __always_inline __must_check unsigned long
+raw_copy_from_user_no_dfcache(void *dst, const void __user *src, unsigned long size)
{
return copy_user_generic(dst, (__force void *)src, size);
}
+#else
+static __always_inline __must_check unsigned long
+raw_copy_from_user(void *dst, const void __user *src, unsigned long size)
+{
+ return copy_user_generic(dst, (__force void *)src, size);
+}
+#endif
static __always_inline __must_check unsigned long
raw_copy_to_user(void __user *dst, const void *src, unsigned long size)
--
2.25.1
Powered by blists - more mailing lists