1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (c) 2023 Huawei Device Co., Ltd.
4 */
5
6 #include <linux/fs_struct.h>
7 #include "ced_log.h"
8 #include "avc.h"
9 #include "objsec.h"
10 #include "ced_detection.h"
11 #include "ced_detection_points.h"
12 #include <linux/version.h>
13
14 enum ced_event_type {
15 EVENT_OK,
16 EVENT_CRED_CHANGED,
17 EVENT_NSPROXY_CHANGED,
18 EVENT_ATTRIBUTE_CHANGED,
19 EVENT_TREE_CHANGED,
20 EVENT_NUM
21 };
22
23 static struct rb_root root_tree = RB_ROOT;
24 static struct rw_semaphore point_lock;
25
26 static const char *gEventContent[EVENT_NUM - 1] = {
27 "cred has been changed illegally.",
28 "nsproxy has been changed illegally.",
29 "attribute has been changed illegally.",
30 "tree has been changed illegally",
31 };
32
print_container_escape_detection(enum ced_event_type type)33 static inline void print_container_escape_detection(enum ced_event_type type)
34 {
35 if (type < EVENT_NUM)
36 ced_log_error("tgid is %d, %s container escape is detected!!!!", current->tgid, gEventContent[type - 1]);
37 }
38
ced_avc_has_perm(u16 tclass,u32 requested)39 static int ced_avc_has_perm(u16 tclass, u32 requested)
40 {
41 struct av_decision avd;
42 int rc;
43
44 #if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0))
45 if (!selinux_initialized(&selinux_state))
46 return 1;
47 #else
48 if (!selinux_initialized())
49 return 1;
50 #endif
51 u32 sid = current_sid();
52 #if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0))
53 rc = avc_has_perm_noaudit(&selinux_state, sid, sid, tclass, requested,
54 AVC_STRICT, &avd);
55 #else
56 rc = avc_has_perm_noaudit(sid, sid, tclass, requested,
57 AVC_STRICT, &avd);
58 #endif
59 return rc;
60 }
61
ced_has_check_perm(void)62 bool ced_has_check_perm(void)
63 {
64 // use selinux label to tell the process is hap process
65 int rc = ced_avc_has_perm(SECCLASS_CED, CED__CONTAINER_ESCAPE_CHECK);
66 if (rc)
67 return false;
68
69 return true;
70 }
71
point_search(pid_t tgid)72 static struct point_info *point_search(pid_t tgid)
73 {
74 struct rb_node *node = root_tree.rb_node;
75 while (node != NULL) {
76 struct point_info *point = container_of(node, struct point_info, node);
77 pid_t result = point->tgid;
78 if (result > tgid)
79 node = node->rb_left;
80 else if (result < tgid)
81 node = node->rb_right;
82 else
83 return point;
84 }
85
86 return NULL;
87 }
88
point_insert(pid_t tgid,struct process_info * info)89 static bool point_insert(pid_t tgid, struct process_info *info)
90 {
91 struct rb_node **new = &root_tree.rb_node;
92 struct rb_node *parent = NULL;
93 struct point_info *point = NULL;
94 pid_t result;
95 /* Figure out where to put new node */
96 while (*new != NULL) {
97 point = container_of((*new), struct point_info, node);
98 result = point->tgid;
99 parent = *new;
100 if (result > tgid)
101 new = &(*new)->rb_left;
102 else if (result < tgid)
103 new = &(*new)->rb_right;
104 else
105 return false;
106 }
107
108 point = kmalloc(sizeof(struct point_info), GFP_KERNEL);
109 if (point == NULL)
110 return false;
111
112 point->tgid = tgid;
113 point->count = 1;
114 point->info = info;
115
116 /* Add new node and rebalance tree. */
117 rb_link_node(&point->node, parent, new);
118 rb_insert_color(&point->node, &root_tree);
119 return true;
120 }
121
point_erase(pid_t pid)122 void point_erase(pid_t pid)
123 {
124 struct point_info *point = point_search(pid);
125 if (point != NULL) {
126 rb_erase(&point->node, &root_tree);
127 kfree(point->info);
128 point->info = NULL;
129 kfree(point);
130 point=NULL;
131 }
132 }
133
has_same_attributes(struct process_info * a,struct process_info * b)134 static bool has_same_attributes(struct process_info *a, struct process_info *b)
135 {
136 if (memcmp(a, b, sizeof(struct process_info)))
137 return false;
138
139 return true;
140 }
141
has_same_cred(const struct cred * a,struct process_info * b)142 static bool has_same_cred(const struct cred *a, struct process_info *b)
143 {
144 if (a->euid.val == b->cred.euid && a->egid.val == b->cred.egid
145 && a->fsuid.val == b->cred.fsuid
146 && memcmp(&a->cap_effective, &b->cred.cap_effective, sizeof(kernel_cap_t)))
147 return true;
148
149 return false;
150 }
151
has_same_nsproxy(const struct nsproxy * a,struct process_info * b)152 static bool has_same_nsproxy(const struct nsproxy *a, struct process_info *b)
153 {
154 if (a->mnt_ns == b->ns.mnt_ns && a->pid_ns_for_children == b->ns.pid_ns
155 && a->net_ns == b->ns.net_ns)
156 return true;
157
158 return false;
159 }
160
ced_initialize(void)161 void ced_initialize(void)
162 {
163 init_rwsem(&point_lock);
164 }
165
setattr_insert_hook(struct task_struct * task)166 void setattr_insert_hook(struct task_struct *task)
167 {
168 if (!ced_has_check_perm())
169 return;
170
171 pid_t tgid = task->tgid;
172 struct process_info *info = process_info_record(task);
173 if (info == NULL)
174 return;
175
176 down_read(&point_lock);
177 struct point_info *result = point_search(task->tgid);
178 if (result != NULL) {
179 up_read(&point_lock);
180 kfree(info);
181 print_container_escape_detection(EVENT_TREE_CHANGED);
182 return;
183 }
184 up_read(&point_lock);
185
186 down_write(&point_lock);
187 bool ret = point_insert(tgid, info);
188 if (!ret) {
189 up_write(&point_lock);
190 kfree(info);
191 ced_log_error("insert point into tree failed");
192 return;
193 }
194 up_write(&point_lock);
195 }
196
check_tree_and_attribute(pid_t tgid,struct process_info * current_info,struct point_info ** point)197 static int check_tree_and_attribute(pid_t tgid, struct process_info *current_info, struct point_info **point)
198 {
199 struct point_info *result = point_search(tgid);
200 if (result == NULL)
201 return EVENT_TREE_CHANGED;
202
203 if (!has_same_attributes(result->info, current_info)) {
204 return EVENT_ATTRIBUTE_CHANGED;
205 }
206 *point = result;
207 return EVENT_OK;
208 }
209
check_cred_atrribute(pid_t tgid,const struct cred * new)210 static int check_cred_atrribute(pid_t tgid, const struct cred *new)
211 {
212 struct point_info *result = point_search(tgid);
213 if (result == NULL)
214 return EVENT_TREE_CHANGED;
215
216 if (!has_same_cred(new, result->info))
217 return EVENT_CRED_CHANGED;
218
219 return EVENT_OK;
220 }
221
check_nsproxy_atrribute(pid_t tgid,const struct nsproxy * new)222 static int check_nsproxy_atrribute(pid_t tgid, const struct nsproxy *new)
223 {
224 struct point_info *result = point_search(tgid);
225 if (result == NULL)
226 return EVENT_TREE_CHANGED;
227
228 if (!has_same_nsproxy(new, result->info))
229 return EVENT_NSPROXY_CHANGED;
230
231 return EVENT_OK;
232 }
233
kernel_clone_hook(struct task_struct * task)234 void kernel_clone_hook(struct task_struct *task)
235 {
236 if (!ced_has_check_perm())
237 return;
238
239 struct process_info *info = process_info_record(task);
240 if (info == NULL)
241 return;
242
243 struct point_info *parent = NULL;
244 // if clone_flags & (CLONE_PARENT|CLONE_THREAD)
245 // p->real_parent = current->real_parent else task->real_parent = current
246 pid_t parent_tgid = task->real_parent->tgid;
247 if (task->real_parent == current->real_parent) {
248 parent_tgid = task->tgid;
249 }
250 // check firstly, judge child task's attributes are different from parent task
251 down_read(&point_lock);
252 int ret = check_tree_and_attribute(parent_tgid, info, &parent);
253 up_read(&point_lock);
254 if (ret) {
255 print_container_escape_detection(ret);
256 kfree(info);
257 info = NULL;
258 return;
259 }
260
261 // if the tgid of thread exist in the tree, it doesn't have to insert
262 // the node into the tree
263 down_write(&point_lock);
264 if (task->tgid == parent->tgid) {
265 parent->count++;
266 up_write(&point_lock);
267 kfree(info);
268 info = NULL;
269 return;
270 }
271
272 if (!point_insert(task->tgid, info)) {
273 up_write(&point_lock);
274 kfree(info);
275 ced_log_error("insert point into tree failed");
276 return;
277 }
278 up_write(&point_lock);
279 }
280
switch_task_namespaces_hook(const struct nsproxy * new)281 void switch_task_namespaces_hook(const struct nsproxy *new)
282 {
283 if (new == NULL || !ced_has_check_perm())
284 return;
285
286 down_read(&point_lock);
287 int ret = check_nsproxy_atrribute(current->tgid, new);
288 up_read(&point_lock);
289 if (ret)
290 print_container_escape_detection(ret);
291 }
292
commit_creds_hook(const struct cred * new)293 void commit_creds_hook(const struct cred *new)
294 {
295 if (!ced_has_check_perm())
296 return;
297
298 down_read(&point_lock);
299 int ret = check_cred_atrribute(current->tgid, new);
300 up_read(&point_lock);
301 if (ret)
302 print_container_escape_detection(ret);
303 }
304
detection_hook(struct task_struct * task)305 void detection_hook(struct task_struct *task)
306 {
307 if (!ced_has_check_perm())
308 return;
309
310 struct process_info *info = process_info_record(task);
311 if (info == NULL)
312 return;
313
314 struct point_info *point = NULL;
315 // check whether the value of node is same as task
316 down_read(&point_lock);
317 int ret = check_tree_and_attribute(task->tgid, info, &point);
318 up_read(&point_lock);
319 if (ret) {
320 print_container_escape_detection(ret);
321 }
322 kfree(info);
323 }
324
exit_hook(struct task_struct * task)325 void exit_hook(struct task_struct *task)
326 {
327 if (!ced_has_check_perm()) {
328 return;
329 }
330
331 down_read(&point_lock);
332 struct point_info *result = point_search(task->tgid);
333 if (result == NULL) {
334 up_read(&point_lock);
335 print_container_escape_detection(EVENT_TREE_CHANGED);
336 return;
337 }
338 up_read(&point_lock);
339
340 down_write(&point_lock);
341 result->count--;
342
343 // when thread number is zero, erase the node of tree
344 if (result->count == 0) {
345 point_erase(task->tgid);
346 }
347 up_write(&point_lock);
348 }
349