dkapture-bpf/policy/frtp.bpf.c

764 lines
16 KiB
C
Raw Permalink Normal View History

// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd
//
// SPDX-License-Identifier: GPL-2.0
/**
* @file frtp.bpf.c
* @brief (File Real-Time Protection) eBPF内核态程序
*
* LSM(Linux Security Module)访
* hook各种文件操作系统调用
* 访访
*
*
* -
* - PID的细粒度访问控制
* -
* -
*
* @version 1.0
* @license GPL
*/
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
2025-09-22 11:50:42 +08:00
#include "task.h"
#include "com.h"
#include "mem.h"
#include "str-utils.h"
#include <asm/errno.h>
#include "jhash.h"
/** @brief 调试输出开关1启用调试信息0禁用 */
#define DEBUG_OUTPUT 0
/** @brief eBPF程序许可证声明必须为GPL兼容 */
char _license[] SEC("license") = "GPL";
/** @brief 操作类型定义,用于表示文件访问模式 */
typedef u32 Action;
/** @brief 文件读取模式标志 */
#define FMODE_READ ((fmode_t)0x1)
/** @brief 文件写入模式标志 */
#define FMODE_WRITE ((fmode_t)0x2)
/** @brief 文件执行模式标志 */
#define FMODE_EXEC ((fmode_t)0x20)
/** @brief 从设备号中提取主设备号 */
#define MAJOR(dev) (u32)((dev & 0xfff00000) >> 20)
/** @brief 从设备号中提取次设备号 */
#define MINOR(dev) (u32)(dev & 0xfffff)
/**
* @brief
*
* inode号组合
*/
struct Target
{
dev_t dev; /**< 设备号 */
ino_t ino; /**< inode号 */
};
/**
* @brief 访
*
* 访
* PID或进程路径模式匹配
*/
struct Rule
{
union
{
struct
{
u32 not_pid; /**< 标志位0表示使用PID1表示使用进程路径 */
pid_t pid; /**< 进程ID */
};
char process[4096]; /**< 进程路径字符串 */
};
Action act; /**< 禁止的操作类型 */
struct Target target; /**< 目标文件标识 */
};
/**
* @brief eBPF程序日志数据结构
*
* ring buffer向用户空间传递违规访问的日志信息
*/
struct BpfData
{
Action act; /**< 违规的操作类型 */
pid_t pid; /**< 违规进程的PID */
struct Target target;
char process[]; /**< 变长字段,包含进程路径,可能包含目标文件名*/
};
/**
* @brief 访
*
* 访使
* 访
*/
struct
{
__uint(type, BPF_MAP_TYPE_HASH); /**< 哈希表类型映射 */
__type(key, u32); /**< 键类型规则ID */
__type(value, struct Rule); /**< 值类型:访问控制规则 */
__uint(max_entries, 400000); /**< 最大条目数 */
} filter SEC(".maps");
/**
* @brief ID到路径映射
*
* ID与其可执行文件路径的映射关系
* 访使LRU策略自动淘汰旧条目
*/
struct
{
__uint(type, BPF_MAP_TYPE_LRU_HASH); /**< LRU哈希表类型 */
__type(key, pid_t); /**< 键类型进程ID */
__type(value, char[4096]); /**< 值类型:进程路径字符串 */
__uint(max_entries, 1024); /**< 最大条目数 */
} pid2path SEC(".maps");
/**
* @brief
*
* 访
* ring buffer机制实现高效的内核到用户空间数据传输
*/
struct
{
__uint(type, BPF_MAP_TYPE_RINGBUF); /**< 环形缓冲区类型 */
__uint(max_entries, 1024 * 1024); /**< 缓冲区大小1MB */
} logs SEC(".maps");
/**
* @brief 访
*
* 访
* ring buffer发送到用户空间
*
* @param pid PID
* @param process
* @param target 访
* @param fname
* @param act
*/
static void audit_log(
pid_t pid,
const char *process,
const struct Target *target,
const char *fname,
Action act
)
{
long ret;
u32 log_bsz;
u32 log_sz;
u32 mkey = __LINE__;
struct BpfData *log = (typeof(log))malloc_page(mkey);
if (!log)
{
bpf_printk("log buffer full");
return;
}
log->pid = pid;
log->act = act & 0XFF;
log->target = *target;
log_bsz = PAGE_SIZE - sizeof(*log);
log_sz = sizeof(*log);
ret = bpf_snprintf(log->process, log_bsz / 2, "%s", (u64 *)&process, 8);
if (ret < 0)
{
bpf_printk("error: bpf_snprintf: %ld", ret);
goto exit;
}
if (ret > log_bsz / 2)
{
ret = log_bsz / 2;
}
log_sz += ret;
ret = bpf_snprintf(log->process + ret, log_bsz / 2, "%s", (u64 *)&fname, 8);
if (ret < 0)
{
bpf_printk("error: bpf_snprintf: %ld", ret);
goto exit;
}
if (ret > log_bsz / 2)
{
ret = log_bsz / 2;
}
log_sz += ret;
ret = bpf_ringbuf_output(&logs, log, log_sz, 0);
if (ret)
{
bpf_printk("error: bpf_perf_event_output: %ld", ret);
}
exit:
if (log)
{
free_page(mkey);
}
}
/**
* @brief
*
*
*/
struct Event
{
const char *proc_path; /**< 进程路径 */
const struct Target *target; /**< 目标文件信息 */
int act; /**< 操作类型 */
};
/**
* @brief
*
* filter映射中的每条规则进行匹配检查访
* 访
*
* @param map BPF映射指针
* @param key
* @param value Rule结构
* @param ctx Event结构
* @return 10
*/
static long
match_callback(struct bpf_map *map, const void *key, void *value, void *ctx)
{
const char *proc_path;
dev_t dev;
ino_t ino;
int act;
struct Event *event = ctx;
struct Rule *rule = value;
proc_path = event->proc_path;
dev = event->target->dev;
ino = event->target->ino;
act = event->act;
if (rule->not_pid)
{
if (!proc_path)
{
return 0;
}
// Check if process path matches
if (wildcard_match(rule->process, proc_path, 4096))
{
return 0;
}
}
else
{
pid_t pid = bpf_get_current_pid_tgid();
if (rule->pid != pid)
{
return 0;
}
}
if (DEBUG_OUTPUT)
{
bpf_printk("proc fit: %s %d", proc_path, act);
}
// Check if file matches
if (rule->target.ino != ino || rule->target.dev != dev)
{
return 0;
}
if (DEBUG_OUTPUT)
{
bpf_printk("target fit: %s %lu %lu %d", proc_path, dev, ino, act);
}
// Check if act is a subset of rule->act
if (act & rule->act)
{
event->act = 0;
}
return 1;
}
/**
* @brief
*
* 访访
*
*
* @param proc_path
* @param target
* @param act
* @return true表示访问被允许false表示访问被禁止
*/
static bool
rules_filter(const char *proc_path, const struct Target *target, int act)
{
struct Event event = {.act = act, .proc_path = proc_path, .target = target};
bpf_for_each_map_elem(&filter, match_callback, &event, 0);
return !!event.act;
}
/**
* @brief
*
* 访
* 访
*
* @param target
* @param mode 访
* @param fname
* @return 0访-EACCES表示拒绝访问
*/
static int
_permission_check(const struct Target *target, fmode_t mode, const char *fname)
{
pid_t pid;
char *proc_path = NULL;
long ret = 0;
pid = bpf_get_current_pid_tgid();
proc_path = bpf_map_lookup_elem(&pid2path, &pid);
if (!rules_filter(proc_path, target, mode))
{
if (proc_path)
{
audit_log(pid, proc_path, target, fname, mode);
}
else
{
char comm[16];
ret = bpf_get_current_comm(comm, sizeof(comm));
if (ret)
{
bpf_printk("fail to get current comm: %ld", ret);
}
else
{
audit_log(pid, comm, target, fname, mode);
}
}
if (DEBUG_OUTPUT)
{
bpf_printk(
"permission denied: %lu %lu %d",
target->dev,
target->ino,
mode
);
}
ret = -EACCES;
}
return ret;
}
/**
* @brief
*
*
* 访
*
* @param dentry
* @param mode 访
* @return 0访访
*/
static int permission_check(struct dentry *dentry, fmode_t mode)
{
struct Target target = {
.ino = dentry->d_inode->i_ino,
.dev = dentry->d_inode->i_sb->s_dev,
};
int ret = _permission_check(&target, mode, "");
if (ret)
{
return ret;
}
// ret = bpf_probe_read_kernel_str(fname, sizeof(fname), dentry->d_name.name);
if (ret)
{
return ret;
}
target.ino = dentry->d_parent->d_inode->i_ino;
target.dev = dentry->d_parent->d_inode->i_sb->s_dev;
return _permission_check(&target, mode, (const char *)dentry->d_name.name);
}
/**
* @brief LSM hook
*
* 访
*
* @param file
* @param ret LSM的返回值
* @return 0
*/
SEC("lsm/file_open")
int BPF_PROG(file_open, struct file *file, int ret)
{
if (ret)
{
return ret;
}
return permission_check(file->f_path.dentry, file->f_mode);
}
/**
* @brief LSM hook
*
*
*
* @param file
* @param ret LSM的返回值
* @return 0
*/
SEC("lsm/file_truncate")
int BPF_PROG(file_truncate, struct file *file, int ret)
{
if (ret)
{
return ret;
}
return permission_check(file->f_path.dentry, FMODE_WRITE);
}
/**
* @brief LSM hook
*
*
*
* @param dir
* @param dentry
* @param ret LSM的返回值
* @return 0
*/
SEC("lsm/path_unlink")
int BPF_PROG(
path_unlink,
const struct path *dir,
struct dentry *dentry,
int ret
)
{
if (ret)
{
return ret;
}
return permission_check(dir->dentry, FMODE_WRITE);
}
/**
* @brief LSM hook
*
*
*
* @param dir
* @param dentry
* @param mode
* @param ret LSM的返回值
* @return 0
*/
SEC("lsm/path_mkdir")
int BPF_PROG(
path_mkdir,
const struct path *dir,
struct dentry *dentry,
umode_t mode,
int ret
)
{
if (ret)
{
return ret;
}
return permission_check(dir->dentry, FMODE_WRITE);
}
/**
* @brief LSM hook
*
*
*
* @param dir
* @param dentry
* @param ret LSM的返回值
* @return 0
*/
SEC("lsm/path_rmdir")
int BPF_PROG(path_rmdir, const struct path *dir, struct dentry *dentry, int ret)
{
if (ret)
{
return ret;
}
return permission_check(dir->dentry, FMODE_WRITE);
}
/**
* @brief LSM hook
*
*
*
* @param dir
* @param dentry
* @param mode
* @param dev
* @param ret LSM的返回值
* @return 0
*/
SEC("lsm/path_mknod")
int BPF_PROG(
path_mknod,
const struct path *dir,
struct dentry *dentry,
umode_t mode,
unsigned int dev,
int ret
)
{
if (ret)
{
return ret;
}
return permission_check(dir->dentry, FMODE_WRITE);
}
/**
* @brief PID到路径映射过滤回调
*
*
*
*
* @param map BPF映射指针
* @param key
* @param value
* @param ctx
* @return 10
*/
static long
pid2path_callback(struct bpf_map *map, const void *key, void *value, void *ctx)
{
char *filepath = *(char **)ctx;
struct Rule *rule = value;
if (!rule)
{
filepath[4095] = 0;
return 0;
}
if (!rule->not_pid)
{
filepath[4095] = 0;
return 0;
}
if (wildcard_match(rule->process, filepath, 4096))
{
filepath[4095] = 0;
return 0;
}
filepath[4095] = 1;
return 1;
}
/**
* @brief PID到路径映射过滤器
*
* PID到路径的映射中
*
*
* @param file_path
* @return true表示需要缓存false表示不需要缓存
*/
static bool pid2path_filter(char *file_path)
{
bpf_for_each_map_elem(&filter, pid2path_callback, &file_path, 0);
bool ret = !!file_path[4095];
file_path[4095] = 0; // use last byte as flag
return ret;
}
/**
* @brief 线退
*
*
* 线退PID到路径的映射
*
* @param map BPF映射指针
* @param key
* @param value
* @param ctx
* @return 10
*/
static long thead_exit_callback(
struct bpf_map *map,
const void *key,
void *value,
void *ctx
)
{
struct Rule *rule = value;
bool *ret = ctx;
if (!rule)
{
*ret = false;
return 0;
}
if (!rule->not_pid)
{
*ret = false;
return 0;
}
*ret = true;
return 1;
}
/**
* @brief 线退
*
* 线退
*
*
* @return true表示需要清理false表示不需要清理
*/
static bool thread_exit_filter(void)
{
bool ret = false;
bpf_for_each_map_elem(&filter, thead_exit_callback, &ret, 0);
return ret;
}
/**
* @brief hook
*
* PID到可执行文件路径的映射关系
* 访
*
* @param bprm
* @param fd
* @param filename
* @param flags
* @return 0
*/
SEC("fexit/bprm_execve")
int BPF_PROG(
bprm_execve,
struct linux_binprm *bprm,
int fd,
struct filename *filename,
int flags
)
{
long ret = 0;
pid_t pid;
char *filepath;
u32 mkey = __LINE__;
filepath = malloc_page(mkey);
if (!filepath)
{
bpf_printk("error: malloc_page");
return 0;
}
ret = bpf_probe_read_kernel(filepath, 4096, filename->iname);
if (ret < 0)
{
bpf_printk("error: bpf_probe_read_kernel_str: %ld", ret);
goto exit;
}
if (!pid2path_filter(filepath))
{
goto exit;
}
pid = bpf_get_current_pid_tgid();
if (DEBUG_OUTPUT)
{
bpf_printk("pid2hash: %s pid: %d", filepath, pid);
}
ret = bpf_map_update_elem(&pid2path, &pid, filepath, BPF_ANY);
if (ret)
{
bpf_printk("error: bpf_map_update_elem: %ld", ret);
goto exit;
}
exit:
if (filepath)
{
free_page(mkey);
}
return 0;
}
/**
* @brief 线退hook
*
* 线退PID到路径映射中的相应条目
*
*
* @param tsk
* @return 0
*/
SEC("fentry/exit_thread")
int BPF_PROG(exit_thread, struct task_struct *tsk)
{
long ret = 0;
pid_t pid;
if (!thread_exit_filter())
{
return 0;
}
pid = bpf_get_current_pid_tgid();
ret = bpf_map_delete_elem(&pid2path, &pid);
if (ret && ret != -ENOENT)
{
bpf_printk("error: bpf_map_delete_elem: %ld", ret);
}
return 0;
}