天天看點

殘缺的LSM架構啟動分析前言LSM 簡介LSM 啟動分析附錄

文章目錄

  • 前言
  • LSM 簡介
  • LSM 啟動分析
    • 相關資料結構
    • early_security_init
    • security_init
    • 填充security_hook_heads全局變量
    • 使用安全子產品中的鈎子函數
  • 附錄
    • 小故事
      • SELinux
      • SMACK
      • Tomoyo
      • AppArmor
      • Yama

前言

介紹了LSM架構中鈎子函數的建立和被使用過程。

建議閱讀本文之前,對LSM有個架構認識,可以參考:基于Flask的Linux安全子產品架構LSM – coursera

參考部落格:Linux Security Modules架構源碼分析

LSM 簡介

來源一:Linux Security Module Usage

Linux安全子產品( Linux Security Module, LSM)架構提供了一種機制,可以将各種安全檢查與新的核心擴充挂鈎。名稱“子產品”有點用詞不當,因為這些擴充實際上并不是可加載的核心子產品。相反,在

"security=..."

給定核心中内置了多個LSM的情況下,它們可以在建構時通過CONFIG_DEFAULT_SECURITY 進行選擇,并且可以在引導時通過核心指令行參數進行覆寫 。

LSM的主要用于使用者的強制通路控制(Mandatory Access Control, MAC)擴充,可提供全面的安全政策。例如包括SELinux,Smack,Tomoyo和AppArmor。除了較大的MAC擴充之外,當Linux本身的核心功能中沒有這些調整時,可以使用LSM來建構其他擴充,以對系統操作進行特定的更改。

可以通過

/sys/kernel/security/lsm

檢視系統目前活動安全子產品的清單。

➜ cat /sys/kernel/security/lsm
lockdown,capability,yama,apparmor
           

參考二:《Linux核心安全子產品深入剖析》 第二部分 強制通路控制。

LSM 啟動分析

start_kernel是系統啟動執行完架構相關代碼後,通用代碼的入口,LSM 的初始化也是在這裡調用。

/*
* Interrupts are still disabled. Do necessary setups, then
* enable them.
*/
...
early_security_init();
...

....
security_init();
...
           
  • early_lsm 用于早期啟動的子產品,當配置 CONFIG_SECURITY_LOCKDOWN_LSM_EARLY 時lockdown 子產品被配置為 early_lsm,調用的函數為 early_security_init。
  • lsm 是除 lockdown 外預設配置的方式,調用的函數為 security_init。

相關資料結構

在開始介紹上面兩個函數之前,我們先簡單看下相關的資料結構。

[PS:需要有哈希表結構的相關背景知識,可以參考:linux核心連結清單結構]

security_hook_heads:via。這是一個結構體,裡面的沒有個元素都是哈希表的表頭。

security.c使用它建立一個名為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 security_hook_heads security_hook_heads __lsm_ro_after_init;
           

security_hook_list:via 。這個結構作為哈希表鍊中的節點。

/*
 * 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_list_options :via。security_list_options采用聯合體結構,是因為不同的函數指針結構不同。使用聯合體,不管hook是清單中的哪種函數類型,都可以指向。

/**
 * 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);
    ...
    ...
}
           

它們三者的使用如下圖所示。通過預置的方式,調用這些鈎子函數。[這個後面會展示代碼介紹]

殘缺的LSM架構啟動分析前言LSM 簡介LSM 啟動分析附錄

early_security_init

early_security_init:via。

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]); // 全局變量security_hook_heads中的所有的頭結點,初始化指向NULL

	for (lsm = __start_early_lsm_info; lsm < __end_early_lsm_info; lsm++) {
		if (!lsm->enabled)
			lsm->enabled = &lsm_enabled_true;
		prepare_lsm(lsm); /* Prepare LSM for initialization. */
		initialize_lsm(lsm);
	}

	return 0;
}
           

__start_early_lsm_info:via。它的結構在

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 = .;
#else
#define LSM_TABLE()
#define EARLY_LSM_TABLE()
#endif
           

通過

include/linux/lsm_hooks.h

包含進來。

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. */
};

