diff --git a/dec/Kconfig b/dec/Kconfig index 9f9cbda..db00022 100644 --- a/dec/Kconfig +++ b/dec/Kconfig @@ -4,9 +4,21 @@ config SECURITY_DEC bool "Data enhance control features" + depends on SECURITY_PATH default y help This option enables file operation permission verification at VFS layer. If unsure, say N. + +config SECURITY_DEC_DEVELOP + bool "Enable Data enhance control develop mode" + + depends on SECURITY_DEC + default y + help + This option controls the operating mode of Data Enhance Control: + - If enabled (Y): DEC runs in DEVELOP mode. + - If disabled (N): DEC runs in PRODUCT mode, which enforces strict + permission checks and log desensitization \ No newline at end of file diff --git a/dec/Makefile b/dec/Makefile index efd9cfe..8864fb0 100644 --- a/dec/Makefile +++ b/dec/Makefile @@ -2,7 +2,7 @@ # Copyright (c) 2024 Huawei Device Co., Ltd. # obj-$(CONFIG_SECURITY_DEC) += \ - dec_misc.o + dec_constraint_tree.o dec_kernel_interface.o dec_misc.o dec_path_tree.o dec_security_hook.o dec_utils.o sysctl.o ccflags-$(CONFIG_SECURITY_DEC) += \ -I$(srctree)/fs/dec diff --git a/dec/dec_common.h b/dec/dec_common.h new file mode 100644 index 0000000..c36956f --- /dev/null +++ b/dec/dec_common.h @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2026 Huawei Device Co., Ltd. + */ + +#ifndef _DEC_COMMON_H +#define _DEC_COMMON_H + +#include +#include +#include +#include + +/* Dec operation modes */ +#define DEC_NONE 0 +#define DEC_READ (1 << 0) +#define DEC_WRITE (1 << 1) +#define DEC_PREFIX (1 << 9) + +#define DEC_TOKENID_MASK 0x00000000FFFFFFFF + +/** + * struct permission - Permission entry for a token + * @tokenid: Unique token identifier + * @mode: Access mode (allowed DEC_READ/DEC_WRITE) + * @userid: User identifier associated with this permission + * @persist_flag: Whether this permission should be persisted + * @timestamp: Time when permission was created/updated + * @rb_node: Red-black tree node for insertion into rb_root + */ +struct permission { + uint64_t tokenid; + uint32_t mode; + int userid; + bool persist_flag; + uint64_t timestamp; + struct rb_node rb_node; +}; + +/** + * struct trie_node - Trie node for path-based permission storage + * @component: Path component (directory/file name) + * @permissions: RB root containing permission entries for this path + * @children: RB root containing child trie nodes + * @has_permissions: Flag indicating if this node has active permissions + * @rb_node: RB node for sorting children + */ +struct trie_node { + char *component; + struct rb_root permissions; + struct rb_root children; + bool has_permissions; + struct rb_node rb_node; +}; + +/** + * struct trie_stack_item - Stack item for trie traversal + * @node: Current trie node being processed + * @parent: Parent node of the current node + * @child_key: Child component name being looked up + * @visited_children: Flag indicating if children have been processed + * @list: List head for linking stack items + */ +struct trie_stack_item { + struct trie_node *node; + struct trie_node *parent; + char *child_key; + bool visited_children; + struct list_head list; +}; + +/** + * struct path_component - Linked list node for path component decomposition + * @list: List head for linking components + * @name: Name of the path component + */ +struct path_component { + struct list_head list; + char name[0]; +}; + +/** + * struct path_tree_params - Parameters for path tree operations + * @path: Target path for permission operation + * @tokenid: Token ID to apply permissions + * @mode: Access mode + * @userid: User ID for the permission + * @persist_flag: Persistence flag for the permission + * @timestamp: Timestamp for the permission + */ +struct path_tree_params { + const char *path; + uint64_t tokenid; + uint32_t mode; + int userid; + bool persist_flag; + uint64_t timestamp; +}; + +#endif /* _DEC_COMMON_H */ \ No newline at end of file diff --git a/dec/dec_constraint_tree.c b/dec/dec_constraint_tree.c new file mode 100644 index 0000000..4a31bfc --- /dev/null +++ b/dec/dec_constraint_tree.c @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2026 Huawei Device Co., Ltd. + */ + +#include +#include +#include +#include + +#include "dec_constraint_tree.h" +#include "dec_common.h" +#include "dec_log.h" +#include "dec_utils.h" + +static struct trie_node *constraint_tree = NULL; +static struct rw_semaphore *dec_constraint_rwsem = NULL; + +static int __init dec_constraint_tree_init(void) +{ + dec_constraint_rwsem = kmalloc(sizeof(struct rw_semaphore), GFP_KERNEL); + if (!dec_constraint_rwsem) { + dec_loge("Failed to allocate rwsem for constraint tree"); + return -ENOMEM; + } + init_rwsem(dec_constraint_rwsem); + + constraint_tree = trie_node_create("/"); + if (!constraint_tree) { + kfree(dec_constraint_rwsem); + dec_constraint_rwsem = NULL; + dec_loge("Failed to create root node for constraint tree"); + return -ENOMEM; + } + dec_logd("Constraint tree initialized successfully"); + return 0; +} + +bool dec_constraint_query(const char *path) +{ + struct list_head comp_list; + struct trie_node *node = constraint_tree; + struct path_component *comp = NULL; + bool found = false; + + INIT_LIST_HEAD(&comp_list); + + if (split_path_to_component_list(path, &comp_list) < 0) { + free_component_list(&comp_list); + return found; + } + + down_read(dec_constraint_rwsem); + /* Check root node first */ + if (node->has_permissions) { + found = true; + goto cleanup; + } + /* Traverse path components to check child nodes */ + list_for_each_entry(comp, &comp_list, list) { + node = find_child(node, comp->name); + if (node == NULL) { + goto cleanup; + } + + if (node->has_permissions) { + found = true; + goto cleanup; + } + } +cleanup: + up_read(dec_constraint_rwsem); + free_component_list(&comp_list); + return found; +} + +int dec_constraint_add(char *path) +{ + if (is_path_valid(path) != 0) { + dec_loge("Invalid path for constraint add: %s", path); + return -EINVAL; + } + int ret = 0; + struct trie_node *node = constraint_tree; + struct list_head comp_list; + INIT_LIST_HEAD(&comp_list); + + ret = split_path_to_component_list(path, &comp_list); + if (ret < 0) { + dec_loge("Failed to split path components for add: %s (err=%d)", path, ret); + free_component_list(&comp_list); + return ret; + } + + down_write(dec_constraint_rwsem); + struct path_component *comp; + list_for_each_entry(comp, &comp_list, list) { + struct trie_node *child = find_child(node, comp->name); + if (child == NULL) { + child = insert_child(node, comp->name); + if (child == NULL) { + dec_loge("Failed to allocate child node for %s", comp->name); + ret = -ENOMEM; + goto cleanup; + } + } + node = child; + } + + node->has_permissions = true; + dec_logi("Constraint added for path: %s", path); +cleanup: + up_write(dec_constraint_rwsem); + free_component_list(&comp_list); + return ret; +} + +/* Register initialization function for filesystem init phase */ +fs_initcall(dec_constraint_tree_init); \ No newline at end of file diff --git a/dec/dec_constraint_tree.h b/dec/dec_constraint_tree.h new file mode 100644 index 0000000..55aa4fd --- /dev/null +++ b/dec/dec_constraint_tree.h @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2026 Huawei Device Co., Ltd. + */ + +#ifndef _DEC_CONSTRAINT_H +#define _DEC_CONSTRAINT_H + +#include + +bool dec_constraint_query(const char *path); +int dec_constraint_add(char *path); +#endif /* _DEC_CONSTRAINT_H */ diff --git a/dec/dec_kernel_interface.c b/dec/dec_kernel_interface.c new file mode 100644 index 0000000..a14e9e7 --- /dev/null +++ b/dec/dec_kernel_interface.c @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2026 Huawei Device Co., Ltd. + */ + +#include +#include + +#include "dec_kernel_interface.h" +#include "dec_constraint_tree.h" +#include "dec_path_tree.h" +#include "dec_common.h" +#include "dec_utils.h" +#include "dec_log.h" +#include "sysctl.h" + +/* DEC enforcement modes */ +#define DEC_MODE_ENFORCED 1 /* Enforce rules (deny access on violation) */ +#define DEC_MODE_PERMISSIVE 0 /* Log violations but allow access */ + +/* Global DEC enforcement mode (0=permissive, 1=enforced) */ +extern int dec_mode; + +static int32_t dec_get_pid(void) +{ + struct task_struct *curr = current; + return curr->pid; +} + +static char *dec_get_pname(void) +{ + struct task_struct *curr = current; + return curr->comm; +} + +static char *mode_to_string(uint32_t mode) +{ + static char mode_str[32]; + mode_str[0] = '\0'; + + if (mode == DEC_READ) { + strcat(mode_str, "r"); + } else if (mode == DEC_WRITE) { + strcat(mode_str, "w"); + } else if (mode == (DEC_READ | DEC_WRITE)) { + strcat(mode_str, "rw"); + } + + return mode_str; +} + +static bool dec_mode_is_enforced(void) +{ + return dec_mode == DEC_MODE_ENFORCED; +} + +int dec_rule_query(struct path_tree_params *params) +{ + uint64_t tokenid; + const char *path; + uint32_t mode; + bool is_persist; + int ret = 0; + + if (!params || !params->path) { + dec_loge("DEC: Invalid parameters for rule query (params=%p, path=%p)", + params, params ? params->path : NULL); + return -EINVAL; + } + + tokenid = params->tokenid & DEC_TOKENID_MASK; + path = params->path; + mode = params->mode; + is_persist = params->persist_flag; + + if (is_path_valid(path) != 0) { + dec_loge("DEC: Invalid path for rule query: %s", path); + return -EINVAL; + } + + /* Bypass check if path is not in constraint tree (not regulated) */ + if (!dec_constraint_query(path)) { + return ret; + } + + /* Perform actual permission check against path tree */ + if (!dec_path_tree_query(tokenid, path, mode, is_persist)) { + ret = -EACCES; + } + + if (ret) { + dec_logw("dec denied for pid=%d pname=\"%s\" policy_path=%s policy_mode=%s permissive=%d", + dec_get_pid(), dec_get_pname(), path, mode_to_string(mode), !dec_mode_is_enforced()); + } + + /* In permissive mode, allow access even if permission check failed */ +#ifdef CONFIG_SECURITY_DEC_DEVELOP + if (!dec_mode_is_enforced()) { + return 0; + } +#endif + return ret; +} \ No newline at end of file diff --git a/dec/dec_kernel_interface.h b/dec/dec_kernel_interface.h new file mode 100644 index 0000000..8f77883 --- /dev/null +++ b/dec/dec_kernel_interface.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2026 Huawei Device Co., Ltd. + */ + +#ifndef _DEC_KERNEL_INTERFACE_H +#define _DEC_KERNEL_INTERFACE_H + +#include +#include + +#include "dec_common.h" + +int dec_rule_query(struct path_tree_params *params); +#endif /* _DEC_KERNEL_INTERFACE_H */ \ No newline at end of file diff --git a/dec/dec_log.h b/dec/dec_log.h new file mode 100644 index 0000000..1fb47a1 --- /dev/null +++ b/dec/dec_log.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2026 Huawei Device Co., Ltd. + */ + +#ifndef _DEC_LOG_H +#define _DEC_LOG_H + +#include + +#define DEC_LOG_PREFIX "DEC: " + +#define dec_logd(fmt, ...) pr_debug(DEC_LOG_PREFIX fmt, ##__VA_ARGS__) +#define dec_logi(fmt, ...) pr_info(DEC_LOG_PREFIX fmt, ##__VA_ARGS__) +#define dec_logw(fmt, ...) pr_warn(DEC_LOG_PREFIX fmt, ##__VA_ARGS__) +#define dec_loge(fmt, ...) pr_err(DEC_LOG_PREFIX fmt, ##__VA_ARGS__) +#define dec_logf(fmt, ...) pr_crit(DEC_LOG_PREFIX fmt, ##__VA_ARGS__) + +#endif /* _DEC_LOG_H */ \ No newline at end of file diff --git a/dec/dec_misc.c b/dec/dec_misc.c index 534ce5c..32d7f4c 100644 --- a/dec/dec_misc.c +++ b/dec/dec_misc.c @@ -3,90 +3,502 @@ * Copyright (c) 2024 Huawei Device Co., Ltd. */ -#include #include +#include +#include #include #include +#include +#include +#include #include -#include +#include +#include #include "dec_misc.h" +#include "dec_security_hook.h" +#include "sysctl.h" +#include "dec_common.h" +#include "dec_log.h" +#include "dec_utils.h" +#include "dec_path_tree.h" +#include "dec_constraint_tree.h" -static int vfs_deal_policy_cmd(unsigned int cmd, void __user *arg) +static const char *cmd_to_string(unsigned int cmd) { - pr_info("vfs dec deal policy cmd:%u\n", cmd); - int ret = 0; - struct dec_policy_info info = { 0 }; + switch (cmd) { + case SET_DEC_RULE_CMD: + case SET_DEC_RULE_CMD_32: + return "SET_DEC_RULE"; + case DEL_DEC_RULE_CMD: + case DEL_DEC_RULE_CMD_32: + return "DEL_DEC_RULE"; + case QUERY_DEC_RULE_CMD: + case QUERY_DEC_RULE_CMD_32: + return "QUERY_DEC_RULE"; + case CHECK_DEC_RULE_CMD: + case CHECK_DEC_RULE_CMD_32: + return "CHECK_DEC_RULE"; + case DESTROY_DEC_RULE_CMD: + case DESTROY_DEC_RULE_CMD_32: + return "DESTROY_DEC_RULE"; + case CONSTRAINT_DEC_RULE_CMD: + case CONSTRAINT_DEC_RULE_CMD_32: + return "CONSTRAINT_DEC_RULE"; + case DEL_DEC_RULE_BY_USER_CMD: + case DEL_DEC_RULE_BY_USER_CMD_32: + return "DEL_DEC_RULE_BY_USER"; + case SET_DEC_PREFIX_CMD: + case SET_DEC_PREFIX_CMD_32: + return "SET_DEC_PREFIX"; + default: + return "UNKNOWN_CMD"; + } +} - ret = copy_from_user(&info, arg, sizeof(info)); - if (ret != 0) { - pr_err("copy from user failed\n"); +static char *copy_user_path(uintptr_t user_path_ptr, uint32_t path_len) +{ + if (path_len == 0 || path_len > PATH_MAX) { + dec_logw("Invalid path length %u (max %d)", path_len, PATH_MAX); + return NULL; + } + + char *kernel_path = kmalloc(path_len + 1, GFP_KERNEL); + if (kernel_path == NULL) { + dec_loge("Failed to allocate memory for path (size %u)", path_len + 1); + return NULL; + } + if (copy_from_user(kernel_path, (const char __user *)user_path_ptr, path_len) != 0) { + dec_loge("Failed to copy path from user space (ptr=0x%lx, len=%u)", + (unsigned long)user_path_ptr, path_len); + kfree(kernel_path); + return NULL; + } + kernel_path[path_len] = '\0'; + return kernel_path; +} + +static void ioctl_set_rule(struct dec_rule_s *info) +{ + int ret = 0; + uint64_t tokenid = info->tokenid & DEC_TOKENID_MASK; + + for (unsigned int i = 0; i < info->path_num; i++) { + char *path = copy_user_path((uintptr_t)info->path[i].path, info->path[i].path_len); + if (!path) { + dec_logw("Failed to get path for index %u", i); + continue; + } + uint32_t mode = info->path[i].mode; + + struct path_tree_params params = {0}; + params.path = path; + params.tokenid = tokenid; + params.mode = mode; + params.userid = info->user_id; + params.persist_flag = info->persist_flag; + params.timestamp = info->timestamp; + + ret = dec_set_rule(¶ms); + if (ret) { + dec_loge("Failed to set rule for path '%s' (tokenid=0x%llx, mode=0x%x): %d", + path, tokenid, mode, ret); + info->path[i].ret_flag = FLAG_FALSE; + } else { + info->path[i].ret_flag = FLAG_TRUE; + } + + kfree(path); + } +} + +static void ioctl_delete_rule(struct dec_rule_s *info) +{ + int ret = 0; + uint64_t tokenid = info->tokenid & DEC_TOKENID_MASK; + + for (unsigned int i = 0; i < info->path_num; i++) { + char *path = copy_user_path((uintptr_t)info->path[i].path, info->path[i].path_len); + if (!path) { + dec_logw("Failed to get path for index %u", i); + continue; + } + uint64_t timestamp = info->timestamp; + ret = dec_delete_rule(tokenid, path, timestamp); + if (ret) { + dec_loge("Failed to delete rule for path %s, ret=%d", path, ret); + info->path[i].ret_flag = FLAG_FALSE; + } else { + info->path[i].ret_flag = FLAG_TRUE; + } + + kfree(path); + } +} + +static void ioctl_query_rule(struct dec_rule_s *info, bool is_persist) +{ + uint64_t tokenid = info->tokenid & DEC_TOKENID_MASK; + + for (unsigned int i = 0; i < info->path_num; i++) { + char *path = copy_user_path((uintptr_t)info->path[i].path, info->path[i].path_len); + if (is_path_valid(path) != 0) { + dec_logw("Invalid path for query: %s", path); + info->path[i].ret_flag = FLAG_FALSE; + if (path) kfree(path); + continue; + } + + /* Bypass check if path is not in constraint tree */ + if (!dec_constraint_query(path)) { + info->path[i].ret_flag = FLAG_TRUE; + dec_logd("Path '%s' not in constraint tree - access allowed", path); + kfree(path); + continue; + } + + /* Check permission in path tree */ + uint32_t mode = info->path[i].mode; + if (dec_path_tree_query(tokenid, path, mode, is_persist)) { + info->path[i].ret_flag = FLAG_TRUE; + } else { + info->path[i].ret_flag = FLAG_FALSE; + } + dec_logd("Query rule for path '%s' (tokenid=0x%llx, mode=0x%x, is_persist=%d): %s", + path, tokenid, mode, is_persist, + info->path[i].ret_flag == FLAG_TRUE ? "ALLOWED" : "DENIED"); + + kfree(path); + } + + return; +} + +static void ioctl_constraint_add(struct dec_rule_s *info) +{ + int ret = 0; + + for (unsigned int i = 0; i < info->path_num; i++) { + char *path = copy_user_path((uintptr_t)info->path[i].path, info->path[i].path_len); + if (!path) { + dec_logw("Failed to get path for index %u", i); + continue; + } + ret = dec_constraint_add(path); + if (ret != 0) { + dec_loge("Failed to add constraint path %s, ret=%d", path, ret); + info->path[i].ret_flag = FLAG_FALSE; + } else { + info->path[i].ret_flag = FLAG_TRUE; + } + + kfree(path); + } +} + +static void ioctl_set_prefix(struct dec_rule_s *info) +{ + int ret = 0; + + for (unsigned int i = 0; i < info->path_num; i++) { + char *path = copy_user_path((uintptr_t)info->path[i].path, info->path[i].path_len); + if (!path) { + dec_logw("Failed to get path for index %u", i); + continue; + } + ret = dec_set_prefix(path); + if (ret) { + dec_loge("Failed to set constraint prefix %s, ret=%d", path, ret); + info->path[i].ret_flag = FLAG_FALSE; + } else { + info->path[i].ret_flag = FLAG_TRUE; + } + + kfree(path); + } +} + +static void ioctl_delete_rule_by_tokenid(struct dec_rule_s *info) +{ + struct dec_destroy_ctx ctx = {0}; + ctx.criteria = DELETE_BY_TOKENID; + ctx.params.tokeninfo.tokenid = info->tokenid & DEC_TOKENID_MASK; + ctx.timestamp = info->timestamp; + dec_destroy_rule_by_id(&ctx); +} + +static void ioctl_delete_rule_by_userid(struct dec_rule_s *info) +{ + for (unsigned int i = 0; i < info->path_num; i++) { + char *path = copy_user_path((uintptr_t)info->path[i].path, info->path[i].path_len); + if (!path) { + dec_logw("Failed to get path for index %u", i); + continue; + } + struct dec_destroy_ctx ctx = {0}; + ctx.criteria = DELETE_BY_USERID; + ctx.params.userinfo.userid = info->user_id; + ctx.params.userinfo.path = path; + ctx.timestamp = info->timestamp; + if (dec_destroy_rule_by_id(&ctx) == 0) { + info->path[i].ret_flag = FLAG_TRUE; + } else { + dec_loge("Failed to delete rule by userid %d and path %s", info->user_id, path); + info->path[i].ret_flag = FLAG_FALSE; + } + + kfree(path); + } +} + +static void dec_rule_32_to_64(struct dec_rule_s_32 *info_32, struct dec_rule_s *info) +{ + if (info_32 == NULL || info == NULL) { + dec_loge("Invalid parameters for 32-to-64 rule conversion"); + return; + } + info->tokenid = info_32->tokenid; + info->timestamp = info_32->timestamp; + info->path_num = info_32->path_num; + info->user_id = info_32->user_id; + info->persist_flag = info_32->persist_flag; + memcpy(info->reserved, info_32->reserved, sizeof(info->reserved)); + + for (unsigned int i = 0; i < info->path_num; i++) { + info->path[i].path = (uintptr_t)(uint32_t)info_32->path[i].path; + info->path[i].path_len = info_32->path[i].path_len; + info->path[i].mode = info_32->path[i].mode; + info->path[i].ret_flag = FLAG_FALSE; + } +} + +static void dec_rule_64_to_32(struct dec_rule_s *info, struct dec_rule_s_32 *info_32) +{ + if (info_32 == NULL || info == NULL) { + dec_loge("Invalid parameters for 64-to-32 rule conversion"); + return; + } + info_32->tokenid = info->tokenid; + info_32->timestamp = info->timestamp; + info_32->path_num = info->path_num; + info_32->user_id = info->user_id; + info_32->persist_flag = info->persist_flag; + memcpy(info_32->reserved, info->reserved, sizeof(info_32->reserved)); + + for (unsigned int i = 0; i < info->path_num; i++) { + info_32->path[i].path = (uint32_t)(uintptr_t)info->path[i].path; + info_32->path[i].path_len = info->path[i].path_len; + info_32->path[i].mode = info->path[i].mode; + info_32->path[i].ret_flag = info->path[i].ret_flag; + } +} + +static int vfs_deal_rule_cmd(unsigned int cmd, void __user *arg) +{ + struct dec_rule_s info = { 0 }; + bool needs_copy_back = true; + + if (copy_from_user(&info, arg, sizeof(info))) { + dec_loge("Failed to copy 64-bit rule from user space"); return -EFAULT; } - pr_info("tokenid:%lu path_num:%u persist_flag:%d\n", info.tokenid, info.path_num, info.persist_flag); - - return ret; -} - -static int vfs_destroy_dec_policy(void __user *arg) -{ - int ret = 0; - uint64_t tokenid; - - ret = copy_from_user(&tokenid, arg, sizeof(tokenid)); - if (ret != 0) { - pr_err("destroy dec policy copy from caller failed\n"); - return -EFAULT; + if (info.path_num > MAX_POLICY_NUM) { + dec_loge("Invalid path count %u (max %d)", info.path_num, MAX_POLICY_NUM); + return -EINVAL; } - pr_info("destroy dec policy tokenid:%ld\n", tokenid); - return 0; -} - -static long dec_ioctl(struct file *file, unsigned int cmd, unsigned long arg) -{ - pr_info("dec ioctl cmd:%u\n", cmd); - int ret = 0; - switch (cmd) { - case SET_DEC_POLICY_CMD: - case DEL_DEC_POLICY_CMD: - case QUERY_DEC_POLICY_CMD: - case CHECK_DEC_POLICY_CMD: - case CONSTRAINT_DEC_POLICY_CMD: - case DENY_DEC_POLICY_CMD: - ret = vfs_deal_policy_cmd(cmd, (void __user *)arg); + case SET_DEC_RULE_CMD: + ioctl_set_rule(&info); break; - case DESTROY_DEC_POLICY_CMD: - ret = vfs_destroy_dec_policy((void __user *)arg); + case DEL_DEC_RULE_CMD: + ioctl_delete_rule(&info); + break; + case QUERY_DEC_RULE_CMD: + ioctl_query_rule(&info, true); /* Persistent rules */ + break; + case CHECK_DEC_RULE_CMD: + ioctl_query_rule(&info, false); /* Temporary rules */ + break; + case DESTROY_DEC_RULE_CMD: + ioctl_delete_rule_by_tokenid(&info); + needs_copy_back = false; + break; + case CONSTRAINT_DEC_RULE_CMD: + ioctl_constraint_add(&info); + break; + case DEL_DEC_RULE_BY_USER_CMD: + ioctl_delete_rule_by_userid(&info); + break; + case SET_DEC_PREFIX_CMD: + ioctl_set_prefix(&info); break; default: - ret = -EINVAL; + dec_loge("Unknown 64-bit DEC command: %u", cmd); + return -EINVAL; break; } + if (needs_copy_back && copy_to_user(arg, &info, sizeof(info))) { + dec_loge("Failed to copy 64-bit rule to user space"); + return -EFAULT; + } + dec_logi("Completed 64-bit DEC command: %s (tokenid=0x%llx, path_num=%u)", + cmd_to_string(cmd), info.tokenid, info.path_num); + return 0; } +static int vfs_deal_rule_cmd_32(unsigned int cmd, void __user *arg) +{ + int ret = 0; + struct dec_rule_s_32 *info_32 = NULL; + struct dec_rule_s *info = NULL; + bool needs_copy_back = true; + + /* Allocate memory for rule structures (prevent stack overflow) */ + info_32 = kmalloc(sizeof(struct dec_rule_s_32), GFP_KERNEL); + info = kmalloc(sizeof(struct dec_rule_s), GFP_KERNEL); + if (!info_32 || !info) { + dec_loge("Failed to allocate memory for 32-bit rule processing"); + ret = -ENOMEM; + goto cleanup; + } + + if (copy_from_user(info_32, arg, sizeof(struct dec_rule_s_32))) { + dec_loge("Failed to copy 32-bit rule from user space"); + ret = -EFAULT; + goto cleanup; + } + + if (info_32->path_num > MAX_POLICY_NUM) { + dec_loge("Invalid path count %u (max %d)", info_32->path_num, MAX_POLICY_NUM); + ret = -EINVAL; + goto cleanup; + } + dec_rule_32_to_64(info_32, info); + switch (cmd) { + case SET_DEC_RULE_CMD_32: + ioctl_set_rule(info); + break; + case DEL_DEC_RULE_CMD_32: + ioctl_delete_rule(info); + break; + case QUERY_DEC_RULE_CMD_32: + ioctl_query_rule(info, true); /* Persistent rules */ + break; + case CHECK_DEC_RULE_CMD_32: + ioctl_query_rule(info, false); /* Temporary rules */ + break; + case DESTROY_DEC_RULE_CMD_32: + ioctl_delete_rule_by_tokenid(info); + needs_copy_back = false; + break; + case CONSTRAINT_DEC_RULE_CMD_32: + ioctl_constraint_add(info); + break; + case DEL_DEC_RULE_BY_USER_CMD_32: + ioctl_delete_rule_by_userid(info); + break; + case SET_DEC_PREFIX_CMD_32: + ioctl_set_prefix(info); + break; + default: + dec_loge("Unknown 32-bit DEC command: %u", cmd); + ret = -EINVAL; + goto cleanup; + } + + if (needs_copy_back) { + dec_rule_64_to_32(info, info_32); + if (copy_to_user(arg, info_32, sizeof(struct dec_rule_s_32))) { + dec_loge("Failed to copy 32-bit rule to user space"); + ret = -EFAULT; + goto cleanup; + } + } + dec_logi("Completed 32-bit DEC command: %s (tokenid=0x%llx, path_num=%u)", + cmd_to_string(cmd), info_32->tokenid, info_32->path_num); + +cleanup: + if (info_32) kfree(info_32); + if (info) kfree(info); + return ret; +} + static int dec_open(struct inode *inode, struct file *filp) { - pr_info("dec open\n"); + dec_logi("dec device opened"); return 0; } static int dec_release(struct inode *inode, struct file *filp) { - pr_info("dec close\n"); + dec_logi("dec device released"); return 0; } +static long dec_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int ret = 0; + + switch (cmd) { + case SET_DEC_RULE_CMD: + case DEL_DEC_RULE_CMD: + case QUERY_DEC_RULE_CMD: + case CHECK_DEC_RULE_CMD: + case DESTROY_DEC_RULE_CMD: + case CONSTRAINT_DEC_RULE_CMD: + case DEL_DEC_RULE_BY_USER_CMD: + case SET_DEC_PREFIX_CMD: + dec_logi("Handling 64-bit ioctl cmd=%s", cmd_to_string(cmd)); + ret = vfs_deal_rule_cmd(cmd, (void __user *)arg); + break; + default: + dec_loge("Unknown 64-bit ioctl cmd=%u", cmd); + ret = -EINVAL; + break; + } + + return ret; +} + +#ifdef CONFIG_COMPAT +static long dec_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int ret = 0; + + switch (cmd) { + case SET_DEC_RULE_CMD_32: + case DEL_DEC_RULE_CMD_32: + case QUERY_DEC_RULE_CMD_32: + case CHECK_DEC_RULE_CMD_32: + case DESTROY_DEC_RULE_CMD_32: + case CONSTRAINT_DEC_RULE_CMD_32: + case DEL_DEC_RULE_BY_USER_CMD_32: + case SET_DEC_PREFIX_CMD_32: + dec_logi("Handling 32-bit ioctl cmd=%s", cmd_to_string(cmd)); + ret = vfs_deal_rule_cmd_32(cmd, compat_ptr(arg)); + break; + default: + dec_loge("Unknown 32-bit ioctl cmd=%u", cmd); + ret = -EINVAL; + break; + } + + return ret; +} +#endif + static const struct file_operations dec_fops = { .owner = THIS_MODULE, .open = dec_open, .release = dec_release, .unlocked_ioctl = dec_ioctl, - .compat_ioctl = dec_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = dec_compat_ioctl, +#endif }; static struct miscdevice dec_misc = { @@ -98,25 +510,30 @@ static struct miscdevice dec_misc = { static int __init dec_init(void) { int err = 0; + dec_logi("Initializing DEC module"); + + dec_sysctl_init(); err = misc_register(&dec_misc); - if (err < 0) { - pr_err("dec device init failed\n"); + if (err) { + dec_loge("Failed to register DEC misc device: %d", err); return err; } - pr_err("dec device init success\n"); + dec_hook_init(); + dec_logi("DEC module initialized successfully"); return 0; } static void __exit dec_exit(void) { + dec_logi("Cleaning up DEC module"); + dec_hook_exit(); misc_deregister(&dec_misc); - pr_info("dec exited"); + dec_logi("DEC misc device deregistered"); } -/* module entry points */ module_init(dec_init); module_exit(dec_exit); -MODULE_LICENSE("GPL"); +MODULE_LICENSE("GPL"); \ No newline at end of file diff --git a/dec/dec_misc.h b/dec/dec_misc.h index 1154ece..71a4927 100644 --- a/dec/dec_misc.h +++ b/dec/dec_misc.h @@ -8,47 +8,99 @@ #include #include -#include +#include +#include -#define MAX_PATH_NUM 8 - -#define DEV_DEC_MINOR 0x25 +/* + * DEC IOCTL command base identifier + * Uses 's' (0x73) as the magic number for DEC subsystem IOCTL commands + */ #define DEC_IOCTL_BASE 's' -#define SET_POLICY_ID 1 -#define DEL_POLICY_ID 2 -#define QUERY_POLICY_ID 3 -#define CHECK_POLICY_ID 4 -#define DESTROY_POLICY_ID 5 -#define CONSTRAINT_POLICY_ID 6 -#define DENY_POLICY_ID 7 + +/* DEC IOCTL command identifiers (subcodes) */ +#define SET_RULE_ID 1 /* Add new DEC access rule */ +#define DEL_RULE_ID 2 /* Delete specific DEC rule */ +#define QUERY_RULE_ID 3 /* Query persistent DEC rules */ +#define CHECK_RULE_ID 4 /* Check temporary DEC rules */ +#define DESTROY_RULE_ID 5 /* Destroy all rules for a token ID */ +#define CONSTRAINT_RULE_ID 6 /* Add path to constraint tree */ +#define DEL_BY_USER_RULE_ID 7 /* Delete rules by user ID */ +#define SET_PREFIX_ID 8 /* Set constraint prefix path */ + +#define MAX_POLICY_NUM 8 +#define DEC_POLICY_HEADER_RESERVED 64 + +enum { + FLAG_FALSE = 0, + FLAG_TRUE = 1, +}; struct path_info { - char* path; - uint32_t path_len; - uint32_t mode; - bool ret_flag; + __u64 path; + __u32 path_len; + __u32 mode; + __u8 ret_flag; }; -struct dec_policy_info { - uint64_t tokenid; - struct path_info path[MAX_PATH_NUM]; - uint32_t path_num; - bool persist_flag; +struct dec_rule_s { + __u64 tokenid; + __u64 timestamp; + struct path_info path[MAX_POLICY_NUM]; + __u32 path_num; + __s32 user_id; + __u64 reserved[DEC_POLICY_HEADER_RESERVED]; + __u8 persist_flag; }; -#define SET_DEC_POLICY_CMD \ - _IOWR(DEC_IOCTL_BASE, SET_POLICY_ID, struct dec_policy_info) -#define DEL_DEC_POLICY_CMD \ - _IOWR(DEC_IOCTL_BASE, DEL_POLICY_ID, struct dec_policy_info) -#define QUERY_DEC_POLICY_CMD \ - _IOWR(DEC_IOCTL_BASE, QUERY_POLICY_ID, struct dec_policy_info) -#define CHECK_DEC_POLICY_CMD \ - _IOWR(DEC_IOCTL_BASE, CHECK_POLICY_ID, struct dec_policy_info) -#define CONSTRAINT_DEC_POLICY_CMD \ - _IOW(DEC_IOCTL_BASE, CONSTRAINT_POLICY_ID, struct dec_policy_info) -#define DENY_DEC_POLICY_CMD \ - _IOWR(DEC_IOCTL_BASE, DENY_POLICY_ID, struct dec_policy_info) -#define DESTROY_DEC_POLICY_CMD \ - _IOW(DEC_IOCTL_BASE, DESTROY_POLICY_ID, uint64_t) +struct path_info_32 { + __u32 path; + __u32 path_len; + __u32 mode; + __u8 ret_flag; +}; + +struct dec_rule_s_32 { + __u64 tokenid; + __u64 timestamp; + struct path_info_32 path[MAX_POLICY_NUM]; + __u32 path_num; + __s32 user_id; + __u64 reserved[DEC_POLICY_HEADER_RESERVED]; + __u8 persist_flag; +}; + +#define SET_DEC_RULE_CMD \ + _IOWR(DEC_IOCTL_BASE, SET_RULE_ID, struct dec_rule_s) +#define DEL_DEC_RULE_CMD \ + _IOWR(DEC_IOCTL_BASE, DEL_RULE_ID, struct dec_rule_s) +#define QUERY_DEC_RULE_CMD \ + _IOWR(DEC_IOCTL_BASE, QUERY_RULE_ID, struct dec_rule_s) +#define CHECK_DEC_RULE_CMD \ + _IOWR(DEC_IOCTL_BASE, CHECK_RULE_ID, struct dec_rule_s) +#define DESTROY_DEC_RULE_CMD \ + _IOWR(DEC_IOCTL_BASE, DESTROY_RULE_ID, struct dec_rule_s) +#define CONSTRAINT_DEC_RULE_CMD \ + _IOW(DEC_IOCTL_BASE, CONSTRAINT_RULE_ID, struct dec_rule_s) +#define DEL_DEC_RULE_BY_USER_CMD \ + _IOWR(DEC_IOCTL_BASE, DEL_BY_USER_RULE_ID, struct dec_rule_s) +#define SET_DEC_PREFIX_CMD \ + _IOWR(DEC_IOCTL_BASE, SET_PREFIX_ID, struct dec_rule_s) + +#define SET_DEC_RULE_CMD_32 \ + _IOWR(DEC_IOCTL_BASE, SET_RULE_ID, struct dec_rule_s_32) +#define DEL_DEC_RULE_CMD_32 \ + _IOWR(DEC_IOCTL_BASE, DEL_RULE_ID, struct dec_rule_s_32) +#define QUERY_DEC_RULE_CMD_32 \ + _IOWR(DEC_IOCTL_BASE, QUERY_RULE_ID, struct dec_rule_s_32) +#define CHECK_DEC_RULE_CMD_32 \ + _IOWR(DEC_IOCTL_BASE, CHECK_RULE_ID, struct dec_rule_s_32) +#define DESTROY_DEC_RULE_CMD_32 \ + _IOWR(DEC_IOCTL_BASE, DESTROY_RULE_ID, struct dec_rule_s_32) +#define CONSTRAINT_DEC_RULE_CMD_32 \ + _IOW(DEC_IOCTL_BASE, CONSTRAINT_RULE_ID, struct dec_rule_s_32) +#define DEL_DEC_RULE_BY_USER_CMD_32 \ + _IOWR(DEC_IOCTL_BASE, DEL_BY_USER_RULE_ID, struct dec_rule_s_32) +#define SET_DEC_PREFIX_CMD_32 \ + _IOWR(DEC_IOCTL_BASE, SET_PREFIX_ID, struct dec_rule_s_32) #endif /* _DEC_MISC_H */ \ No newline at end of file diff --git a/dec/dec_path_tree.c b/dec/dec_path_tree.c new file mode 100644 index 0000000..323c682 --- /dev/null +++ b/dec/dec_path_tree.c @@ -0,0 +1,567 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2026 Huawei Device Co., Ltd. + */ + +#include +#include +#include +#include +#include + +#include "dec_path_tree.h" +#include "dec_common.h" +#include "dec_utils.h" +#include "dec_log.h" + +/* Prefix tokenid greater then tokenid mask */ +#define PREFIX_TOKENID (1ULL << 32) +#define PREFIX_USERID (1 << 30) + +enum query_state { + DEC_INIT, + DEC_ALLOWED, + DEC_NOT_ALLOWED, +}; + +static struct trie_node *path_tree = NULL; +static struct rw_semaphore *dec_path_tree_rwsem = NULL; + +static int __init dec_path_tree_init(void) +{ + dec_path_tree_rwsem = kmalloc(sizeof(struct rw_semaphore), GFP_KERNEL); + if (!dec_path_tree_rwsem) { + dec_loge("Failed to allocate rwsem for path tree"); + return -ENOMEM; + } + init_rwsem(dec_path_tree_rwsem); + + path_tree = trie_node_create("/"); + if (!path_tree) { + dec_loge("Failed to create root node for path tree"); + kfree(dec_path_tree_rwsem); + dec_path_tree_rwsem = NULL; + return -ENOMEM; + } + dec_logd("Path tree initialized successfully"); + return 0; +} + +static struct list_head *create_path_stack(void) +{ + struct list_head *stack = kmalloc(sizeof(struct list_head), GFP_KERNEL); + if (!stack) { + dec_loge("Failed to allocate memory for path stack"); + return NULL; + } + + INIT_LIST_HEAD(stack); + return stack; +} + +static int add_to_path_stack(struct list_head *stack, struct trie_node *node, + struct trie_node *parent, const char *child_key) +{ + struct trie_stack_item *item = kmalloc(sizeof(struct trie_stack_item), GFP_KERNEL); + if (!item) + return -ENOMEM; + + item->node = node; + item->parent = parent; + item->child_key = child_key ? kstrdup(child_key, GFP_KERNEL) : NULL; + item->visited_children = false; + INIT_LIST_HEAD(&item->list); + + list_add(&item->list, stack); + return 0; +} + +static void destroy_path_stack(struct list_head *stack) +{ + if (stack) { + struct trie_stack_item *item, *tmp; + list_for_each_entry_safe(item, tmp, stack, list) { + list_del(&item->list); + if (item->child_key) + kfree(item->child_key); + kfree(item); + } + kfree(stack); + } +} + +static struct permission *find_permission(struct trie_node *node, uint64_t tokenid) +{ + struct rb_node *rb_node = node->permissions.rb_node; + + while (rb_node) { + struct permission *perm = container_of(rb_node, struct permission, rb_node); + if (tokenid < perm->tokenid) { + rb_node = rb_node->rb_left; + } else if (tokenid > perm->tokenid) { + rb_node = rb_node->rb_right; + } else { + return perm; + } + } + + return NULL; +} + +int insert_permission(struct trie_node *node, struct permission *new_perm) +{ + if (!node || !new_perm) { + dec_loge("Invalid input to insert_permission (node=%p, new_perm=%p)", + node, new_perm); + return -EINVAL; + } + + struct rb_node **new_rb_node = &(node->permissions.rb_node); + struct rb_node *parent_rb_node = NULL; + struct permission *exist_perm = NULL; + + while (*new_rb_node) { + parent_rb_node = *new_rb_node; + exist_perm = container_of(parent_rb_node, struct permission, rb_node); + + if (new_perm->tokenid < exist_perm->tokenid) { + new_rb_node = &((*new_rb_node)->rb_left); + } else if (new_perm->tokenid > exist_perm->tokenid) { + new_rb_node = &((*new_rb_node)->rb_right); + } else { + dec_loge("Token ID 0x%llx already exists in node - insert failed", + (unsigned long long)new_perm->tokenid); + return -EEXIST; + } + } + + rb_link_node(&new_perm->rb_node, parent_rb_node, new_rb_node); + rb_insert_color(&new_perm->rb_node, &node->permissions); + + dec_logi("Inserted permission for token ID 0x%llx into node", + (unsigned long long)new_perm->tokenid); + return 0; +} + +static bool should_delete_permission(struct permission *perm, struct dec_destroy_ctx *ctx) +{ + if (!perm) { + dec_loge(" NULL permission passed to should_delete_permission"); + return false; + } + bool id_match = false; + bool timestamp_match = (ctx->timestamp == 0) || (ctx->timestamp >= perm->timestamp); + switch (ctx->criteria) { + case DELETE_BY_TOKENID: + id_match = perm->tokenid == ctx->params.tokeninfo.tokenid; + return id_match && timestamp_match; + case DELETE_BY_USERID: + id_match = perm->userid == ctx->params.userinfo.userid; + return id_match && timestamp_match; + default: + dec_loge("Invalid delete criteria: %d", ctx->criteria); + return false; + } +} + +static struct trie_node *get_node_by_path(const char *path) +{ + struct list_head comp_list; + struct trie_node *node = path_tree; + struct path_component *comp = NULL; + + if (is_path_valid(path) != 0) { + dec_loge("Invalid path '%s' in get_node_by_path", path); + return NULL; + } + + INIT_LIST_HEAD(&comp_list); + if (split_path_to_component_list(path, &comp_list) < 0) + goto cleanup; + + list_for_each_entry(comp, &comp_list, list) { + node = find_child(node, comp->name); + if (node == NULL) + break; + } + +cleanup: + free_component_list(&comp_list); + return node; +} + +static enum query_state update_state(struct permission *perm, uint32_t mode, bool is_persist, enum query_state state) +{ + enum query_state current_status = state; + /* If mode doesn't match, deny access (if in initial state) */ + if ((perm->mode & mode) != mode) { + if (current_status == DEC_INIT) { + return DEC_NOT_ALLOWED; + } + return current_status; + } + + /* Temporary rule check (inherits parent directory permissions) */ + if (!is_persist) { + if (current_status == DEC_INIT) { + return DEC_ALLOWED; + } + return current_status; + } + + /* Persistent rule check (explicit permission required) */ + if (perm->persist_flag) { + return DEC_ALLOWED; + } + + return current_status; +} + +bool dec_path_tree_query(uint64_t tokenid, const char *path, uint32_t mode, bool is_persist) +{ + struct list_head comp_list; + struct trie_node *node = path_tree; + struct path_component *comp = NULL; + + INIT_LIST_HEAD(&comp_list); + if (split_path_to_component_list(path, &comp_list) < 0) { + dec_loge("Failed to split path '%s' into components for query", path); + free_component_list(&comp_list); + return false; + } + + down_read(dec_path_tree_rwsem); + enum query_state state = DEC_INIT; + /* Check root node ("/") first */ + struct permission *root_prefix_perm = find_permission(node, PREFIX_TOKENID); + if (root_prefix_perm != NULL) { + if ((root_prefix_perm->mode & DEC_PREFIX) != 0) { + state = DEC_INIT; + } + } + struct permission *root_perm = find_permission(node, tokenid); + if (root_perm != NULL) { + state = update_state(root_perm, mode, is_persist, state); + } + + /* Traverse path components to check child nodes */ + list_for_each_entry(comp, &comp_list, list) { + node = find_child(node, comp->name); + if (!node) { + dec_logd("Path component '%s' not found in tree - stopping traversal", comp->name); + break; + } + + /* Check global prefix constraint for this node */ + struct permission *prefix_perm = find_permission(node, PREFIX_TOKENID); + if (prefix_perm != NULL) { + if ((prefix_perm->mode & DEC_PREFIX) != 0) { + state = DEC_INIT; + } + } + + /* Check token-specific permission for this node */ + struct permission *perm = find_permission(node, tokenid); + if (perm != NULL) { + state = update_state(perm, mode, is_persist, state); + } + } + + up_read(dec_path_tree_rwsem); + free_component_list(&comp_list); + + return (state == DEC_ALLOWED); +} + +int dec_set_rule(struct path_tree_params *params) +{ + struct list_head comp_list; + struct trie_node *node = path_tree; + struct permission *perm = NULL; + int ret = 0; + ret = is_path_valid(params->path); + if (ret != 0) { + return ret; + } + dec_logi("Setting rule - token=0x%llx, path='%s', mode=0x%x, timestamp=%llu", + params->tokenid, params->path, params->mode, params->timestamp); + + INIT_LIST_HEAD(&comp_list); + ret = split_path_to_component_list(params->path, &comp_list); + if (ret < 0) { + free_component_list(&comp_list); + return ret; + } + + down_write(dec_path_tree_rwsem); + struct path_component *comp; + list_for_each_entry(comp, &comp_list, list) { + struct trie_node *child = find_child(node, comp->name); + if (!child) { + child = insert_child(node, comp->name); + if (!child) { + dec_loge("Failed to create child node for '%s'", comp->name); + ret = -ENOMEM; + goto cleanup; + } + } + + node = child; + } + + struct permission *existing_perm = find_permission(node, params->tokenid); + if (existing_perm != NULL) { + existing_perm->mode |= params->mode; + existing_perm->userid = params->userid; + existing_perm->persist_flag = params->persist_flag; + existing_perm->timestamp = params->timestamp; + dec_logi("Updated existing permission - token=0x%llx, mode=0x%x", + existing_perm->tokenid, existing_perm->mode); + goto cleanup; + } + + perm = kmalloc(sizeof(struct permission), GFP_KERNEL); + if (!perm) { + dec_loge("Failed to allocate memory for new permission"); + ret = -ENOMEM; + goto cleanup; + } + perm->tokenid = params->tokenid; + perm->mode = params->mode; + perm->userid = params->userid; + perm->persist_flag = params->persist_flag; + perm->timestamp = params->timestamp; + ret = insert_permission(node, perm); + if (ret) { + dec_loge("Failed to insert permission for token 0x%llx (err=%d)", + params->tokenid, ret); + kfree(perm); + goto cleanup; + } + node->has_permissions = true; + dec_logi("Added new permission: tokenid=%llu, mode=%u", perm->tokenid, perm->mode); + +cleanup: + up_write(dec_path_tree_rwsem); + free_component_list(&comp_list); + return ret; +} + +int dec_delete_rule(uint64_t tokenid, const char *path, uint64_t timestamp) +{ + dec_logi("Deleting rule - token=0x%llx, path='%s', timestamp=%llu", + tokenid, path, timestamp); + struct list_head comp_list; + struct trie_node *node = path_tree; + struct list_head *path_stack; + int ret = 0; + ret = is_path_valid(path); + if (ret != 0) { + return ret; + } + + INIT_LIST_HEAD(&comp_list); + + ret = split_path_to_component_list(path, &comp_list); + if (ret < 0) { + free_component_list(&comp_list); + return ret; + } + path_stack = create_path_stack(); + if (!path_stack) { + dec_loge("Failed to create path stack for delete operation"); + free_component_list(&comp_list); + return -ENOMEM; + } + + down_write(dec_path_tree_rwsem); + struct path_component *comp; + list_for_each_entry(comp, &comp_list, list) { + struct trie_node *child = find_child(node, comp->name); + if (!child) { + dec_loge("Path component '%s' not found in tree", comp->name); + ret = -ENOENT; + goto cleanup; + } + if (add_to_path_stack(path_stack, child, node, comp->name) < 0) { + dec_loge("Failed to add '%s' to path stack", comp->name); + ret = -ENOMEM; + goto cleanup; + } + node = child; + } + + struct permission *perm = find_permission(node, tokenid); + if (perm == NULL) { + dec_loge("Permission for token 0x%llx not found at path '%s'", + tokenid, path); + ret = -ENOENT; + goto cleanup; + } + if (timestamp != 0 && timestamp < perm->timestamp) { + dec_loge("DEC: Timestamp %llu not newer than permission timestamp %llu", + timestamp, perm->timestamp); + ret = -ENOENT; + goto cleanup; + } + rb_erase(&perm->rb_node, &node->permissions); + kfree(perm); + + /* Clean up empty nodes (traverse stack in reverse) */ + node->has_permissions = !RB_EMPTY_ROOT(&node->permissions); + struct trie_stack_item *item, *tmp; + list_for_each_entry_safe_reverse(item, tmp, path_stack, list) { + if (!item->node->has_permissions && RB_EMPTY_ROOT(&item->node->children) && item->parent) { + /* Remove empty node from parent */ + rb_erase(&item->node->rb_node, &item->parent->children); + trie_node_destroy(item->node); + item->node = NULL; + } else { + break; /* Stop at first non-empty node */ + } + } + +cleanup: + up_write(dec_path_tree_rwsem); + free_component_list(&comp_list); + destroy_path_stack(path_stack); + return ret; +} + +int dec_destroy_rule_by_id(struct dec_destroy_ctx *ctx) +{ + enum delete_criteria criteria = ctx->criteria; + if (criteria != DELETE_BY_TOKENID && criteria != DELETE_BY_USERID) { + dec_loge("Invalid deletion criteria %d (must be token or user ID)", criteria); + return -EINVAL; + } + + struct list_head stack_list; + INIT_LIST_HEAD(&stack_list); + struct trie_stack_item *first_item = kmalloc(sizeof(struct trie_stack_item), GFP_KERNEL); + if (!first_item) { + dec_loge("Failed to allocate initial stack item for bulk delete"); + return -ENOMEM; + } + + down_write(dec_path_tree_rwsem); + first_item->node = path_tree; + first_item->parent = NULL; + first_item->child_key = NULL; + first_item->visited_children = false; + + if (criteria == DELETE_BY_USERID) { + struct trie_node *target_node = get_node_by_path(ctx->params.userinfo.path); + if (!target_node) { + dec_loge("Path '%s' not found for user ID deletion", ctx->params.userinfo.path); + up_write(dec_path_tree_rwsem); + kfree(first_item); + return -ENOENT; + } + first_item->node = target_node; + } + + INIT_LIST_HEAD(&first_item->list); + list_add(&first_item->list, &stack_list); + + /* Depth-first traversal of trie */ + while (!list_empty(&stack_list)) { + struct trie_stack_item *item = list_first_entry(&stack_list, struct trie_stack_item, list); + struct trie_node *curr_node = item->node; + + if (!item->visited_children) { + /* First visit: process permissions and queue children */ + item->visited_children = true; + + /* Delete matching permissions in current node */ + struct rb_node *rb_iter = rb_first(&curr_node->permissions); + while (rb_iter) { + struct permission *perm = container_of(rb_iter, struct permission, rb_node); + struct rb_node *next_rb = rb_next(rb_iter); + + if (should_delete_permission(perm, ctx)) { + rb_erase(&perm->rb_node, &curr_node->permissions); + dec_logi("Deleted permission - token=0x%llx, user=%d, node='%s'", + (unsigned long long)perm->tokenid, perm->userid, + curr_node->component ?: "root"); + kfree(perm); + } + + rb_iter = next_rb; + } + + /* Update node permission status */ + curr_node->has_permissions = !RB_EMPTY_ROOT(&curr_node->permissions); + + /* Queue child nodes for traversal (post-order) */ + struct rb_node *child_rb = rb_first(&curr_node->children); + while (child_rb) { + struct trie_node *child_node = container_of(child_rb, struct trie_node, rb_node); + struct trie_stack_item *child_item = kmalloc(sizeof(struct trie_stack_item), GFP_KERNEL); + if (!child_item) { + dec_loge("Failed to allocate child stack item during bulk delete"); + break; + } + + child_item->node = child_node; + child_item->parent = curr_node; + child_item->child_key = child_node->component ? kstrdup(child_node->component, GFP_KERNEL) : NULL; + child_item->visited_children = false; + INIT_LIST_HEAD(&child_item->list); + + /* Insert after current item (post-order traversal) */ + list_add(&child_item->list, &stack_list); + + child_rb = rb_next(child_rb); + } + } else { + /* Second visit: clean up empty nodes */ + if (!item->node->has_permissions && + RB_EMPTY_ROOT(&item->node->children) && + item->parent != NULL) + { + rb_erase(&item->node->rb_node, &item->parent->children); + dec_logd("Removed empty node: %s", item->node->component ?: "root"); + if (item->node->component) + kfree(item->node->component); + kfree(item->node); + item->node = NULL; + } + + list_del(&item->list); + if (item->child_key) + kfree(item->child_key); + kfree(item); + } + } + + up_write(dec_path_tree_rwsem); + + struct trie_stack_item *item, *tmp; + list_for_each_entry_safe(item, tmp, &stack_list, list) { + list_del(&item->list); + if (item->child_key) + kfree(item->child_key); + kfree(item); + } + + return 0; +} + +int dec_set_prefix(const char *prefix) +{ + int ret = 0; + + /* Initialize parameters for global prefix rule */ + struct path_tree_params param = {0}; + param.path = (char *)prefix; + param.tokenid = PREFIX_TOKENID; + param.userid = PREFIX_USERID; + param.mode = DEC_PREFIX; + ret = dec_set_rule(¶m); + + return ret; +} + +/* Register initialization function for filesystem init phase */ +fs_initcall(dec_path_tree_init); \ No newline at end of file diff --git a/dec/dec_path_tree.h b/dec/dec_path_tree.h new file mode 100644 index 0000000..87b3229 --- /dev/null +++ b/dec/dec_path_tree.h @@ -0,0 +1,40 @@ +/* +// SPDX-License-Identifier: GPL-2.0-or-later + * Copyright (c) 2026 Huawei Device Co., Ltd. + */ + +#ifndef _DEC_PATH_TREE_H +#define _DEC_PATH_TREE_H + +#include + +#include "dec_common.h" + +enum delete_criteria { + DELETE_BY_TOKENID = 1, + DELETE_BY_USERID, +}; + +union dec_dectroy_params { + struct { + uint64_t tokenid; + } tokeninfo; + struct { + int userid; + const char *path; + } userinfo; +}; + +struct dec_destroy_ctx { + enum delete_criteria criteria; + union dec_dectroy_params params; + uint64_t timestamp; +}; + +bool dec_path_tree_query(uint64_t tokenid, const char *path, uint32_t mode, bool is_persist); +int dec_set_rule(struct path_tree_params *params); +int dec_delete_rule(uint64_t tokenid, const char *path, uint64_t timestamp); +int dec_destroy_rule_by_id(struct dec_destroy_ctx *ctx); + +int dec_set_prefix(const char *prefix); +#endif /* _DEC_PATH_TREE_H */ \ No newline at end of file diff --git a/dec/dec_security_hook.c b/dec/dec_security_hook.c new file mode 100644 index 0000000..6576bf1 --- /dev/null +++ b/dec/dec_security_hook.c @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2026 Huawei Device Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dec_security_hook.h" +#include "dec_common.h" +#include "dec_kernel_interface.h" +#include "dec_log.h" + +#define SHAREFS_SUPER_MAGIC 0x20230212 + +static uint64_t get_tokenid(void) +{ + struct task_struct *curr = current; + return curr->token; +} + +static bool is_sharefs_magic(uint32_t fs_magic) +{ + return fs_magic == SHAREFS_SUPER_MAGIC; +} + +static uint32_t dec_acc_permission_change(uint32_t may_mask) +{ + uint32_t dec_flags = 0; + + if (may_mask & MAY_READ) { + dec_flags |= DEC_READ; + } + + if ((may_mask & MAY_WRITE) || (may_mask & MAY_APPEND)) { + dec_flags |= DEC_WRITE; + } + + return dec_flags; +} + +static uint32_t dec_acc_open_change(uint32_t file_flags) +{ + uint32_t dec_flags = 0; + uint32_t acc_mode = file_flags & O_ACCMODE; + + if (acc_mode == O_RDONLY || acc_mode == O_RDWR) { + dec_flags |= DEC_READ; + } + + if (acc_mode == O_WRONLY || acc_mode == O_RDWR) { + dec_flags |= DEC_WRITE; + } + + if (file_flags & (O_TRUNC | O_APPEND)) { + dec_flags |= DEC_WRITE; + } + + return dec_flags; +} + +static char *dec_get_path_buf(void) +{ + return kmalloc(PATH_MAX, GFP_KERNEL); +} + +static void dec_free_path_buf(char *buf) +{ + if (buf) { + kfree(buf); + } +} + +static int dec_generic_path_check(const struct path *dir, uint32_t dec_mode, const char *check_name) +{ + char *path_buf = NULL; + const char *full_path = NULL; + int ret = 0; + uint64_t tokenid = 0; + + if (!dir || !dir->dentry || !dir->dentry->d_inode) { + dec_loge("%s: invalid param", check_name); + return -EINVAL; + } + uint32_t fs_magic = dir->dentry->d_inode->i_sb->s_magic; + if (!is_sharefs_magic(fs_magic)) { + return 0; + } + + path_buf = dec_get_path_buf(); + if (!path_buf) { + dec_loge("%s: path_buf malloc failed", check_name); + return -ENOMEM; + } + + full_path = d_path(dir, path_buf, PATH_MAX); + if (IS_ERR(full_path)) { + dec_loge("%s: get dir path failed, err=%ld", check_name, PTR_ERR(full_path)); + ret = PTR_ERR(full_path); + goto out; + } + + tokenid = get_tokenid(); + + struct path_tree_params params = {0}; + params.path = full_path; + params.tokenid = tokenid; + params.mode = dec_mode; + params.userid = 0; + params.persist_flag = false; + params.timestamp = 0; + ret = dec_rule_query(¶ms); + +out: + dec_free_path_buf(path_buf); + return ret; +} + +static int dec_check_file_common(struct file *file, uint32_t dec_mode, const char *check_name) +{ + if (!file || !file->f_inode) { + dec_loge("%s: invalid param", check_name); + return -EINVAL; + } + + return dec_generic_path_check(&file->f_path, dec_mode, check_name); +} + +static int dec_check_permission(struct file *file, int may_mask) +{ + if (may_mask <= 0) { + return 0; + } + uint32_t dec_mode = dec_acc_permission_change(may_mask); + return dec_check_file_common(file, dec_mode, __func__); +} + +static int dec_check_open(struct file *file) +{ + if (!file) { + dec_loge("%s: invalid param", __func__); + return -EINVAL; + } + uint32_t dec_mode = dec_acc_open_change(file->f_flags); + return dec_check_file_common(file, dec_mode, __func__); +} + +static int dec_path_mkdir(const struct path *dir, struct dentry *dentry, umode_t mode) +{ + (void)dentry; + (void)mode; + return dec_generic_path_check(dir, DEC_WRITE, __func__); +} + +static int dec_path_rmdir(const struct path *dir, struct dentry *dentry) +{ + (void)dentry; + return dec_generic_path_check(dir, DEC_WRITE, __func__); +} + +static int dec_path_unlink(const struct path *dir, struct dentry *dentry) +{ + (void)dentry; + return dec_generic_path_check(dir, DEC_WRITE, __func__); +} + +static int dec_path_mknod(const struct path *dir, struct dentry *dentry, umode_t mode, dev_t dev) +{ + (void)dev; + /* Only check DEC permissions for standard file types */ + switch (mode & S_IFMT) { + case S_IFREG: + case S_IFDIR: + case S_IFLNK: + break; + default: + return 0; + } + return dec_generic_path_check(dir, DEC_WRITE, __func__); +} + +static int dec_path_rename(const struct path *old_dir, struct dentry *old_dentry, + const struct path *new_dir, struct dentry *new_dentry, + unsigned int flags) +{ + (void)old_dentry; + (void)new_dentry; + (void)flags; + int ret = 0; + + ret = dec_generic_path_check(old_dir, DEC_WRITE, __func__); + if (ret != 0) { + return ret; + } + + ret = dec_generic_path_check(new_dir, DEC_WRITE, __func__); + return ret; +} + +static int dec_path_access(const struct path *path, int mode) +{ + + uint32_t dec_mode = dec_acc_permission_change(mode); + if (dec_mode == DEC_NONE) { + return 0; + } + return dec_generic_path_check(path, dec_mode, __func__); +} + +static struct security_hook_list dec_hooks[] __ro_after_init = { + LSM_HOOK_INIT(file_permission, dec_check_permission), + LSM_HOOK_INIT(file_open, dec_check_open), + LSM_HOOK_INIT(path_mknod, dec_path_mknod), + LSM_HOOK_INIT(path_mkdir, dec_path_mkdir), + LSM_HOOK_INIT(path_rmdir, dec_path_rmdir), + LSM_HOOK_INIT(path_rename, dec_path_rename), + LSM_HOOK_INIT(path_unlink, dec_path_unlink), + LSM_HOOK_INIT(path_access, dec_path_access), +}; + +int dec_hook_init(void) +{ + dec_logi("dec security hooks init"); + security_add_hooks(dec_hooks, ARRAY_SIZE(dec_hooks), "dec_lsm"); + return 0; +} + +void dec_hook_exit(void) +{ + dec_logi("dec security hooks exited"); +} \ No newline at end of file diff --git a/dec/dec_security_hook.h b/dec/dec_security_hook.h new file mode 100644 index 0000000..cfeb31a --- /dev/null +++ b/dec/dec_security_hook.h @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2026 Huawei Device Co., Ltd. + */ + +#ifndef _DEC_SECURITY_HOOK_H +#define _DEC_SECURITY_HOOK_H + +int dec_hook_init(void); +void dec_hook_exit(void); +#endif /* _DEC_SECURITY_HOOK_H */ \ No newline at end of file diff --git a/dec/dec_utils.c b/dec/dec_utils.c new file mode 100644 index 0000000..f766d0e --- /dev/null +++ b/dec/dec_utils.c @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2026 Huawei Device Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include + +#include "dec_utils.h" +#include "dec_log.h" + +int is_path_valid(const char *path) +{ + const char *start; + const char *end; + size_t path_len; + bool prev_is_slash = false; + + if (!path) { + dec_loge("path is NULL"); + return -EINVAL; + } + + path_len = strlen(path); + if (path_len == 0 || path_len > PATH_MAX) { + dec_loge("invalid path length: %zu (must be 1~%d)", path_len, PATH_MAX); + return -EINVAL; + } + + /* Must be an absolute path */ + if (*path != '/') { + dec_loge("path '%s' is not absolute (must start with '/')", path); + return -EINVAL; + } + + /* Path with only '/' */ + if (path_len == 1) { + return 0; + } + + start = path; + + /* Validate each path component */ + while (*start) { + if (*start == '/') { + /* Reject consecutive '/' */ + if (prev_is_slash) { + dec_loge("path '%s' contains consecutive '/'", path); + return -EINVAL; + } + prev_is_slash = true; + start++; + continue; + } + prev_is_slash = false; + + size_t comp_len; + end = strchr(start, '/'); + if (!end) { + comp_len = strlen(start); + } else { + comp_len = end - start; + } + + /* Check component length validity */ + if (comp_len == 0 || comp_len >= NAME_MAX) { + dec_loge("invalid component length %zu in path '%s'", comp_len, path); + return -EINVAL; + } + + /* Reject single '.' component */ + if (comp_len == 1 && *start == '.') { + dec_loge("path '%s' contains invalid component '.'", path); + return -EINVAL; + } + + /* Reject '..' component */ + if (comp_len == 2 && *start == '.' && *(start + 1) == '.') { + dec_loge("path '%s' contains invalid component '..'", path); + return -EINVAL; + } + + if (!end) + break; + + start = end; + } + + return 0; +} + + +struct trie_node *trie_node_create(const char *component) +{ + if (!component) { + return NULL; + } + + struct trie_node *node = kzalloc(sizeof(struct trie_node), GFP_KERNEL); + if (!node) { + return NULL; + } + node->component = kstrdup(component, GFP_KERNEL); + if (!node->component) { + kfree(node); + return NULL; + } + node->permissions = RB_ROOT; + node->children = RB_ROOT; + node->has_permissions = false; + + return node; +} + +struct trie_node *find_child(struct trie_node *node, const char *component) +{ + if (node == NULL || component == NULL || strlen(component) == 0) { + return NULL; + } + + struct rb_node *rb_node = node->children.rb_node; + struct trie_node *child = NULL; + while (rb_node) { + child = container_of(rb_node, struct trie_node, rb_node); + if (child->component == NULL) { + return NULL; + } + int cmp = strcmp(component, child->component); + + if (cmp < 0) + rb_node = rb_node->rb_left; + else if (cmp > 0) + rb_node = rb_node->rb_right; + else + return child; + } + + return NULL; +} + +struct trie_node *insert_child(struct trie_node *parent, const char *component) +{ + if (parent == NULL || component == NULL) { + return NULL; + } + + struct rb_node **new_node = &(parent->children.rb_node); + struct rb_node *parent_rb = NULL; + struct trie_node *child = NULL; + int cmp = 0; + + while (*new_node) { + parent_rb = *new_node; + child = container_of(parent_rb, struct trie_node, rb_node); + if (child->component == NULL) { + new_node = &((*new_node)->rb_right); + continue; + } + + cmp = strcmp(component, child->component); + if (cmp < 0) { + new_node = &((*new_node)->rb_left); + } else if (cmp > 0) { + new_node = &((*new_node)->rb_right); + } else { + return NULL; /* Node already exists, insertion failed */ + } + } + + struct trie_node *new_child = trie_node_create(component); + if (new_child == NULL) { + dec_loge("failed to create trie node"); + return NULL; + } + + rb_link_node(&new_child->rb_node, parent_rb, new_node); + rb_insert_color(&new_child->rb_node, &parent->children); + + return new_child; +} + + void trie_node_destroy(struct trie_node *node) +{ + struct rb_node *rb_node; + struct permission *perm; + struct trie_node *child; + + if (node == NULL) + return; + + while ((rb_node = rb_first(&node->permissions))) { + perm = container_of(rb_node, struct permission, rb_node); + rb_erase(rb_node, &node->permissions); + kfree(perm); + } + + while ((rb_node = rb_first(&node->children))) { + child = container_of(rb_node, struct trie_node, rb_node); + rb_erase(rb_node, &node->children); + trie_node_destroy(child); + } + + if (node->component) + kfree(node->component); + + kfree(node); +} + + +int split_path_to_component_list(const char *path, struct list_head *comp_list) +{ + const char *start; + const char *end; + int ret = 0; + + if (comp_list == NULL) { + dec_loge("split_path_to_component_list: comp_list is NULL"); + return -EINVAL; + } + + start = path; + while (*start == '/') + start++; + + while (*start) { + size_t len; + end = strchr(start, '/'); + if (end == NULL) { + len = strlen(start); + } else if (end > start) { + len = end - start; + } else { + start++; + continue; + } + + struct path_component *comp = kmalloc(sizeof(*comp) + len + 1, GFP_KERNEL); + if (!comp) { + dec_loge("failed to allocate path_component"); + free_component_list(comp_list); + return -ENOMEM; + } + + memcpy(comp->name, start, len); + comp->name[len] = '\0'; + INIT_LIST_HEAD(&comp->list); + list_add_tail(&comp->list, comp_list); + + if (end == NULL) + break; + + start = end + 1; + while (*start == '/') + start++; + } + + return 0; +} + +void free_component_list(struct list_head *comp_list) +{ + struct path_component *comp, *tmp; + list_for_each_entry_safe(comp, tmp, comp_list, list) { + list_del(&comp->list); + kfree(comp); + } +} \ No newline at end of file diff --git a/dec/dec_utils.h b/dec/dec_utils.h new file mode 100644 index 0000000..eb1d4e9 --- /dev/null +++ b/dec/dec_utils.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2026 Huawei Device Co., Ltd. + */ + +#ifndef _DEC_UTILS_H +#define _DEC_UTILS_H + +#include "dec_common.h" + +int is_path_valid(const char *path); +struct trie_node *trie_node_create(const char *component); +struct trie_node *find_child(struct trie_node *node, const char *component); +struct trie_node *insert_child(struct trie_node *parent, const char *component); +void trie_node_destroy(struct trie_node *node); +int split_path_to_component_list(const char *path, struct list_head *comp_list); +void free_component_list(struct list_head *comp_list); + +#endif /* _DEC_UTILS_H */ \ No newline at end of file diff --git a/dec/sysctl.c b/dec/sysctl.c new file mode 100644 index 0000000..806c6e4 --- /dev/null +++ b/dec/sysctl.c @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2026 Huawei Device Co., Ltd. + */ + +#include + +#include "sysctl.h" +#include "dec_log.h" + +int dec_mode = 1; +#define dec_console_loglevel dec_mode + +static int dec_proc_dointvec(struct ctl_table *table, int write, + void __user *buffer, size_t *lenp, loff_t *ppos) +{ + dec_logi("dec_mode changed to %d", dec_console_loglevel); + return proc_dointvec(table, write, buffer, lenp, ppos); +} + +static struct ctl_table dec_sysctls[] = { + { + .procname = "dec_mode", + .data = &dec_console_loglevel, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = dec_proc_dointvec, + }, + {} +}; + +void __init dec_sysctl_init(void) +{ +#ifdef CONFIG_SECURITY_DEC_DEVELOP + register_sysctl_init("kernel", dec_sysctls); +#endif +} diff --git a/dec/sysctl.h b/dec/sysctl.h new file mode 100644 index 0000000..ec4961d --- /dev/null +++ b/dec/sysctl.h @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2026 Huawei Device Co., Ltd. + */ + +#ifndef _SYSCTL_H +#define _SYSCTL_H + +void __init dec_sysctl_init(void); + +#endif /* _SYSCTL_H */ \ No newline at end of file