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(&list[i]);:对list[i]所指向的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)

相关推荐
檀越剑指大厂15 分钟前
【Linux系列】Shell 脚本中的条件判断:`[ ]`与`[[ ]]`的比较
linux·运维·服务器
2301_819287122 小时前
ce第六次作业
linux·运维·服务器·网络
武汉联从信息3 小时前
如何使用linux日志管理工具来管理oracle osb服务器日志文件?
linux·运维·服务器
Aileen_0v03 小时前
【AI驱动的数据结构:包装类的艺术与科学】
linux·数据结构·人工智能·笔记·网络协议·tcp/ip·whisper
州周3 小时前
Ftp目录整个下载
linux·服务器·数据库
Jackey_Song_Odd4 小时前
Ubuntu 24.04.1 解决部分中文字符(门、径)显示错误的问题
linux·ubuntu
kaixin_learn_qt_ing4 小时前
Linux export命令
linux
余额不足121384 小时前
C语言基础十六:枚举、c语言中文件的读写操作
linux·c语言·算法
冷曦_sole4 小时前
linux-19 根文件系统(一)
linux·运维·服务器
AI大模型学徒4 小时前
Linux(二)_清理空间
linux·运维·服务器