🐧

【Kernel】copy_to_user() / copy_from_user() / get_user() / put_user()

2024/07/26に公開

※ Linux kernel v6.10.1 のコードベースを元に調査したものです。

copy_to_user() / copy_from_user() / get_user() / put_user()

  • put_user(): ユーザー空間へ単一の値 (int, char, long など) を渡す
  • get_user(): ユーザー空間から単一の値を受け取る
  • copy_to_user(): ユーザー空間に任意の量のデータを渡す
  • copy_from_user(): ユーザー空間から任意の量のデータを受け取る

https://www.kernel.org/doc/html/latest/kernel-hacking/hacking.html#copy-to-user-copy-from-user-get-user-put-user

copy_to_user() / copy_from_user() / get_user() / put_user()

Defined in include/linux/uaccess.h / asm/uaccess.h

[SLEEPS]

put_user() and get_user() are used to get and put single values (such as an int, char, or long) from and to userspace. A pointer into userspace should never be simply dereferenced: data should be copied using these routines. Both return -EFAULT or 0.

copy_to_user() and copy_from_user() are more general: they copy an arbitrary amount of data to and from userspace.

Warning:
Unlike put_user() and get_user(), they return the amount of uncopied data (ie. 0 still means success).

[Yes, this objectionable interface makes me cringe. The flamewar comes up every year or so. --RR.]

The functions may sleep implicitly. This should never be called outside user context (it makes no sense), with interrupts disabled, or a spinlock held.

x86

put_user()

前述の通り、ユーザー空間に単一の値を書き込む関数である。

また、コメントにある通り、ページフォルトが有効な場合は、関数がスリープする場合がある。

/**
 * put_user - Write a simple value into 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.
 *
 * This macro copies a single simple value from kernel space to user
 * space.  It supports simple types like char and int, but not larger
 * data types like structures or arrays.
 *
 * @ptr must have pointer-to-simple-variable type, and @x must be assignable
 * to the result of dereferencing @ptr.
 *
 * Return: zero on success, or -EFAULT on error.
 */
#define put_user(x, ptr) ({ might_fault(); do_put_user_call(put_user,x,ptr); })

might_fault() は、やや複雑そうだったので、本記事では割愛します。後日別の記事にする可能性はありです。

do_put_user_call()

結論だけをいうと、サイズに応じて __put_user_1(), __put_user_2(), __put_user_4(), __put_user_8() のいずれかが呼び出される。

もう少しだけ具体的にアセンブリの呼び出し部分を見る。

  • 返り値 __ret_pu は RCX レジスタに出力される。
  • ユーザー空間のポインタ __ptr_pu は、__ret_pu と同様に RCX レジスタに保持する。
  • ユーザー空間に渡す値 __val_pu は、RAX レジスタに保持する。
/*
 * ptr must be evaluated and assigned to the temporary __ptr_pu before
 * the assignment of x to __val_pu, to avoid any function calls
 * involved in the ptr expression (possibly implicitly generated due
 * to KASAN) from clobbering %ax.
 */
#define do_put_user_call(fn,x,ptr)					\
({									\
	int __ret_pu;							\
	void __user *__ptr_pu;						\
	register __typeof__(*(ptr)) __val_pu asm("%"_ASM_AX);		\
	__typeof__(*(ptr)) __x = (x); /* eval x once */			\
	__typeof__(ptr) __ptr = (ptr); /* eval ptr once */		\
	__chk_user_ptr(__ptr);						\
	__ptr_pu = __ptr;						\
	__val_pu = __x;							\
	asm volatile("call __" #fn "_%c[size]"				\
		     : "=c" (__ret_pu),					\
			ASM_CALL_CONSTRAINT				\
		     : "0" (__ptr_pu),					\
		       "r" (__val_pu),					\
		       [size] "i" (sizeof(*(ptr)))			\
		     :"ebx");						\
	instrument_put_user(__x, __ptr, sizeof(*(ptr)));		\
	__builtin_expect(__ret_pu, 0);					\
})

__put_user_1, __put_user_2, __put_user_4, __put_user_8

check_range については後述。

