Linux内核访问用户空间文件:get_fs()/set_fs()的使用
测试环境:Ubuntu 14.04+Kernel 4.4.0-31
关键词:KERNEL_DS、USER_DS、get_fs()、set_fs()、addr_limit、access_ok。
参考代码:https://elixir.bootlin.com/linux/v4.4/source
内核空间和用户空间交换数据的方式有很多,比如用户空间发起的系统调用、proc、虚拟文件系统等。
内核空间主动发起的有get_user/put_user、信号、netlink等。
这里介绍get_user/put_user的使用以及背后的原理。
1. 构造测试环境:Ubuntu下创建module
要让内核空间主动发起,需要创建一个module,然后插入到内核中。
从内核中发起创建kernel_file,并写入内容。
最后从用户空间进行验证。
1.1 测试源码
首先,编写module源码:
#include <linux/module.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/uaccess.h>static char buf[] ="来自内核的访问\n"; static char buf1[32];int __init test_init(void) {struct file *fp;mm_segment_t fs;loff_t pos;printk("test enter\n");fp =filp_open("/home/jenkins/lubaoquan/test/kernel_file",O_RDWR | O_CREAT,0644);if (IS_ERR(fp)){printk("create file error\n");return -1;}fs =get_fs();set_fs(KERNEL_DS);pos =0;vfs_write(fp,buf, sizeof(buf), &pos);pos =0;vfs_read(fp,buf1, sizeof(buf), &pos);printk("Write contet=%s\n",buf1);filp_close(fp,NULL);set_fs(fs);return 0; } void __exit test_exit(void) {printk("test exit\n"); }module_init(test_init); module_exit(test_exit);MODULE_LICENSE("GPL");
编写Makefile文件:
obj-m :=read_userspace.o #要生成的模块名 read_userspace-objs:= read_userspace_file.o #生成这个模块名所需要的目标文件KDIR := /lib/modules/`uname -r`/build PWD := $(shell pwd)default:make -C $(KDIR) M=$(PWD) modulesclean:rm -rf *.o *.cmd *.ko *.mod.c .tmp_versions Module.symvers modules.order
1.2 编译
执行make命令,就可以得到read_userspace.ko文件。
1.3 测试
sudo insmod read_userspace.ko-----------------插入模组
sudo lsmod | grep read_userspace--------------验证是否插入成功
sudo rmmod read_userspace----------------------移除模组
测试结果如下,可以看出kernel_file是由root用户创建的。
可以看出内容符合预期。
3. 代码分析
fp =filp_open("/home/jenkins/lubaoquan/test/kernel_file",O_RDWR | O_CREAT,0644);---------------------创建用户空间文件,获取文件句柄。if (IS_ERR(fp)){printk("create file error\n");return -1;}fs =get_fs();----------------------------------------------------------------------------------------获取当前线程的thread_info->addr_limit。set_fs(KERNEL_DS);-----------------------------------------------------------------------------------将能访问的空间thread_info->addr_limit扩大到KERNEL_DS。pos =0;vfs_write(fp,buf, sizeof(buf), &pos);----------------------------------------------------------------调用vfs_write写内容pos =0;vfs_read(fp,buf1, sizeof(buf), &pos);----------------------------------------------------------------调用vfs_read读取内容printk("Write contet=%s\n",buf1);filp_close(fp,NULL);---------------------------------------------------------------------------------关闭文件set_fs(fs);------------------------------------------------------------------------------------------将thread_info->addr_limit切换回原来值
4. 原理
4.1 set_fs和get_fs
有下面代码可知KERNEL_DS范围很大,到0xffffffffffffffff。
而USER_DS范围较小,到0x7ffffffff000。
由Linux内存分布图可知,KERNEL_DS意味着可以访问整个内存所有空间,USER_DS只能访问用户空间内存。
通过set_fs可以改变thread_info->addr_limit的大小。
/** For historical reasons, the following macros are grossly misnamed:*/ #define KERNEL_DS ((mm_segment_t) { ~0UL }) /* cf. access_ok() */ #define USER_DS ((mm_segment_t) { TASK_SIZE-1 }) /* cf. access_ok() */#define VERIFY_READ 0 #define VERIFY_WRITE 1#define get_ds() (KERNEL_DS) #define get_fs() (current_thread_info()->addr_limit) #define set_fs(x) (current_thread_info()->addr_limit = (x))#define TASK_SIZE DEFAULT_TASK_SIZE
4.2 vfs_write和vfs_read对addr_limit的检查
将代码修改一下,不进行addr_limit扩大,看看结果如何。
#include <linux/module.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/uaccess.h>static char buf[] ="来自内核的访问\n"; static char buf1[32];int __init test_init(void) {struct file *fp;mm_segment_t fs;loff_t pos;int ret;printk("KERNEL_DS=0x%llx USER_DS=0x%llx get_fs()=0x%llx\n", KERNEL_DS, USER_DS, get_fs());fp =filp_open("/home/jenkins/lubaoquan/test/kernel_file",O_RDWR | O_CREAT,0644);if (IS_ERR(fp)){printk("create file error\n");return -1;}fs =get_fs();//set_fs(KERNEL_DS); pos =0;printk("fp=%p, buf=%p get_fs()=0x%llx\n", fp, buf, get_fs());ret = vfs_write(fp,buf, sizeof(buf), &pos);printk("ret=%d\n", ret);pos =0;printk("fp=%p, buf1=%p\n", fp, buf1);ret = vfs_read(fp,buf1, sizeof(buf), &pos);printk("ret=%d Write contet=%s\n", ret, buf1);filp_close(fp,NULL);//set_fs(fs); return 0; } void __exit test_exit(void) {printk("test exit\n"); }module_init(test_init); module_exit(test_exit);MODULE_LICENSE("GPL");
执行结果如下,可以看出fp、buf、buf1都位于内核空间。而当前空间的get_fs()为0x7ffffffff000,这些地址都超出当前空间。
所以vfs_read和vfs_write返回值都是-14,即“Bad address”。
[49001.240705] KERNEL_DS=0xffffffffffffffff USER_DS=0x7ffffffff000 get_fs()=0x7ffffffff000 [49001.240713] fp=ffff8800cae06900, buf=ffffffffc0305000 get_fs()=0x7ffffffff000 [49001.240714] ret=-14 [49001.240715] fp=ffff8800cae06900, buf1=ffffffffc03053c0 [49001.240716] ret=-14 Write contet= [49013.464812] test exit
简单看一下vfs_write和vfs_read,两者都调用access_ok对地址合法性进行检查,严禁addr大于当前get_fs()。
此处buf和buf1都不满足条件,所以返回-EFAULT。
#define __access_ok(addr, size, segment) \ ({ \__chk_user_ptr(addr); \(likely((unsigned long) (addr) <= (segment).seg) \&& ((segment).seg == KERNEL_DS.seg \|| likely(REGION_OFFSET((unsigned long) (addr)) < RGN_MAP_LIMIT))); \ }) #define access_ok(type, addr, size) __access_ok((addr), (size), get_fs())ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos) { ...if (unlikely(!access_ok(VERIFY_READ, buf, count)))return -EFAULT; ... }ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos) { ...if (unlikely(!access_ok(VERIFY_WRITE, buf, count)))return -EFAULT; ... }
将测试代码红色部分打开,扩大addr_limit空间。
可以看出当前thread_info->addr_limit变成了0xffffffffffffffff。
所以vfs_write和vfs_read的access_ok检查得以通过,程序得到正确执行。
[48937.547119] KERNEL_DS=0xffffffffffffffff USER_DS=0x7ffffffff000 get_fs()=0x7ffffffff000 [48937.547138] fp=ffff8800c8300c00, buf=ffffffffc02f3000 get_fs()=0xffffffffffffffff [48937.547155] ret=23 [48937.547158] fp=ffff8800c8300c00, buf1=ffffffffc02f33c0 [48937.547164] ret=23 Write contet=\xffffffe6\xffffff9d\xffffffa5\xffffff9d\xffffffa5\xffffffe8\xffffff87\xffffffaa\xffffff87\xffffffaa\xffffffe5\xffffff86\xffffff85\xffffff86\xffffff85\xffffffe6\xffffffa0\xffffffb8\xffffffa0\xffffffb8\xffffffe7\xffffff9a\xffffff84\xffffff9a\xffffff84\xffffffe8\xffffffae\xffffffbf\xffffffae\xffffffbf\xffffffe9\xffffff97\xffffffae\xffffff97\xffffffae [48937.547164] [48940.600703] test exit
5. 小结
只有使用上面的方法,才能在内核中使用open,write等的系统调用。
其实这样做的主要原因是open,write的参数在用户空间,在这些系统调用的实现里需要对参数进行检查,就是检查它的参数指针地址是不是用户空间的。
系统调用本来是提供给用户空间的程序访问的,所以,对传递给它的参数(比如上面的buf、buf1),它默认会认为来自用户空间。
在vfs_write()函数中,为了保护内核空间,一般会用get_fs()得到的值来和USER_DS进行比较,从而防止用户空间程序“蓄意”破坏内核空间。
为了解决这个问题, set_fs(KERNEL_DS)将其能访问的空间限制扩大到KERNEL_DS,这样就可以在内核顺利使用系统调用了!
内核使用系统调用参数肯定是内核空间,为了不让这些系统调用检查参数所以必须设置 set_fs(KERNEL_DS)才能使用该系统调用。
vfs_write的流程可调用access_ok,而access_ok会判断访问的buf是否在0~addr_limit之间,如何是就ok;否则-EFAULT,这显然是为用户准备的检查。
addr_limit一般设为USER_DS,在内核空间,buf肯定>USER_DS,必须修改addr_limit,这就是set_fs的由来。
转载于:https://www.cnblogs.com/arnoldlu/p/8879800.html
Linux内核访问用户空间文件:get_fs()/set_fs()的使用相关推荐
- linux内核与用户空间的九种通信机制
目前Linux提供了9种机制完成内核与用户空间的数据交换,分别是内核启动参数.模块参数与 sysfs.sysctl.系统调用.netlink.procfs.seq_file.debugfs和relay ...
- Openwrt按键检测分析-窥探Linux内核与用户空间通讯机制netlink使用
首先看一下Openwrt系统中关于按键功能的使用和修改,以18.06版本为例 按键功能实现在脚本中, 比如18.06/package/base-files/files/etc/rc.button/re ...
- 一种内核到用户空间的高效数据传输技术
级别: 中级 桂 剑 (guijian@cn.ibm.com), IBM 中国开发中心,Linux Performance 项目软件工程师 2006 年 12 月 28 日 Relay 是一种从 Li ...
- 是谁不让访问用户空间
近日在给NDB增加新功能,让它可以访问用户空间.但在ARM平台上遇到一个问题,如果CPU中断时位于内核空间,那么访问任何用户空间的地址都失败. 失败的基本症状是调试器中打印出一串串的问号. 而JTAG ...
- Linux启动时间优化-内核和用户空间启动优化实践
小伙伴们有兴趣想了解内容和更多相关学习资料的请点赞收藏+评论转发+关注我,后面会有很多干货.我有一些面试题.架构.设计类资料可以说是程序员面试必备!所有资料都整理到网盘了,需要的话欢迎下载!私信我回复 ...
- linux内核打开文件数,放开Linux内核对用户进程可打开文件数和TCP连接的限制
一. 检查linux内核 uname -a lsb_release -a 二.用户进程可打开文件数限制 1)vim /etc/security/limits.conf * - n ...
- Linux内核和用户空间通信的方法
Linux内核和用户空间通信的方法(二)- 使用netlink 作者:Kendo 2006-9-3 这是一篇学习笔记,主要是对<Linux 系统内核空间与用户空间通信的实现与分析>中的 ...
- I/O流(包括操作系统与内核,用户空间),I/O工作原理,Java I/O流的设计及Java IO系统
文章目录 一.操作系统与内核 1.1操作系统 1.2内核 1.3 关系图 二.内核空间和用户空间 2.1:目的: 2.2.内核空间(Kernel-space): 2.3.用户空间(User-space ...
- linux kernel and user space通信机制,Linux内核与用户空间通信机制研究.pdf
ISSN 1009-3044 E-mail:info@CCCC.net.CR ComputerKnowledgeandTechnology电脑知识与技术 http://www.dnzs.net.cn ...
最新文章
- 第一篇博客,java学生管理系统(挑战全网最全)
- 动态划分VLAN的方法中不包括(23)。【答案】C
- python 编程一日一练-「每日一练」巧用python生成随机数
- VTK序列图像的读取
- 四大微博OAuth认证
- Java异常中受检异常非受检异常与RuntimeException异常关系
- 关于齐次坐标的理解记录
- Ruby Shanghai 2014年终聚会总结
- vue-source的实例
- js读写php文件,在js中如何读取本地文件
- 同步VS异步,阻塞VS非阻塞
- Spring整合RabbitMQ
- python面试自我介绍_如何拿到半数面试公司Offer——我的Python求职之路
- Canonical发布企业级Kubernetes 1.14重点让使用者简单管理
- 学习“用 深度卷积神经域 预测蛋白质二级结构”(1)
- 线性代数学习指导与MATLAB编程实践,21世纪高等理工科重点课程辅导丛书:线性代数学习指导与MATLAB编程实践...
- EMERGENCYEUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEYRE NOT
- Go代码调优利器-火焰图
- 用python在excel中做批量计算(包括单元格为空值时的处理情况)
- 铱金属配合物Ir(dpbpa)2bphen+PF6-|Ir(dtbpa)3|Ir(dtq)2bpy+PF6-的合成
热门文章
- 用Exchange 2000开发企业办公自动化系统
- 如何订立合规的电子劳动合同 ——关于人社部发布《电子劳动合同订立指引》的深度解读(一)
- 拼多多无货源开店怎么样?
- 怎么样才能变得自律呢?
- 10进制与26进制转化
- 360推广如何提高权重
- 有效字符区域 获取textview_Android之TextView 详细字典
- Google的cAdvisor
- 关于程序员与测试员的关系
- (一) 常见异常的捕获 // // (1) 编写一个程序,分别生成ArrayIndexOutOfBoundsException类型和ArithmeticException类型的异常。