• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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