STAC 命令 は EFLAGS レジスタの AC フラグをセットする。これはカーネルが、ユーザー空間のメモリにアクセスすることを許可するための命令である。CLAC 命令 は、逆に EFLAGS レジスタの AC フラグをクリアします。

前述の通り、RCX レジスタにはユーザー空間のポインタ、RAX レジスタにはユーザー空間に渡す値がセットされている。
movb %al,(%_ASM_CX) 等の場所で実際に値がコピーされている。

その後 xor %ecx,%ecx を実行し、ECX レジスタを 0 にセットし、返り値として __ret_pu にセットされる。

SYM_FUNC_START(__put_user_1)
	check_range size=1
	ASM_STAC
1:	movb %al,(%_ASM_CX)
	xor %ecx,%ecx
	ASM_CLAC
	RET
SYM_FUNC_END(__put_user_1)
EXPORT_SYMBOL(__put_user_1)
SYM_FUNC_START(__put_user_2)
	check_range size=2
	ASM_STAC
3:	movw %ax,(%_ASM_CX)
	xor %ecx,%ecx
	ASM_CLAC
	RET
SYM_FUNC_END(__put_user_2)
EXPORT_SYMBOL(__put_user_2)
SYM_FUNC_START(__put_user_4)
	check_range size=4
	ASM_STAC
5:	movl %eax,(%_ASM_CX)
	xor %ecx,%ecx
	ASM_CLAC
	RET
SYM_FUNC_END(__put_user_4)
EXPORT_SYMBOL(__put_user_4)
SYM_FUNC_START(__put_user_8)
	check_range size=8
	ASM_STAC
7:	mov %_ASM_AX,(%_ASM_CX)
#ifdef CONFIG_X86_32
8:	movl %edx,4(%_ASM_CX)
#endif
	xor %ecx,%ecx
	ASM_CLAC
	RET
SYM_FUNC_END(__put_user_8)
EXPORT_SYMBOL(__put_user_8)

check_range

前述の通り、RCX にはユーザースペースポインタが設定されていることが前提となる。
RBX に一旦移動し、最上位ビットを使った 64-bit のマスクを作成し、OR をとっている。
したがって、最上位ビットが 0 の場合、与えられたポインタそのままが返され、そうでない場合は ~0 のポインタが返される。

.macro check_range size:req
.if IS_ENABLED(CONFIG_X86_64)
	mov %rcx, %rbx
	sar $63, %rbx
	or %rbx, %rcx
.else
	cmp $TASK_SIZE_MAX-\size+1, %ecx
	jae .Lbad_put_user
.endif
.endm

以下のメモリマップを考慮すると、与えられたポインタがユーザースペースのものであれば、そのままの値を返し、カーネルスペースであれば unused hole を示す ~0 を返すことになる。

https://www.kernel.org/doc/Documentation/x86/x86_64/mm.txt

========================================================================================================================
    Start addr    |   Offset   |     End addr     |  Size   | VM area description
========================================================================================================================
                  |            |                  |         |
 0000000000000000 |    0       | 00007fffffffffff |  128 TB | user-space virtual memory, different per mm
__________________|____________|__________________|_________|___________________________________________________________
                  |            |                  |         |
 0000800000000000 | +128    TB | ffff7fffffffffff | ~16M TB | ... huge, almost 64 bits wide hole of non-canonical
                  |            |                  |         |     virtual memory addresses up to the -128 TB
                  |            |                  |         |     starting offset of kernel mappings.
__________________|____________|__________________|_________|___________________________________________________________
                                                            |
                                                            | Kernel-space virtual memory, shared between all processes:
