Yanyg - Software Engineer

Linux内核copy_to_user/copy_from_user用户态地址分析

目录

偶有一天,老吴抛出一个问题:用kmalloc分配一块内存,能否将其用于copy_to_user的用户态地址?

多年前跟踪过copy_to_user、ioctl系列函数,记得类似函数进行有效检查、允许缺页中断与堆栈溢出,之后执行强制地址转换使用,其他就无差异了。聊时答复kmalloc直接使用即可,与老吴聊之后,总觉得不踏实,之后写了一个简单程序测试:

char *p = (char*)kmalloc(128, GFP_USER);
if (p) {
        strncpy(p, "Check Copy To User", 12);
        n = copy_to_user(p, "KKKK", 4);
        p[12] = '\0';
}

编译模块加载,并打印字符串p的结果。程序未报错,但是KKKK未能拷贝。后与培聊,答复之前版本ioctl直接使用最后一个参数传递的arg是正常的,但最近版本工作异常了。

本文分析copy_to_user、copy_from_user、ioctl的具体实现,并分析相关防范技术。

后续技术答复要谨慎进行。

1 copy_to_user分析

static __always_inline unsigned long __must_check
copy_to_user(void __user *to, const void *from, unsigned long n)
{
        int sz = __compiletime_object_size(from);

        kasan_check_read(from, n);
        might_fault();

        if (likely(sz < 0 || sz >= n)) {
                check_object_size(from, n, true);
                n = _copy_to_user(to, from, n);
        } else if (!__builtin_constant_p(n))
                copy_user_overflow(sz, n);
        else
                __bad_copy_user();

        return n;
}

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

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

#define access_ok(type, addr, size)                                     \
({                                                                      \
        WARN_ON_IN_IRQ();                                               \
        likely(!__range_not_ok(addr, size, user_addr_max()));           \
})

#define __range_not_ok(addr, size, limit)                               \
({                                                                      \
        __chk_user_ptr(addr);                                           \
        __chk_range_not_ok((unsigned long __force)(addr), size, limit); \
})

static inline bool __chk_range_not_ok(unsigned long addr, unsigned long size, unsigned long limit)
{
        /*
         * If we have used "sizeof()" for the size,
         * we know it won't overflow the limit (but
         * it might overflow the 'addr', so it's
         * important to subtract the size from the
         * limit, not add it to the address).
         */
        if (__builtin_constant_p(size))
                return unlikely(addr > limit - size);

        /* Arbitrary sizes? Be careful about overflow */
        addr += size;
        if (unlikely(addr < size))
                return true;
        return unlikely(addr > limit);
}

__chk_user_ptr 只有在__CHECKER__

2 References

LWN x86: Supervisor Mode Access Prevention
https://lwn.net/Articles/517251/