extern struct lsm_info __start_lsm_info[], __end_lsm_info[];
extern struct lsm_info __start_early_lsm_info[], __end_early_lsm_info[];

#define DEFINE_LSM(lsm)							\
	static struct lsm_info __lsm_##lsm				\
		__used __section(.lsm_info.init)			\
		__aligned(sizeof(unsigned long))

#define DEFINE_EARLY_LSM(lsm)						\
	static struct lsm_info __early_lsm_##lsm			\
		__used __section(.early_lsm_info.init)			\
		__aligned(sizeof(unsigned long))
           

正如前文所說,early_lsm用于早期啟動的子產品,當配置 CONFIG_SECURITY_LOCKDOWN_LSM_EARLY 時lockdown 子產品被配置為 early_lsm,調用的函數為 early_security_init。DEFINE_EARLY_LSM(lockdown) :via。将early_lsm的相關資訊填充到__start_early_lsm_info[] ~__end_early_lsm_info[]中。

#ifdef CONFIG_SECURITY_LOCKDOWN_LSM_EARLY
DEFINE_EARLY_LSM(lockdown) = {
#else
DEFINE_LSM(lockdown) = {
#endif
	.name = "lockdown",
	.init = lockdown_lsm_init,
};
    
// 宏展開;我不很明白這個__section是如何将這個元素,放入到__start_early_lsm_info[]清單中的;
static struct lsm_info __early_lsm_lockdown = {
    .name = "lockdown",
    .init = lockdown_lsm_init,
}; __used __section(.early_lsm_info.init) __aligned(sizeof(unsigned long))
           

initialize_lsm:via。調用各自安全子產品自己的init函數。

/* 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(); // 調用各自對應的init函數
		WARN(ret, "%s failed to initialize: %d\n", lsm->name, ret);
	}
}
           

小結:

early_security_init

根據lsm_info[]中的資訊,調用early_lsm中各自的init函數。目前,整個系統中,隻有當配置 CONFIG_SECURITY_LOCKDOWN_LSM_EARLY 時lockdown 子產品被配置為 early_lsm。

security_init

security_init:via

char *lsm_names;

/**
 * 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); /*将early_lsm子產品名拷貝到lsm_names中*/
	}

	/* Load LSMs in specified order. */
	ordered_lsm_init();

	return 0;
}
           

ordered_lsm_init:via。我不知道這個函數的作用。根據注釋可以知道,它将LSM的内容排了一個順序,然後加載。加載函數,同樣是上面介紹的

initialize_lsm

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);
}
           

小結:

security_init

也會調用lsm中各個安全子產品的init函數。

填充security_hook_heads全局變量

我們以selinux為例,看它是如何填充security_hook_heads全局變量。

(1)via:selinux使用DEFINE_LSM宏,将相關資訊填充為一個lsm_info結構體變量。這個結構體變量位于__start_lsm_info和 __start_lsm_info之間。

/* 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_boot,
	.blobs = &selinux_blob_sizes,
	.init = selinux_init,
};

// 等價于
static struct lsm_info __lsm_selinux = {
	.name = "selinux",
	.flags = LSM_FLAG_LEGACY_MAJOR | LSM_FLAG_EXCLUSIVE,
	.enabled = &selinux_enabled_boot,
	.blobs = &selinux_blob_sizes,
	.init = selinux_init,
}; __used __section(.early_lsm_info.init) __aligned(sizeof(unsigned long))
           

(2)security_init函數會調用結構中的init函數。對于selinux而言,調用selinux_init函數。

selinux_init:via。這個是selinux的初始化函數。

static __init int selinux_init(void)
{
	pr_info("SELinux:  Initializing.\n");

	memset(&selinux_state, 0, sizeof(selinux_state));
	enforcing_set(&selinux_state, selinux_enforcing_boot);
	selinux_state.checkreqprot = selinux_checkreqprot_boot;
	selinux_ss_init(&selinux_state.ss);
	selinux_avc_init(&selinux_state.avc);

	/* Set the security state for the initial task. */
	cred_init_security();

	default_noexec = !(VM_DATA_DEFAULT_FLAGS & VM_EXEC);

	avc_init();

	avtab_cache_init();

	ebitmap_cache_init();

	hashtab_cache_init();
	/*其中的security_add_hooks函數,會将鈎子函數放置到合适的位置。至于這個位置在哪,咱們慢慢看。*/
	security_add_hooks(selinux_hooks, ARRAY_SIZE(selinux_hooks), "selinux"); 

	if (avc_add_callback(selinux_netcache_avc_callback, AVC_CALLBACK_RESET))
		panic("SELinux: Unable to register AVC netcache callback\n");

	if (avc_add_callback(selinux_lsm_notifier_avc_callback, AVC_CALLBACK_RESET))
		panic("SELinux: Unable to register AVC LSM notifier callback\n");

	if (selinux_enforcing_boot)
		pr_debug("SELinux:  Starting in enforcing mode\n");
	else
		pr_debug("SELinux:  Starting in permissive mode\n");

	fs_validate_description("selinux", selinux_fs_parameters);

	return 0;
}
           