____________________________________________________________|___________________________________________________________
                  |            |                  |         |
 ffff800000000000 | -128    TB | ffff87ffffffffff |    8 TB | ... guard hole, also reserved for hypervisor
 ffff880000000000 | -120    TB | ffff887fffffffff |  0.5 TB | LDT remap for PTI
 ffff888000000000 | -119.5  TB | ffffc87fffffffff |   64 TB | direct mapping of all physical memory (page_offset_base)
 ffffc88000000000 |  -55.5  TB | ffffc8ffffffffff |  0.5 TB | ... unused hole
 ffffc90000000000 |  -55    TB | ffffe8ffffffffff |   32 TB | vmalloc/ioremap space (vmalloc_base)
 ffffe90000000000 |  -23    TB | ffffe9ffffffffff |    1 TB | ... unused hole
 ffffea0000000000 |  -22    TB | ffffeaffffffffff |    1 TB | virtual memory map (vmemmap_base)
 ffffeb0000000000 |  -21    TB | ffffebffffffffff |    1 TB | ... unused hole
 ffffec0000000000 |  -20    TB | fffffbffffffffff |   16 TB | KASAN shadow memory
__________________|____________|__________________|_________|____________________________________________________________
                                                            |
                                                            | Identical layout to the 56-bit one from here on:
____________________________________________________________|____________________________________________________________
                  |            |                  |         |
 fffffc0000000000 |   -4    TB | fffffdffffffffff |    2 TB | ... unused hole
                  |            |                  |         | vaddr_end for KASLR
 fffffe0000000000 |   -2    TB | fffffe7fffffffff |  0.5 TB | cpu_entry_area mapping
 fffffe8000000000 |   -1.5  TB | fffffeffffffffff |  0.5 TB | ... unused hole
 ffffff0000000000 |   -1    TB | ffffff7fffffffff |  0.5 TB | %esp fixup stacks
 ffffff8000000000 | -512    GB | ffffffeeffffffff |  444 GB | ... unused hole
 ffffffef00000000 |  -68    GB | fffffffeffffffff |   64 GB | EFI region mapping space
 ffffffff00000000 |   -4    GB | ffffffff7fffffff |    2 GB | ... unused hole
 ffffffff80000000 |   -2    GB | ffffffff9fffffff |  512 MB | kernel text mapping, mapped to physical address 0
 ffffffff80000000 |-2048    MB |                  |         |
 ffffffffa0000000 |-1536    MB | fffffffffeffffff | 1520 MB | module mapping space
 ffffffffff000000 |  -16    MB |                  |         |
    FIXADDR_START | ~-11    MB | ffffffffff5fffff | ~0.5 MB | kernel-internal fixmap range, variable size and offset
 ffffffffff600000 |  -10    MB | ffffffffff600fff |    4 kB | legacy vsyscall ABI
 ffffffffffe00000 |   -2    MB | ffffffffffffffff |    2 MB | ... unused hole
__________________|____________|__________________|_________|___________________________________________________________

このパッチも併せて読むと良い。

get_user()

/**
 * get_user - Get a simple variable from user space.
 * @x:   Variable to store result.
 * @ptr: Source address, in user space.
 *
 * Context: User context only. This function may sleep if pagefaults are
 *          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
 * data types like structures or arrays.
 *
 * @ptr must have pointer-to-simple-variable type, and the result of
 * dereferencing @ptr must be assignable to @x without a cast.
 *
 * Return: zero on success, or -EFAULT on error.
 * On error, the variable @x is set to zero.
 */
#define get_user(x,ptr) ({ might_fault(); do_get_user_call(get_user,x,ptr); })

do_get_user_call()

get_user() からも __get_user() からも使われる関数とのこと。
結果的にはサイズに応じた __get_user_<size>() を呼び出す。

アセンブリの呼び出し部分をもう少し見る。

  • 返り値 __ret_gu は、RAX レジスタに出力される。
  • ユーザーから受け取る値 __val_gu は、RDX レジスタに出力される。
  • ユーザー空間のポインタ ptr は、__ret_gu と同様に RAX レジスタに保持する。
/*
 * This is used for both get_user() and __get_user() to expand to
 * the proper special function call that has odd calling conventions
 * due to returning both a value and an error, and that depends on
 * the size of the pointer passed in.
 *
 * Careful: we have to cast the result to the type of the pointer
 * for sign reasons.
 *
 * The use of _ASM_DX as the register specifier is a bit of a
 * simplification, as gcc only cares about it as the starting point
 * and not size: for a 64-bit value it will use %ecx:%edx on 32 bits
 * (%ecx being the next register in gcc's x86 register sequence), and
 * %rdx on 64 bits.
 *
 * Clang/LLVM cares about the size of the register, but still wants
 * the base register for something that ends up being a pair.
 */
