简单介绍下 Linux 中的内核模块编写,包括了内核签名机制的配置。
简单示例
如下是 Makefile 文件。
ifneq ($(KERNELRELEASE),)
obj-m := hello.o
else
KERNEL_DIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
make -C $(KERNEL_DIR) SUBDIRS=$(PWD) modules
# rm -r -f .tmp_versions *.mod.c .*.cmd *.o *.symvers
endif
clean:
rm -rf *.o *.ko *.mod.c *.order *.symvers .*.cmd .tmp_versions
.PHONY:clean
下面是驱动的测试程序。
/* FILE: hello.c */
#include <linux/init.h>
#include <linux/module.h>
static int __init hello_init(void)
{
printk(KERN_ALERT "hello module!\n");
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_ALERT "bye module!\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Andy <justkidding@gmail.com>");
MODULE_DESCRIPTION("A simple Hello World Module");
MODULE_ALIAS("the simplest module");
然后可以通过如的方式进行测试。
# insmod hello.ko
# lsmod
# dmesg | tail -5
Module Size Used by
hello 12496 0
# rmmod hello
内核签名机制
在插入到内核模块时,可能会报如下错误。
# insmod hello.ko
insmod: ERROR: could not insert module hello.ko: Required key not available
原因是内核启用了 Module signature verification ,可以通过如下命令检测内核配置项。
----- 查看内核的配置项
$ grep "CONFIG_MODULE_SIG" /boot/config-`uname -r`
----- 查看当前系统key
# keyctl list %:.system_keyring
内核启动时,会有类似如下的输出。
Loaded X.509 cert 'CentOS Linux kpatch signing key: xxxx'
Loaded X.509 cert 'CentOS Linux Driver update signing key: xxxx'
Loaded X.509 cert 'CentOS Linux kernel signing key: xxxx'
EFI: Loaded cert 'Lenovo Ltd.: ThinkPad Product CA 2012: xxxx' linked to '.system_keyring'
EFI: Loaded cert 'Lenovo UEFI CA 2014: xxxx' linked to '.system_keyring'
主要是由于目前 BIOS 支持 EFI,如果支持 UEFI Secure Boot 启动,那么内核所有模块都必须使用 UEFI Secure key 签名;当然,如果 BIOS 支持关闭 UEFI Secure Boot,那么可以在 BIOS 的 boot 项中关闭 UEFI Secure Boot 。
否则只能为自己制作一个。
接下来看看如何使用,主要包括了如下工具:
- openssl,生成X509公私秘钥对。
- sign-file,对内核模块使用X509公私秘钥对签名。
- mokutil,手动注册公钥到系统。
- keyctl,手动取消注册公钥到系统。
下面看看如何设置。
配置示例
1. 生成配置文件
# cat << EOF > configuration_file.config
[ req ]
default_bits = 4096
distinguished_name = req_distinguished_name
prompt = no
string_mask = utf8only
x509_extensions = myexts
[ req_distinguished_name ]
O = Organization
CN = Organization signing key
emailAddress = E-mail address
[ myexts ]
basicConstraints=critical,CA:FALSE
keyUsage=digitalSignature
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid
EOF
2. 生成秘钥
一般会把公私钥放在 /usr/src/kernels/
目录下,对应内核版本可以通过 uname -r
命令查看,不过还是建议方法 HOME 目录下。
$ openssl req -x509 -new -nodes -utf8 -sha256 -days 36500 \
-batch -config configuration_file.config -outform DER \
-out public_key.der -keyout private_key.priv
3. 导入到 mok 列表
上步生成了一对公私钥,接下来将其添加到 mok 列表中,此时会需要输入密码,该密码会在确认 MOK 请求的时候输入。
# mokutil --import public_key.der
4. 重启系统
# shutdown -r now
在启动时,shim.efi 会发现新添加的 KEY,并会启动 MokManager.efi 模块,此时需要选择 Eroll Key 选项,然后输入上面的密码。
5. 查看 key ring
接着 key ring 会添加到内核中,其中描述是配置文件中指定的 req_distinguished_name.O 中。
# keyctl list %:.system_keyring | grep "Organization"
# cat /proc/keys
同样,也可以查看系统的启动日志。
# dmesg | grep 'EFI: Loaded cert'
6. 添加到模块中
/usr/src/kernels/$(uname -r)/scripts/sign-file sha256 private_key.priv public_key.der hello.ko
7. 添加 hello.ko
仍然同上,直接插入模块即可。
# insmod hello.ko
PROC 文件系统
最初 proc 文件系统是为提供一种内核及其模块向进程 (process) 发送信息的机制,这就是 proc 名字的由来。通过 proc 可以和内核内部数据结构进行交互,获取对于进程的有用信息,并可在运行时改变设置内核参数。
后来,这个系统的使用有些混乱和失控,于是在新内核中建议使用 sysfs 实现内核信息向用户空间的导出,例如,模块参数。
$ ls /sys/module/hid/parameters # 查看HID(Human Interface Devices)的参数
Linux 内核和用户空间的通信可通过 /proc 目录下的文件实现,编译时需要 CONFIG_PROC_FS
配置,通常来说,可写的配置项一般保存在 /proc/sys 目录下。其它介绍如下:
1. 进程相关的目录
proc 目录下以 PID 作为目录,存储了进程相关信息,可以通过 cat 查看。
$ cat /proc/1/cmdline # 查看init命令启动参数
2. 通用系统信息
包括了内存、文件系统、设备驱动、系统总线、电源等管理,如 devices、diskstats、meminfo、modules 等。
3. 系统控制信息
用来检测修改系统的运行参数,存在于 /proc/sys
目录下,可以使用 cat、echo 来查看或修改系统的运行参数。不过此处修改只是临时,如果要持久化需要修改 /etc/sysctl.conf
中的配置。
接下来看看内核中的实现。
内核实现
相关的 API 实现在 fs/proc/generic.c
文件中,常见的 /proc/meminfo
、/proc/stat
等统计项的源码在 fs/proc 目录下,具体的实现可以查看这里的源码。
很多简单的示例可以参考源码中的 loadavg.c 实现。
问题排查
正常来说,在通过如上的配置之后可以正常加载,不过仍有可能会出现部分问题。
参考
- 关于 Signed Kernel Modules 可以参考 Gentoo 中的文档 Signed kernel module support。
- 关于 proc 的文档,可参考源码 Documentation/filesystems 目录下的 seq_file.txt 以及 proc.txt 。