security_add_hooks:via ;

static struct security_hook_list selinux_hooks[] :via

LSM_HOOK_INIT:via

/**
 * 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);
	}

	/*
	 * Don't try to append during early_security_init(), we'll come back
	 * and fix this up afterwards.
	 */
	if (slab_is_available()) {
		if (lsm_append(lsm, &lsm_names) < 0)
			panic("%s - Cannot get early memory.\n", __func__);
	}
}

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),
    ....
    ....
}


#define LSM_HOOK_INIT(HEAD, HOOK) \
	{ .head = &security_hook_heads.HEAD, .hook = { .HEAD = HOOK } }
           

我們将上面的宏展開來看。

selinux_hooks[0] = security_hook_list {
    struct hlist_node		list;
    // head指向security_hook_heads全局變量中的一個選項。這是哈希表的表頭。
	struct hlist_head		*head = &security_hook_heads.binder_set_context_mgr; 
    // union結構的hook,存儲着對應鈎子函數具體實作所在位置
	union security_list_options	hook = {.binder_set_context_mgr=selinux_binder_set_context_mgr}; 
	char				*lsm;
}
           

(3)security_add_hooks使用hlist_add_tail_rcu,将security_hook_list結構的節點放入哈希表中。每個節點中的hook存儲着一個鈎子函數。

最後的填充結構,也就是這張圖所示的樣子。

殘缺的LSM架構啟動分析前言LSM 簡介LSM 啟動分析附錄

使用安全子產品中的鈎子函數

(4)以binder_ioctl_set_ctx_mgr函數為例。該函數中插入了安全鈎子:

security_binder_set_context_mgr

。如果檢查不通過的話,退出。

static int binder_ioctl_set_ctx_mgr(struct file *filp,
				    struct flat_binder_object *fbo)
{
    ....
	ret = security_binder_set_context_mgr(proc->tsk);
	if (ret < 0)
		goto out;
   ....
}
           

(5)security_binder_set_context_mgr :via。可以看到它會周遊security_hook_heads全局變量中,以security_hook_heads.binder_set_context_mgr為哈希表頭的鈎子函數。

int security_binder_set_context_mgr(struct task_struct *mgr)
{
	return call_int_hook(binder_set_context_mgr, 0, mgr);
}


#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;							\
})
           

附錄

小故事

來源:《Linux核心安全子產品深入剖析》 第二部分 強制通路控制。

SELinux

在 Linux 核心安全領域, SELinux 可謂鼎鼎大名。幾乎所有接觸過 Linux 安全和試圖接觸Linux 安全的人都或多或少了解過 SELinux。了解的結果是大部分人對 SELinux 望而卻步,小部分人略知一二後對 SELinux 敬而遠之。作者懷疑是否有人在了解 SELinux 之後還會對 SELinux推崇備至。

SELinux 的全稱是 Security Enhanced Linux,中文直譯為安全增強的 Linux。美國國家安全局(National Security Agency——NSA)主導了 SELinux 的開發工作。