#define do_get_user_call(fn,x,ptr)					\
({									\
	int __ret_gu;							\
	register __inttype(*(ptr)) __val_gu asm("%"_ASM_DX);		\
	__chk_user_ptr(ptr);						\
	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);					\
	(x) = (__force __typeof__(*(ptr))) __val_gu;			\
	__builtin_expect(__ret_gu, 0);					\
})

__get_user_1, __get_user_2, __get_user_4, __get_user_8

check_range については後述。

STAC 命令と CLAC 命令は前述の通り。

前述の通り、RAX レジスタにユーザー空間のポインタ、RDX レジスタに読み込んだ値を入れる。
movzbl (%_ASM_AX),%edx の部分が値の読み込んでいる部分である。

最後に返り値である EAX レジスタを 0 にセットしてリターンする。

	.text
SYM_FUNC_START(__get_user_1)
	check_range size=1
	ASM_STAC
1:	movzbl (%_ASM_AX),%edx
	xor %eax,%eax
	ASM_CLAC
	RET
SYM_FUNC_END(__get_user_1)
EXPORT_SYMBOL(__get_user_1)

SYM_FUNC_START(__get_user_2)
	check_range size=2
	ASM_STAC
2:	movzwl (%_ASM_AX),%edx
	xor %eax,%eax
	ASM_CLAC
	RET
SYM_FUNC_END(__get_user_2)
EXPORT_SYMBOL(__get_user_2)

SYM_FUNC_START(__get_user_4)
	check_range size=4
	ASM_STAC
3:	movl (%_ASM_AX),%edx
	xor %eax,%eax
	ASM_CLAC
	RET
SYM_FUNC_END(__get_user_4)
EXPORT_SYMBOL(__get_user_4)

SYM_FUNC_START(__get_user_8)
	check_range size=8
	ASM_STAC
#ifdef CONFIG_X86_64
4:	movq (%_ASM_AX),%rdx
#else
4:	movl (%_ASM_AX),%edx
5:	movl 4(%_ASM_AX),%ecx
#endif
	xor %eax,%eax
	ASM_CLAC
	RET
SYM_FUNC_END(__get_user_8)
EXPORT_SYMBOL(__get_user_8)

check_range

64-bit の場合は、put_user() と同じマスキングをしている。

32-bit の場合は、最上位ビットを使ったマスキングは使えず、array_index_mask_nospec() と同じ Bounds Clipping を使っている。(詳細は割愛)

.macro check_range size:req
.if IS_ENABLED(CONFIG_X86_64)
	mov %rax, %rdx
	sar $63, %rdx
	or %rdx, %rax
.else
	cmp $TASK_SIZE_MAX-\size+1, %eax
.if \size != 8
	jae .Lbad_get_user
.else
	jae .Lbad_get_user_8
.endif
	sbb %edx, %edx		/* array_index_mask_nospec() */
	and %edx, %eax
.endif
.endm

copy_to_user()

static __always_inline unsigned long __must_check
copy_to_user(void __user *to, const void *from, unsigned long n)
{
	if (check_copy_size(from, n, true))
		n = _copy_to_user(to, from, n);
	return n;
}

check_copy_size()

一旦、この記事では深掘りしない。基本的には true が返るものと想定。

static __always_inline __must_check bool
check_copy_size(const void *addr, size_t bytes, bool is_source)
{
	int sz = __builtin_object_size(addr, 0);
	if (unlikely(sz >= 0 && sz < bytes)) {
		if (!__builtin_constant_p(bytes))
			copy_overflow(sz, bytes);
		else if (is_source)
			__bad_copy_from();
		else
			__bad_copy_to();
		return false;
	}
	if (WARN_ON_ONCE(bytes > INT_MAX))
		return false;
	check_object_size(addr, bytes, is_source);
	return true;
}

_copy_to_user()

インラインバージョンと通常バージョンが定義されている。違いとしては、static inline __must_check がついてるくらい。

should_fail_usercopy() は、ざっとみた感じは意図的にコピーを失敗させたい時に使うものっぽいのでスルー。

