mirror of
https://github.com/openharmony/kernel_linux_common_modules.git
synced 2026-08-27 01:31:36 -04:00
security:dec:Enable DEC(Dynamic Enhance Control) control policy
--------------------------------------- DEC(Dynamic Enhance Control) is a dynamic file access enhancement mechanism. It resolves DAC interception and sharefs security risks, precisely controlling file access via a kernel permission database, ioctl interfaces and LSM framework. DEC will bind the token ID of the application and the paths it can access. The user-level sandboxmanager module and appspawn module will configure the relevant policies. When the application accesses the control paths under the sharefs file system, DEC checks the: create, read, write, remove, rename, move, access of files by the application. Signed-off-by: staralien <chengxin@servicecenter-harmonytsc.com>
This commit is contained in:
+12
@@ -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
@@ -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
|
||||
|
||||
@@ -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 */
|
||||
@@ -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);
|
||||
@@ -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 */
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -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
@@ -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(¶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");
|
||||
+86
-34
@@ -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 */
|
||||
@@ -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(¶m);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Register initialization function for filesystem init phase */
|
||||
fs_initcall(dec_path_tree_init);
|
||||
@@ -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 */
|
||||
@@ -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(¶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");
|
||||
}
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 */
|
||||
Reference in New Issue
Block a user