debug 内核自动加载模块事件及禁止用户加载内核模块配置

今天在学习内核 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 用户)。

总结

已有的知识在扩展的同时也需要不断的整合,整合提高了知识的适用范围,让我对知识本身的理解更加深入!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值