LSM(linux security module)作为一个单独模块,通过在kernel编译过程中的编译flag:CONFIG_SECURITY 控制是否启用该模块中定义的安全相关的功能。具体配置信息见:kernel/linux-4.9/security/Kconfig

当在编译阶段打开该配置开关,kernel中的用于管理各个进程的task_struct对象会增加一个安全相关的引用。如下所示:

kernel/4.9/include/linux/sched.h

1739 struct task_struct { 
        ...
1948 /* process credentials */
1949         const struct cred __rcu *ptracer_cred; /* Tracer's credentials at attach */                                                                                                                       
1950         const struct cred __rcu *real_cred; /* objective and real subjective task
1951                                          * credentials (COW) */
1952         const struct cred __rcu *cred;  /* effective (overridable) subjective task
1953                                          * credentials (COW) */
1954         char comm[TASK_COMM_LEN]; /* executable name excluding path
1955                                      - access with [gs]et_task_comm (which lock
1956                                        it with task_lock())
1957                                      - initialized normally by setup_new_exec */
        ...
2258 };

kernel/4.9/include/linux/cred.h

110struct cred {
...
141#ifdef CONFIG_SECURITY
142	void		*security;	/* subjective LSM security */
143#endif
...
148};

通过进程对象task_struct中间接引用的security对象(void*),实现各种各样的安全模块。安全模块的初始化过程如下:

Step 1:在kernel初始化过程中call security_init;

kernel/4.9/init/main.c

 481 asmlinkage __visible void __init start_kernel(void))
 482 {
 483         char *command_line;
 484         char *after_dashes;
       ...
 632         cred_init();
       ...
 637         security_init();
       ...
 664 }

Step 2:在security_init函数中加载各个安全模块; kernel/4.9/security/security.c

  55 int __init security_init(void)
  56 {
  57         pr_info("Security Framework initialized\n");
  58 
  59         /*
  60          * Load minor LSMs, with the capability module always first.
  61          */
  62         capability_add_hooks();                                                                                                                                                                           
  63         yama_add_hooks();
  64         loadpin_add_hooks();
  65 
  66         /*
  67          * Load all the remaining security modules.
  68          */
  69         do_security_initcalls();
  70 
  71         return 0;
  72 }

其中下面的这三行,分别对应的是三种安全检查的初始化

62	capability_add_hooks();
63	yama_add_hooks();
64	loadpin_add_hooks();

capability:https://www.kernel.org/doc/htmldocs/lsm/cap.html

The LSM kernel patch moves most of the existing POSIX.1e capabilities logic into an optional security module stored in the file security/capability.c. This change allows users who do not want to use capabilities to omit this code entirely from their kernel, instead using the dummy module for traditional superuser logic or any other module that they desire. This change also allows the developers of the capabilities logic to maintain and enhance their code more freely, without needing to integrate patches back into the base kernel.

yama:https://www.kernel.org/doc/html/v4.16/admin-guide/LSM/Yama.html

Yama is a Linux Security Module that collects system-wide DAC security protections that are not handled by the core kernel itself. This is selectable at build-time with CONFIG_SECURITY_YAMA, and can be controlled at run-time through sysctls in /proc/sys/kernel/yama:

loadpin:https://www.kernel.org/doc/html/v4.16/admin-guide/LSM/LoadPin.html

LoadPin is a Linux Security Module that ensures all kernel-loaded files (modules, firmware, etc) all originate from the same filesystem, with the expectation that such a filesystem is backed by a read-only device such as dm-verity or CDROM. This allows systems that have a verified and/or unchangeable filesystem to enforce module and firmware loading restrictions without needing to sign the files individually. The LSM is selectable at build-time with CONFIG_SECURITY_LOADPIN, and can be controlled at boot-time with the kernel command line option “loadpin.enabled”. By default, it is enabled, but can be disabled at boot (“loadpin.enabled=0”). LoadPin starts pinning when it sees the first file loaded. If the block device backing the filesystem is not read-only, a sysctl is created to toggle pinning: /proc/sys/kernel/loadpin/enabled. (Having a mutable filesystem means pinning is mutable too, but having the sysctl allows for easy testing on systems with a mutable filesystem.)

LSM安全结构主要的实现方式,即是hook kernel中的某些函数调用,然后在hook后的函数实现中添加安全相关的逻辑。

以上述的第一个capability安全模块为例,了解一下LSM(linux security module)的基础框架。其中涉及到的几个重要的对象包括以下几个: 1)capability_hooks:即将要被hook的函数列表; 2)列表中的各个hook函数的实现; 3)因为某个函数可能被多个安全模块hook,所以对hook函数实现构建一个stack结构。

主要有几个步骤: **Step 1:**实现capability安全模块中的hook函数(默认已经OK); **Step 2:**用已实现的hook函数,初始化capability_hooks; **Step 3:**把capability_hooks中的函数添加到stack结构中。

