风之栖息地

SELinux源码分析--内核态

字数统计: 2.6k阅读时长: 10 min
2023/03/14 Share

为了更加熟悉SELinux,针对这个安全机制的源码部分做了一些梳理,结合了官方的指南丰富了其中的源码实现细节。SELinux博大精深,这篇讲述该机制的内核部分实现,包括贯穿整个机制的两个核心——对系统行为的检查以及标签的转换,以及比较核心的数据和接口。

备注:我的源码分析中忽略了mls机制,在一些检查点中针对mls有额外的处理。

核心流程一:对系统调用行为的鉴权

我这里拿fork系统调用为例子🌰,整个调用过程核心流程如下图所示:

首先是系统调用的入口函数里会存在一些hook点,比如这里的fork系统调用在copy_process中存在security_task_alloc,而这个是LSM的hook点位。SELinux的hooks.cselinux_hooks结构体数组将SELinux相关的检查函数用LSM_HOOK_INIT设置到对应的hook点上。

在某个进程触发fork系统调用,到达安全检测点会跳转到selinux_task_alloc函数。该函数则是SELinux检测这个系统调用是否有权限执行。不同的系统调用有不同的检查逻辑,最终会调用的权限检查函数也有所不同,fork系统调用的检查函数是avc_has_perm

avc_has_perm函数在avc.c中,用于检查系统调用是否被授权。其中有两个主要函数分别为avc_has_perm_noauditavc_auditavc_has_perm_noaudit主要做具体的权限检查,而avc_audit则是记录avc日志信息。

avc_has_perm_noaudit首先给自己加上rcu的读锁,然后从已有的avc数据中查找是否缓存过,找得到就直接复制一份出来,没有找到则进入avc_compute_av计算av数据。根据av数据中的规则权限和请求的权限求得这次请求是否被允许,如果行为不允许,会进入avc_denied将这次的av结果更新。

avc_compute_av主要包含两个函数,其一是 security_compute_av,负责计算av数据;其二是avc_insert将计算到的avc数据插入缓存中。security_compute_av通过ssidtsid在sidtab中搜索到scontexttcontext进入到整个流程的核心函数context_struct_compute_av

context_struct_compute_av函数含有多个检查访问是否合法的步骤。

  1. avd内容的初始化;
  2. 遍历检查对象类和权限,并根据节点的类型(AVTAB_ALLOWED、AVTAB_AUDITALLOW、AVTAB_AUDITDENY、AVTAB_XPERMS)将数据整合进avd中;
  3. cond_compute_av检查是否有条件语句规则包含在这次的访问判断中,如果存在则会对权限做修改;
  4. 通过constraint_expr_eval查看是否存在额外约束,根据额外约束移除权限;
  5. 如果进程标签转换正在执行,需要检测是否有transitiondyntransition权限,是否有角色的改变。
  6. 检查是否有typebounds规则约束应用在这次访问中,根据额外约束修正权限
  7. 这些结果都保存在avd数据中

avc_audit会先利用avc_audit_required检查是否要记录audit日志,再调用slow_avc_audit函数构造具体的日志信息。avc_audit_required内部会先把请求的权限和取反后的允许权限做与操作,这样就能得到是否拒绝该行为。但这里会有另外一个位avd->auditdeny,如果该位被置为0,则这个拒绝行为不会被记录在audit日志中。

当整个鉴权过程结束之后会从selinux_task_alloc返回,一路返回到fork自身的copy_process中继续系统调用执行。

核心流程二:进程的标签转换

SELinux中存在一种规则type_transition,让一个带着标签的进程,执行另外一个带着标签的文件时,发生标签转换,从原有的标签转换成其他设置的标签。

type_transition unconfined_t secure_services_exec_t : process ext_gateway_t;

unconfined_t的进程执行了带着secure_services_exec_t的文件,触发标签转换,该文件执行成功变成进程后为ext_gateway_t。但要真正的执行转换还需要有三个权限:

