copy_{to,from}_user Vs memcpy

简介: 熟悉Linux内核的开发人员都知道,Linux下的进程地址空间分为内核空间和用户空间,对于32bit系统来说,典型的空间划分为:1G(内核空间)+3G(用户空间)

Linux地址空间


熟悉Linux内核的开发人员都知道,Linux下的进程地址空间分为内核空间和用户空间,对于32bit系统来说,典型的空间划分为:1G(内核空间)+3G(用户空间),对于这种划分来说,内核空间地址范围:0xC000 0000 ~0xFFFF FFFF,用户空间地址范围为:0x0000 0000 ~ 0xBFFF FFFF。当然,为了需要,我们可以将地址空间配置成其他方式,比如2G:2G等等。


网络异常,图片无法展示
|

# Linux虚拟地址机制


大家知道,Linux进程中使用的地址是虚拟地址,进程在操作这些地址时,MMU通过页表完成虚拟地址物理地址之间的映射,如果页表miss,就触发缺页中断,然后,内核通过缺页异常处理机制分配可用的物理页,更新关于虚拟地址和物理地址页表项,从而完成整个虚拟地址的操作过程。


网络异常,图片无法展示
|


简单明确了Linux系统关于地址空间的管理机制之后,下面,就来具体讨论一下今天的主题:在内核中,如何实现内核空间和用户空间的数据交互。


内核APIs


内核提供了用于内核空间和用户空间数据相互拷贝的API:


  • access_ok:用于检查用户空间指针的有效性;


  • get_user:用于从用户空间拷贝一个简单的变量到内核空间,比如,1、2、4、8字节的变量。


  • put_user:用于从内核空间拷贝一个简单的变量到用户空间。


  • copy_to_user:用于从内核空间拷贝一个块数据到用户空间。


  • copy_from_user:用于从用户空间拷贝一块数据到内核空间。


上面这些API一般都和具体的CPU架构相关,比如,X86架构相关的实现都在/linux/arch/x86/include/asm/uaccess.h和/linux/arch/x86/lib/usercopy_32.c、usercopy_64.c这些文件中。


get/put_user与copy_(to,from}_user的区别是所拷贝的数据类型不同,前者用于简单的数据,比如,int、long、char等,而后者用于一整块数据的拷贝。


网络异常,图片无法展示
|


网络异常,图片无法展示
|


下面详细的讲解一下上述各个API的具体作用。


access_ok


函数的原型如下:


access_ok(type, addr, size)


  • type: VERIFY_WRITE、VERIFY_READ检查一段地址区域是否可读或者可写。


  • 用于指定检查以addr为起始地址,长度为size为的用户空间是否可读或者可写。


  • 如果可正常访问,函数返回0,否则返回-EFAULT。


  • 该函数仅用于检测用户空间,不用于内核空间。


get/put_user


函数原型如下: get/_user( x, ptr ); put/_user( x, ptr );


get_user和put_user用于在内核空间和用户空间拷贝一个简单的数据,对于像结构体这样的数据需要使用copy_{to,from}_user。access_ok函数会检测ptr指针的有效性,之后,通过get_user_x或者 put_user_x完成数据拷贝。相对于copy_{to,from}_user,对于小型数据的拷贝,这两个函数的性能更好。两个函数都是执行成功之后返回0。


copy_{to, from}_user


函数的原型如下:


copy_to_user(to, from, n)
copy_from_user(to, from, n)


两个函数用于在内核空间和用户空间之间拷贝数据,这个函数都是成功时返回0,失败时返回大于0的数据,表明未能拷贝成功的字节个数。两个函数都是通过access_ok来检测用户空间的地址的有效性,之后的数据拷贝实现,与CPU的体系结构有关,比如,ARM平台下,


_copy_from_user(void *to, const void __user *from, unsigned long n)
{
  unsigned long res = n;
  might_fault();
  if (likely(access_ok(VERIFY_READ, from, n))) {
    kasan_check_write(to, n);
    res = raw_copy_from_user(to, from, n);
  }
  if (unlikely(res))
    memset(to + (n - res), 0, res);
  return res;
}


最终会调用arm平台下的raw_copy_from_user:


raw_copy_to_user(void __user *to, const void *from, unsigned long n)
{
#ifndef CONFIG_UACCESS_WITH_MEMCPY
  unsigned int __ua_flags;
  __ua_flags = uaccess_save_and_enable();
  n = arm_copy_to_user(to, from, n);
  uaccess_restore(__ua_flags);
  return n;
#else
  return arm_copy_to_user(to, from, n);
#endif
}