下面通过阅读capability模块代码的方式剖析上述的三个步骤。

Step 1:hook函数的实现,以cap_capable函数为例: kernel/4.9/security/commoncap.c

 110 int cap_capable(const struct cred *cred, struct user_namespace *targ_ns,                                                                                                                                  
 111                 int cap, int audit)
 112 {
 113         int ret = __cap_capable(cred, targ_ns, cap, audit);
 114         ...
 129         return ret;
 130 }

Step 2:初始化capability_hooks,如1099行所示,用上述的hook函数cap_capable,替换函数capable(略长,不需要了解细节的同学可以忽略)。

kernel/4.9/security/commoncap.c

1096 #ifdef CONFIG_SECURITY
1097 
1098 struct security_hook_list capability_hooks[] __lsm_ro_after_init = {
1099         LSM_HOOK_INIT(capable, cap_capable),
1100         LSM_HOOK_INIT(settime, cap_settime),
1101         ...
1117 };
1118 

上述代码显示,添加hook函数是通过调用"LSM_HOOK_INIT(capable, cap_capable)"来实现,其中LSM_HOOK_INIT是个宏,定义在kernel/4.9/include/linux/lsm_hooks.h文件中,如下:

1937 /*
1938  * Initializing a security_hook_list structure takes
1939  * up a lot of space in a source file. This macro takes
1940  * care of the common case and reduces the amount of
1941  * text involved.
1942  */
1943 #define LSM_HOOK_INIT(HEAD, HOOK) \
1944         { .head = &security_hook_heads.HEAD, .hook = { .HEAD = HOOK } }
1945 
1946 extern struct security_hook_heads security_hook_heads; 

对上述kernel/4.9/security/commoncap.c:1099行,展开后即是:

{.head = &security_hook_heads.capable, .hook={.capable = cap_capable} }

相当于在capability_hooks数组中,对其中一个security_hook_list类型对象做了初始化。初始化过程涉及到三个主要类型的定义,分别是:struct security_hook_list, struct security_hook_heads和union security_list_options:

kernel/4.9/include/linux/lsm_hooks.h

1367 union security_list_options {
...
1386         int (*capable)(const struct cred *cred, struct user_namespace *ns,
1387                         int cap, int audit);
1388         int (*quotactl)(int cmds, int type, int id, struct super_block *sb);
1389         int (*quota_on)(struct dentry *dentry);
1390         int (*syslog)(int type);
1391         int (*settime)(const struct timespec64 *ts, const struct timezone *tz);
...
1701 };
1702 
1703 struct security_hook_heads {                                                                                                                                                                              
...
1712         struct list_head capable;
1713         struct list_head quotactl;
1714         struct list_head quota_on;
1715         struct list_head syslog;
1716         struct list_head settime;
...
1925 };
1926 
1927 /*
1928  * Security module hook list structure.
1929  * For use with generic list macros for common operations.
1930  */
1931 struct security_hook_list {
1932         struct list_head                list;
1933         struct list_head                *head;
1934         union security_list_options     hook;
1935 };

从中可以看到union security_list_options是一系列函数指针的union(注意:所有的函数指针必须是在coding阶段写死的,不能动态的添加),其中包含了类型为‘capable’类型的函数指针。而struct security_hook_list包含了两个list和一个名称为hook的函数指针(类型为security_list_options)。

回到kernel/4.9/security/commoncap.c:1099行,这一行完成的功能就是在capability_hooks数组中添加了这样一个struct security_hook_list对象(如下),head指向了一个全局变量security_hook_heads中的capable成员变量,hook成员变量指向了最终需要hook后的函数实现。

{
.head = &security_hook_heads.capable,
.hook=
    {
		   .capable = cap_capable
		}
}

全局变量security_hook_heads中的capable成员变量被初始化了什么呢? 该全局变量是在kernel/4.9/security/security.c中被初始化,如下所示:

kernel/4.9/security/security.c

1640 struct security_hook_heads security_hook_heads __lsm_ro_after_init = { 
...
1656         .capable =      LIST_HEAD_INIT(security_hook_heads.capable),
1657         .quotactl =     LIST_HEAD_INIT(security_hook_heads.quotactl),
1658         .quota_on =     LIST_HEAD_INIT(security_hook_heads.quota_on),
1659         .syslog =       LIST_HEAD_INIT(security_hook_heads.syslog),
1660         .settime =      LIST_HEAD_INIT(security_hook_heads.settime),
...
2009 };

对"LIST_HEAD_INIT(security_hook_heads.capable)"宏展开,得到的结果是:

.capable = {&security_hook_heads.capable = &security_hook_heads.capable}

看起来好像没什么意义,只是把自己初始化为自己,没有看明白什么意义?