access_ok() については、この記事を参照。

instrument_copy_to_user() は instrumentation 関連なので一旦スルー。

https://elixir.bootlin.com/linux/v6.10.1/source/include/linux/uaccess.h#L163

#ifdef INLINE_COPY_TO_USER
static inline __must_check unsigned long
_copy_to_user(void __user *to, const void *from, unsigned long n)
{
	might_fault();
	if (should_fail_usercopy())
		return n;
	if (access_ok(to, n)) {
		instrument_copy_to_user(to, from, n);
		n = raw_copy_to_user(to, from, n);
	}
	return n;
}
#else
extern __must_check unsigned long
_copy_to_user(void __user *, const void *, unsigned long);
#endif

https://elixir.bootlin.com/linux/v6.10.1/source/lib/usercopy.c#L39

#ifndef INLINE_COPY_TO_USER
unsigned long _copy_to_user(void __user *to, const void *from, unsigned long n)
{
	might_fault();
	if (should_fail_usercopy())
		return n;
	if (likely(access_ok(to, n))) {
		instrument_copy_to_user(to, from, n);
		n = raw_copy_to_user(to, from, n);
	}
	return n;
}
EXPORT_SYMBOL(_copy_to_user);
#endif

raw_copy_to_user()

copy_user_generic() のラッパー。

static __always_inline __must_check unsigned long
raw_copy_to_user(void __user *dst, const void *src, unsigned long size)
{
	return copy_user_generic((__force void *)dst, src, size);
}

copy_user_generic()

最初と最後に stac()clac() を読んで、ユーザー空間へのアクセスを有効にしている。

FSRM (Fast Short REP MOV) をサポートしている場合は rep movs を使い、そうでない場合は rep_movs_alternative を使っている。

FSRM が利用可能とここでは想定することにする。

アセンブリの部分をちょっと確認。

  • len は RCX レジスタにセットされる。
  • to は DI レジスタにセットされる。
  • from は SI レジスタにセットされる。

REP 命令 は、RCX = 0 になるまでループする。
MOVSB 命令 は、RSI から RDI へ1バイトコピーする。

static __always_inline __must_check unsigned long
copy_user_generic(void *to, const void *from, unsigned long len)
{
	stac();
	/*
	 * If CPU has FSRM feature, use 'rep movs'.
	 * Otherwise, use rep_movs_alternative.
	 */
	asm volatile(
		"1:\n\t"
		ALTERNATIVE("rep movsb",
			    "call rep_movs_alternative", ALT_NOT(X86_FEATURE_FSRM))
		"2:\n"
		_ASM_EXTABLE_UA(1b, 2b)
		:"+c" (len), "+D" (to), "+S" (from), ASM_CALL_CONSTRAINT
		: : "memory", "rax");
	clac();
	return len;
}

copy_from_user()

static __always_inline unsigned long __must_check
copy_from_user(void *to, const void __user *from, unsigned long n)
{
	if (check_copy_size(to, n, false))
		n = _copy_from_user(to, from, n);
	return n;
}

_copy_from_user()

_copy_to_user() と同様に、インラインバージョンと通常バージョンが定義されている。
インラインバージョンには、speculation barrier がない。

raw_copy_from_user() が本体で、もし失敗した場合は memset() で 0 にクリーンアップする。

インラインバージョン

https://elixir.bootlin.com/linux/v6.10.1/source/include/linux/uaccess.h#L143

#ifdef INLINE_COPY_FROM_USER
static inline __must_check unsigned long
_copy_from_user(void *to, const void __user *from, unsigned long n)
{
	unsigned long res = n;
	might_fault();
	if (!should_fail_usercopy() && likely(access_ok(from, n))) {
		instrument_copy_from_user_before(to, from, n);
		res = raw_copy_from_user(to, from, n);
		instrument_copy_from_user_after(to, from, n, res);
	}
	if (unlikely(res))
		memset(to + (n - res), 0, res);
	return res;
}
#else
extern __must_check unsigned long
_copy_from_user(void *, const void __user *, unsigned long);
#endif

通常バージョン

https://elixir.bootlin.com/linux/v6.10.1/source/lib/usercopy.c#L16

