diff --git a/container_escape_detection/Makefile b/container_escape_detection/Makefile index 3fd48e618fc5241aa35b1d96d2fdb8820de422f8..b78410b2136512fb81b6b6c28ad4f1559123e83b 100644 --- a/container_escape_detection/Makefile +++ b/container_escape_detection/Makefile @@ -2,6 +2,7 @@ # Copyright (c) 2023 Huawei Device Co., Ltd. # obj-$(CONFIG_SECURITY_CONTAINER_ESCAPE_DETECTION) += core/ced_detection.o +obj-$(CONFIG_SECURITY_CONTAINER_ESCAPE_DETECTION) += core/ced_permission.o obj-$(CONFIG_SECURITY_CONTAINER_ESCAPE_DETECTION) += core/ced_module.o ccflags-$(CONFIG_SECURITY_CONTAINER_ESCAPE_DETECTION) := \ diff --git a/container_escape_detection/core/ced_detection.c b/container_escape_detection/core/ced_detection.c index 3981b4d082c232c6f94ba7cc784180999c0ed41b..3f0ff8c6d20d42299720bedc7df80204cbdc49c1 100644 --- a/container_escape_detection/core/ced_detection.c +++ b/container_escape_detection/core/ced_detection.c @@ -8,121 +8,332 @@ #include "avc.h" #include "objsec.h" #include "ced_detection.h" +#include "ced_detection_points.h" enum ced_event_type { - EVENT_CRED_ROOT, - EVENT_NSPROXY_ROOT, - EVENT_HAS_ROOT, + EVENT_OK, + EVENT_CRED_CHANGED, + EVENT_NSPROXY_CHANGED, + EVENT_ATTRIBUTE_CHANGED, + EVENT_TREE_CHANGED, EVENT_NUM }; -extern struct task_struct init_task; +static struct rb_root root_tree = RB_ROOT; +static struct rw_semaphore point_lock; -static const char *gEventContent[EVENT_NUM] = { - "cred has been changed to root.", - "nsproxy has been changed to init.", - "process has been rooted." +static const char *gEventContent[EVENT_NUM - 1] = { + "cred has been changed illegally.", + "nsproxy has been changed illegally.", + "attribute has been changed illegally.", + "tree has been changed illegally", }; static inline void print_container_escape_detection(enum ced_event_type type) { - if (type < EVENT_NUM) { - ced_log_error("tgid is %d, %s container escape is detected!!!!", current->tgid, gEventContent[type]); - } + if (type < EVENT_NUM) + ced_log_error("tgid is %d, %s container escape is detected!!!!", current->tgid, gEventContent[type - 1]); } static int ced_avc_has_perm(u16 tclass, u32 requested) { struct av_decision avd; - u32 sid = current_sid(); int rc; + if (!selinux_initialized(&selinux_state)) + return 1; + + u32 sid = current_sid(); rc = avc_has_perm_noaudit(&selinux_state, sid, sid, tclass, requested, AVC_STRICT, &avd); return rc; } -static bool ced_has_check_perm(void) +bool ced_has_check_perm(void) { // use selinux label to tell the process is hap process int rc = ced_avc_has_perm(SECCLASS_CED, CED__CONTAINER_ESCAPE_CHECK); - if (rc) { + if (rc) return false; + + return true; +} + +static struct point_info *point_search(pid_t tgid) +{ + struct rb_node *node = root_tree.rb_node; + while (node != NULL) { + struct point_info *point = container_of(node, struct point_info, node); + pid_t result = point->tgid; + if (result > tgid) + node = node->rb_left; + else if (result < tgid) + node = node->rb_right; + else + return point; } + return NULL; +} + +static bool point_insert(pid_t tgid, struct process_info *info) +{ + struct rb_node **new = &root_tree.rb_node; + struct rb_node *parent = NULL; + struct point_info *point = NULL; + pid_t result; + /* Figure out where to put new node */ + while (*new != NULL) { + point = container_of((*new), struct point_info, node); + result = point->tgid; + parent = *new; + if (result > tgid) + new = &(*new)->rb_left; + else if (result < tgid) + new = &(*new)->rb_right; + else + return false; + } + + point = kmalloc(sizeof(struct point_info), GFP_KERNEL); + if (point == NULL) + return false; + + point->tgid = tgid; + point->count = 1; + point->info = info; + + /* Add new node and rebalance tree. */ + rb_link_node(&point->node, parent, new); + rb_insert_color(&point->node, &root_tree); return true; } -static uint64_t process_ns_pac_hash(const struct nsproxy *nsproxy) +void point_erase(pid_t pid) { - uint64_t pac_hash = 0; - uintptr_t ns_ptr = (uintptr_t)nsproxy->mnt_ns; - pac_hash ^= ns_ptr; - ns_ptr = (uintptr_t)nsproxy->pid_ns_for_children; - pac_hash ^= ns_ptr; - ns_ptr = (uintptr_t)nsproxy->net_ns; - pac_hash ^= ns_ptr; - return pac_hash; + struct point_info *point = point_search(pid); + if (point != NULL) { + rb_erase(&point->node, &root_tree); + kfree(point->info); + point->info = NULL; + kfree(point); + point=NULL; + } } -static bool is_container_process(const struct nsproxy *new) +static bool has_same_attributes(struct process_info *a, struct process_info *b) { - uint64_t current_pac_hash = process_ns_pac_hash(new); - uint64_t init_task_ns_pac = process_ns_pac_hash(init_task.nsproxy); - if (current_pac_hash == init_task_ns_pac) { + if (memcmp(a, b, sizeof(struct process_info))) return false; - } else { + + return true; +} + +static bool has_same_cred(const struct cred *a, struct process_info *b) +{ + if (a->euid.val == b->cred.euid && a->egid.val == b->cred.egid + && a->fsuid.val == b->cred.fsuid + && memcmp(&a->cap_effective, &b->cred.cap_effective, sizeof(kernel_cap_t))) + return true; + + return false; +} + +static bool has_same_nsproxy(const struct nsproxy *a, struct process_info *b) +{ + if (a->mnt_ns == b->ns.mnt_ns && a->pid_ns_for_children == b->ns.pid_ns + && a->net_ns == b->ns.net_ns) return true; + + return false; +} + +void ced_initialize(void) +{ + init_rwsem(&point_lock); +} + +void setattr_insert_hook(struct task_struct *task) +{ + if (!ced_has_check_perm()) + return; + + pid_t tgid = task->tgid; + struct process_info *info = process_info_record(task); + if (info == NULL) + return; + + down_read(&point_lock); + struct point_info *result = point_search(task->tgid); + if (result != NULL) { + up_read(&point_lock); + kfree(info); + print_container_escape_detection(EVENT_TREE_CHANGED); + return; + } + up_read(&point_lock); + + down_write(&point_lock); + bool ret = point_insert(tgid, info); + if (!ret) { + up_write(&point_lock); + kfree(info); + ced_log_error("insert point into tree failed"); + return; } + up_write(&point_lock); } -static bool detection_promotion_privilege(const struct cred *new) +static int check_tree_and_attribute(pid_t tgid, struct process_info *current_info, struct point_info **point) { - const struct cred *init_cred = get_task_cred(&init_task); - bool flag = false; - if (new->euid.val == 0 || new->egid.val == 0 || new->fsuid.val == 0 - || !memcmp(&new->cap_effective, &init_cred->cap_effective, sizeof(kernel_cap_t))) { - flag = true; + struct point_info *result = point_search(tgid); + if (result == NULL) + return EVENT_TREE_CHANGED; + + if (!has_same_attributes(result->info, current_info)) { + return EVENT_ATTRIBUTE_CHANGED; } - return flag; + *point = result; + return EVENT_OK; } -void switch_task_namespaces_hook(const struct nsproxy *new) +static int check_cred_atrribute(pid_t tgid, const struct cred *new) { - if (!ced_has_check_perm()) { + struct point_info *result = point_search(tgid); + if (result == NULL) + return EVENT_TREE_CHANGED; + + if (!has_same_cred(new, result->info)) + return EVENT_CRED_CHANGED; + + return EVENT_OK; +} + +static int check_nsproxy_atrribute(pid_t tgid, const struct nsproxy *new) +{ + struct point_info *result = point_search(tgid); + if (result == NULL) + return EVENT_TREE_CHANGED; + + if (!has_same_nsproxy(new, result->info)) + return EVENT_NSPROXY_CHANGED; + + return EVENT_OK; +} + +void kernel_clone_hook(struct task_struct *task) +{ + if (!ced_has_check_perm()) + return; + + struct process_info *info = process_info_record(task); + if (info == NULL) + return; + + struct point_info *parent = NULL; + // if clone_flags & (CLONE_PARENT|CLONE_THREAD) + // p->real_parent = current->real_parent else task->real_parent = current + pid_t parent_tgid = task->real_parent->tgid; + if (task->real_parent == current->real_parent) { + parent_tgid = task->tgid; + } + // check firstly, judge child task's attributes are different from parent task + down_read(&point_lock); + int ret = check_tree_and_attribute(parent_tgid, info, &parent); + up_read(&point_lock); + if (ret) { + print_container_escape_detection(ret); + kfree(info); + info = NULL; return; } - if (new == NULL) { + // if the tgid of thread exist in the tree, it doesn't have to insert + // the node into the tree + down_write(&point_lock); + if (task->tgid == parent->tgid) { + parent->count++; + up_write(&point_lock); + kfree(info); + info = NULL; return; } - if (!is_container_process(new)) { - print_container_escape_detection(EVENT_NSPROXY_ROOT); + if (!point_insert(task->tgid, info)) { + up_write(&point_lock); + kfree(info); + ced_log_error("insert point into tree failed"); + return; } + up_write(&point_lock); +} + +void switch_task_namespaces_hook(const struct nsproxy *new) +{ + if (new == NULL || !ced_has_check_perm()) + return; + + down_read(&point_lock); + int ret = check_nsproxy_atrribute(current->tgid, new); + up_read(&point_lock); + if (ret) + print_container_escape_detection(ret); } void commit_creds_hook(const struct cred *new) { - if (!ced_has_check_perm()) { + if (!ced_has_check_perm()) return; - } - if (detection_promotion_privilege(new)) { - print_container_escape_detection(EVENT_CRED_ROOT); - } + down_read(&point_lock); + int ret = check_cred_atrribute(current->tgid, new); + up_read(&point_lock); + if (ret) + print_container_escape_detection(ret); } void detection_hook(struct task_struct *task) +{ + if (!ced_has_check_perm()) + return; + + struct process_info *info = process_info_record(task); + if (info == NULL) + return; + + struct point_info *point = NULL; + // check whether the value of node is same as task + down_read(&point_lock); + int ret = check_tree_and_attribute(task->tgid, info, &point); + up_read(&point_lock); + if (ret) { + print_container_escape_detection(ret); + } + kfree(info); +} + +void exit_hook(struct task_struct *task) { if (!ced_has_check_perm()) { return; } - const struct cred *cred = get_task_cred(task); + down_read(&point_lock); + struct point_info *result = point_search(task->tgid); + if (result == NULL) { + up_read(&point_lock); + print_container_escape_detection(EVENT_TREE_CHANGED); + return; + } + up_read(&point_lock); - if ((!is_container_process(task->nsproxy) || (detection_promotion_privilege(cred)))) { - print_container_escape_detection(EVENT_HAS_ROOT); + down_write(&point_lock); + result->count--; + + // when thread number is zero, erase the node of tree + if (result->count == 0) { + point_erase(task->tgid); } -} \ No newline at end of file + up_write(&point_lock); +} diff --git a/container_escape_detection/core/ced_module.c b/container_escape_detection/core/ced_module.c index 2e35464ee47f08f7b08ad9f7c02ca9b4db3b6bd6..ff3fd6adbd181581bb6b654f09fef6a68869f03f 100644 --- a/container_escape_detection/core/ced_module.c +++ b/container_escape_detection/core/ced_module.c @@ -7,19 +7,25 @@ #include #include "ced_detection.h" #include "ced_log.h" +#include "ced_permission.h" void ced_register_ced_hooks(void) { - REGISTER_HCK_LITE_HOOK(ced_detection_lhck, detection_hook); - REGISTER_HCK_LITE_HOOK(ced_switch_task_namespaces_lhck, switch_task_namespaces_hook); - REGISTER_HCK_LITE_HOOK(ced_commit_creds_lhck, commit_creds_hook); - ced_log_info("ced_register_ced_hooks"); + REGISTER_HCK_LITE_HOOK(ced_setattr_insert_lhck, setattr_insert_hook); + REGISTER_HCK_LITE_HOOK(ced_detection_lhck, detection_hook); + REGISTER_HCK_LITE_HOOK(ced_switch_task_namespaces_lhck, switch_task_namespaces_hook); + REGISTER_HCK_LITE_HOOK(ced_commit_creds_lhck, commit_creds_hook); + REGISTER_HCK_LITE_HOOK(ced_exit_lhck, exit_hook); + REGISTER_HCK_LITE_HOOK(ced_kernel_clone_lhck, kernel_clone_hook); + REGISTER_HCK_LITE_HOOK(ced_switch_task_namespaces_permission_lhck, switch_task_namespaces_permission_hook); + ced_log_info("ced_register_ced_hooks"); } static int __init ced_module_init(void) { - ced_register_ced_hooks(); - return 0; + ced_register_ced_hooks(); + ced_initialize(); + return 0; } module_init(ced_module_init); diff --git a/container_escape_detection/core/ced_permission.c b/container_escape_detection/core/ced_permission.c new file mode 100644 index 0000000000000000000000000000000000000000..c89a5fd8e5fda49d75ad381f2cf8ea2583f1ac9f --- /dev/null +++ b/container_escape_detection/core/ced_permission.c @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ + +#include "ced_detection.h" +#include "ced_log.h" + +void switch_task_namespaces_permission_hook(const struct nsproxy *new, int *ret) +{ + *ret = 0; + if (new == NULL) + return; + + if (ced_has_check_perm()) { + *ret = -EPERM; + ced_log_error("switch task namespace is not permitted in container process"); + return; + } +} \ No newline at end of file diff --git a/container_escape_detection/include/ced_detection.h b/container_escape_detection/include/ced_detection.h index aed4f5d27da3b51d192e407353d3d0912f0b7a0f..a1b150af787726acd2612fd7e5a282c91a6cc59c 100644 --- a/container_escape_detection/include/ced_detection.h +++ b/container_escape_detection/include/ced_detection.h @@ -9,8 +9,13 @@ #include #include +void ced_initialize(void); void detection_hook(struct task_struct *task); +void setattr_insert_hook(struct task_struct *task); +void exit_hook(struct task_struct *task); void switch_task_namespaces_hook(const struct nsproxy *new); void commit_creds_hook(const struct cred *new); +void kernel_clone_hook(struct task_struct *task); +bool ced_has_check_perm(void); #endif /* _CED_DETECTION_H */ \ No newline at end of file diff --git a/container_escape_detection/include/ced_detection_points.h b/container_escape_detection/include/ced_detection_points.h new file mode 100644 index 0000000000000000000000000000000000000000..45eb2babca36f19bb75c7d1c26ec652528e9314d --- /dev/null +++ b/container_escape_detection/include/ced_detection_points.h @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ + +#ifndef _LINUX_CED_DETECTION_POINTS_H +#define _LINUX_CED_DETECTION_POINTS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct cred_info { + uid_t euid; + gid_t egid; + uid_t fsuid; + kernel_cap_t cap_effective; +}; + +static inline void cred_info_record(struct cred_info *info, const struct cred *cred) +{ + info->euid = cred->euid.val; + info->egid = cred->egid.val; + info->fsuid = cred->fsuid.val; + + memcpy(&info->cap_effective.cap[0], &cred->cap_effective.cap[0], sizeof(info->cap_effective.cap)); +} + +struct ns_info { + struct mnt_namespace *mnt_ns; + struct pid_namespace *pid_ns; + struct net *net_ns; +}; + +static inline void ns_info_record(struct ns_info *info, const struct nsproxy *nsproxy) +{ + if (nsproxy) { + info->mnt_ns = nsproxy->mnt_ns; + info->pid_ns = nsproxy->pid_ns_for_children; + info->net_ns = nsproxy->net_ns; + } +} + +struct process_info { + struct cred_info cred; + struct ns_info ns; +}; + +struct point_info { + struct rb_node node; + pid_t tgid; + uint32_t count; + struct process_info *info; +}; + +static inline struct process_info *process_info_record(struct task_struct *task) +{ + struct process_info *info = NULL; + const struct cred *cred = get_task_cred(task); + if (cred == NULL) { + return NULL; + } + + info = kmalloc(sizeof(struct process_info), GFP_KERNEL); + if (info == NULL) { + return NULL; + } + memset(info, 0, sizeof(struct process_info)); + + cred_info_record(&info->cred, cred); + + if (task->nsproxy != NULL) { + ns_info_record(&info->ns, task->nsproxy); + } + + return info; +} + +#endif /* _LINUX_CED_DETECTION_POINTS_H */ \ No newline at end of file diff --git a/container_escape_detection/include/ced_permission.h b/container_escape_detection/include/ced_permission.h new file mode 100644 index 0000000000000000000000000000000000000000..d0a8498d64f4df37acbbae1f765cffec86df6555 --- /dev/null +++ b/container_escape_detection/include/ced_permission.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ + +#ifndef _CED_PERMISSION_H +#define _CED_PERMISSION_H + +#include +#include + +void switch_task_namespaces_permission_hook(const struct nsproxy *new, int *ret); + +#endif /* _CED_PERMISSION_H */ \ No newline at end of file