get_user and put_user Accessors

get_user(x, ptr) and put_user(x, ptr) are the single-value, typed cousins of copy_to_user and copy_from_user. Where the copy primitives move an arbitrary-length buffer, these move exactly one scalar — a char, short, int, long, long long, or pointer — across the userspace/kernel boundary, and they pick the right load/store width automatically from the type of the pointer. The win is efficiency: a get_user compiles to a tiny, specially-calling-convention assembly stub (or a single inline mov) rather than a general-purpose copy loop with a length argument. They share the copy primitives’ safety discipline — validate the user address, gate with SMAP, and annotate the access instruction in the exception table so a fault returns -EFAULT instead of oopsing — but condense it for the common case of grabbing one field. The x86 macros and assembly are in arch/x86/include/asm/uaccess.h, getuser.S, and putuser.S (Linux 6.12 LTS). On success they return 0; on fault -EFAULT, and on a get_user fault the output variable is set to zero.

Mental Model

If copy_to_user and copy_from_user is the freight elevator for crossing the boundary with a payload of arbitrary size, get_user/put_user are the pneumatic tube for sending exactly one item. You feed in a typed pointer; the macro reads sizeof(*ptr) at compile time, dispatches to the matching one-byte / two-byte / four-byte / eight-byte specialized routine, and the result lands in your variable with the correct sign-extension. There is no length argument to get wrong, and no general copy loop — just one annotated machine instruction that either succeeds or is caught by the fault hook.

flowchart TB
  C["get_user(val, uptr)<br/>uptr: int __user *"] --> SZ["compiler reads sizeof(*uptr)<br/>= 4 here"]
  SZ --> DISP["dispatch by size:<br/>call __get_user_4"]
  subgraph ASM["__get_user_4 (getuser.S)"]
    RNG["check_range:<br/>mask bad ptr to all-ones<br/>(USER_PTR_MAX, 6.12)"] --> STAC["STAC (open SMAP)"]
    STAC --> MOV["1: movl (rax), edx<br/>_ASM_EXTABLE_UA(1b, fault)"]
    MOV --> OK["xor eax,eax (ret=0)"]
    OK --> CLAC["CLAC (close SMAP)"]
  end
  MOV -.->|"user page faults"| FX["handle_exception:<br/>edx=0, eax=-EFAULT, CLAC"]
  CLAC --> RET["eax=0, edx=value"]
  FX --> RET2["eax=-EFAULT, edx=0"]

The path of get_user for an int. What it shows: the type of the pointer alone drives which specialized routine runs; the routine range-checks the address, opens the SMAP window, executes one mov that is registered in the exception table, and either returns the value in edx with eax=0 or — on fault — zeros the value and returns eax=-EFAULT, always closing the SMAP window. The insight: unlike a copy, there is no byte count — the result is binary (succeeded with a value, or -EFAULT), and the size is resolved entirely at compile time, so the runtime cost is one instruction plus the SMAP toggle.

How the Macro Picks the Right Size

The cleverness is that get_user/put_user are macros, not functions, so they can inspect the static type of ptr and the value x. On x86, the dispatch happens through do_get_user_call in uaccess.h:

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

Walking it: __inttype(*(ptr)) computes the smallest unsigned integer type that fits the pointed-to type — so the value register __val_gu has the right width. __chk_user_ptr(ptr) is a sparse annotation that warns if ptr is not a __user pointer (a compile-time guard against passing a kernel pointer). The key line is "call __" #fn "_%c[size]": the [size] "i" (sizeof(*(ptr))) injects the compile-time constant size into the function name via the %c operand modifier, so a 1-byte pointer assembles a call __get_user_1, a 4-byte pointer a call __get_user_4, an 8-byte pointer a call __get_user_8. The pointer goes in %rax ("0" (ptr)), the error code comes back in %rax ("=a"), and the value in %rdx (_ASM_DX). After the call, (x) = (__force __typeof__(*(ptr))) __val_gu casts the raw value back to the pointer’s real type — restoring signedness, which is “a bit of a simplification” the comment flags as the reason for the explicit cast. instrument_get_user is the KASAN/KMSAN hook.

