要使能一个 LSM 有两个步骤:
1、该 LSM 的编译选项被打开
各 LSM 代码中都会使用 DEFINE_LSM()
在内核中创建该模块。这个宏的定义如下:
1// include/linux/lsm_hooks.h
2
3#define DEFINE_LSM(lsm) \
4 static struct lsm_info __lsm_##lsm \
5 __used __section(".lsm_info.init") \
6 __aligned(sizeof(unsigned long))
其实就是创建了一个 struct lsm_info
的实例,只不过指定放置在了 __section(".lsm_info.init")
这个特殊的节中。这个节在内核文件中的位置由链接脚本安排,起始和终止地址分别由 __start_lsm_info
和 __end_lsm_info
指定:
1// include/asm-generic/vmlinux.lds.h
2
3#ifdef CONFIG_SECURITY
4#define LSM_TABLE() . = ALIGN(8); \
5 __start_lsm_info = .; \
6 KEEP(*(.lsm_info.init)) \
7 __end_lsm_info = .;
8#define EARLY_LSM_TABLE() . = ALIGN(8); \
9 __start_early_lsm_info = .; \
10 KEEP(*(.early_lsm_info.init)) \
11 __end_early_lsm_info = .;
12#else
13#define LSM_TABLE()
14#define EARLY_LSM_TABLE()
15#endif
因此通过这两个地址,我们就可以找到所有编译进内核的 LSM 模块的 struct lsm_info
实例。在下面提到的函数中就有这样的过程。
2、用户明确指定要开启该 LSM
指定的方式有以下几种,优先级递减:
- 内核启动参数
lsm=
:以逗号分隔的 LSM 名称列表 - 内核启动参数
security=
:某一个 LSM 的名称 - 编译选项
CONFIG_LSM
:以逗号分隔的 LSM 名称列表- 还有几个形如
CONFIG_DEFAULT_SECURITY_*
的编译选项,仅为向后兼容而保留,会被转换成CONFIG_LSM
- 还有几个形如
启动过程的具体函数调用如下:
1# init/main.c
2start_kernel
3# security/security.c
4 security_init
5 ordered_lsm_init
6 ordered_lsm_parse # 在 LSM_TABLE 中查找由"lsm="、"security="、CONFIG_LSM 指定的模块,将它们加入 ordered_lsms
7 prepare_lsm # (遍历 ordered_lsms 时逐个调用此函数)确认模块是否开启,并配置 lsm->enabled 标志
8 initialize_lsm # (遍历 ordered_lsms 时逐个调用此函数)对开启的模块进行初始化
几个指定方式的优先级主要体现在
ordered_lsm_init()
中:1/* 2 * lsm= ~> chosen_lsm_order 3 * security= ~> chosen_major_lsm 4 * CONFIG_LSM ~> builtin_lsm_order 5 */ 6if (chosen_lsm_order) { 7 if (chosen_major_lsm) { 8 pr_info("security= is ignored because it is superseded by lsm=\n"); 9 chosen_major_lsm = NULL; 10 } 11 ordered_lsm_parse(chosen_lsm_order, "cmdline"); 12} else 13 ordered_lsm_parse(builtin_lsm_order, "builtin");
另外
ordered_lsm_parse()
中也有一些,主要是区分security=
和CONFIG_LSM
。
注意:SELinux、AppArmor、Smack 等几个主要的 LSM 模块在定义时有标注 LSM_FLAG_EXCLUSIVE
,表明它们相互之间是互斥的,每次只能使能其中的一个。