(1)allow unconfined_t ext_gateway_t : process transition;标签间的转换权限

(2)allow unconfined_t secure_services_exec_t : file { execute read getattr };进程执行文件的权限

(3)allow ext_gateway_t secure_services_exec_t : file entrypoint;转换标签必须有进入点权限

因为是要拉起另外一个进程所以入口都是在do_execveat_common中。

整个过程分为三个阶段

第一个阶段是检查进程标签转换的标签内容

首先从程序执行入口处进入alloc_bprm,这里会分配linux_binprm结构体,这个结构体记录了二进制执行程序的相关信息,包括核心的凭证和权限,参数,环境变量,文件名等内容。经过一系列的字符串拷贝和参数检查,进入到bprm_execve执行新的二进制程序。bprm_execve函数中有LSM的hook点位security_bprm_creds_for_exec,最终调用selinux_bprm_creds_for_exec

selinux_bprm_creds_for_exec会调用security_transition_sid检查程序的标签转换内容,而这个函数只是security_compute_sid的一层封装,security_compute_sid中会确认是否需要计算新的sid。大概分成下面的流程:

  1. 搜索sidtab中的ssid和tsid,获得scontexttcontext
  2. 设置用户id、角色id和标签类型;
  3. 检查是否有type_transition规则在av数据中或者条件av数据中
  4. 如果存在标签转换,检查是否有role_transition角色转换,并设置好角色
  5. 检查新的标签内容是否合法,不合法内容将会被日志记录;
  6. 调用sidtab_context_to_sid获得新标签的sid,并在sidtab中更新

第二个阶段检查进程是否有转换等权限

selinux_bprm_creds_for_exec中执行完security_transition_sid后,会有许多的avc_has_perm函数用来确认是否有相关的转换权限。首先会检查,是否有execute_no_trans权限,拥有该权限会直接返回,没有则继续检查transitionentrypoint等其他权限。

第三个阶段检查执行二进制的相关权限

在前面两个阶段的检查结束后,会返回到bprm_execve中继续执行到exec_binprm,这个函数会调用search_binary_handler寻找二进制文件相应的处理器,并在处理其中执行它的.load_binary函数指针执行到load_xxx_binary中,这些函数最后的入口点在begin_new_exec

begin_new_exec会在最后执行security_bprm_committing_credssecurity_bprm_committed_creds这两个hook点对应,selinux_bprm_committing_credsselinux_bprm_committed_creds。第一个函数用于进程初始化凭证,这里会检查是否有权限限制,第二个函数针对不会继承信号状态的进程做信号清除。

核心数据和接口

SELinux状态信息的全局变量

struct selinux_state selinux_state;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct selinux_state {
#ifdef CONFIG_SECURITY_SELINUX_DISABLE
bool disabled;
#endif
#ifdef CONFIG_SECURITY_SELINUX_DEVELOP
bool enforcing;
#endif
bool checkreqprot;
bool initialized;
bool policycap[__POLICYDB_CAP_MAX];

struct page *status_page;
struct mutex status_lock;

struct selinux_avc *avc;
struct selinux_policy __rcu *policy;
struct mutex policy_mutex;
} __randomize_layout;

一个记录SELinux的全局状态的结构体,里面包括了SELinux的开启状态,是否为enforcing模式,是否初始化,规则指针,avc记录缓存指针等信息。

hooks挂载函数表

hooks挂载都在hooks.c文件中,把SELinux的检查函数挂载在不同系统调用的安全函数hook点中。

static struct security_hook_list selinux_hooks[] __lsm_ro_after_init

1
2
3
4
5
6
...
LSM_HOOK_INIT(inode_create, selinux_inode_create),
LSM_HOOK_INIT(inode_link, selinux_inode_link),
LSM_HOOK_INIT(inode_unlink, selinux_inode_unlink),
LSM_HOOK_INIT(inode_symlink, selinux_inode_symlink),
...

av数据管理

管理av数据的相关结构体

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
struct selinux_avc {
unsigned int avc_cache_threshold;
struct avc_cache avc_cache;
};

