Commit 12700c17 authored by Arnd Bergmann's avatar Arnd Bergmann
Browse files

uaccess: generalize access_ok()



There are many different ways that access_ok() is defined across
architectures, but in the end, they all just compare against the
user_addr_max() value or they accept anything.

Provide one definition that works for most architectures, checking
against TASK_SIZE_MAX for user processes or skipping the check inside
of uaccess_kernel() sections.

For architectures without CONFIG_SET_FS(), this should be the fastest
check, as it comes down to a single comparison of a pointer against a
compile-time constant, while the architecture specific versions tend to
do something more complex for historic reasons or get something wrong.

Type checking for __user annotations is handled inconsistently across
architectures, but this is easily simplified as well by using an inline
function that takes a 'const void __user *' argument. A handful of
callers need an extra __user annotation for this.

Some architectures had trick to use 33-bit or 65-bit arithmetic on the
addresses to calculate the overflow, however this simpler version uses
fewer registers, which means it can produce better object code in the
end despite needing a second (statically predicted) branch.

Reviewed-by: default avatarChristoph Hellwig <hch@lst.de>
Acked-by: Mark Rutland <mark.rutland@arm.com> [arm64, asm-generic]
Acked-by: default avatarGeert Uytterhoeven <geert@linux-m68k.org>
Acked-by: default avatarStafford Horne <shorne@gmail.com>
Acked-by: default avatarDinh Nguyen <dinguyen@kernel.org>
Signed-off-by: default avatarArnd Bergmann <arnd@arndb.de>
parent 23fc539e
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -898,6 +898,13 @@ config HAVE_SOFTIRQ_ON_OWN_STACK
	  Architecture provides a function to run __do_softirq() on a
	  separate stack.

config ALTERNATE_USER_ADDRESS_SPACE
	bool
	help
	  Architectures set this when the CPU uses separate address
	  spaces for kernel and user space pointers. In this case, the
	  access_ok() check on a __user pointer is skipped.

config PGTABLE_LEVELS
	int
	default 2
+5 −29
Original line number Diff line number Diff line
@@ -20,28 +20,7 @@
#define get_fs()  (current_thread_info()->addr_limit)
#define set_fs(x) (current_thread_info()->addr_limit = (x))

#define uaccess_kernel()	(get_fs().seg == KERNEL_DS.seg)

/*
 * Is a address valid? This does a straightforward calculation rather
 * than tests.
 *
 * Address valid if:
 *  - "addr" doesn't have any high-bits set
 *  - AND "size" doesn't have any high-bits set
 *  - AND "addr+size-(size != 0)" doesn't have any high-bits set
 *  - OR we are in kernel mode.
 */
#define __access_ok(addr, size) ({				\
	unsigned long __ao_a = (addr), __ao_b = (size);		\
	unsigned long __ao_end = __ao_a + __ao_b - !!__ao_b;	\
	(get_fs().seg & (__ao_a | __ao_b | __ao_end)) == 0; })

#define access_ok(addr, size)				\
({							\
	__chk_user_ptr(addr);				\
	__access_ok(((unsigned long)(addr)), (size));	\
})
#include <asm-generic/access_ok.h>

/*
 * These are the main single-value transfer routines.  They automatically
@@ -105,7 +84,7 @@ extern void __get_user_unknown(void);
	long __gu_err = -EFAULT;				\
	unsigned long __gu_val = 0;				\
	const __typeof__(*(ptr)) __user *__gu_addr = (ptr);	\
	if (__access_ok((unsigned long)__gu_addr, size)) {	\
	if (__access_ok(__gu_addr, size)) {			\
		__gu_err = 0;					\
		switch (size) {					\
		  case 1: __get_user_8(__gu_addr); break;	\
@@ -200,7 +179,7 @@ extern void __put_user_unknown(void);
({								\
	long __pu_err = -EFAULT;				\
	__typeof__(*(ptr)) __user *__pu_addr = (ptr);		\
	if (__access_ok((unsigned long)__pu_addr, size)) {	\
	if (__access_ok(__pu_addr, size)) {			\
		__pu_err = 0;					\
		switch (size) {					\
		  case 1: __put_user_8(x, __pu_addr); break;	\
@@ -316,17 +295,14 @@ raw_copy_to_user(void __user *to, const void *from, unsigned long len)

extern long __clear_user(void __user *to, long len);

extern inline long
static inline long
clear_user(void __user *to, long len)
{
	if (__access_ok((unsigned long)to, len))
	if (__access_ok(to, len))
		len = __clear_user(to, len);
	return len;
}

#define user_addr_max() \
        (uaccess_kernel() ? ~0UL : TASK_SIZE)

extern long strncpy_from_user(char *dest, const char __user *src, long count);
extern __must_check long strnlen_user(const char __user *str, long n);

+0 −29
Original line number Diff line number Diff line
@@ -23,35 +23,6 @@

#include <linux/string.h>	/* for generic string functions */


