Linux 内核添加自定义系统调用

网上的教程[1]大多使用老版本内核,许多内容已经不再适用了。本文依托于我的项目 mycall 进行讲解,旨在把自己踩过的坑全部记录下来,具体实现请参考源码。实验在 ubuntu 20.04 amd64 虚拟机(内核版本 5.15.0-105-generic)中进行,如有错误或建议(如不适用于 6.x 内核),欢迎在我的 Github Page repo 提 issue。 本文将先后介绍给内核添加自定义系统调用的两种方式: 通过内核模块将系统调用插入正在运行的内核中。 将系统调用添加到内核源码中,再重新编译安装内核。 通过内核模块添加系统调用 这个方法最简单但是坑也最多,因为内核开发组显然不希望我们通过内核模块修改/覆盖系统调用[2],并且做了诸多限制,为此我们只能用一些 trick。 定义系统调用 从 Linux 4.17 开始,x86 下系统调用服务例程只接收 struct pt_regs * 一个参数[9]。因此系统调用的定义为如下形式: 1 2 3 4 asmlinkage long sys_mycall(struct pt_regs *regs) { ... } 根据 Linux x86 calling convention,Linux 系统调用通过寄存器传递参数:rax 存储系统调用号,rdi 存储第一个参数, etc. pt_regs 就是一个包含了寄存器值的结构体[10],需要从中读取参数。 获取系统调用表地址 要插入系统调用,首先需要能够找到系统调用表的地址 sys_call_table。可惜 2.6 版本以后内核就不再 export sys_call_table 了[3],只能寻求其他办法: 从 System.map 读取 编译内核时生成的内核符号表中包含系统调用表的地址,可以通过以下命令获取: 1 cat /boot/System.map-$(uname -r) | grep sys_call_table 但是若内核开启了 KASLR (Kernel Address Space Layout Randomization),实际地址会和 System....

April 27, 2024 · 3 min · 501 words

Hadoop/HDFS 虚拟机集群部署及 MapReduce 实验

大数据软件生态分为三方面: 数据存储:Hadoop HDFS, HBase, KUDU, 阿里云 OSS, AWS S3 数据计算:Hadoop MapReduce, Hive, Spark, Flink 数据传输:Kafka, Pulsar, Flume, Sqoop Hadoop 是一个大数据整体解决方案,包括三大组件:分布式数据存储 HDFS,分布式数据计算 MapReduce 和分布式资源调度 YARN。本科的时候就做过 Hadoop 实验,几年之后对 Hadoop 是啥都几乎没概念了,刚好又需要做个实验,就记录一下实验过程,从简单的 URLs 统计看 Hadoop 和 HDFS 的基本功能。 本实验在虚拟机 VirtualBox 中完成,系统镜像为 Ubuntu。 虚拟机集群部署 准备虚拟机 首先准备三台虚拟机,主机名和配置如下。 节点 CPU Mem ubuntu-server-0 1 4GB ubuntu-server-1 1 2GB ubuntu-server-2 1 2GB 在 Vbox 中将虚拟机网络都配制成桥接模式,以便相互之间可以连通。 设置虚拟机固定 IP,免得重启后发生变动。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 # 1....

September 16, 2023 · 9 min · 1824 words

浅尝 SELinux