These routines use a non-standard calling convention by design — they return both an error code and a value, in fixed registers, and “should not modify any other registers, as they get called from within inline assembly.” That is why they are hand-written assembly stubs rather than ordinary C functions; the standard C ABI cannot express “return a value in rdx and an error in rax while clobbering nothing else.”

put_user is the mirror, via do_put_user_call, with a twist in the convention: the pointer goes in %rcx, the value in %rax (:%edx for the 8-byte case on 32-bit), the error returns in %rcx, and %rbx is clobbered (used to hold a task pointer in the range check). The macro carefully evaluates ptr and x into temporaries before loading the value register, “to avoid any function calls involved in the ptr expression (possibly implicitly generated due to KASAN) from clobbering %ax.”

The Assembly Stub, Symbol by Symbol

Here is __get_user_4 (read a 4-byte value) from getuser.S:

SYM_FUNC_START(__get_user_4)
    check_range size=4
    ASM_STAC
    UACCESS movl (%_ASM_AX),%edx     /* the fault-able load */
    xor %eax,%eax                     /* eax = 0 (success) */
    ASM_CLAC
    RET
SYM_FUNC_END(__get_user_4)

check_range validates the address (detailed below). ASM_STAC opens the SMAP window. The UACCESS movl (%_ASM_AX),%edx is a macro that expands to a labeled movl plus an exception-table entry:

.macro UACCESS op src dst
1:  \op \src,\dst
    _ASM_EXTABLE_UA(1b, __get_user_handle_exception)
.endm

So the load at label 1 is registered with the fixup __get_user_handle_exception. If the user page faults, the kernel’s exception machinery (see The uaccess Exception Table and Fault Handling) looks up the faulting instruction pointer, finds this entry, and jumps to:

SYM_CODE_START_LOCAL(__get_user_handle_exception)
    ASM_CLAC                          /* close SMAP even on the error path */
.Lbad_get_user:
    xor %edx,%edx                     /* value = 0 */
    mov $(-EFAULT),%_ASM_AX           /* error = -EFAULT */
    RET

This is why the man-page contract “On error, the variable @x is set to zero” holds: the fixup explicitly zeros the value register (xor %edx,%edx) before returning -EFAULT. And critically, the fixup runs ASM_CLAC first — closing the SMAP window even when the access faulted, so no userspace-accessible window leaks past the failed call.

put_user is structurally identical but stores: __put_user_4 does 5: movl %eax,(%_ASM_CX) (store the value from %eax to the address in %rcx), with _ASM_EXTABLE_UA(5b, __put_user_handle_exception), and its fixup sets %ecx = -EFAULT. The extable entries for putuser are gathered at the bottom of the file rather than inline, but the mechanism is the same.

The Range Check — and Why get_user and put_user Differ in 6.12

The check_range macro is the per-accessor equivalent of access_ok(), and here the two accessors are not symmetric in Linux 6.12. get_user’s check_range (x86-64) in getuser.S:

.macro check_range size:req
.if IS_ENABLED(CONFIG_X86_64)
    movq $0x0123456789abcdef,%rdx     /* placeholder for USER_PTR_MAX */
  1:
  .pushsection runtime_ptr_USER_PTR_MAX,"a"
    .long 1b - 8 - .                  /* boot-time patch site */
  .popsection
    cmp %rax, %rdx                    /* USER_PTR_MAX vs ptr */
    sbb %rdx, %rdx                    /* carry -> 0 or all-ones mask */
    or %rdx, %rax                     /* in range: ptr; else all-ones */
.else
    cmp $TASK_SIZE_MAX-\size+1, %eax
    jae .Lbad_get_user
    sbb %edx, %edx
    and %edx, %eax
.endif
.endm

