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/