用 Fedora 的时候,发现系统默认启用了 SELinux,还动不动弹警告窗口,于是稍微研究了一下。 我们平常经常接触的存取控制机制叫做自主式存取控制 (DAC, Discretionary Access Control),如 user/group/other 对文件的 rwx 权限就是 DAC。SELinux 是一种强制式存取控制 (MAC, Mandatory Access Control) 的实现(没错,SELinux 只是最常见的一种 MAC,其实还有 TOMOYO、AppArmor 等等),能够提供更细粒度的权限控制,如某个类型的进程被允许读某个类型的文件。 SELinux 的实现依赖 LSM (Linux Security Modules)。LSM 在很多 Linux 内核服务(如程序执行、文件操作、socket操作、IPC操作、内存段、信号量、sysctl、syslog、audit)中插入了 hook,第三方的存取控制机制如 SELinux、AppArmor、SMACK、TOMOYO 就是利用这些 hook 实现的。 在 SELinux 下,每个进程和资源都被赋予了 SELinux context (i.e. SELinux label),利用这些 context 可以编写一系列 rules 来定义进程和进程之间、进程和资源之间允许哪些交互(白名单方式,除非你在 rule 中显式地允许了,否则 SELinux 默认不允许任何交互,这就是为什么叫做强制式),这些 rules 组合在一起就是 SELinux policy。需要注意的是,DAC 和 MAC 是合作而不是互斥关系,要在通过 DAC 的检查后才能检查 SELinux policy 的 rules ,也就是说如果你的访问连 DAC 都过不去那就不用劳烦 SELinux 了。...

July 13, 2023 · 21 min · 4373 words

虚拟地址空间:局部空间与全局空间 | 用户态与内核态

局部空间与全局空间 32 位机的虚拟地址位数为 32 位,每个任务都有自己独立的 4GB 虚拟地址空间。所谓独立,指的是不同任务相同的虚拟地址映射到不同的物理地址,本质上是依赖每个任务有自己的 PDT/PT。 这 4GB 虚拟地址空间又分为两部分:低端虚拟地址位于局部空间/用户空间、高端虚拟地址位于全局空间/内核空间。虚拟地址的高低端分界线依赖于操作系统的实现,如 Linux 的分界线位于 3GB 处,0 - 3GB 为低端虚拟地址,3GB - 4GB 为高端虚拟地址。 所有任务共享全局空间(所有任务的高端虚拟地址指向相同的内核页表),有各自的局部空间(LDTR 指向当前任务的 LDT,低端线性地址指向各自的页表)。 下图参考《x86 汇编语言 从实模式到保护模式》绘制,虚拟空间中 0 - 2GB 为局部空间,2 - 4GB 为全局空间,所有段均处于平坦模式。 用户态与内核态 特权级是处理器用来区分不同执行环境的一种机制。例如,在 x86 架构中,有四个特权级(Ring 0 到 Ring 3),其中 Ring 0(最高特权级)是内核态,操作系统内核在这里运行,拥有全部权限;Ring 3(最低特权级)是用户态,普通应用程序运行于此,权限受到限制。由固件负责实施特权级保护。 DPL:在描述符中指定,代表着一个段的特权级。 CPL:在 CS 段选择子中指定,代表着当前执行的代码段的特权级。 RPL:在段选择子中指定,代表请求者的特权级。 IOPL: 在 TSS 的 EFLAGS 中指定,代表着当前任务的输入输出特权级。 在绝大多数时候,请求者都是当前程序自己,因此 CPL = RPL。有时可能用户程序需要传入一个选择子作为参数,让内核例程代为访问,进入内核态后 CPL 变为 0,这时操作系统必须负责把这个选择子的 RPL 设置为用户程序自己的 CPL。 特权级在以下场景中发挥作用: 特权指令使用条件:CPL = 0。 访问数据段条件:CPL 和 RPL $\leq$ 目标 DPL。 访问代码段条件:除从高特权级例程返回外,不允许从高到低转移,因为操作系统不会引用可靠性比自己低的代码。一般来说,控制转移只允许发生在两个特权级相同的代码段之间(CPL 和 RPL = 目标 DPL);若目标代码段是依从的,可以从低到高,但转移后不允许改变 CPL(CPL 和 RPL $\geq$ 目标 DPL);还可以通过调用门从低到高(用 jmp far 不改变 CPL,用 call far 会把 CPL 提升到目标 DPL)。 访问调用门条件:目标 DPL $\leq$ CPL 和 RPL $\leq$ 调用门描述符 DPL。 态 !...

September 9, 2022 · 1 min · 206 words