0%

SELinux 域转换流程分析

本文将简单分析 SELinux 域转换部分的源码。

数据结构

1
2
3
4
5
6
7
8
struct task_security_struct {
u32 osid; /* SID prior to last execve */
u32 sid; /* current SID */
u32 exec_sid; /* exec SID */
u32 create_sid; /* fscreate SID */
u32 keycreate_sid; /* keycreate SID */
u32 sockcreate_sid; /* fscreate SID */
} __randomize_layout;

Security ID (SID) 是安全上下文的编号,用来提升搜索速度。

进程执行 execve 系统调用时,进程的安全上下文可能发生变化,新安全上下文:

  • 来自进程的旧安全上下文和被执行文件的安全上下文进行运算后的结果;
  • 来自进程的执行安全上下文(exec_sid)。

源码

execve 系统调用开始

1
2
3
4
5
6
7
SYSCALL_DEFINE3 (execve,
const char __user *, filename,
const char __user *const __user *, argv,
const char __user *const __user *, envp)
{
return do_execve (getname (filename), argv, envp);
}
1
2
3
4
5
6
7
8
9
//fs/exec.c
static int do_execve(struct filename *filename,
const char __user *const __user *__argv,
const char __user *const __user *__envp)
{
struct user_arg_ptr argv = { .ptr.native = __argv };
struct user_arg_ptr envp = { .ptr.native = __envp };
return do_execveat_common (AT_FDCWD, filename, argv, envp, 0);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//fs/exec.c
static int do_execveat_common(int fd, struct filename *filename,
struct user_arg_ptr argv,
struct user_arg_ptr envp,
int flags)
{
struct linux_binprm *bprm;
int retval;
//...
bprm = alloc_bprm (fd, filename);
//...
retval = bprm_execve (bprm, fd, filename, flags);
//...
return retval;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

```c
//fs/exec.c
static int bprm_execve (struct linux_binprm *bprm,
int fd, struct filename *filename, int flags)
{
//...

/* Set the unchanging part of bprm->cred */
retval = security_bprm_creds_for_exec (bprm);
if (retval)
goto out;

retval = exec_binprm (bprm);
if (retval < 0)
goto out;

//...
}
1
2
3
4
5
//security/security.c
int security_bprm_creds_for_exec(struct linux_binprm *bprm)
{
return call_int_hook (bprm_creds_for_exec, 0, bprm);
}

该函数会使用宏 call_int_hook 调用预先注册好的钩函数。

定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//security/security.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; \
})

其中宏 hlist_for_each_entry 的作用是遍历给定 hlist 的 member。在这里即为调用注册了的 bprm_creds_for_exec 钩函数。

1
2
3
4
5
6
7
8
9
10
11
//tools/include/linux/lish.h
/**
* hlist_for_each_entry - iterate over list of given type
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the hlist_node within the struct.
*/
#define hlist_for_each_entry (pos, head, member) \
for (pos = hlist_entry_safe ((head)->first, typeof (*(pos)), member);\
pos; \
pos = hlist_entry_safe ((pos)->member.next, typeof (*(pos)), member))

看一下 SELinux 在 bprm_creds_for_exec 上注册的函数:

1
2
3
4
5
6
//security/selinux/hooks.c
static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = {
//...
LSM_HOOK_INIT (bprm_creds_for_exec, selinux_bprm_creds_for_exec),
//...
}

于是会调用 selinux_bprm_creds_for_exec,此函数将:

  • 设置进程的旧安全上下文数据结构 task_security_struct 和文件 inode 的安全上下文 inode_security_struct,并初始化进程的新安全上下文。
  • 如果进程设置了 exec_sid,则赋给新的 sid,否则:
  • 调用 services.c 中的 security_transition_sid 函数,这个函数又会调用 security_compute_sid 函数去计算一个新的 SID。该函数将:
    • 初始化源安全上下文、目的安全上下文和新安全上下文。
    • 在 SID 表中搜索源和目的 SID,获得源和目的安全上下文。
    • 根据客体的 class 的默认配置,设置新安全上下文的 SELinux 用户 id,角色和类型。
    • 检查 AV 表中是否有一个对应的 type_transition 规则。
    • 如果是 type_transitiontype_change,继续检查是否有一个 role_transition 规则
    • 调用 mls.c 中的 mis_compute_sid 函数检查是否有 MLS 属性。如果启用了 MLS,还会设置 MLS 上下文。
    • 调用 compute_sid_context_to_sid 函数检查上下文是否合法,若不合法则记录在审计日志中。
    • 最终调用 sidtab.c 中的 sidtab_context_to_sid 函数为新的上下文获得一个 SID,它将搜索 SID 表并插入一个新的条目,若失败则写到日志中。
  • 检查一下 NNP(No New Privileges)和 Nosuid 规则
  • 检查 SID 是否发生变化(在这个例子中有),然后使用 avc.c 中的 avc_has_perm 函数来检查进程类的转换权限和文件类的入口点权限(在这个例子中是允许的),然后设置新的 SID,之后再检查一下其他的权限,最后返回到 bprm_execve 函数中。
selinux_bprm_creds_for_exec 函数
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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
//security/selinux/hooks.c
static int selinux_bprm_creds_for_exec(struct linux_binprm *bprm)
{
const struct task_security_struct *old_tsec;
struct task_security_struct *new_tsec;
struct inode_security_struct *isec;
struct common_audit_data ad;
struct inode *inode = file_inode (bprm->file);
int rc;

/* SELinux context only depends on initial program or script and not
* the script interpreter */

old_tsec = selinux_cred (current_cred ());
new_tsec = selinux_cred (bprm->cred);
isec = inode_security (inode);

/* Default to the current task SID. */
new_tsec->sid = old_tsec->sid;
new_tsec->osid = old_tsec->sid;

/* Reset fs, key, and sock SIDs on execve. */
new_tsec->create_sid = 0;
new_tsec->keycreate_sid = 0;
new_tsec->sockcreate_sid = 0;

if (old_tsec->exec_sid) {
new_tsec->sid = old_tsec->exec_sid;
/* Reset exec SID on execve. */
new_tsec->exec_sid = 0;

/* Fail on NNP or nosuid if not an allowed transition. */
rc = check_nnp_nosuid (bprm, old_tsec, new_tsec);
if (rc)
return rc;
} else {
/* Check for a default transition on this program. */
rc = security_transition_sid (&selinux_state, old_tsec->sid,
isec->sid, SECCLASS_PROCESS, NULL,
&new_tsec->sid);
if (rc)
return rc;

/*
* Fallback to old SID on NNP or nosuid if not an allowed
* transition.
*/
rc = check_nnp_nosuid (bprm, old_tsec, new_tsec);
if (rc)
new_tsec->sid = old_tsec->sid;
}

ad.type = LSM_AUDIT_DATA_FILE;
ad.u.file = bprm->file;

if (new_tsec->sid == old_tsec->sid) {
rc = avc_has_perm (&selinux_state,
old_tsec->sid, isec->sid,
SECCLASS_FILE, FILE__EXECUTE_NO_TRANS, &ad);
if (rc)
return rc;
} else {
/* Check permissions for the transition. */
rc = avc_has_perm (&selinux_state,
old_tsec->sid, new_tsec->sid,
SECCLASS_PROCESS, PROCESS__TRANSITION, &ad);
if (rc)
return rc;

rc = avc_has_perm (&selinux_state,
new_tsec->sid, isec->sid,
SECCLASS_FILE, FILE__ENTRYPOINT, &ad);
if (rc)
return rc;

/* Check for shared state */
if (bprm->unsafe & LSM_UNSAFE_SHARE) {
rc = avc_has_perm (&selinux_state,
old_tsec->sid, new_tsec->sid,
SECCLASS_PROCESS, PROCESS__SHARE,
NULL);
if (rc)
return -EPERM;
}

/* Make sure that anyone attempting to ptrace over a task that
* changes its SID has the appropriate permit */
if (bprm->unsafe & LSM_UNSAFE_PTRACE) {
u32 ptsid = ptrace_parent_sid ();
if (ptsid != 0) {
rc = avc_has_perm (&selinux_state,
ptsid, new_tsec->sid,
SECCLASS_PROCESS,
PROCESS__PTRACE, NULL);
if (rc)
return -EPERM;
}
}

/* Clear any possibly unsafe personality bits on exec: */
bprm->per_clear |= PER_CLEAR_ON_SETID;

/* Enable secure mode for SIDs transitions unless
the noatsecure permission is granted between
the two SIDs, i.e. ahp returns 0. */
rc = avc_has_perm (&selinux_state,
old_tsec->sid, new_tsec->sid,
SECCLASS_PROCESS, PROCESS__NOATSECURE,
NULL);
bprm->secureexec |= !!rc;
}

return 0;
}
security_compute_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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
//security/selinux/ss/service.c
static int security_compute_sid(struct selinux_state *state,
u32 ssid,
u32 tsid,
u16 orig_tclass, // Process
u32 specified, // AVTAB_TRANSITION
const char *objname, // NULL
u32 *out_sid,
bool kern) //true
{
struct selinux_policy *policy;
struct policydb *policydb;
struct sidtab *sidtab;
struct class_datum *cladatum = NULL;
struct context *scontext, *tcontext, newcontext;
struct sidtab_entry *sentry, *tentry;
struct avtab_key avkey;
struct avtab_datum *avdatum;
struct avtab_node *node;
u16 tclass;
int rc = 0;
bool sock;

if (!selinux_initialized (state)) {
switch (orig_tclass) {
case SECCLASS_PROCESS: /* kernel value */
*out_sid = ssid;
break;
default:
*out_sid = tsid;
break;
}
goto out;
}

context_init (&newcontext);

rcu_read_lock ();

policy = rcu_dereference (state->policy);

if (kern) {
tclass = unmap_class (&policy->map, orig_tclass);
sock = security_is_socket_class (orig_tclass);
} else {
tclass = orig_tclass;
sock = security_is_socket_class (map_class (&policy->map,
tclass));
}

policydb = &policy->policydb;
sidtab = policy->sidtab;

sentry = sidtab_search_entry (sidtab, ssid);
if (!sentry) {
pr_err ("SELinux: % s: unrecognized SID % d\n",
__func__, ssid);
rc = -EINVAL;
goto out_unlock;
}
tentry = sidtab_search_entry (sidtab, tsid);
if (!tentry) {
pr_err ("SELinux: % s: unrecognized SID % d\n",
__func__, tsid);
rc = -EINVAL;
goto out_unlock;
}

scontext = &sentry->context;
tcontext = &tentry->context;

if (tclass && tclass <= policydb->p_classes.nprim)
cladatum = policydb->class_val_to_struct [tclass - 1];

/* Set the user identity. */
switch (specified) {
case AVTAB_TRANSITION:
case AVTAB_CHANGE:
if (cladatum && cladatum->default_user == DEFAULT_TARGET) {
newcontext.user = tcontext->user;
} else {
/* notice this gets both DEFAULT_SOURCE and unset */
/* Use the process user identity. */
newcontext.user = scontext->user;
}
break;
case AVTAB_MEMBER:
/* Use the related object owner. */
newcontext.user = tcontext->user;
break;
}

/* Set the role to default values. */
if (cladatum && cladatum->default_role == DEFAULT_SOURCE) {
newcontext.role = scontext->role;
} else if (cladatum && cladatum->default_role == DEFAULT_TARGET) {
newcontext.role = tcontext->role;
} else {
if ((tclass == policydb->process_class) || sock)
newcontext.role = scontext->role;
else
newcontext.role = OBJECT_R_VAL;
}

/* Set the type to default values. */
if (cladatum && cladatum->default_type == DEFAULT_SOURCE) {
newcontext.type = scontext->type;
} else if (cladatum && cladatum->default_type == DEFAULT_TARGET) {
newcontext.type = tcontext->type;
} else {
if ((tclass == policydb->process_class) || sock) {
/* Use the type of process. */
newcontext.type = scontext->type;
} else {
/* Use the type of the related object. */
newcontext.type = tcontext->type;
}
}

/* Look for a type transition/member/change rule. */
avkey.source_type = scontext->type;
avkey.target_type = tcontext->type;
avkey.target_class = tclass;
avkey.specified = specified;
avdatum = avtab_search (&policydb->te_avtab, &avkey);

/* If no permanent rule, also check for enabled conditional rules */
if (!avdatum) {
node = avtab_search_node (&policydb->te_cond_avtab, &avkey);
for (; node; node = avtab_search_node_next (node, specified)) {
if (node->key.specified & AVTAB_ENABLED) {
avdatum = &node->datum;
break;
}
}
}

if (avdatum) {
/* Use the type from the type transition/member/change rule. */
newcontext.type = avdatum->u.data;
}

/* if we have a objname this is a file trans check so check those rules */
if (objname)
filename_compute_type (policydb, &newcontext, scontext->type,
tcontext->type, tclass, objname);

/* Check for class-specific changes. */
if (specified & AVTAB_TRANSITION) {
/* Look for a role transition rule. */
struct role_trans_datum *rtd;
struct role_trans_key rtk = {
.role = scontext->role,
.type = tcontext->type,
.tclass = tclass,
};

rtd = policydb_roletr_search (policydb, &rtk);
if (rtd)
newcontext.role = rtd->new_role;
}

/* Set the MLS attributes.
This is done last because it may allocate memory. */
rc = mls_compute_sid (policydb, scontext, tcontext, tclass, specified,
&newcontext, sock);
if (rc)
goto out_unlock;

/* Check the validity of the context. */
if (!policydb_context_isvalid (policydb, &newcontext)) {
rc = compute_sid_handle_invalid_context (state, policy, sentry,
tentry, tclass,
&newcontext);
if (rc)
goto out_unlock;
}
/* Obtain the sid for the context. */
rc = sidtab_context_to_sid (sidtab, &newcontext, out_sid);
out_unlock:
rcu_read_unlock ();
context_destroy (&newcontext);
out:
return rc;
}