The 64-bit path uses the same branchless masking trick as mask_user_address (see copy_to_user and copy_from_user): the movq $0x0123… is a placeholder patched at boot with USER_PTR_MAX (the highest legal user address, accounting for LAM — Linear Address Masking); cmp sets carry if the pointer exceeds it; sbb %rdx,%rdx turns carry into a 0 or 0xFFFF…F mask; or %rdx,%rax leaves the pointer unchanged when in range or forces it to all-ones (a non-canonical, guaranteed-to-fault address) when out of range. Because it is branchless, there is no Spectre-v1 misprediction window, so no lfence is needed.

put_user’s check_range (x86-64) in putuser.S uses the older sign-bit smear:

.macro check_range size:req
.if IS_ENABLED(CONFIG_X86_64)
    mov %rcx, %rbx
    sar $63, %rbx                     /* arithmetic shift: sign bit -> all bits */
    or %rbx, %rcx                     /* set high bit ptr -> all-ones */
.else
    cmp $TASK_SIZE_MAX-\size+1, %ecx
    jae .Lbad_put_user
.endif
.endm

This relies on the canonical-address layout: a user address has its top bit clear, a kernel address has it set. sar $63 (arithmetic shift right by 63) replicates the sign bit across the whole register — 0 for a user address, all-ones for a kernel address — and or then forces any kernel-side pointer to all-ones, which faults. It is branchless too, but it is the pre-6.12 approach; get_user was upgraded to the USER_PTR_MAX runtime-const masking while put_user was not.

This asymmetry is verifiable, not a guess: in v6.10 and v6.11, get_user’s check_range also used the sar $63 sign-bit smear; the USER_PTR_MAX masking landed in 6.12. The put_user path still carries the older form at the 6.12 tag.

Uncertain

Verify: why get_user adopted USER_PTR_MAX masking in 6.12 while put_user kept the sar $63 sign-bit smear, and whether put_user later converges. Reason: the change is real (confirmed by diffing 6.10/6.11/6.12 getuser.S/putuser.S), but whether the divergence is a deliberate end state or a mid-flight conversion of the uaccess range-check series is not established from the source alone. To resolve: read the 6.12 merge commit and cover letter for the USER_PTR_MAX/getuser series, and diff putuser.S at 6.18 LTS. uncertain

The __get_user / __put_user Unchecked Variants

The leading-underscore variants skip the range check. __get_user dispatches to __get_user_nocheck_N (and __put_user to __put_user_nocheck_N), which omit check_range and go straight to STAC, the load/store, and CLAC. The get_user nocheck stubs still emit ASM_BARRIER_NOSPEC (an lfence, conditional on X86_FEATURE_LFENCE_RDTSC) because, without the masking range check, an unfenced speculative load past an unvalidated pointer would be a Spectre-v1 gadget.

The convention is identical to the copy primitives: the underscore is a warning that the caller must have already validated the pointer with access_ok() (or be inside a user_access_begin() region). The kernel doc comment is explicit: “Caller must check the pointer with access_ok() before calling this function.” Using __get_user on an unvalidated user pointer reintroduces the kernel-address-dereference vulnerability that the whole uaccess scheme exists to prevent.

The batched form, unsafe_get_user/unsafe_put_user, goes further still — it inlines the access entirely (no function call) and takes an error label to goto on fault, for use inside a user_access_begin()user_access_end() block where the SMAP window is opened once and many accesses share it. This is how hot paths (e.g. signal-frame setup) avoid paying STAC/CLAC and validation per field.

Configuration / Realistic Usage

Reading a scalar argument and writing a scalar result, the idiomatic way:

SYSCALL_DEFINE2(get_set_flag, int __user *, in, int __user *, out)
{
    int flag;
 
    /* Pull one int IN. Nonzero return => fault => -EFAULT. */
    if (get_user(flag, in))
        return -EFAULT;
 
    flag = process(flag);
 
    /* Push one int OUT. */
    if (put_user(flag, out))
        return -EFAULT;
 
    return 0;
}

Commentary: get_user(flag, in) reads sizeof(int) == 4 bytes via __get_user_4, deposits the value into flag, and returns 0 or -EFAULT. On fault, flag is set to 0 by the fixup — but you must still check the return, because 0 is a legitimate value. put_user(flag, out) writes it back. Note the argument order is (value/dest_var, ptr) for both — a frequent source of confusion: get_user’s first argument is the destination variable (lvalue), put_user’s first argument is the source value. The pointer is always second.

