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