SELinux 的曆史可以追溯到 NSA 的三次開發安全作業系統的努力。第一次是在 1992 年到1993 年間, NSA 與安全計算公司(Secure Computing Corporation——SCC)合作開發了以 Mach作業系統為載體的 DTMach,那時的 DTMach 就已經實作了類型增強(Type Enforcement——TE),後來類型增強成為 SELinux 最主要的通路控制機制。第二次是 NSA 和 SCC 在 DTMach 基礎上開發的 DTOS( Distributed Trusted Operating System)。第三次是 NSA、 SCC 和猶他大學合作的 Flux 項目,将 DTOS 安全架構移植到一個名為 Fluke 的科研作業系統上, Flux項目最大的成果是實作了一個能支援動态管理的安全政策架構——Flask( Flux AdvancedSecurity Kernel)。随後 Flask 衍生出衆多後代,包括 Linux 之上的 SELinux、 OpenSolaris上的 FMAC、 BSD 上的 TrustedBSD、 Darwin 上的 SEDarwin、 Xen 上的 XSM( Xen SecurityModules),以及在使用者态應用領域的 SEPostgreSQL、 SE-DBUS、 XACE( X Access ControlExtension) 。

在科研領域取得突破後, NSA 進而希望安全作業系統能夠被廣大使用者接受并使用。是以以Linux 為載體的 SELinux 就誕生了。 SELinux 的第一個開放源代碼版本以核心更新檔的方式釋出于 2000 年 12 月 22 日。随後, NSA 進行了近三年不懈的努力,終于在 2003 年 8 月 8 日使 SELinux 并入 Linux 2.6.0-test3 主線。

SELinux 的安全機制包含:基于角色的通路控制(Role Based Access Control, RBAC)、類型增強(Type Enforcement, TE)和多級安全(Multi Level Security, MLS)。

提到 SELinux,作者想到的是兩個詞:權威和複雜。 SELinux 的理想是讓 SELinux 的安全機制可以覆寫 Linux 系統的方方面面,這個理想實作了。 SELinux 之後的 4 個安全子產品都沒有做到全系統覆寫。

SELinux 的第二個理想是讓 Linux 系統中所有的開發人員、管理人員和使用人員都自覺地學習 SELinux,使用 SELinux。這個理想沒有實作。因為大家都在抱怨 SELinux 太複雜了。