copy_{to,from}_user Vs memcpy


还有一个经常被误用的API,那就是,memcpy,大家可能听过,或者看过memcpy不能用于用户空间和内核空间之间的数据交互,那原因是什么呢?本节将会详细的讲解copy_{to,from}_user与memcpy的区别和联系。


之所以在进行内核空间与用户空间拷贝时,使用copy_{to,from}_user,主要有两个原因:


  • copy*函数会通过access_ok检测用户空间地址有效性。这样可以防止内核操作非法的地址,比如,用户传过来的是内核空间的地址,如果内核检测该地址,那么就会写坏该地址指向的内容,造成系统崩溃。同时,也会系统安全问题,比如,黑客会故意传入一个内核地址,并且这个地址保存着关键信息,比如用户id,内核在没有检测的情况下,会修改地址中的内容,比如,将用户id写为0,那么黑客就可以获取系统的最高权限。参考CVE-2017-5123内核漏洞


  • copy*函数可以在发生page fault时,进行自我修复。所谓的page fault时,是说用户空间的地址是一个非法的地址(非栈,非堆地址),这是内核在访问该地址时,MMU会检测到该地址的非法性,从而产生一个异常。对于该异常,内核有两种处理方式:


  • 向当前进行发送SIGSEGV信号,并抛出Oops信息,如果内核开启了/proc/sys/kernel/panic_on_oops,那么内核直接panic。
  • 处理该异常,正常返回。


  • 如果这时使用的是copy_{to,from}_user函数的话,其会捕获该异常,并正常返回到用户空间。但是如果使用的是memcpy的话,对不起,内核直接Oops或者panic。


  • 为了更加安全,硬件加入了PAN功能(Privileged Access Never),其可以限制内核访问用户空间的能力,所以,访问之前必须通过硬件指令关闭PAN,访问完之后,开启PAN,ARM V8架构增加了这项功能。只有copy_{to,from}_user函数具有打开和关闭PAN的能力,所以,对于开启了PAN功能的平台可,copy_{to,from}_user是唯一的选择。


所以,对于一个合格的内核开发人员,在涉及到用户空间和内核空间数据拷贝的场景时,杜绝使用memcpy是避免出现bugs的首要注意事项。



相关文章
Tp5 SplFileObject::__construct(/tmp/phpvuOdFd): failed to open stream: No such file or director解决方法
Tp5 SplFileObject::__construct(/tmp/phpvuOdFd): failed to open stream: No such file or director解决方法
134 0
|
6月前
|
存储 Python
copy
【6月更文挑战第10天】
40 0
|
7月前
|
算法 Unix Linux
select函数中的文件描述符(File Descriptor)范围
select函数中的文件描述符(File Descriptor)范围
95 0
select函数中的文件描述符(File Descriptor)范围
|
7月前
|
C#
C# File.Copy介绍
C# File.Copy介绍
|
7月前
|
算法 C++ 容器
【C++11算法】find_if_not、 copy_if、copy_n
【C++11算法】find_if_not、 copy_if、copy_n
132 0
解决办法:error LNK2005: "void * __cdecl operator new(unsigned int)" 已经在 LIBCMTD.lib(new.obj) 中定义
解决办法:error LNK2005: "void * __cdecl operator new(unsigned int)" 已经在 LIBCMTD.lib(new.obj) 中定义
278 0
|
C++
C++ std::map报错的解决办法:_Rb_tree_increment(std::_Rb_tree_node_base const
C++ std::map报错的解决办法:_Rb_tree_increment(std::_Rb_tree_node_base const
1301 0
Can‘t read file : End of file found 文件:txn_current、current svn无法正常读取文件
Can‘t read file : End of file found 文件:txn_current、current svn无法正常读取文件
Can‘t read file : End of file found 文件:txn_current、current svn无法正常读取文件
|
存储 安全 Linux
copy_{to, from}_user()的思考与服务器运用
引言我们对copy_{to,from}_user()接口的使用应该是再熟悉不过吧。基本Linux书籍都会介绍它的作用。毕竟它是kernel space和user space沟通的桥梁。所有的数据交互都应该使用类似这种接口。
2427 0

热门文章

最新文章