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

I read about a specifier __user

in Linux kernel that is used to tag function parameters for Sparse to validate function arguments.

In particular, this

int foo1( char * buf );

      

no need to pass a pointer to user space for buf

.

How about another way? IN

int foo2( char __user * buf );

      

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

+3


source to share


2 answers


Keep in mind that the main thing __user

means that the pointer is pointing to the user's address space. It does not have to be provided by the user, but it does mean that it cannot be a pointer to kernel memory.



On some architectures, such as x86 and x86_64, the kernel and user memory live in the same address space and differ only by the boundary (i.e. the kernel is higher than 3G in x86). On these architectures, it __user

is mainly used as an annotation to remind developers to use it with caution. However, this is not always the case! Some architectures, such as PowerPC, actually use a separate address space for the kernel; __user

takes on a new meaning on these architectures as it indicates that a special function (eg copy_from_user

) should be used to access the pointer . Normal memory access won't work at all, as it will try to dereference the pointer in the kernel address space, where it is probably not valid.

+3


source


A __user

pointer is likely to be passed to copy_from_user

, copy_to_user

or something similar.

In x86-64 they 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)

      

Pay attention to cmpq TI_addr_limit(%rax),%rcx

which makes sure your pointer is less than or equal to current_thread_info()->addr_limit

.

This limit is set by a macro set_fs()

that is called in several places. In particular, it can be called with USER_DS

, which is defined as:

#define USER_DS         MAKE_MM_SEG(TASK_SIZE_MAX)

      



TASK_SIZE_MAX

defined as:

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

      

On x86-64, the kernel is mapped to a high (or negative) virtual address space. Therefore, the kernel pointer will not be able to do this check.


In conclusion, I think the pointer __user

should not actually be provided by the user, but is guaranteed to be a valid user space address. I believe the only limitation is that you must be sure that the pointer you pass is valid in your current context (which, depending on what we're talking about, could change at any time).

+2


source







All Articles