Linux 安全 - LSM源码分析

文章目录

  • 前言
  • 一、简介
    • [1.1 DAC 和 MAC](#1.1 DAC 和 MAC)
    • [1.2 LSM 调用流程图](#1.2 LSM 调用流程图)
  • 二、LSM相关数据结构
    • [2.1 struct security_hook_list](#2.1 struct security_hook_list)
    • [2.2 union security_list_options](#2.2 union security_list_options)
    • [2.3 structure security_hook_heads](#2.3 structure security_hook_heads)
  • 三、security_bprm_check
  • [四、LSM 源码分析](#四、LSM 源码分析)
    • [3.1 early_security_init](#3.1 early_security_init)
    • [3.2 security_init](#3.2 security_init)
      • [3.2.1 security_init](#3.2.1 security_init)
      • [3.2.2 ordered_lsm_init](#3.2.2 ordered_lsm_init)
      • [3.2.3 initialize_lsm](#3.2.3 initialize_lsm)
  • 五、LSM初始化例程
    • [5.1 selinux](#5.1 selinux)
    • [5.2 apparmor](#5.2 apparmor)
  • 参考资料

前言

这篇文章介绍了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

c 复制代码
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;

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

c 复制代码
/**
 * 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);
	......
}

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

2.3 structure security_hook_heads

c 复制代码
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;
	......

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

三、security_bprm_check

c 复制代码
SYSCALL_DEFINE5(execveat
	-->do_execveat()
		-->do_execveat_common()
			-->__do_execve_file()
				--exec_binprm()
					-->search_binary_handler()
						-->security_bprm_check()
c 复制代码
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);
}

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

c 复制代码
struct security_hook_heads security_hook_heads __lsm_ro_after_init;
c 复制代码
#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;							\
})

宏 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 源码分析

c 复制代码
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()

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

3.1 early_security_init

c 复制代码
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;
}

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(&listi);:对listi所指向的hlist_head进行初始化,将其设置为空链表。

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

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

c 复制代码
/* 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);
	}
}

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函数,对当前安全模块进行初始化。

c 复制代码
/* 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);
	}
}

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

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

3.2 security_init

3.2.1 security_init

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

c 复制代码
-- Capability module
-- Minor LSMs
-- Major LSM
c 复制代码
// 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 = .;

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

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

这些宏执行以下操作:

(1)

LSM_TABLE() 宏:

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

(2)

EARLY_LSM_TABLE() 宏:

c 复制代码
. = 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 符号为当前位置。

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

c 复制代码
// 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;
}

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

c 复制代码
/* 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);
}

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

c 复制代码
/* 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);
	}
}

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

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

c 复制代码
selinux_init()
apparmor_init()
tomoyo_init()
smack_init()

五、LSM初始化例程

5.1 selinux

c 复制代码
/* 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,
};

这段代码的作用是定义了一个名为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宏:

c 复制代码
#define DEFINE_LSM(lsm)							\
	static struct lsm_info __lsm_##lsm				\
		__used __section(.lsm_info.init)			\
		__aligned(sizeof(unsigned long))

宏定义用于定义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信息结构体。

c 复制代码
static __init int selinux_init(void)
{
	security_add_hooks(selinux_hooks, ARRAY_SIZE(selinux_hooks), "selinux");
}
c 复制代码
/**
 * 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);
	}
	......
}
c 复制代码
/*
 * 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;
c 复制代码
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),
	......
}
c 复制代码
/*
 * 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)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链表的头部。

c 复制代码
struct security_hook_heads {
	.....
	struct hlist_head ptrace_access_check;
	......
}

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

c 复制代码
union security_list_options {
	......
	int (*ptrace_access_check)(struct task_struct *child,
					unsigned int mode);
	......
}

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

比如:

c 复制代码
LSM_HOOK_INIT(ptrace_access_check, selinux_ptrace_access_check)

等价于:

c 复制代码
{ .head = &security_hook_heads.ptrace_access_check, .hook = { .ptrace_access_check= selinux_ptrace_access_check} }

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

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

5.2 apparmor

apparmor 和 selinux 初始化过程相同:

c 复制代码
DEFINE_LSM(apparmor) = {
	.name = "apparmor",
	.flags = LSM_FLAG_LEGACY_MAJOR | LSM_FLAG_EXCLUSIVE,
	.enabled = &apparmor_enabled,
	.blobs = &apparmor_blob_sizes,
	.init = apparmor_init,
};
c 复制代码
static int __init apparmor_init(void)
{
	......
	security_add_hooks(apparmor_hooks, ARRAY_SIZE(apparmor_hooks),
				"apparmor");
	......
}
c 复制代码
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),
	......
}

如果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)

相关推荐
FoldWinCard4 小时前
D5 Linux 网络及端口命令
linux·运维·服务器
2601_954526754 小时前
【工业传感与算法实战】温漂补偿与零点抗漂破局:基于二阶多项式拟合的 C/C++ 边缘校准算法,深度拆解“压力变送器什么牌子好”的技术硬指标
c语言·c++·算法
qq_448011164 小时前
C语言中的变量和函数的定义与声明
android·c语言·开发语言
零涂毕业设计5 小时前
毕业设计模块开发-OLED显示屏(IIC协议0.96寸)STM32 ESP32 FPGA Linux驱动免费分享
linux·stm32·单片机·嵌入式·esp32·fpga·oled
tedcloud1235 小时前
OmniRoute怎么部署?开源AI模型路由平台Linux部署教程
linux·服务器·人工智能·开源·音视频
霸道流氓气质5 小时前
KMS 密钥管理服务(Key Management Service)原理与实践
linux·服务器·数据库
SakitamaX5 小时前
LVS(Linux Virtual Server)概念详解
linux·运维·lvs
小张同学a.5 小时前
Linux系统管理
linux·运维
阿龙的工作记录6 小时前
深入理解Linux设备驱动模型
linux·嵌入式硬件·工作记录
代码村新手6 小时前
Linux的基本指令
linux·运维·服务器