深入理解系统调用
- 找一个系统调用,系统调用号为学号最后2位相同的系统调用
- 通过汇编指令触发该系统调用
- 通过gdb跟踪该系统调用的内核处理过程
- 重点阅读分析系统调用入口的保存现场、恢复现场和系统调用返回,以及重点关注系统调用过程中内核堆栈状态的变化
一、查找系统调用
学号后两位为92,对应的十六进制数位0x5c(后面汇编有用)
在linux内核中查找系统调用,路径为 arch/x86/entry/syscalls/syscall_64.tbl,对应的是64位的系统调用;
如图92号系统调用,92
对应的系统调用为
__x64_sys_chown
,其对应的
API
函数为
chown
。
由文档可知chown可以用来改变文件的所有者或组。
使用示例:
1,更改文件的所有者:
Chown tim program.c
文件 program.c 的所有者更改为tim。作为所有者,tim 可以使用 chmod 命令允许或拒绝其他用户访问 program.c。
2,更改目录的所有者:
chown -R john:build /tmp/src
将目录 /tmp/src 中所有文件的所有者和组更改为用户 john 和组 build
- R 递归式地改变指定目录及其下的所有子目录和文件的拥有者。
- v 显示chown命令所做的工作。
接着如下图在./linux-5.4.34/tools/include/nolibc/nolibc.h中找到了chown的实现
二、通过汇编指令触发92号系统调用
首先使用C语言出发系统调用,测试效果
本系统中有两个用户分别为akira 和 root,
username user id group id
akira 1000 1000
root 0 0
chown_test.c
#include#include #include int main(){ chown("/home/akira/Downloads/test.txt", 0, 0); printf("Done!\n"); return 0; }
chown_test2.c
#include#include #include int main(){ chown("/home/akira/Downloads/test.txt", 1000, 1000); printf("Done!\n"); return 0; }
编译,记得加-static参数
Test_chown1 用来将所有者和组变为0 0,即root root;
Test_chown2 用来将所有者和组变为1000 1000, 即 akira akira;
因为涉及到root用户和组,所以执行chown_test的时候需要使用sudo,提升权限;
测试结果如图所示:
使用内嵌汇编进行系统调用
系统调用号一定要转成16进制形式
代码如下:
asm_chown.c
#include#include #include int main(){ char path[] = "/home/akira/Downloads/test.txt"; int ret = -15; unsigned int owner = 0, group = 0; //chown("/home/akira/Downloads/test.txt", 0, 0); unsigned long number = 92; asm volatile( "movq %1, %%rdi\n" "movq %2, %%rsi\n" "movq %3, %%rdx\n" "movl $0x5c, %%eax\n\t" "syscall\n\t" "movq %%rax,%0\n\t" :"=m"(ret) :"p"(path), "m"(owner), "m"(group) //:"rax", "rdi", "rsi", "rdx" ); //if(ret) printf("Done: %d\n", ret); return 0; }
asm_chown2.c
#include#include #include int main(){ char path[] = "/home/akira/Downloads/test.txt"; int ret = -15; unsigned int owner = 1000, group = 1000; //chown("/home/akira/Downloads/test.txt", 0, 0); // unsigned long number = 92; asm volatile( "movq %1, %%rdi\n" "movq %2, %%rsi\n" "movq %3, %%rdx\n" "movl $0x5c, %%eax\n\t" "syscall\n\t" "movq %%rax,%0\n\t" :"=m"(ret) :"p"(path), "m"(owner), "m"(group) //:"rax", "rdi", "rsi", "rdx" ); //if(ret) printf("Done: %d\n", ret); return 0; }
编译
上图即为测试结果。
三、通过gdb跟踪该系统调用的内核处理过程
首先将之前的编译好的asmChown1和asmChown2以及测试文件test.txt复制到rootfs/home/ 目录下,然后重新打包文件系统
terminal1
$ find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../rootfs.cpio.gz $ qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz -S -s -nographic -append "console=ttyS0"
terminal2 $ cd linux-5.4.34 $ gdb vmlinux $ target remote:1234
因为实验环境无法添加用户以及组,这里直接运行asmChown1,设置断点查看效果;
首先可以用返回变查看使用此代码触发92号系统调用的内部汇编代码,如下图,可见和之前的汇编代码类似,
直接调用了0x5c,即92号系统调用。
启动调试后,模拟实验环境home目录
设置断点 __x64_sys_chown(chown对应的entry_point)
执行asmChown1
可见程序直接返回了do_fchownat();
触发断点时的内核堆栈
结束运行时的内核堆栈
在linux代码中1寻找do_fchownat(),实现如下
int do_fchownat(int dfd, const char __user *filename, uid_t user, gid_t group, int flag) { struct path path; int error = -EINVAL; int lookup_flags; if ((flag & ~(AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) != 0) goto out; lookup_flags = (flag & AT_SYMLINK_NOFOLLOW) ? 0 : LOOKUP_FOLLOW; if (flag & AT_EMPTY_PATH) lookup_flags |= LOOKUP_EMPTY; retry: error = user_path_at(dfd, filename, lookup_flags, &path); if (error) goto out; error = mnt_want_write(path.mnt); if (error) goto out_release; error = chown_common(&path, user, group); mnt_drop_write(path.mnt); out_release: path_put(&path); if (retry_estale(error, lookup_flags)) { lookup_flags |= LOOKUP_REVAL; goto retry; } out: return error; }
阅读代码可以发现,chown除了92号系统调用,并没有触发其他的系统调用。