#ifndef INLINE_COPY_FROM_USER
unsigned long _copy_from_user(void *to, const void __user *from, unsigned long n)
{
	unsigned long res = n;
	might_fault();
	if (!should_fail_usercopy() && likely(access_ok(from, n))) {
		/*
		 * Ensure that bad access_ok() speculation will not
		 * lead to nasty side effects *after* the copy is
		 * finished:
		 */
		barrier_nospec();
		instrument_copy_from_user_before(to, from, n);
		res = raw_copy_from_user(to, from, n);
		instrument_copy_from_user_after(to, from, n, res);
	}
	if (unlikely(res))
		memset(to + (n - res), 0, res);
	return res;
}
EXPORT_SYMBOL(_copy_from_user);
#endif

barrier_nospec()

LFENCE 命令がバリアとして利用されている。

/* Prevent speculative execution past this barrier. */
#define barrier_nospec() alternative("", "lfence", X86_FEATURE_LFENCE_RDTSC)

raw_copy_from_user()

raw_copy_to_user() と同様に copy_user_generic() を呼び出す。

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);
}

arm64

put_user()

__put_user のラッパー。

#define put_user	__put_user

__put_user()

#define __put_user(x, ptr)						\
({									\
	int __pu_err = 0;						\
	__put_user_error((x), (ptr), __pu_err);				\
	__pu_err;							\
})

__put_user_error()

access_ok() でユーザー空間のポインタかチェックした後に、uaccess_mask_ptr() でポインタのサニタイズを行う。

#define __put_user_error(x, ptr, err)					\
do {									\
	__typeof__(*(ptr)) __user *__p = (ptr);				\
	might_fault();							\
	if (access_ok(__p, sizeof(*__p))) {				\
		__p = uaccess_mask_ptr(__p);				\
		__raw_put_user((x), __p, (err));			\
	} else	{							\
		(err) = -EFAULT;					\
	}								\
} while (0)

uaccess_mask_ptr()

BIC 命令は、以下の操作をする命令である。