Step 3:好吧,终于到了第三步,把capability_hooks中的函数添加到一个stack结构中,以实现在多个安全模块中,对某个函数的多重安全监测。代码实现如下:

kernel/4.9/include/linux/lsm_hooks.h

1948 static inline void security_add_hooks(struct security_hook_list *hooks,                                                                                                                                   
1949                                       int count)
1950 {
1951         int i;
1952         
1953         for (i = 0; i < count; i++)
1954                 list_add_tail_rcu(&hooks[i].list, hooks[i].head);
1955 }

kernel/4.9/security/commoncap.c

1119 void __init capability_add_hooks(void)                                                                                                                                                                    
1120 {
1121         security_add_hooks(capability_hooks, ARRAY_SIZE(capability_hooks));
1122 }
1123 

如上代码所示,对capability_hooks中的所有成员变量(类型为security_hook_list),将其中的成员变量list添加到成员变量head之前,组成一个链表(如果后续有其他安全模块对capable函数增加其他的hook函数实现,新的hook实现又会被放到当前hook函数之前,类似于一个栈结构)。

但是,截止到目前为止,还是没有看到capability模块中是如何实现对原'capable'函数实现hook函数的替换的。入口在哪里呢?通过阅读security.c中的代码,发现代码入口在函数security_capable中实现,代码如下所示: kernel/4.9/security/security.c

 119 #define call_int_hook(FUNC, IRC, ...) ({                        \                                                                                                                                         
 120         int RC = IRC;                                           \
 121         do {                                                    \
 122                 struct security_hook_list *P;                   \
 123                                                                 \
 124                 list_for_each_entry(P, &security_hook_heads.FUNC, list) { \
 125                         RC = P->hook.FUNC(__VA_ARGS__);         \
 126                         if (RC != 0)                            \
 127                                 break;                          \
 128                 }                                               \
 129         } while (0);                                            \
 130         RC;                                                     \
 131 })
 ...
 186 int security_capable(const struct cred *cred, struct user_namespace *ns,                                                                                                                                  
 187                      int cap)
 188 {
 189         return call_int_hook(capable, 0, cred, ns, cap, SECURITY_CAP_AUDIT);
 190 }

通过以上步骤,在kernel启动以后,实现了对kernel中的某些函数的hook。 如果有被hook的系统调用发生,则会处罚selinux的安全检查。 实现安全检查的逻辑如下: 1)在每个task_struct对象中保留对struct cred对象的引用,struct cred *real_cred; 2)从real_cred对象中有个security引用(void*类型); 3)从security对象中提取需要安全上下文; 4)通过对比进程的安全上下文,和目标文件的安全上下文,决定deny/permissive当前的系统调用。

以selinux为例,当有binder_transaction系统调用被hook住,且进入selinux安全检查流程,具体流程如下:

通过task_struct获取selinux的安全上下文:

kernel/4.9/security/selinux/hooks.c

 193 static inline u32 cred_sid(const struct cred *cred)                                                                                                                                                       
 194 {
 195         const struct task_security_struct *tsec;
 196 
 197         tsec = cred->security;
 198         return tsec->sid;
 199 }
 204 static inline u32 task_sid(const struct task_struct *task)
 205 {
 206         u32 sid;
 207 
 208         rcu_read_lock();
 209         sid = cred_sid(__task_cred(task));
 210         rcu_read_unlock();
 211         return sid;
 212 }

存放在read_cred对象中的security引用为task_security_struct,具体定义如下: kernel/4.9/security/selinux/include/objsec.h

 32 struct task_security_struct {  
 33         u32 osid;               /* SID prior to last execve */
 34         u32 sid;                /* current SID */
 35         u32 exec_sid;           /* exec SID */
 36         u32 create_sid;         /* fscreate SID */
 37         u32 keycreate_sid;      /* keycreate SID */
 38         u32 sockcreate_sid;     /* fscreate SID */
 39 };

通过调用task_sid()函数,获取每个进程的selinux安全上下文:sid。

然后通过检查当前的policy,决定当前的sid是否有权限完成对应的binder_transaction调用。

kernel/4.9/security/selinux/hooks.c

2068 static int selinux_binder_transaction(struct task_struct *from,
2069                                       struct task_struct *to)
2070 {
2071         u32 mysid = current_sid();
2072         u32 fromsid = task_sid(from);
2073         u32 tosid = task_sid(to);
2074         int rc;
2075 
2076         if (mysid != fromsid) {
2077                 rc = avc_has_perm(mysid, fromsid, SECCLASS_BINDER,
2078                                   BINDER__IMPERSONATE, NULL);
2079                 if (rc)
2080                         return rc;
2081         }
2082 
2083         return avc_has_perm(fromsid, tosid, SECCLASS_BINDER, BINDER__CALL,
2084                             NULL);
2085 }