struct avc_cache {
struct hlist_head slots[AVC_CACHE_SLOTS]; /* head for avc_node->list */
spinlock_t slots_lock[AVC_CACHE_SLOTS]; /* lock for writes */
atomic_t lru_hint; /* LRU hint for reclaim scan */
atomic_t active_nodes;
u32 latest_notif; /* latest revocation notification */
};

struct avc_node {
struct avc_entry ae;
struct hlist_node list; /* anchored in avc_cache->slots[i] */
struct rcu_head rhead;
};

struct avc_entry {
u32 ssid;
u32 tsid;
u16 tclass;
struct av_decision avd;
struct avc_xperms_node *xp_node;
};

struct av_decision {
u32 allowed;
u32 auditallow;
u32 auditdeny;
u32 seqno;
u32 flags;
};

selinux_avc->avc_cache.slots是挂着avc_node的list。而avc_code的ae管理着基本的请求源和目标对象的id值,通过这两个id能找到对应的标签,avd则是记录请求的权限。

策略数据管理

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
/* The policy database */
struct policydb {
int mls_enabled;

/* symbol tables */
struct symtab symtab[SYM_NUM];
#define p_commons symtab[SYM_COMMONS]
#define p_classes symtab[SYM_CLASSES]
#define p_roles symtab[SYM_ROLES]
#define p_types symtab[SYM_TYPES]
#define p_users symtab[SYM_USERS]
#define p_bools symtab[SYM_BOOLS]
#define p_levels symtab[SYM_LEVELS]
#define p_cats symtab[SYM_CATS]

/* symbol names indexed by (value - 1) */
char **sym_val_to_name[SYM_NUM];

/* class, role, and user attributes indexed by (value - 1) */
struct class_datum **class_val_to_struct;
struct role_datum **role_val_to_struct;
struct user_datum **user_val_to_struct;
struct type_datum **type_val_to_struct;

/* type enforcement access vectors and transitions */
struct avtab te_avtab;

/* role transitions */
struct hashtab role_tr;

/* file transitions with the last path component */
/* quickly exclude lookups when parent ttype has no rules */
struct ebitmap filename_trans_ttypes;
/* actual set of filename_trans rules */
struct hashtab filename_trans;
/* only used if policyvers < POLICYDB_VERSION_COMP_FTRANS */
u32 compat_filename_trans_count;

/* bools indexed by (value - 1) */
struct cond_bool_datum **bool_val_to_struct;
/* type enforcement conditional access vectors and transitions */
struct avtab te_cond_avtab;
/* array indexing te_cond_avtab by conditional */
struct cond_node *cond_list;
u32 cond_list_len;

/* role allows */
struct role_allow *role_allow;

/* security contexts of initial SIDs, unlabeled file systems,
TCP or UDP port numbers, network interfaces and nodes */
struct ocontext *ocontexts[OCON_NUM];

/* security contexts for files in filesystems that cannot support
a persistent label mapping or use another
fixed labeling behavior. */
struct genfs *genfs;

/* range transitions table (range_trans_key -> mls_range) */
struct hashtab range_tr;

/* type -> attribute reverse mapping */
struct ebitmap *type_attr_map_array;

struct ebitmap policycaps;

struct ebitmap permissive_map;

/* length of this policy when it was loaded */
size_t len;

unsigned int policyvers;

unsigned int reject_unknown : 1;
unsigned int allow_unknown : 1;

u16 process_class;
u32 process_trans_perms;
} __randomize_layout;

比较关键的成员是两个avtab,里面记录了av相关的数据内容,用于计算访问权限。

selinuxfs对外接口