BIC <Xd>, <Xn>, <Xm>{, <shift> #<amount>}

Operation

constant bits(datasize) operand1 = X[n, datasize];
constant bits(datasize) operand2 = ShiftReg(m, shift_type, shift_amount, datasize);

X[d, datasize] = operand1 AND NOT(operand2);

以下のパッチに記載の通り、bit 55 がクリアされてれば TTBR0 VA レンジ、bit 55 がセットされてれば TTBR1 VA レンジとなる。

https://lore.kernel.org/all/20220922151053.3520750-1-mark.rutland@arm.com/

Regardless of the configured VA size or whether TBI is in use, the
address space can be divided into three ranges:

  • The TTBR0 VA range, for which any valid pointer has bit 55 clear,
    and any non-tag bits [63-56] must match bit 55 (i.e. must be clear).

  • The TTBR1 VA range, for which any valid pointer has bit 55 set, and
    any non-tag bits [63-56] must match bit 55 (i.e. must be set).

  • The gap between the TTBR0 and TTBR1 ranges, where bit 55 may be set or
    clear, but any access will result in a fault.

TTBR0 (Translation Table Base Register 0)TTBR1 (Translation Table Base Register 1) は、Translation Table のベースアドレスを入れるレジスタである。

したがって、uaccess_mask_ptr() は bit 55 をクリアすることで、強制的にユーザー空間のアドレスにしている。

/*
 * Sanitize a uaccess pointer such that it cannot reach any kernel address.
 *
 * Clearing bit 55 ensures the pointer cannot address any portion of the TTBR1
 * address range (i.e. any kernel address), and either the pointer falls within
 * the TTBR0 address range or must cause a fault.
 */
#define uaccess_mask_ptr(ptr) (__typeof__(ptr))__uaccess_mask_ptr(ptr)
static inline void __user *__uaccess_mask_ptr(const void __user *ptr)
{
	void __user *safe_ptr;

	asm volatile(
	"	bic	%0, %1, %2\n"
	: "=r" (safe_ptr)
	: "r" (ptr),
	  "i" (BIT(55))
	);

	return safe_ptr;
}

__raw_put_user()

uaccess_ttbr0_enable() でユーザー空間へのアクセスを有効にし、uaccess_ttbr0_disable() で無効にする。(詳細は一旦割愛)

/*
 * We must not call into the scheduler between uaccess_ttbr0_enable() and
 * uaccess_ttbr0_disable(). As `x` and `ptr` could contain blocking functions,
 * we must evaluate these outside of the critical section.
 */
#define __raw_put_user(x, ptr, err)					\
do {									\
	__typeof__(*(ptr)) __user *__rpu_ptr = (ptr);			\
	__typeof__(*(ptr)) __rpu_val = (x);				\
	__chk_user_ptr(__rpu_ptr);					\
									\
	uaccess_ttbr0_enable();						\
	__raw_put_mem("sttr", __rpu_val, __rpu_ptr, err, U);		\
	uaccess_ttbr0_disable();					\
} while (0)

__raw_put_mem()

サイズの大きさに応じたアセンブリを呼び出す。

#define __raw_put_mem(str, x, ptr, err, type)					\
do {										\
	__typeof__(*(ptr)) __pu_val = (x);					\
	switch (sizeof(*(ptr))) {						\
	case 1:									\
		__put_mem_asm(str "b", "%w", __pu_val, (ptr), (err), type);	\
		break;								\
	case 2:									\
		__put_mem_asm(str "h", "%w", __pu_val, (ptr), (err), type);	\
		break;								\
	case 4:									\
		__put_mem_asm(str, "%w", __pu_val, (ptr), (err), type);		\
		break;								\
	case 8:									\
		__put_mem_asm(str, "%x", __pu_val, (ptr), (err), type);		\
		break;								\
	default:								\
		BUILD_BUG();							\
	}									\
} while (0)

__put_mem_asm()

#define __put_mem_asm(store, reg, x, addr, err, type)			\
	asm volatile(							\
	"1:	" store "	" reg "1, [%2]\n"			\
	"2:\n"								\
	_ASM_EXTABLE_##type##ACCESS_ERR(1b, 2b, %w0)			\
	: "+r" (err)							\
	: "rZ" (x), "r" (addr))

get_user()

基本的には put_user() と同じ感じ。

#define get_user	__get_user

__get_user()

#define __get_user(x, ptr)						\
({									\
	int __gu_err = 0;						\
	__get_user_error((x), (ptr), __gu_err);				\
	__gu_err;							\
})

__get_user_error()

#define __get_user_error(x, ptr, err)					\
do {									\
	__typeof__(*(ptr)) __user *__p = (ptr);				\
	might_fault();							\
	if (access_ok(__p, sizeof(*__p))) {				\
		__p = uaccess_mask_ptr(__p);				\
		__raw_get_user((x), __p, (err));			\
	} else {							\
		(x) = (__force __typeof__(x))0; (err) = -EFAULT;	\
	}								\
} while (0)

__raw_get_user()

/*
 * We must not call into the scheduler between uaccess_ttbr0_enable() and
 * uaccess_ttbr0_disable(). As `x` and `ptr` could contain blocking functions,
 * we must evaluate these outside of the critical section.
 */
#define __raw_get_user(x, ptr, err)					\
do {									\
	__typeof__(*(ptr)) __user *__rgu_ptr = (ptr);			\
	__typeof__(x) __rgu_val;					\
	__chk_user_ptr(ptr);						\
									\
	uaccess_ttbr0_enable();						\
	__raw_get_mem("ldtr", __rgu_val, __rgu_ptr, err, U);		\
	uaccess_ttbr0_disable();					\
									\
	(x) = __rgu_val;						\
} while (0)

__raw_get_mem()

#define __raw_get_mem(ldr, x, ptr, err, type)					\
do {										\
	unsigned long __gu_val;							\
	switch (sizeof(*(ptr))) {						\
	case 1:									\
		__get_mem_asm(ldr "b", "%w", __gu_val, (ptr), (err), type);	\
		break;								\
	case 2:									\
		__get_mem_asm(ldr "h", "%w", __gu_val, (ptr), (err), type);	\
		break;								\
	case 4:									\
		__get_mem_asm(ldr, "%w", __gu_val, (ptr), (err), type);		\
		break;								\
	case 8:									\
		__get_mem_asm(ldr, "%x",  __gu_val, (ptr), (err), type);	\
		break;								\
	default:								\
		BUILD_BUG();							\
	}									\
	(x) = (__force __typeof__(*(ptr)))__gu_val;				\
} while (0)

__get_mem_asm()

/*
 * The "__xxx" versions of the user access functions do not verify the address
 * space - it must have been done previously with a separate "access_ok()"
 * call.
 *
 * The "__xxx_error" versions set the third argument to -EFAULT if an error
 * occurs, and leave it unchanged on success.
 */
#define __get_mem_asm(load, reg, x, addr, err, type)			\
	asm volatile(							\
	"1:	" load "	" reg "1, [%2]\n"			\
	"2:\n"								\
	_ASM_EXTABLE_##type##ACCESS_ERR_ZERO(1b, 2b, %w0, %w1)		\
	: "+r" (err), "=r" (x)						\
	: "r" (addr))

copy_to_user()

copy_to_user()_copy_to_user() はアーキテクチャ依存しない部分。
raw_copy_to_user() がアーキテクチャ依存。

raw_copy_to_user()

#define raw_copy_to_user(to, from, n)					\
({									\
	unsigned long __actu_ret;					\
	uaccess_ttbr0_enable();						\
	__actu_ret = __arch_copy_to_user(__uaccess_mask_ptr(to),	\
				    (from), (n));			\
	uaccess_ttbr0_disable();					\
	__actu_ret;							\
})

__arch_copy_to_user()

ちょっとこの辺のアセンブリを全部追う元気が残ってない。

end	.req	x5
srcin	.req	x15
SYM_FUNC_START(__arch_copy_to_user)
	add	end, x0, x2
	mov	srcin, x1
#include "copy_template.S"
	mov	x0, #0
	ret

	// Exception fixups
9997:	cmp	dst, dstin
	b.ne	9998f
	// Before being absolutely sure we couldn't copy anything, try harder
	ldrb	tmp1w, [srcin]
USER(9998f, sttrb tmp1w, [dst])
	add	dst, dst, #1
9998:	sub	x0, end, dst			// bytes not copied
	ret
SYM_FUNC_END(__arch_copy_to_user)
EXPORT_SYMBOL(__arch_copy_to_user)

copy_template.S

同様にアセンブリを全部追う元気が残ってない。
ただ、意図してる操作はコメントにある通り。

/*
 * Copy a buffer from src to dest (alignment handled by the hardware)
 *
 * Parameters:
 *	x0 - dest
 *	x1 - src
 *	x2 - n
 * Returns:
 *	x0 - dest
 */

copy_from_user()

copy_from_user()_copy_from_user() はアーキテクチャ依存しない部分。
raw_copy_from_user() がアーキテクチャ依存。

raw_copy_from_user()

extern unsigned long __must_check __arch_copy_from_user(void *to, const void __user *from, unsigned long n);
#define raw_copy_from_user(to, from, n)					\
({									\
	unsigned long __acfu_ret;					\
	uaccess_ttbr0_enable();						\
	__acfu_ret = __arch_copy_from_user((to),			\
				      __uaccess_mask_ptr(from), (n));	\
	uaccess_ttbr0_disable();					\
	__acfu_ret;							\
})

__arch_copy_from_user()

end	.req	x5
srcin	.req	x15
SYM_FUNC_START(__arch_copy_from_user)
	add	end, x0, x2
	mov	srcin, x1
#include "copy_template.S"
	mov	x0, #0				// Nothing to copy
	ret

	// Exception fixups
9997:	cmp	dst, dstin
	b.ne	9998f
	// Before being absolutely sure we couldn't copy anything, try harder
USER(9998f, ldtrb tmp1w, [srcin])
	strb	tmp1w, [dst], #1
9998:	sub	x0, end, dst			// bytes not copied
	ret
SYM_FUNC_END(__arch_copy_from_user)
EXPORT_SYMBOL(__arch_copy_from_user)
GitHubで編集を提案

Discussion