The batched, validate-once pattern for several fields of the same user struct:

if (!user_access_begin(uptr, sizeof(*uptr)))   /* one access_ok + STAC */
    return -EFAULT;
unsafe_get_user(a, &uptr->a, efault);
unsafe_get_user(b, &uptr->b, efault);
unsafe_put_user(result, &uptr->c, efault);
user_access_end();                              /* one CLAC */
return 0;
efault:
user_access_end();                              /* close window on error too */
return -EFAULT;

Here user_access_begin validates the whole range and opens SMAP once; each unsafe_* is a bare inline mov that jumps to efault on fault; user_access_end closes SMAP. The error label must also call user_access_end() so the window is never left open.

Failure Modes and Common Misunderstandings

Argument-order confusion. get_user(x, p) puts the result into x; put_user(x, p) writes the value of x. Swapping them does not compile (the macro assigns to its first argument for get_user), but mentally inverting them produces wrong code that does compile in the put_user direction.

Using them for a struct or array. These accessors are for simple scalars only — “It supports simple types like char and int, but not larger data types like structures or arrays.” Passing a struct pointer would dispatch on sizeof(struct), hit the default: case, and fail to link (__get_user_bad). For aggregates use [[copy_to_user and copy_from_user|copy_from_user/copy_to_user]] or copy_struct_from_user().

Ignoring the return on get_user. Because the fixup zeros the output on fault, sloppy code that reads x without checking the return cannot distinguish “user passed 0” from “the read faulted.” Always test the return; 0 from get_user means success, not “the value was zero.”

Calling in atomic context. Like the copy primitives, these can sleep (a fault may fault-in the page). The might_fault() annotation in get_user/put_user (but not the __-variants, which presume pagefault_disable()) catches misuse in atomic sections under lockdep.

Assuming get_user and put_user validate identically. As shown, in 6.12 they use different range-check assembly. For correctness this does not matter (both reject kernel pointers), but anyone reading the assembly expecting symmetry will be surprised.

Alternatives and When to Choose Them

  • [[copy_to_user and copy_from_user|copy_from_user / copy_to_user]] — for any buffer larger than one scalar, or any aggregate. The length is explicit and runtime-variable. Use these the moment you have more than one value or a struct/array.
  • copy_struct_from_user() — for versioned, extensible struct syscall arguments.
  • strncpy_from_user() — for NUL-terminated user strings of unknown length.
  • unsafe_get_user / unsafe_put_user inside user_access_begin() — for a batch of scalar accesses to one validated region; amortizes validation and the SMAP toggle. Choose this over a loop of get_user calls in a hot path.
  • get_user_pages() — when the kernel needs durable, direct mapped access to user pages (DMA, zero-copy), not a one-shot read. See get_user_pages and Page Pinning.

The decision is essentially: one scalar of known sizeget_user/put_user; a buffer or structcopy_*_user; many scalars in one region, hot pathuser_access_begin + unsafe_*; long-lived direct accessget_user_pages.

Production Notes

get_user/put_user appear all over the syscall and driver ioctl surface wherever a single flag, file descriptor, length, or pointer crosses the boundary — they are among the most-called functions in the kernel. Because each is a tiny annotated instruction, they are dramatically cheaper than a copy_from_user of sizeof(int), which is why the kernel style guide steers single-value transfers to them. The non-standard register calling convention and the hand-written assembly stubs exist precisely so that the common “grab one int from userspace” operation costs almost nothing beyond the unavoidable SMAP toggle and the load itself. The unsafe_* + user_access_begin batching was introduced to shave the per-access STAC/CLAC cost out of latency-sensitive paths like signal delivery, where the kernel writes a whole signal frame to the user stack field by field; opening the SMAP window once for the whole frame, rather than per field, is a measurable win on the signal-heavy workloads that motivated SMAP-aware optimization in the first place (LWN, Supervisor mode access prevention).

See Also