Alexander Klauer
Alexander Klauer

Reputation: 1037

Can I pass a pointer to linux kernel space for a __user parameter?

I've read about the __user specifier in the Linux kernel being used to tag function parameters for Sparse to check function arguments.

In particular, given

int foo1( char * buf );

one must not pass a pointer to user space for buf.

How about the other way round? In

int foo2( char __user * buf );

may or must I pass a user-supplied pointer? I'd guess the former because a user-supplied pointer could really be anything, but I haven't found a formal specification of __user anywhere.

Upvotes: 4

Views: 3959

Answers (2)

Jonathon Reinhart
Jonathon Reinhart

Reputation: 137438

A __user pointer is likely to be passed to copy_from_user, copy_to_user, or something similar.

In x86-64, these are implemented in copy_user_64.S:

/* Standard copy_to_user with segment limit checking */
ENTRY(_copy_to_user)
        CFI_STARTPROC
        GET_THREAD_INFO(%rax)
        movq %rdi,%rcx
        addq %rdx,%rcx
        jc bad_to_user
        cmpq TI_addr_limit(%rax),%rcx
        ja bad_to_user
        ALTERNATIVE_JUMP X86_FEATURE_REP_GOOD,X86_FEATURE_ERMS, \
                copy_user_generic_unrolled,copy_user_generic_string,    \
                copy_user_enhanced_fast_string
        CFI_ENDPROC
ENDPROC(_copy_to_user)

/* Standard copy_from_user with segment limit checking */
ENTRY(_copy_from_user)
        CFI_STARTPROC
        GET_THREAD_INFO(%rax)
        movq %rsi,%rcx
        addq %rdx,%rcx
        jc bad_from_user
        cmpq TI_addr_limit(%rax),%rcx
        ja bad_from_user
        ALTERNATIVE_JUMP X86_FEATURE_REP_GOOD,X86_FEATURE_ERMS, \
                copy_user_generic_unrolled,copy_user_generic_string,    \
                copy_user_enhanced_fast_string
        CFI_ENDPROC
ENDPROC(_copy_from_user)

Note the cmpq TI_addr_limit(%rax),%rcx which will make sure your pointer is less than or equal to current_thread_info()->addr_limit.

This limit is set by the set_fs() macro, which is called in a number of places. In particular, it can be called with USER_DS which is define as:

#define USER_DS         MAKE_MM_SEG(TASK_SIZE_MAX)

TASK_SIZE_MAX is defined as:

/*
 * User space process size. 47bits minus one guard page.
 * ...
 */
#define TASK_SIZE_MAX   ((1UL << 47) - PAGE_SIZE)

In x86-64, the kernel is mapped in the high (or negative) virtual address space. So a kernel pointer is going to fail this check.


In conclusion, I think a __user pointer mustn't be actually supplied by the user, but guaranteed to be a valid user-space address. I think the only constraint is that you must be sure that the pointer you're passing is valid in your current context (which, depending on where we're talking about, could change at any time).

Upvotes: 3

user149341
user149341

Reputation:

Keep in mind that what __user fundamentally means is that the pointer points to user address space. It doesn't necessarily have to have been supplied by the user, but it does mean that it can't be a pointer to kernel memory.

On some architectures, such as x86 and x86_64, kernel and user memory live in the same address space, and are distinguished only by a boundary (i.e, kernel above 3G in x86). On these architectures, __user is primarily used as an annotation to remind developers that it should be treated with care. However, this is not always the case! Some architectures, such as PowerPC, actually use a separate address space entirely for the kernel; on these architectures, __user takes on a new importance, as it indicates that a special function (like copy_from_user) must be used to access the pointer. Normal memory access will not work at all, as it will attempt to dereference the pointer in the kernel address space, where it is likely not valid.

Upvotes: 5

Related Questions