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