#define __kernel_ok		(uaccess_kernel())

/*
 * Algorithmically, for __user_ok() we want do:
 * 	(start < TASK_SIZE) && (start+len < TASK_SIZE)
 * where TASK_SIZE could either be retrieved from thread_info->addr_limit or
 * emitted directly in code.
 *
 * This can however be rewritten as follows:
 *	(len <= TASK_SIZE) && (start+len < TASK_SIZE)
 *
 * Because it essentially checks if buffer end is within limit and @len is
 * non-ngeative, which implies that buffer start will be within limit too.
 *
 * The reason for rewriting being, for majority of cases, @len is generally
 * compile time constant, causing first sub-expression to be compile time
 * subsumed.
 *
 * The second part would generate weird large LIMMs e.g. (0x6000_0000 - 0x10),
 * so we check for TASK_SIZE using get_fs() since the addr_limit load from mem
 * would already have been done at this call site for __kernel_ok()
 *
 */
#define __user_ok(addr, sz)	(((sz) <= TASK_SIZE) && \
				 ((addr) <= (get_fs() - (sz))))
#define __access_ok(addr, sz)	(unlikely(__kernel_ok) || \
				 likely(__user_ok((addr), (sz))))

/*********** Single byte/hword/word copies ******************/

#define __get_user_fn(sz, u, k)					\
+1 −19
Original line number Diff line number Diff line
@@ -55,21 +55,6 @@ extern int __put_user_bad(void);

#ifdef CONFIG_MMU

/*
 * We use 33-bit arithmetic here.  Success returns zero, failure returns
 * addr_limit.  We take advantage that addr_limit will be zero for KERNEL_DS,
 * so this will always return success in that case.
 */
#define __range_ok(addr, size) ({ \
	unsigned long flag, roksum; \
	__chk_user_ptr(addr);	\
	__asm__(".syntax unified\n" \
		"adds %1, %2, %3; sbcscc %1, %1, %0; movcc %0, #0" \
		: "=&r" (flag), "=&r" (roksum) \
		: "r" (addr), "Ir" (size), "0" (TASK_SIZE) \
		: "cc"); \
	flag; })

/*
 * This is a type: either unsigned long, if the argument fits into
 * that type, or otherwise unsigned long long.
@@ -241,15 +226,12 @@ extern int __put_user_8(void *, unsigned long long);

#else /* CONFIG_MMU */

#define __addr_ok(addr)		((void)(addr), 1)
#define __range_ok(addr, size)	((void)(addr), 0)

#define get_user(x, p)	__get_user(x, p)
#define __put_user_check __put_user_nocheck

#endif /* CONFIG_MMU */

#define access_ok(addr, size)	(__range_ok(addr, size) == 0)
#include <asm-generic/access_ok.h>

#ifdef CONFIG_CPU_SPECTRE
/*
+4 −7
Original line number Diff line number Diff line
@@ -26,13 +26,7 @@
#include <asm/memory.h>
#include <asm/extable.h>

static inline int __access_ok(const void __user *ptr, unsigned long size)
{
	unsigned long limit = TASK_SIZE_MAX;
	unsigned long addr = (unsigned long)ptr;

	return (size <= limit) && (addr <= (limit - size));
}
static inline int __access_ok(const void __user *ptr, unsigned long size);

/*
 * Test whether a block of memory is a valid user space address.
@@ -54,6 +48,9 @@ static inline int access_ok(const void __user *addr, unsigned long size)

	return likely(__access_ok(addr, size));
}
#define access_ok access_ok

#include <asm-generic/access_ok.h>

/*
 * User access enabling/disabling.
Loading