// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2024 Huawei Device Co., Ltd. */ #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 const char *cmd_to_string(unsigned int cmd) { 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"; } } 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; } if (info.path_num > MAX_POLICY_NUM) { dec_loge("Invalid path count %u (max %d)", info.path_num, MAX_POLICY_NUM); return -EINVAL; } switch (cmd) { case SET_DEC_RULE_CMD: ioctl_set_rule(&info); break; 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: 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) { dec_logi("dec device opened"); return 0; } static int dec_release(struct inode *inode, struct file *filp) { 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, #ifdef CONFIG_COMPAT .compat_ioctl = dec_compat_ioctl, #endif }; static struct miscdevice dec_misc = { .minor = MISC_DYNAMIC_MINOR, .name = "dec", .fops = &dec_fops, }; static int __init dec_init(void) { int err = 0; dec_logi("Initializing DEC module"); dec_sysctl_init(); err = misc_register(&dec_misc); if (err) { dec_loge("Failed to register DEC misc device: %d", err); return err; } 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); dec_logi("DEC misc device deregistered"); } module_init(dec_init); module_exit(dec_exit); MODULE_LICENSE("GPL");