SELinux文件系统的文件接口,用户态通过这些接口与内核功能交互。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static const struct tree_descr selinux_files[] = {
[SEL_LOAD] = {"load", &sel_load_ops, S_IRUSR|S_IWUSR},
[SEL_ENFORCE] = {"enforce", &sel_enforce_ops, S_IRUGO|S_IWUSR},
[SEL_CONTEXT] = {"context", &transaction_ops, S_IRUGO|S_IWUGO},
[SEL_ACCESS] = {"access", &transaction_ops, S_IRUGO|S_IWUGO},
[SEL_CREATE] = {"create", &transaction_ops, S_IRUGO|S_IWUGO},
[SEL_RELABEL] = {"relabel", &transaction_ops, S_IRUGO|S_IWUGO},
[SEL_USER] = {"user", &transaction_ops, S_IRUGO|S_IWUGO},
[SEL_POLICYVERS] = {"policyvers", &sel_policyvers_ops, S_IRUGO},
[SEL_COMMIT_BOOLS] = {"commit_pending_bools", &sel_commit_bools_ops, S_IWUSR},
[SEL_MLS] = {"mls", &sel_mls_ops, S_IRUGO},
[SEL_DISABLE] = {"disable", &sel_disable_ops, S_IWUSR},
[SEL_MEMBER] = {"member", &transaction_ops, S_IRUGO|S_IWUGO},
[SEL_CHECKREQPROT] = {"checkreqprot", &sel_checkreqprot_ops, S_IRUGO|S_IWUSR},
[SEL_REJECT_UNKNOWN] = {"reject_unknown", &sel_handle_unknown_ops, S_IRUGO},
[SEL_DENY_UNKNOWN] = {"deny_unknown", &sel_handle_unknown_ops, S_IRUGO},
[SEL_STATUS] = {"status", &sel_handle_status_ops, S_IRUGO},
[SEL_POLICY] = {"policy", &sel_policy_ops, S_IRUGO},
[SEL_VALIDATE_TRANS] = {"validatetrans", &sel_transition_ops,
S_IWUGO},
/* last one */ {""}
};

sidtab结构

记录标签内容和对应sid值,在鉴权过程中可以只通过sid值找到标签。

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
55
56
57
58
59
60
61
62
struct sidtab_entry {
u32 sid;
u32 hash;
struct context context;
#if CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0
struct sidtab_str_cache __rcu *cache;
#endif
struct hlist_node list;
};

union sidtab_entry_inner {
struct sidtab_node_inner *ptr_inner;
struct sidtab_node_leaf *ptr_leaf;
};

struct sidtab_node_leaf {
struct sidtab_entry entries[SIDTAB_LEAF_ENTRIES];
};

struct sidtab_node_inner {
union sidtab_entry_inner entries[SIDTAB_INNER_ENTRIES];
};

struct sidtab_isid_entry {
int set;
struct sidtab_entry entry;
};

struct sidtab_convert_params {
struct convert_context_args *args;
struct sidtab *target;
};

struct sidtab {
/*
* lock-free read access only for as many items as a prior read of
* 'count'
*/
union sidtab_entry_inner roots[SIDTAB_MAX_LEVEL + 1];
/*
* access atomically via {READ|WRITE}_ONCE(); only increment under
* spinlock
*/
u32 count;
/* access only under spinlock */
struct sidtab_convert_params *convert;
bool frozen;
spinlock_t lock;

#if CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0
/* SID -> context string cache */
u32 cache_free_slots;
struct list_head cache_lru_list;
spinlock_t cache_lock;
#endif

/* index == SID - 1 (no entry for SECSID_NULL) */
struct sidtab_isid_entry isids[SECINITSID_NUM];

/* Hash table for fast reverse context-to-sid lookups. */
DECLARE_HASHTABLE(context_to_sid, SIDTAB_HASH_BITS);
};
CATALOG
  1. 1. 核心流程一:对系统调用行为的鉴权
  2. 2. 核心流程二:进程的标签转换
  3. 3. 核心数据和接口
    1. 3.1. SELinux状态信息的全局变量
    2. 3.2. hooks挂载函数表
    3. 3.3. av数据管理
    4. 3.4. 策略数据管理
    5. 3.5. selinuxfs对外接口
    6. 3.6. sidtab结构