今天在学习内核 sysctl 配置的时候看到模块加载的两个配置,觉得有些意思就来研究研究,没想到竟然又找到了【禁止用户加载内核模块】的一个新方法,在本文中记录一下。
备注:本文的示例代码测试环境为 openEuler 20.03 LTS 虚拟机。
kernel.modprobe 配置
默认配置:
kernel.modprobe = /sbin/modprobe
此配置用于指定内核自动加载模块时调用的用户态模块加载程序的绝对路径。
当内核主动请求加载某个模块时就会调用 kernel.modprobe 中配置的程序来尝试加载。例如,当用户态通过 mount 系统调用传递给内核一个未知的文件系统时,内核会使用用户态的辅助命令来自动加载目标文件系统模块,这个用户态的辅助命令将需要的模块插入内核中。
这一过程实际是通过【usermodehelper】功能实现的,我在 创建内核线程并通过内核线程调用用户态程序 这篇文章中曾经描述过这一个功能的使用。
如果我们需要定位内核自动加载模块的问题,可以编写一个新的内核模块加载命令(例如一个 shell 脚本的封装)并给 shell 脚本添加可执行程序,然后使用 systcl 将新的命令的绝对路径写入到 kernel.modprobe 配置中,这样当内核再次调用用户态程序加载模块时就会调用到新的命令。
当 kernel.modprobe 内容被清空时,内核自动加载模块的功能被关闭。内核既不会尝试执行一个 usermode helper 也不会调用 LSM 模块的 kernel_module_request hook 接口。
kernel.modprobe 修改示例
编写如下 modprobe 自定义脚本:
#!/bin/bash
echo "$0 $@" >> /tmp/modprobe.log
exec /sbin/modprobe "$@"
此脚本将内核调用命令保存到 /tmp/modprobe.log 文件中,然后使用参数执行 /sbin/modprobe 程序。脚本路径保存为 /tmp/modprobe,执行如下命令添加可执行权限并修改 kernel.modprobe 的内容:
[root@openeuler]# chmod a+x /tmp/modprobe
[root@openeuler]# echo /tmp/modprobe > /proc/sys/kernel/modprobe
上面通过写入文件修改 kernel.modprobe,也可以执行 sudo sysctl -w kernel.modprobe="/tmp/modprobe"
完成。
内核主动请求加载模块示例模块 demo
为了真实研究上述流程,我编写了如下示例 demo:
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#define STRING_LEN 32
struct module *module;
/*
* Variable for strings
*/
char module_name[STRING_LEN] = "nlmon";
module_param_string(module_name, module_name, STRING_LEN, S_IRUGO | S_IWUSR);
static int __init demo_init(void)
{
printk(KERN_INFO "Kernel tries to insmod %s module\n", module_name);
request_module(module_name);
if(!try_module_get(module)) {
printk("%s module get failed.\n", module_name);
return -EINVAL;;
}
return 0;
}
static void __exit demo_exit(void)
{
if (module) {
module_put(module);
}
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
如上 demo 实现的功能为在模块初始化的时候主动加载 module_name(默认值为 nlmon) 参数指定的内核模块。将上述代码保存为 kernel_load_module.c,使用如下 Makefile 文件进行编译:
MYPROC = kernel_load_module
obj-m += kernel_load_module.o
allofit: modules
KROOT="/usr/src/kernels/4.19.90-2204.4.0.0146.oe1.x86_64/"
modules:
@${MAKE} -C ${KROOT} M=${PWD} modules
modules_install:
@$(MAKE) -C $(KROOT) M=$(PWD) modules_install
kernel_clean:
@$(MAKE) -C $(KROOT) M=$(PWD) clean
clean: kernel_clean
rm -rf Module.symvers modules.order
insert: modules
sudo dmesg -c
sudo insmod ${MYPROC}.ko
remove: clean
sudo rmmod ${MYPROC}
编译成功后运行测试成功,过程记录如下:
[root@openeuler]# insmod ./kernel_load_module.ko
[root@openeuler kernel_load_module]# cat /tmp/modprobe.log
/tmp/modprobe -q -- nlmon
[root@openeuler kernel_load_module]# lsmod | grep nlmon
nlmon 16384 0
可以看到 nlmon 模块重新加载,同时 /tmp/modprobe.log 中也记录了一条模块加载信息。
kernel.modules_disabled
此配置可以禁止用户加载内核模块(包含 root 用户)。默认配置为 0,当被设置为 1 后其值就不能修改且内核模块既不能加载也不能卸载。
示例过程如下:
[root@openeuler]# sysctl -w kernel.modules_disabled=0
sysctl: setting key "kernel.modules_disabled": Invalid argument
[root@openeuler]# modprobe uio
modprobe: ERROR: could not insert 'uio': Operation not permitted
[root@openeuler]# strace -f modprobe uio 2>&1 | grep module
.............................................................
stat("/sys/module/uio", 0x7fffb5dc56b0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/modules/4.19.90-2106.3.0.0095.oe1.x86_64/kernel/drivers/uio/uio.ko", O_RDONLY|O_CLOEXEC) = 3
finit_module(3, "", 0) = -1 EPERM (Operation not permitted)
上述示例中,当 sysctl 尝试将 modules_disabled 配置从 1 修改为 0 后,返回了非法参数的错误,与上文描述一致。
同时当 modules_disabled 设置为 1 后,模块无法加载,finit_module 系统调用返回无权限(注意我使用的是 root 用户)。
总结
已有的知识在扩展的同时也需要不断的整合,整合提高了知识的适用范围,让我对知识本身的理解更加深入!