!181 merge master into weekly_20260126

security:dec:Enable DEC(Dynamic Enhance Control) control policy

Created-by: staralien
Commit-by: staralien
Merged-by: ohci1
Description: ### 一、内容说明(相关的Issue)

https://gitcode.com/openharmony/security_selinux_adapter/issues/4703

### 二、建议测试周期和提测地址  
  建议测试完成时间:xxxx.xx.xx  
  投产上线时间:xxxx.xx.xx  
  提测地址:CI环境/压测环境  
  测试账号:  

### 三、变更内容
  * 3.1 关联PR列表

  * 3.2 数据库和部署说明  
    1. 常规更新 
    2. 重启unicorn
    3. 重启sidekiq
    4. 迁移任务:是否有迁移任务,没有写 "无"
    5. rake脚本:`bundle exec xxx RAILS_ENV = production`;没有写 "无"

  * 3.4 其他技术优化内容(做了什么,变更了什么)
    - 重构了 xxxx 代码
    - xxxx 算法优化


  * 3.5 废弃通知(什么字段、方法弃用?)



  * 3.6  后向不兼容变更(是否有无法向后兼容的变更?)


  
### 四、研发自测点(自测哪些?冒烟用例全部自测?)
  自测测试结论:


### 五、测试关注点(需要提醒QA重点关注的、可能会忽略的地方)
  检查点:

| 需求名称 | 是否影响xx公共模块 | 是否需要xx功能 | 需求升级是否依赖其他子产品 |
|------|------------|----------|---------------|
| xxx  | 否          | 需要       | 不需要           |
|      |            |          |               |

  接口测试:

  性能测试:

  并发测试:

  其他:



See merge request: openharmony/kernel_linux_common_modules!181
This commit is contained in:
ohci1
2026-01-26 17:26:37 +08:00
18 changed files with 2134 additions and 86 deletions
+12
View File
@@ -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
+1 -1
View File
@@ -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
+100
View File
@@ -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 <linux/types.h>
#include <linux/rbtree.h>
#include <linux/spinlock.h>
#include <linux/list.h>
/* 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 */
+119
View File
@@ -0,0 +1,119 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) 2026 Huawei Device Co., Ltd.
*/
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/rwsem.h>
#include <linux/slab.h>
#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);
+13
View File
@@ -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 <linux/types.h>
bool dec_constraint_query(const char *path);
int dec_constraint_add(char *path);
#endif /* _DEC_CONSTRAINT_H */
+103
View File
@@ -0,0 +1,103 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) 2026 Huawei Device Co., Ltd.
*/
#include <linux/sched.h>
#include <linux/string.h>
#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;
}
+15
View File
@@ -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 <linux/types.h>
#include <linux/errno.h>
#include "dec_common.h"
int dec_rule_query(struct path_tree_params *params);
#endif /* _DEC_KERNEL_INTERFACE_H */
+19
View File
@@ -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 <linux/printk.h>
#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 */
+468 -51
View File
@@ -3,90 +3,502 @@
* Copyright (c) 2024 Huawei Device Co., Ltd.
*/
#include <linux/cdev.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/compat.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/limits.h>
#include <linux/string.h>
#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(&params);
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");
+86 -34
View File
@@ -8,47 +8,99 @@
#include <linux/ioctl.h>
#include <linux/types.h>
#include <stdbool.h>
#include <linux/rbtree.h>
#include <linux/spinlock.h>
#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 */
+567
View File
@@ -0,0 +1,567 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) 2026 Huawei Device Co., Ltd.
*/
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/rwsem.h>
#include <linux/errno.h>
#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(&param);
return ret;
}
/* Register initialization function for filesystem init phase */
fs_initcall(dec_path_tree_init);
+40
View File
@@ -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 <linux/types.h>
#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 */
+242
View File
@@ -0,0 +1,242 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) 2026 Huawei Device Co., Ltd.
*/
#include <linux/lsm_hooks.h>
#include <linux/security.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/namei.h>
#include <linux/cred.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/dcache.h>
#include <linux/path.h>
#include <linux/mount.h>
#include <linux/limits.h>
#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(&params);
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");
}
+11
View File
@@ -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 */
+271
View File
@@ -0,0 +1,271 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) 2026 Huawei Device Co., Ltd.
*/
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/string.h>
#include <linux/limits.h>
#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);
}
}
+19
View File
@@ -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 */
+37
View File
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) 2026 Huawei Device Co., Ltd.
*/
#include <linux/sysctl.h>
#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
}
+11
View File
@@ -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 */