• Linux 安全 - LSM源码分析


    前言

    这篇文章介绍了LSM相关知识:Linux 安全 - LSM机制,接下来源码分析LSM模块。

    一、简介

    1.1 DAC 和 MAC

    (1)
    DAC(Discretionary Access Control)基于访问控制是一种根据主体或组的身份限制对对象访问的手段。DAC使用用户和组权限,实现访问控制。DAC的一个问题是其基本操作是可传递的。一个特权用户可以创建其他特权用户,并且这些用户可能具有对受限对象的访问权限。

    传统的Linux UID/GID机制,Linux通过用户、进程、文件的UID/GID来进行权限管理的。Linux将文件的权限划分为读、写和执行三种,分别用字母r、w和x表示。每一个文件有三组读、写和执行权限,分别是针对文件的所有者(u)、文件所有者所属的组(g)以及除前两种之外的其它用户(o)。这样,如果一个用户想要将一个自己创建的文件交给另外一个用户访问,那么只需要相应地设置一下这个文件的其它用户权限位就可以了。文件的权限控制在所有者手中。

    (2)
    MAC(Mandatory Access Control)中,主体(例如用户、进程、线程)和对象(例如文件、套接字、内存段)都具有一组安全属性。这些安全属性通过MAC策略进行集中管理。在MAC的情况下,用户/组不做任何访问决策,而是由安全属性进行管理。

    每当主体尝试访问对象时,都会由操作系统内核强 制执行授权规则–检查安全属性并决定是否可进行访问。同样,任何主体对任何对象的任何操作都将根据一组授权规则(策略)进行测试,决定操作是否被允许。

    MAC提供了更严格的访问控制,它不依赖于用户或组的身份,而是根据预定义的安全策略和属性来进行访问决策。这种策略可能基于许多因素,如用户角色、标签、上下文等。通过使用MAC,可以实现更细粒度的访问控制,并提供更高的安全性,以防止特权用户滥用权限或绕过访问控制规则。

    在Linux中,MAC功能由安全模块(Linux Security Module)提供,如SELinux(Security-Enhanced Linux)和AppArmor。这些安全模块通过管理对象的安全上下文和定义访问策略来实现MAC。与DAC相比,MAC提供了更强大的安全能力,特别适用于需要更严格访问控制的环境,如多用户系统、服务器和敏感数据系统。

    1.2 LSM 调用流程图

    LSM通过在内核代码中恰好在访问内核对象(比如inode)之前放置挂钩来调解对内核对象的访问。
    在这里插入图片描述
    当用户空间中的进程调用open()系统调用打开文件时,涉及以下步骤:

    (1)用户空间中的进程调用open()系统调用,并提供文件路径作为参数。

    (2)系统调度该系统调用,并使用提供的文件路径获取与文件关联的内核文件对象和inode对象。

    (3)如果open()系统调用的参数不正确或无效,则返回错误。

    (4)内核执行常规的"自主访问控制"(DAC)文件权限检查。它验证当前用户是否具有打开文件所需的权限。如果用户没有所需的权限,则终止系统调用,并将错误返回给用户空间。

    (5)如果DAC检查通过,并且用户具有所需的权限,则Linux安全模块(LSM)框架开始工作。对于每个已启用的LSM,框架调用file_open钩子函数。每个LSM都有机会执行额外的安全检查或修改文件打开操作的行为。如果任何LSM钩子函数返回错误,则终止系统调用,并将错误返回给用户空间。

    (6)最后,如果所有的安全检查,包括DAC和LSM钩子函数,都通过了,文件将被打开给进程。创建一个新的文件描述符,并返回给用户空间的进程,允许对已打开文件进行进一步操作。

    当进程调用open()打开文件时,内核首先执行DAC文件权限检查,如果通过,然后调用LSM框架执行额外的安全检查。只有当所有的安全检查都通过后,文件才会被打开,并将文件描述符返回给进程。

    从上图可以看出,LSM挂钩是在执行DAC和其他健全性检查之后应用的。

    通过系统调用进入内核之后,系统首先进行传统的DAC权限检查(传统权限检查主要是基于用户的,用户通过验证之后就可以访问资源),通过之后才会进行MAC强制访问控制 ,从图上看来,LSM实现MAC强制访问控制主要通过LSM安全模块的钩子函数实现。

    LSM是一种基于MAC的控制形式,常见的有SELinux/AppArmor。

    以下是selinux LSM 简易图:
    在这里插入图片描述
    在这里插入图片描述

    二、LSM相关数据结构

    2.1 struct security_hook_list

    struct hlist_node {
    	struct hlist_node *next, **pprev;
    };
    
    struct hlist_head {
    	struct hlist_node *first;
    };
    
    union security_list_options {
    	.....
    
    	int (*ptrace_access_check)(struct task_struct *child,
    					unsigned int mode);
    	......
    }
    
    /*
     * Security module hook list structure.
     * For use with generic list macros for common operations.
     */
    struct security_hook_list {
    	struct hlist_node		list;
    	struct hlist_head		*head;
    	union security_list_options	hook;
    	char				*lsm;
    } __randomize_layout;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    security_hook_list结构体用于表示安全模块钩子列表的结构。这个结构体被用于在通用的列表操作中进行常见操作。

    结构体的成员含义:
    (1)list:struct hlist_node类型的成员,用于将security_hook_list结构体链接到一个哈希链表中。哈希链表提供了高效的查找和插入操作。

    (2)head:指向struct hlist_head类型的指针,指向哈希链表的头部。哈希链表的头部包含指向链表中第一个元素的指针。

    (3)hook:union security_list_options类型的联合体,表示安全模块钩子的选项。这是一个通用的选项字段,具体的含义可能取决于具体的安全模块和使用场景。

    (4)lsm:一个指向字符(char)类型的指针,用于存储与此安全模块钩子相关的LSM(Linux Security Module)的名称。LSM是Linux内核的安全模块架构,用于实现各种安全策略和功能。

    (5)__randomize_layout:一个特殊的属性,用于在内存中随机化这个结构体的布局。这有助于增加系统的安全性,使攻击者更难利用结构体的布局来进行攻击。

    这个结构体的设计使得可以使用通用的列表宏(generic list macros)对安全模块钩子进行常见操作,例如插入、删除和遍历。它提供了一种灵活的方式来管理安全模块钩子,并与其他系统组件进行交互。

    2.2 union security_list_options

    /**
     * union security_list_options - Linux Security Module hook function list
     *
     * Security hooks for program execution operations.
     *
     **/
    
    union security_list_options {
    	int (*binder_set_context_mgr)(struct task_struct *mgr);
    	int (*binder_transaction)(struct task_struct *from,
    					struct task_struct *to);
    	int (*binder_transfer_binder)(struct task_struct *from,
    					struct task_struct *to);
    	int (*binder_transfer_file)(struct task_struct *from,
    					struct task_struct *to,
    					struct file *file);
    
    	int (*ptrace_access_check)(struct task_struct *child,
    					unsigned int mode);
    	int (*ptrace_traceme)(struct task_struct *parent);
    	......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    为LSM定义的安全挂钩的函数指针的联合,在内核代码中的各种关键路径上调用。

    2.3 structure security_hook_heads

    struct security_hook_heads {
    	struct hlist_head binder_set_context_mgr;
    	struct hlist_head binder_transaction;
    	struct hlist_head binder_transfer_binder;
    	struct hlist_head binder_transfer_file;
    	struct hlist_head ptrace_access_check;
    	struct hlist_head ptrace_traceme;
    	......
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    该数据结构包含与每个钩子相对应的链表的头部,考虑到LSM的堆叠属性,从而允许它们以正确的顺序执行。

    三、security_bprm_check

    SYSCALL_DEFINE5(execveat
    	-->do_execveat()
    		-->do_execveat_common()
    			-->__do_execve_file()
    				--exec_binprm()
    					-->search_binary_handler()
    						-->security_bprm_check()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    int security_bprm_check(struct linux_binprm *bprm)
    {
    	int ret;
    
    	ret = call_int_hook(bprm_check_security, 0, bprm);
    	if (ret)
    		return ret;
    	return ima_bprm_check(bprm);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    函数 security_bprm_check 就是一个LSM HOOK 点,用于在执行二进制文件之前进行安全性检查。

    struct security_hook_heads security_hook_heads __lsm_ro_after_init;
    
    • 1
    #define call_int_hook(FUNC, IRC, ...) ({			\
    	int RC = IRC;						\
    	do {							\
    		struct security_hook_list *P;			\
    								\
    		hlist_for_each_entry(P, &security_hook_heads.FUNC, list) { \
    			RC = P->hook.FUNC(__VA_ARGS__);		\
    			if (RC != 0)				\
    				break;				\
    		}						\
    	} while (0);						\
    	RC;							\
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    宏 call_int_hook,用于调用安全模块的钩子函数。

    这个宏的作用是依次调用安全模块的钩子函数,并将它们的返回值进行处理。

    宏接受三个参数:
    FUNC:安全模块钩子函数的名称。这个宏将通过 FUNC 参数拼接出对应的钩子函数列表的成员。
    IRC:初始返回值(Initial Return Code)。这个值将被用作初始的返回值,如果所有的钩子函数都返回0,则最终的返回值将是初始返回值。
    …:可变参数,用于传递给钩子函数的参数。

    宏的工作流程如下:
    使用 hlist_for_each_entry 宏遍历存储在 security_hook_heads.FUNC 中的钩子函数列表。
    对于每个钩子函数,调用 P->hook.FUNC 并传递可变参数 __VA_ARGS__。
    如果钩子函数的返回值 RC 不等于0,则跳出循环。

    四、LSM 源码分析

    start_kernel()
    	-->early_security_init()
    	......
    	-->security_init()
    		/* Load LSMs in specified order. */
    		-->ordered_lsm_init()
    			-->initialize_lsm()
    				-->lsm->init()
    					-->selinux_init()
    					-->apparmor_init()
    					-->tomoyo_init()
    					-->smack_init()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    由于selinux、apparmor、tomoyo和smack都是LSM_FLAG_EXCLUSIVE 独占模式标志,因此只能选择其中的一个启动。

    3.1 early_security_init

    struct security_hook_heads security_hook_heads __lsm_ro_after_init;
    
    int __init early_security_init(void)
    {
    	int i;
    	struct hlist_head *list = (struct hlist_head *) &security_hook_heads;
    	struct lsm_info *lsm;
    
    	for (i = 0; i < sizeof(security_hook_heads) / sizeof(struct hlist_head);
    	     i++)
    		INIT_HLIST_HEAD(&list[i]);
    
    	for (lsm = __start_early_lsm_info; lsm < __end_early_lsm_info; lsm++) {
    		if (!lsm->enabled)
    			lsm->enabled = &lsm_enabled_true;
    		prepare_lsm(lsm);
    		initialize_lsm(lsm);
    	}
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    early_security_init函数是在系统初始化过程中早期调用的安全初始化函数。

    (1)将security_hook_heads的地址强制转换为struct hlist_head指针,并将其赋值给名为list的变量。security_hook_heads是一个全局变量,它存储了一组hlist_head结构体,用于存储不同安全钩子的链表头部。

    (2)循环遍历security_hook_heads数组,计算需要迭代的次数。通过sizeof(security_hook_heads)除以sizeof(struct hlist_head),可以确定数组中有多少个hlist_head元素。

    (3)INIT_HLIST_HEAD(&list[i]);:对list[i]所指向的hlist_head进行初始化,将其设置为空链表。

    (4)循环遍历__start_early_lsm_info到__end_early_lsm_info之间的lsm_info结构体数组,这些结构体包含了早期加载的安全模块信息。

    (5)调用prepare_lsm函数,对当前安全模块进行准备工作。

    /* Prepare LSM for initialization. */
    static void __init prepare_lsm(struct lsm_info *lsm)
    {
    	int enabled = lsm_allowed(lsm);
    
    	/* Record enablement (to handle any following exclusive LSMs). */
    	set_enabled(lsm, enabled);
    
    	/* If enabled, do pre-initialization work. */
    	if (enabled) {
    		if ((lsm->flags & LSM_FLAG_EXCLUSIVE) && !exclusive) {
    			exclusive = lsm;
    			init_debug("exclusive chosen: %s\n", lsm->name);
    		}
    
    		lsm_set_blob_sizes(lsm->blobs);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    lsm_allowed(lsm):该函数判断给定的 LSM 是否被允许启用。
    set_enabled(lsm, enabled):该函数记录 LSM 的启用状态,以便处理后续的互斥 LSM。
    如果 LSM 被启用:
    检查是否为互斥 LSM(lsm->flags & LSM_FLAG_EXCLUSIVE)并且当前没有已选择的互斥 LSM(!exclusive),如果是,则将该 LSM 设置为当前互斥 LSM(exclusive = lsm)。
    调用 lsm_set_blob_sizes(lsm->blobs) 设置该 LSM 的相关数据块大小。

    prepare_lsm函数通过调用lsm_allowed函数进行判断,如果有模块的flag中加入了LSM_FLAG_EXCLUSIVE,则注册成独占模块,其他声明了LSM_FLAG_EXCLUSIVE的模块就不会被启用。常见Linux系统中lockdown,yama,loadpin,safesetid,integrity以及capbility均为非独占模块,在内核中可以同时加载;而selinux,apparmor以及smack均是独占模块,不能同时加载。

    (6)调用initialize_lsm函数,对当前安全模块进行初始化。

    /* Initialize a given LSM, if it is enabled. */
    static void __init initialize_lsm(struct lsm_info *lsm)
    {
    	if (is_enabled(lsm)) {
    		int ret;
    
    		init_debug("initializing %s\n", lsm->name);
    		ret = lsm->init();
    		WARN(ret, "%s failed to initialize: %d\n", lsm->name, ret);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    如果给定的 LSM 已启用,调用 lsm->init() 初始化该 LSM。

    总的来说,这段代码的作用是在系统初始化早期对安全模块进行初始化。它遍历安全钩子的链表头部,将每个链表头部初始化为空链表。然后,对早期加载的安全模块进行准备和初始化操作,确保它们的状态正确并可用于后续的安全处理。

    3.2 security_init

    3.2.1 security_init

    LSM内核调用security_init进行LSM框架初始化,该初始化按以下顺序加载已启用的受支持的Linux安全模块:

    – Capability module
    – Minor LSMs
    – Major LSM
    
    • 1
    • 2
    • 3
    // linux-5.4.18/include/asm-generic/vmlinux.lds.h
    
    #ifdef CONFIG_SECURITY
    #define LSM_TABLE()	. = ALIGN(8);					\
    			__start_lsm_info = .;				\
    			KEEP(*(.lsm_info.init))				\
    			__end_lsm_info = .;
    #define EARLY_LSM_TABLE()	. = ALIGN(8);				\
    			__start_early_lsm_info = .;			\
    			KEEP(*(.early_lsm_info.init))			\
    			__end_early_lsm_info = .;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    vmlinux.lds.h 是 Linux 内核源代码中的一个文件,用于定义链接器脚本(Linker Script)来控制内核镜像的链接和布局。

    LSM_TABLE() 和 EARLY_LSM_TABLE() 宏被用于定义与 Linux 安全模块(LSM)相关的信息表。这些宏定义了链接器脚本中的一系列指令,用于在生成的内核镜像中定位和保留 LSM 相关的初始化数据。

    这些宏执行以下操作:
    (1)
    LSM_TABLE() 宏:

    . = ALIGN(8);:将当前位置对齐到下一个 8 字节边界。
    __start_lsm_info = .;:设置 __start_lsm_info 符号为当前位置。
    KEEP(*(.lsm_info.init)):保留所有 .lsm_info.init 节的内容。
    __end_lsm_info = .;:设置 __end_lsm_info 符号为当前位置。
    
    • 1
    • 2
    • 3
    • 4

    (2)
    EARLY_LSM_TABLE() 宏:

    . = ALIGN(8);:将当前位置对齐到下一个 8 字节边界。
    __start_early_lsm_info = .;:设置 __start_early_lsm_info 符号为当前位置。
    KEEP(*(.early_lsm_info.init)):保留所有 .early_lsm_info.init 节的内容。
    __end_early_lsm_info = .;:设置 __end_early_lsm_info 符号为当前位置。
    
    • 1
    • 2
    • 3
    • 4

    这些宏的目的是在链接器脚本中定义特定的位置和符号,以便将与 LSM 相关的初始化数据放置在正确的位置,并在内核镜像中保留这些数据。

    // linux-5.4.18/include/linux/lsm_hooks.h
    
    /*
     * Security blob size or offset data.
     */
    struct lsm_blob_sizes {
    	int	lbs_cred;
    	int	lbs_file;
    	int	lbs_inode;
    	int	lbs_ipc;
    	int	lbs_msg_msg;
    	int	lbs_task;
    };
    
    enum lsm_order {
    	LSM_ORDER_FIRST = -1,	/* This is only for capabilities. */
    	LSM_ORDER_MUTABLE = 0,
    };
    
    struct lsm_info {
    	const char *name;	/* Required. */
    	enum lsm_order order;	/* Optional: default is LSM_ORDER_MUTABLE */
    	unsigned long flags;	/* Optional: flags describing LSM */
    	int *enabled;		/* Optional: controlled by CONFIG_LSM */
    	int (*init)(void);	/* Required. */
    	struct lsm_blob_sizes *blobs; /* Optional: for blob sharing. */
    };
    
    /**
     * security_init - initializes the security framework
     *
     * This should be called early in the kernel initialization sequence.
     */
    int __init security_init(void)
    {
    	struct lsm_info *lsm;
    
    	pr_info("Security Framework initializing\n");
    
    	/*
    	 * Append the names of the early LSM modules now that kmalloc() is
    	 * available
    	 */
    	for (lsm = __start_early_lsm_info; lsm < __end_early_lsm_info; lsm++) {
    		if (lsm->enabled)
    			lsm_append(lsm->name, &lsm_names);
    	}
    
    	/* Load LSMs in specified order. */
    	ordered_lsm_init();
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    security_init函数用于初始化安全框架。它应该在内核初始化序列的早期调用。

    (1)定义一个指向lsm_info结构体的指针变量lsm,用于遍历LSM模块的数组。循环遍历__start_early_lsm_info到__end_early_lsm_info之间的lsm_info结构体数组,这些结构体包含了早期加载的安全模块的信息。

    (2)调用ordered_lsm_init函数,按照指定的顺序加载LSM模块。

    这段代码的作用是初始化安全框架。它遍历早期加载的安全模块数组,将启用的模块名称追加到链表中。然后,按照指定的顺序加载LSM模块。这个函数在内核初始化的早期阶段被调用,用于确保安全框架在系统启动时得到正确的初始化。

    3.2.2 ordered_lsm_init

    /* Boot-time LSM user choice */
    static __initdata const char *chosen_lsm_order;
    static __initdata const char *chosen_major_lsm;
    
    static __initconst const char * const builtin_lsm_order = CONFIG_LSM;
    
    /* Ordered list of LSMs to initialize. */
    static __initdata struct lsm_info **ordered_lsms;
    
    static void __init ordered_lsm_init(void)
    {
    	struct lsm_info **lsm;
    
    	ordered_lsms = kcalloc(LSM_COUNT + 1, sizeof(*ordered_lsms),
    				GFP_KERNEL);
    
    	if (chosen_lsm_order) {
    		if (chosen_major_lsm) {
    			pr_info("security= is ignored because it is superseded by lsm=\n");
    			chosen_major_lsm = NULL;
    		}
    		ordered_lsm_parse(chosen_lsm_order, "cmdline");
    	} else
    		ordered_lsm_parse(builtin_lsm_order, "builtin");
    
    	for (lsm = ordered_lsms; *lsm; lsm++)
    		prepare_lsm(*lsm);
    
    	init_debug("cred blob size     = %d\n", blob_sizes.lbs_cred);
    	init_debug("file blob size     = %d\n", blob_sizes.lbs_file);
    	init_debug("inode blob size    = %d\n", blob_sizes.lbs_inode);
    	init_debug("ipc blob size      = %d\n", blob_sizes.lbs_ipc);
    	init_debug("msg_msg blob size  = %d\n", blob_sizes.lbs_msg_msg);
    	init_debug("task blob size     = %d\n", blob_sizes.lbs_task);
    
    	/*
    	 * Create any kmem_caches needed for blobs
    	 */
    	if (blob_sizes.lbs_file)
    		lsm_file_cache = kmem_cache_create("lsm_file_cache",
    						   blob_sizes.lbs_file, 0,
    						   SLAB_PANIC, NULL);
    	if (blob_sizes.lbs_inode)
    		lsm_inode_cache = kmem_cache_create("lsm_inode_cache",
    						    blob_sizes.lbs_inode, 0,
    						    SLAB_PANIC, NULL);
    
    	lsm_early_cred((struct cred *) current->cred);
    	lsm_early_task(current);
    	for (lsm = ordered_lsms; *lsm; lsm++)
    		initialize_lsm(*lsm);
    
    	kfree(ordered_lsms);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    ordered_lsm_init函数用于按照指定的顺序初始化LSM模块。

    代码含义如下:
    (1)定义一个指向lsm_info结构体指针的指针变量lsm,用于遍历LSM模块的指针数组。
    (2)使用kcalloc函数为ordered_lsms分配内存,大小为LSM_COUNT + 1个指针的大小。这个数组用于存储按顺序加载的LSM模块的指针。
    (3)如果存在chosen_lsm_order,表示在内核命令行中指定了LSM的加载顺序。调用ordered_lsm_parse函数解析chosen_lsm_order,解析类型为"cmdline",将解析结果存储在ordered_lsms数组中。
    (4)如果没有指定chosen_lsm_order,则使用内核编译时的默认LSM加载顺序。调用ordered_lsm_parse函数解析builtin_lsm_order,解析类型为"builtin",将解析结果存储在ordered_lsms数组中。
    (5)遍历ordered_lsms数组,对于每个非空指针,调用prepare_lsm函数进行LSM的准备工作。
    (6)打印LSM需要的blob大小的调试信息,包括cred、file、inode、ipc、msg_msg和task blob的大小。
    (7)如果blob_sizes.lbs_file不为0,使用kmem_cache_create函数创建一个名为lsm_file_cache的内存缓存,用于存储file blob。
    (8)如果blob_sizes.lbs_inode不为0,使用kmem_cache_create函数创建一个名为lsm_inode_cache的内存缓存,用于存储inode blob。
    (9)调用lsm_early_cred函数,传递当前进程的cred结构作为参数,进行早期的cred blob初始化。
    (10)调用lsm_early_task函数,传递当前进程作为参数,进行早期的task blob初始化。
    (11)再次遍历ordered_lsms数组,对于每个非空指针,调用initialize_lsm函数进行LSM的初始化。

    这段代码的作用是按照指定的顺序初始化LSM模块。它根据指定的LSM加载顺序解析LSM模块,并进行准备工作和初始化。同时,它还创建了用于存储file blob和inode blob的内存缓存。在LSM初始化之前,它对cred blob和task blob进行了早期的初始化。完成初始化后,释放了用于存储LSM模块指针的内存。

    3.2.3 initialize_lsm

    /* Initialize a given LSM, if it is enabled. */
    static void __init initialize_lsm(struct lsm_info *lsm)
    {
    	if (is_enabled(lsm)) {
    		int ret;
    
    		init_debug("initializing %s\n", lsm->name);
    		ret = lsm->init();
    		WARN(ret, "%s failed to initialize: %d\n", lsm->name, ret);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    lsm->init()表示调用给定LSM的初始化函数。

    这个函数用于初始化给定的LSM,前提是该LSM是启用的。它首先检查LSM是否启用,然后调用LSM的初始化函数:

    selinux_init()
    apparmor_init()
    tomoyo_init()
    smack_init()
    
    • 1
    • 2
    • 3
    • 4

    五、LSM初始化例程

    5.1 selinux

    /* SELinux requires early initialization in order to label
       all processes and objects when they are created. */
    DEFINE_LSM(selinux) = {
    	.name = "selinux",
    	.flags = LSM_FLAG_LEGACY_MAJOR | LSM_FLAG_EXCLUSIVE,
    	.enabled = &selinux_enabled,
    	.blobs = &selinux_blob_sizes,
    	.init = selinux_init,
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这段代码的作用是定义了一个名为selinux的LSM模块的信息结构体,并初始化了结构体的成员。

    (1).name = “selinux”:设置了name成员,表示LSM模块的名称是"selinux"。

    (2).flags = LSM_FLAG_LEGACY_MAJOR | LSM_FLAG_EXCLUSIVE:设置了flags成员,表示LSM模块的标志。LSM_FLAG_LEGACY_MAJOR标志表示该模块是遗留的主要模块,LSM_FLAG_EXCLUSIVE标志表示该模块是独占的,不与其他模块共存。比如 selinux 不能和 apparmor 同时存在。

    (3).enabled = &selinux_enabled:设置了enabled成员,指向一个名为selinux_enabled的变量。这个变量用于指示selinux模块是否启用。

    (4).blobs = &selinux_blob_sizes:设置了blobs成员,指向一个名为selinux_blob_sizes的变量。这个变量用于指定selinux模块需要的各个blob的大小。

    (5).init = selinux_init:设置了init成员,指向一个名为selinux_init的函数。这个函数用于初始化selinux模块。

    通过DEFINE_LSM宏创建的结构体被赋值给selinux,从而定义了一个名为selinux的LSM模块的信息结构体。这个结构体包含了模块的名称、标志、启用状态、blob大小和初始化函数等信息。

    DEFINE_LSM宏:

    #define DEFINE_LSM(lsm)							\
    	static struct lsm_info __lsm_##lsm				\
    		__used __section(.lsm_info.init)			\
    		__aligned(sizeof(unsigned long))
    
    • 1
    • 2
    • 3
    • 4

    宏定义用于定义LSM模块的信息结构体,这个宏定义包含了以下几个部分:
    (1)DEFINE_LSM(lsm):定义了一个名为DEFINE_LSM的宏,宏的参数是lsm,用于指定LSM模块的名称。

    (2)static struct lsm_info _lsm##lsm:定义了一个名为__lsm_加上lsm参数的结构体,该结构体用于存储LSM模块的信息。使用static关键字使结构体具有文件作用域。

    (3)__used:这是一个编译器特定的属性,用于告诉编译器即使没有使用到这个结构体,也不要优化掉它。

    (4)__section(.lsm_info.init):这是一个编译器特定的属性,用于指定将这个结构体放置在名为.lsm_info.init的特定节(section)中。这个节可能是用于存放LSM模块信息的特定节。

    (5)__aligned(sizeof(unsigned long)):这是一个编译器特定的属性,用于指定结构体的对齐方式。在这里,结构体的对齐方式被设置为unsigned long的大小。

    这个宏定义用于创建一个静态的LSM信息结构体,该结构体存储了LSM模块的相关信息,并通过编译器的属性设置将它放置在特定的节中。这个宏定义通常用于在LSM模块的源文件中定义LSM信息结构体。

    static __init int selinux_init(void)
    {
    	security_add_hooks(selinux_hooks, ARRAY_SIZE(selinux_hooks), "selinux");
    }
    
    • 1
    • 2
    • 3
    • 4
    /**
     * security_add_hooks - Add a modules hooks to the hook lists.
     * @hooks: the hooks to add
     * @count: the number of hooks to add
     * @lsm: the name of the security module
     *
     * Each LSM has to register its hooks with the infrastructure.
     */
    void __init security_add_hooks(struct security_hook_list *hooks, int count,
    				char *lsm)
    {
    	int i;
    
    	for (i = 0; i < count; i++) {
    		hooks[i].lsm = lsm;
    		hlist_add_tail_rcu(&hooks[i].list, hooks[i].head);
    	}
    	......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    /*
     * Security module hook list structure.
     * For use with generic list macros for common operations.
     */
    struct security_hook_list {
    	struct hlist_node		list;
    	struct hlist_head		*head;
    	union security_list_options	hook;
    	char				*lsm;
    } __randomize_layout;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = {
    	LSM_HOOK_INIT(binder_set_context_mgr, selinux_binder_set_context_mgr),
    	LSM_HOOK_INIT(binder_transaction, selinux_binder_transaction),
    	LSM_HOOK_INIT(binder_transfer_binder, selinux_binder_transfer_binder),
    	LSM_HOOK_INIT(binder_transfer_file, selinux_binder_transfer_file),
    
    	LSM_HOOK_INIT(ptrace_access_check, selinux_ptrace_access_check),
    	LSM_HOOK_INIT(ptrace_traceme, selinux_ptrace_traceme),
    	......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    /*
     * Initializing a security_hook_list structure takes
     * up a lot of space in a source file. This macro takes
     * care of the common case and reduces the amount of
     * text involved.
     */
    #define LSM_HOOK_INIT(HEAD, HOOK) \
    	{ .head = &security_hook_heads.HEAD, .hook = { .HEAD = HOOK } }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    (1)LSM_HOOK_INIT(HEAD, HOOK):定义了一个名为LSM_HOOK_INIT的宏,宏的参数是HEAD和HOOK,用于指定security_hook_list结构体的成员。

    (2){ .head = &security_hook_heads.HEAD, .hook = { .HEAD = HOOK } }:这是一个结构体初始化的语法,用于初始化security_hook_list结构体的成员。具体来说:

    .head = &security_hook_heads.HEAD:初始化head成员,将其指向security_hook_heads结构体中的HEAD成员的地址。security_hook_heads是一个全局变量,用于存储各个hook链表的头部。

    struct security_hook_heads {
    	.....
    	struct hlist_head ptrace_access_check;
    	......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    .hook = { .HEAD = HOOK }:初始化hook成员,其中的.HEAD = HOOK将HOOK的值赋给hook中对应的HEAD成员。这里的HEAD是一个宏参数,用于指定具体的hook函数

    union security_list_options {
    	......
    	int (*ptrace_access_check)(struct task_struct *child,
    					unsigned int mode);
    	......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    struct security_hook_heads这个全局变量用于存储各个hook链表的头部。

    比如:

    LSM_HOOK_INIT(ptrace_access_check, selinux_ptrace_access_check)
    
    • 1

    等价于:

    { .head = &security_hook_heads.ptrace_access_check, .hook = { .ptrace_access_check= selinux_ptrace_access_check} }
    
    • 1

    这个宏定义用于简化初始化security_hook_list结构体的操作。通过宏调用,可以用较少的代码初始化security_hook_list结构体中的head和hook成员。

    如果Linux使用的是 selinux,那么执行 exec系统调用时,执行 security_bprm_check hook点就是执行 selinux_ptrace_access_check 函数。

    5.2 apparmor

    apparmor 和 selinux 初始化过程相同:

    DEFINE_LSM(apparmor) = {
    	.name = "apparmor",
    	.flags = LSM_FLAG_LEGACY_MAJOR | LSM_FLAG_EXCLUSIVE,
    	.enabled = &apparmor_enabled,
    	.blobs = &apparmor_blob_sizes,
    	.init = apparmor_init,
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    static int __init apparmor_init(void)
    {
    	......
    	security_add_hooks(apparmor_hooks, ARRAY_SIZE(apparmor_hooks),
    				"apparmor");
    	......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    static struct security_hook_list apparmor_hooks[] __lsm_ro_after_init = {
    	LSM_HOOK_INIT(ptrace_access_check, apparmor_ptrace_access_check),
    	LSM_HOOK_INIT(ptrace_traceme, apparmor_ptrace_traceme),
    	LSM_HOOK_INIT(capget, apparmor_capget),
    	LSM_HOOK_INIT(capable, apparmor_capable),
    	......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    如果Linux使用的是 apparmor,那么执行 exec系统调用时,执行 security_bprm_check hook点就是执行 apparmor_ptrace_access_check函数。

    参考资料

    Linux 5.4.18

    https://pwl999.blog.csdn.net/article/details/107066666
    https://blog.csdn.net/feelabclihu/article/details/121059328
    https://github.com/kubearmor/KubeArmor/wiki/Introduction-to-Linux-Security-Modules-(LSMs)

  • 相关阅读:
    CSRF跨域请求伪造
    一个字符串数组里的id和json数组里的元素对比
    【100天精通Python】Day53:Python 数据分析_NumPy数据操作和分析进阶
    Azure 机器学习:在 Azure 机器学习中使用 Azure OpenAI 模型
    JSP request对象功能详解说明
    Codeforces 346B 不包含子串的LCS
    数智人才培养丨学生学徒阶段成果分享
    在C++和C中static关键字的用法,在C++和C中const关键字的用法
    视频直播点播平台EasyDSS如何通过接口实现文件合成?
    java基于springboot+nodejs+vue网上投标招标系统ssm项目介绍
  • 原文地址:https://blog.csdn.net/weixin_45030965/article/details/133069910