SELinux 的開發者認為自己努力地開發了一個完美的安全系統,但是發現大家不用,而且随着時間的推移,使用率并沒有增加。 SELinux 的一個開發者 Dan Walsh 為此制作了一個網站(http://stopdisablingselinux.com/)鼓勵人們使用 SELinux。

殘缺的LSM架構啟動分析前言LSM 簡介LSM 啟動分析附錄

SMACK

SMACK 是“ Simplified Mandatory Access Control Kernel”的縮寫。它的作者是 Casey Schaufler,目前在 Intel 從事手機作業系統 Tizen 的安全開發工作。 SMACK 于 2008 年 4 月 16日進入了 Linux 2.6.25,是繼 SELinux 之後第二個進入 Linux 核心主線的安全子產品。

SMACK 的出現在 Linux 核心社群引發了很大的争論。争論的背景是 SELinux 在 2003 年進入了 Linux 核心主線之後并未能如預想的那樣為廣大系統管理者和應用開發者了解和接受。管理者在發現系統因 SELinux 政策配置問題而不能正常運作時, 往往是簡單地關閉 SELinux 功能,而不是去調試和修改 SELinux 政策。應用開發者的開發工作隻包括開發應用的功能,不包括開發應用相關的 SELinux 政策。 Linux 發行版,比如 RedHat,面對衆多的應用隻能為一部分核心應用開發SELinux 政策。這就造成了 SELinux 安全政策滞後于應用,使用者在運作應用時,SELinux 常常阻礙應用的正常運作。

既然 LSM 機制的目的是允許多個安全子產品并存,既然 SELinux 的主要問題是複雜難用(至少表面上看起來是),那就設計出一種簡單的安全子產品來作為 SELinux 的替代品。 SMACK 就是循着這個思路而産生的。

SMACK 的出現讓 SELinux 的開發者和擁護者備受打擊。它不僅動搖了 SELinux 的地位,而且讓 SELinux 開發團隊的理想更加難以實作。有了 SMACK,核心安全開發人員還要繼續作Linux核心開發社群的二等公民, Linux應用的開發者會繼續忽視SELinux, 而不是學習SELinux。面對 SMACK, SELinux 開發者的自然反應就是阻撓 SMACK 進入 Linux 核心主線。而 SMACK的開發者 Casey Schaufler 偏偏是一個不屈不撓的人,他一次又一次地送出自己的作品。

這時兩個重量級人物站出來支援 SMACK,第一個是 Andrew Morton,他說:“我不是很懂安全。在讀過你送出的代碼後,我的觀點是代碼本身的品質很好,但是似乎SELinux 可以有相同的功能。将上面那個‘但是’作為不接受 SMACK 的理由對我而言有些困難。我更傾向于接受 SMACK,然後看大家是否用它。”Andrew Morton 雖然表示 SMACK 在功能上并沒有超越 SELinux,但是明确表示傾向于将SMACK 納入 Linux 核心主線。

這還不夠。第二個重量級人物站了出來,這個人是 Linux 的“仁慈的獨裁者”——Linus Torvalds。這次的郵件要“刺激”得多 。郵件大意是排程算法是“硬科學”,而安全不是“硬科學”, 因為安全無法定量地度量。 盡管 Stephen Smalley 争辯說 SMACK 能做的事隻是 SELinux 的一個子集。但是 Linus Torvalds 關心的根本不是具體的安全功能,他要用 SMACK 的進入主 線來保留 LSM 機制,改變 SELinux 一家獨大的局面。大佬一錘定音, SMACK 進入 Linux 核心主線!

SMACK 的強制通路控制機制是類型增強(Type Enforcement)。與 SELinux 不同, SMACK的工作機制隻有類型增強,沒有基于角色的通路控制和多級安全。是以它更簡單。類型在 SMACK 中的展現是标簽。

殘缺的LSM架構啟動分析前言LSM 簡介LSM 啟動分析附錄

Tomoyo

Tomoyo 是另一個 Linux 核心安全子產品。 SELinux 和 SMACK 的名字都來自英文單詞縮寫,Tomoyo 則不同,它是一個日本女性的名字,寫作漢語是“友子”或“知子” 。 Tomoyo 的開發者是日本的 NTT Data 公司,開發起始于 2003 年 3 月,此時距離 SELinux 被 Linux 主線接受隻有五個月。Tomoyo 的開發者不會不知道成熟的 SELinux 已經占盡先機。那麼這些日本的 Linux核心安全人員為什麼要自己開發一個新的核心安全子產品,而不是使用已有的 SELinux 呢?客觀地說,Tomoyo 的确有獨到之處。

SMACK 鼓吹的是簡單。 Tomoyo 推出的則是另一個賣點,而這又涉及一場至今沒有結果的争論:基于 inode 的安全與基于路徑的安全,哪一個更安全?

要為檔案引入安全屬性,很自然地想到安全屬性不是檔案内容,而是屬于中繼資料,應該與inode 關聯。于是很多檔案系統引入了擴充屬性,一些核心安全子產品将安全屬性存儲在檔案的擴充屬性中,這種方式就是基于 inode 的安全。基于 inode 的安全的優點主要有兩個:

  • 檔案的安全屬性與檔案路徑無關。檔案可以在不同目錄間移動,不管它怎麼移動,它的安全屬性都沒有變化。
  • 同一個檔案可以有多個連結,從不同連結通路檔案,其安全屬性總是一樣的。

基于 inode 的安全的缺點是:

  • 檔案系統必須支援擴充屬性,并且挂載檔案系統時必須使用擴充屬性。現在這個問題已經基本得到解決了。 目前 Linux 上大多數檔案系統已經支援擴充屬性, 并且挂載時預設使用擴充屬性。
  • 删除檔案時,檔案的安全屬性會随之消失。再在原先的路徑處建立同名檔案,并不能保證新檔案和老檔案的安全屬性相同。
  • 安裝軟體和更新軟體需要保證系統中新的檔案具有正确的安全屬性。新檔案來自軟體包,新的安全屬性自然也應該來自軟體包。于是有了下一個要求:衆多軟體包格式也需要支援檔案的擴充屬性,比如 tar、 cpio 等。

下面說說基于路徑的安全。

從使用者角度看,使用者通過路徑通路檔案,使用者态程序無法用 inode 号來通路檔案。即使是基于inode 的安全,使用者讀寫安全屬性也要先通過路徑找到檔案,然後才能通路檔案的安全屬性。那麼能否将檔案的安全屬性簡單地與檔案路徑對應起來呢?比如/bin/bash 的安全屬性是“system-shell”,/usr/local/bin/bash 的安全屬性是“local-shell”。不把這些安全屬性存儲在檔案的擴充屬性中,而是存儲在系統内部的一張表裡,這就是基于路徑的安全的實作原理。這樣做的優點是:

  • 不需要檔案系統有額外支援。
  • 不怕檔案更新,對打包格式也沒有額外要求。使用者甚至可以為還不存在的檔案定義安全屬性。

基于路徑的安全的缺點是:同一個檔案可能有多個安全屬性,簡單地建立連結就可能讓檔案擁有另一個安全屬性。

殘缺的LSM架構啟動分析前言LSM 簡介LSM 啟動分析附錄

AppArmor

AppArmor 源于 1998 年 WireX 公司開發的 SubDomain。 WireX 将 SubDomain 內建進一個名為 Immunix 的 Linux 發行版。 随後, WireX 公司的名字也更改為 Immunix。 在 2005 年, Novell收購了 Immunix 公司。收購的目的就是為了 Immunix 背後的 SubDomain。為了突出這個安全産品, Novell 将 SubDomain 更名為 AppArmor,意思是“Application Armor”——應用裝甲。轉眼到了 2007 年, Novell 放棄了 AppArmor,裁撤了 AppArmor 的開發團隊。這直接導緻 AppArmor 的開發停滞。直到 2009 年 5 月,維護 Ubuntu 開發的 Canonical 公司接手了 AppArmor 的開發和維護工作。在 Canonical 公司的努力下, AppArmor 在 2010 年 7 月終于進入了 Linux 主線。AppArmor 的開發工作開始得相當早,卻是幾個主要安全子產品中最後一個被 Linux 主線接受的。

同 Tomoyo 一樣, AppArmor 也是基于路徑的。 AppArmor 的獨特之處在于它并不關注全系統的安全!它隻會為特别标明的程序提供強制通路控制,其他的程序都工作在不受控制的狀态 。 AppArmor,應用裝甲,真是物如其名,它為某個或某些應用提供安全防護。

這樣做當然不夠安全,但是卻易于使用。它的推崇者說 AppArmor 是核心幾個主要安全子產品中最容易學習和使用的。

AppArmor 這麼做有一定道理。在現實中,一個系統迫切需要安全加強的往往隻是一個或幾個應用。例如一個 Web 伺服器,隻有 Web 服務程序和外界交流, Web 服務安全了。

殘缺的LSM架構啟動分析前言LSM 簡介LSM 啟動分析附錄

Yama

Yama 是一個源自古印度語的英文單詞,翻譯成漢語就是“閻羅”,閻羅是印度神話中掌管地獄的神。

Yama 可以稱為半個安全子產品,說它是“半個”,原因是:

  • 它是目前(3.14) Linux 主線中最簡單的安全子產品,隻用到了 4 個 LSM 鈎子函數,這4 個鈎子函數都和 ptrace 相關。
  • 它沒有一個完整的安全概念在背後支撐,多級安全、基于角色的通路控制、類型增強等都和它無關,它是針對具體問題——ptrace——的安全加強。
  • 它可以和其他安全子產品同時起作用,系統裡可以既有 SELinux 的通路控制,又有 Yama對 ptrace 的控制,而 SELinux、 SMACK、 Tomoyo、 AppArmor 這四者之間是互斥的,不能同時存在。

從某種角度看, Yama 堪稱完美。首先, Yama 解決的是一類實際的安全問題,而不是某種虛無缥缈的假想的安全威脅。其次, Yama 可以和别的安全子產品共存。 Yama 承認自己隻做了很小一部分工作,如果使用者想要更全面的安全,可以啟用另一個安全子產品來和 Yama 合作。其實,核心各個安全子產品所做的工作重複之處甚多。

繼續閱讀