浅谈4.X内核和5.X内核LSM模块初始化差异

	最近在帮同事解一个LSM子模块的问题的时候,发现4.X内核和5.X内核的初始化流程存在较大的差异。借此问题,我也研究了一下两个大版本内核的LSM模块,最终有所获。
	先是在网上查找资料,然而目前网上很少有介绍5.X内核的lsm模块的新特性的,外网大部分是询问贴,且没啥人回答,百度就更不用说了,完全搜不到相关内容。无奈,只能自己研究了。
	通过对比两个版本内核源码,以及查看2018年9月以后的内核security模块的邮件列表(http://kernsec.org/pipermail/linux-security-module-archive/),最终有所获,迫不及待想记录下。
	此文章里贴的代码都是Linux内核官方源码,下载地址:https://mirrors.edge.kernel.org/pub/linux/kernel,当然,也可以从官方源码仓库https://www.kernel.org下载,但是后者下载速度很慢,前者快一些。4.X以4.19源码为例,5.X以5.10源码为例。

=================================== 分割线 ===========================================================

先简单说下LSM模块吧(此贴不讨论LSM模块本身,Linux安全模块的百度百科已经讲的很详细了)
截取的百度百科的部分内容如下:
Linux安全模块(Linux Secrity Module,简称LSM)是一种轻量级通用访问控制框架,适合于多种访问控制模型在它上面以内核可加载模块的形实现。用户可以根据自己的需求选择合适的安全模块加载到内核上实现。
安全模块通过模块注册函数加载进入LSM,对内核关键资源设置安全信息,并将自己的安全策略函数与LSM的hook函数进行关联。用户进程提出系统资源访问访问请求,转入内核空间,并做传统的Linux系统DAC(Discretionary Access Control)判断。之后,LSM调用hook函数,该hook函数以指针的形式与特定安全模块的安全策略函数关联,用以判断对该对象的访问是否符合安全策略,从而进行访问控制。LSM基本原理如图所示:
在这里插入图片描述
下面分析4.19内核和5.10内核之间lsm模块的差异,我分析代码的思路是:初始化接口是什么->初始化流程是什么->如何使能。本次分析就通过此思路开始。

1、首先,分析初始化接口,相对于4.19内核,5.10内核LSM的初始化接口有较大改变
4.19内核中,LSM的初始化通过security_init()完成即可(security/security.c中定义):
在这里插入图片描述

在这里插入图片描述
但是在5.10内核中,新增了early_security_init()接口,在security_init()接口之前、start_kernel的早期被调用:
在这里插入图片描述
security_init()的部分代码挪到了early_security_init()中,且security_init()的实现也大变样:
在这里插入图片描述
2、然后,分析初始化流程。
4.19中的初始化流程为:start_kernel->security_init->…
首先在start_kernel()中调用如下:
在这里插入图片描述
然后,在security_init()中初始化security_hook_heads,以及显示调用capability_add_hooks()、yama_add_hooks()、loadpin_add_hooks(),最后调用do_security_initcalls()将所有其他LSM安全模块的初始化接口加入内核的初始化代码段:
do_security_initcalls()顺序调用__security_initcall_start到__security_initcall_end之间的代码段(.security_initcall.init,在vmlinux.lds.h中定义),各个LSM安全模块初始化接口都是通过security_initcall()接口加入.security_initcall.init段,如下图所示:
在这里插入图片描述
security_initcall()的定义如下(include/linux/init.h):
在这里插入图片描述
security_initcall()宏最终将入参fn插入到初始化时的代码段.security_initcall.init,.security_initcall.init段在include/asm-generic/vmlinux.lds.h中定义,其定义如下:
在这里插入图片描述
可知,.security_initcall.init段就是__security_initcall_start到__security_initcall_end之间的段,在do_security_initcalls()接口中,第54到62行就是顺序执行此段内的代码,也就是顺序执行各LSM模块通过security_initcall()宏注册的自己模块的初始化接口。需要注意的是,.security_initcall.init段中存放的各个lsm模块初始化接口的顺序,只与编译时的链接顺序有关,内核代码本身并没有对它们排序。

5.10中的初始化流程为:
start_kernel()先在开始的时候调用early_security_init():
在这里插入图片描述
early_security_init()中初始化security_hook_heads(在4.19中,这是security_init()干的活),并遍历__start_early_lsm_info到__end_early_lsm_info之间的lsm_info结构体,调用结构体的init成员进行初始化。early_security_init()完成后,再在后续调用security_init():
在这里插入图片描述
security_init()不再对security_hook_heads初始化(early_security_init()已经初始化过了),而是先再次遍历__start_early_lsm_info到__end_early_lsm_info之间的lsm模块结构体,调用lsm_append()将模块名append到lsm_names变量(因为此接口会给lsm_names变量申请内存,但是early_security_init阶段时还未初始化内存模块,故现在才append模块名到lsm_names变量保存),以供securityfs的lsm_read接口使用(用户查看/sys/kernel/security/lsm的时候,最终调用的就是lsm_read接口,如下图所示)。
在这里插入图片描述
最后,调用ordered_lsm_init->ordered_lsm_parse->append_ordered_lsm给__start_lsm_info到__end_lsm_info段之间的lsm模块排序到ordered_lsms数组,最后通过for循环遍历ordered_lsms数组,按顺序调用initialize_lsm()接口初始化各lsm模块(security/security.c中):
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
与4.19不同的是,5.10删掉了security_initcall宏,新增了DEFINE_LSM和DEFINE_EARLY_LSM宏,通过这两个宏定义各个LSM模块:
在这里插入图片描述
在这里插入图片描述
可以看到,DEFINE_EARLY_LSM宏实际是定义了一个struct lsm_info的结构体,并将其放到.early_lsm_info.init段,DEFINE_LSM宏同理,但是将定义的结构体放到.lsm_info.init段。5.10内核在include/asm-generic/vmlinux.lds.h中定义了这两个段:__start_early_lsm_info到__end_early_lsm_info之间为.early_lsm_info.init段;__start_lsm_info到__end_lsm_info之间为.lsm_info.init段。并且,删除了4.19以前版本中定义的.security_initcall.init段。同样的,两个段之间各模块结构体存放的顺序只与编译时的链接顺序有关,内核代码本身并没有对它们排序,但是与4.19不同的是,5.10内核对各个lsm模块的初始化是有顺序的,从前面的初始化流程就知道,.early_lsm_info.init段的模块是最先初始化的,然后security_init再对.lsm_info.init段的各个lsm模块进行排序(order),并在最后根据排序的结果,调用它们各自的初始化接口。
以lockdown、capability和apparmor模块为例(结合前面贴的代码分析):
在这里插入图片描述
调用DEFINE_EARLY_LSM和DEFINE_LSM定义本子模块专属lsm_info结构体的同时初始化结构体的全部或部分字段。其中.init字段保存的就是各个子模块自己的初始化接口。
从ordered_lsm_parse()接口可以看到,进接口后首先解析并排序.order字段为LSM_ORDER_FIRST的模块,也就是说,.order为LSM_ORDER_FIRST的模块的初始化优先级是仅次于DEFINE_EARLY_LSM宏定义的模块的。目前5.10内核只有capability模块是此order,其他模块都是默认的LSM_ORDER_MUTABLE。

3、最后,分析如何使能LSM各子模块。
4.19中,通过指定内核参数“security=模块名”,就可以打开指定的LSM模块,但是只能通过此方式指定打开一个。对应的源码在security/security.c中:
在这里插入图片描述
__setup是内核的一个宏定义,其作用是将其第二个参数加入到专为解析启动阶段内核参数的.init.setup段,在内核启动阶段执行.init.setup的代码来解析第一个参数指定的内核参数。由choose_lsm的实现可以知道,解析的值将保存到chosen_lsm变量中,此变量只能保存一个SECURITY_NAME_MAX长度的字符串,即只能保存一个模块名。
chosen_lsm变量会在selinux、smack、tomoyo、apparmor这四个模块初始化函数的开头处被用到,如图所示,以SELinux为例:
在这里插入图片描述
可以看到,开头处调用security_module_enable()接口判断selinux模块是否使能,security_module_enable()就是将chosen_lsm的值与自己的模块名做比较,只有完全匹配,才能进行后续的初始化也就是说,selinux、smack、tomoyo、apparmor这四个模块只有一个能被初始化:
在这里插入图片描述
在这里插入图片描述
若没有在启动阶段指定内核参数,则chosen_lsm将保存默认值CONFIG_DEFAULT_SECURITY,故,分析security/Kconfig文件:
在这里插入图片描述
可以看到,CONFIG_DEFAULT_SECURITY只能是selinux、smack、tomoyo、apparmor的其中之一,此处也说明了selinux、smack、tomoyo、apparmor四个安全模块是无法共存的。
注意:此处只是DEFAULT_SECURITY_SELINUX、DEFAULT_SECURITY_SMACK、DEFAULT_SECURITY_TOMOYO、DEFAULT_SECURITY_APPARMOR只能选择其一(或者都不选择,即选择DAC),SECURITY_SELINUX、SECURITY_SMACK、SECURITY_TOMOYO、SECURITY_APPARMOR依然可以同时打开,且只有打开SECURITY_XXX后,对应的DEFAULT_SECURITY_XXX才能成为备选项。也就是说,这四个模块编译阶段都可以编译进内核,只是在系统启动时,只能打开一个,配置界面如下:
在这里插入图片描述
在这里插入图片描述

5.10版本内核中,使能LSM子模块的实现完全改变:
在这里插入图片描述
可以看到,与4.19相比,5.10多了”lsm=”与“lsm.debug”参数,后者跟调试有关,暂时没去研究。主要分析“security=”和“lsm=”。
“security=”中的值将保存到chosen_major_lsm,“lsm=”中的值将保存到chosen_lsm_order。它们都是char *指针变量,没有显示地限制长度,或者说,没有显示地限制可以指定多少个lsm模块名。那么,是否真的可以通过指定内核参数来批量开启多个lsm模块呢。另外,注意到还定义了一个builtin_lsm_order指针变量,且是const类型,其值由CONFIG_LSM唯一确定,这里后面再分析。
先分析ordered_lsm_init接口(security_init()在其最后调用),发现:
1、若在命令行里指定了“lsm=”参数的值,则将走324~326行的分支,由代码可知,chosen_major_lsm将被设置为NULL,也就是说,“security=”将被忽略!
2、若指定了“lsm=”参数的值,则将chosen_lsm_order作为第一个入参传入ordered_lsm_parse,否则,将builtin_lsm_order传入,builtin_lsm_order就是上面提到的,在编译后就唯一确定了的const变量
在这里插入图片描述
继续分析ordered_lsm_parse接口:
在这里插入图片描述
246~249就是前面提到过的,先将.order为LSM_ORDER_FIRST的模块排序排在前面。
从252~270行可以发现,若“security=”生效,则.flags位设置了LSM_FLAG_LEGACY_MAJOR但在“security=”中未指定的模块将被关闭。此外,注意到264行使用的是strcmp,模块名要和“security=”后的字符串完全匹配才能返回0。也就是说,“security=”只在指定一个模块名时才有意义,且只有指定的那个模块才能生效。5.10版本中,flags设置了LSM_FLAG_LEGACY_MAJOR的模块就是selinux、apparmor、smack、tomoyo这四个:
在这里插入图片描述
272~284行是解析第一个入参order的,启动命令行里指定了“lsm=”时,order为chosen_lsm_order,否则,order为builtin_lsm_order。由275行的while循环以及注释可知,这里将拆解出以逗号分隔的所有模块名,也就是说,chosen_lsm_order和builtin_lsm_order都能通过“module1,module2,module3”的形式指定多个lsm模块,但是如果在启动命令行中通过“lsm=”显示指定了要使能的lsm模块,则builtin_lsm_order将失效。换句话说,builtin_lsm_order保存的是不指定内核参数时的缺省默认值。同4.19,这个默认值是通过内核配置文件在编译时指定的,在4.19中是CONFIG_DEFAULT_SECURITY,在5.10中为CONFIG_LSM。Kconfig文件与配置界面如下所示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

那么问题来了。4.19中,selinux、apparmor、smack、tomoyo不管是通过启动命令行指定“security=”还是默认配置,都只能开启一个。从上面的分析来看,5.10通过“security=”和默认配置也只能开启他们中的一个。但是,通过“lsm=”可以配置多个啊,而且ordered_lsm_parse中也并未对此做限制!而且很明显,security/Kconfig文件的280、281和284行里,也都配置了多个!只是顺序不同罢了。这真的可行吗?
答案是:不可行。
我们需要继续分析代码,回到ordered_lsm_init接口:
在这里插入图片描述
ordered_lsm_init中,调用完ordered_lsm_parse后,立即遍历ordered_lsms,调用prepare_lsm,在prepare_lsm中,将使selinux、apparmor、smack、tomoyo只能使能一个:
在这里插入图片描述
在这里插入图片描述
可以看到,217~221行将第一个.flags位为LSM_FLAG_EXCLUSIVE的lsm模块赋予exclusive变量,然后后续所有.flags为LSM_FLAG_EXCLUSIVE的lsm模块都将在lsm_allowed接口中被去使能!
再回到ordered_lsm_init接口:
在这里插入图片描述
在ordered_lsm_init接口的最后,遍历ordered_lsms调用initialize_lsm(),initialize_lsm()接口中,只有所有使能的lsm才会最终调用自己注册的初始化回调函数lsm->init(),实现lsm模块的实际初始化:
在这里插入图片描述
此时再回到ordered_lsm_parse接口的最后进行分析:
在这里插入图片描述
可以看到,301~305行,将所有未在ordered列表中的模块都去使能,也就是说,所有未通过启动命令行显示指定以及未在默认清单里指定的lsm模块,都将被去使能,即使它们通过DEFINE_LSM定义且编译进内核了。因此,即使通过“lsm=”指定了selinux,apparmor,smack,tomoyo中的多个模块,最终也只有最前面的那个模块能生效!比如,若设置了“lsm=selinux,smack,apparmor”,则只有selinux会开启,smack和apparmor均不会开启。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值