添加关于文件拼接的系统调用
前言
近一年来没写过博客了,从今天开始打算重操旧业。主要原因还是因为逐渐地意识到了写博客的重要性。许多知识如果没有得到有效的记录也不过是过往烟云,再加上如今课业繁重,操作系统与计算机网络两座大山,加上自己对嵌入式系统的浓厚兴趣,我必须得做点详细的文章,以此来review。
背景
本次记录源于Hdu操作系统的第一个实验作业,要求在Linux内核中添加入自己的一个系统调用,功能在备选方案中自定。
观览一圈下来,发现大部分的功能实现其实并不难,大多其实是基于kernel的一些现有函数的包装,说不上什么难度。挑来跳去,我最后选了一个关于文件拼接的题目,题目如下:
把指定文件1的内容追加到指定文件2的末尾。
初看题目乍看简单,不就是文件copy吗~开始Coding…
过程
1. 添加系统调用号
在syscall_64.tbl(linuxXXX/arch/x86/entry/syscalls/syscall_64.tbl)下添加内容:
1 | // ... |
| 值 | 说明 |
|---|---|
| 448 | 系统调用号 |
| common | 适用机器位数(x32/x64/common) |
| append_file | 暴露给用户的函数名 |
| sys_append_file | 实际的函数名称 |
当然调用号需要基于实际情况而定(每个版本的linux都会发生变化)
2. 添加函数声明
在syscalls.h(linuxXXX/include/kernel/syscalls.h)下添加内容:
1 | asmlinkage long sys_append_file(const char *file1, const char *file2); |
3. 添加函数定义
在sys.c(linuxXXX/kernel/sys.c)中添加:
1 | /** |
Q:为什么在sys.c中添加?
A:主要是方便…实际应该在自己的.c中写,不过这种做法要写额外的makefile,不过似乎可以加快后期的编译工作?(猜想)
Q:SYSCALL_DEFINEx是什么?
A:参考Linux系统调用(syscall)原理
4. 开始我们的工作
终于,我们可以开始编写我们的函数啦!但是问题来了:我在内核态下可以使用read,write,open之类的函数吗??开始STFW…
终于了解到了一个叫做filp_open的函数,函数原型如下:
1 | struct file *filp_open(const char *filename, int flags, umode_t mode) |
函数功能同open,其中mode我们可以暂且先忽略(置0),原因是其主要用作给新文件设定权限,但是我们在此不存在新文件的创建,因此无需考虑。
1 | target_p = filp_open(target, O_WRONLY | O_APPEND, 0); |
同理我们可以看到有类似read,write的函数:
1 | ssize_t vfs_read(struct file* filp, char __user* buffer, size_t len, loff_t* pos); |
我们参考相关文档,可以写出个大概,主要逻辑如下:
1 | oldfs = get_fs(); |
此时看到这里,想必你和我一定都是一脸懵逼的…主要原因在于set_fs与get_fs是什么?
又是一波STFW,好了,其实这是为了可以访问整个内核空间,避免检查。
详情请见get_fs()和set_fs()解析。
噼里啪啦终于正好了,等我们兴高采烈地敲下make -j8编译后,糟糕的事情来了:
implicit declaration of function ‘get_fs’
WTF!和说好的不一样哈?原因竟然是在内核版本5.10版本后set_fs等宏被移除了!
详情请见Linux 5.10 finally ditches decades-old tool that caused security bugs
笑死,感觉自己是小丑。那此时我们就陷入迷茫了,What should we do? STFW!
1 | ssize_t kernel_read(struct file* filp, char __user* buffer, size_t len, loff_t* pos); |
Yes!! 我们马上就要成功了
1 | make -j8 |
已杀死??有点窒息。看一眼dmesg: unable to handle page fault for address,似乎是页面错误,这不太对啊,但是仔细想想看前面关于内存访问的操作…
没错!在filp_open函数中,我们直接使用了用户态传入的文件名指针,而该指针指向的正是用户态的内存空间,而在kernel dev中,这样的行为是禁止的!因此,我们需要使用类如copy_from_user的函数来帮助我们把文件名拷贝入内核空间!
在这里,针对字符串,我们采用的是内核下的字符串拷贝函数:
1 | long strncpy_from_user (char * dst, const char __user * src, long count); |
最终代码如下:
1 |
|
测试代码如下:
1 |
|
a.txt:
abc
b.txt:
efg
result(a.txt):
abcefg
参考文献
http://gityuan.com/2016/05/21/syscall/
https://stackoverflow.com/questions/1184274/read-write-files-within-a-linux-kernel-module/53917617#53917617
http://blog.chinaunix.net/uid-24237502-id-35023.html
https://www.zdnet.com/article/linux-5-10-finally-ditches-decades-old-tool-that-caused-security-bugs/