手把手教你为Linux内核新增一个LSM模块:以自定义文件访问控制为例
手把手教你为Linux内核新增一个LSM模块:以自定义文件访问控制为例
在Linux内核开发领域,安全模块的实现一直是高级开发者关注的焦点。LSM(Linux Security Modules)框架作为内核安全的核心基础设施,为开发者提供了扩展系统安全能力的标准化接口。不同于常见的SELinux或AppArmor等成熟方案,本文将带您从零开始构建一个针对特定场景的轻量级安全模块,实现基于用户组和文件路径的精细化访问控制。
1. 环境准备与基础概念
在开始编码前,我们需要确保开发环境配置正确。推荐使用Ubuntu 20.04 LTS或更新版本作为开发系统,内核版本最好选择5.4以上以获得完整的LSM支持。以下是必备组件:
sudo apt update sudo apt install build-essential libncurses-dev flex bison libssl-dev libelf-devLSM框架的核心思想是通过在内核关键路径插入Hook点来实现安全策略的强制执行。与传统的DAC(自主访问控制)不同,我们的模块将实现MAC(强制访问控制)特性。当系统调用执行到关键操作时,内核会依次调用:
- 原始功能检查
- DAC权限验证
- 注册的LSM Hook函数
这种设计使得安全模块可以无缝集成到现有系统中,而无需修改内核核心代码。理解这点对后续开发至关重要——我们不是在修改内核,而是在扩展它。
2. 模块骨架搭建
首先创建模块的基本文件结构:
custom_lsm/ ├── Makefile ├── custom_lsm.c └── Kconfig在custom_lsm.c中,我们需要定义模块的元信息和初始化函数:
#include <linux/lsm_hooks.h> #include <linux/module.h> #include <linux/init.h> static int __init custom_lsm_init(void) { pr_info("Custom LSM module loaded\n"); return 0; } static void __exit custom_lsm_exit(void) { pr_info("Custom LSM module unloaded\n"); } security_initcall(custom_lsm_init); module_exit(custom_lsm_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("Custom LSM for file access control");对应的Makefile内容应为:
obj-$(CONFIG_SECURITY_CUSTOM_LSM) := custom_lsm.o这个基础框架虽然简单,但已经包含了模块加载/卸载的基本逻辑。security_initcall宏确保我们的初始化函数在内核安全子系统初始化时被调用。
3. 实现文件访问控制逻辑
现在我们来实现核心功能——限制特定用户组对指定路径的访问。首先定义我们的策略数据结构:
struct file_restriction { const char *path_prefix; gid_t restricted_gid; int may_read; int may_write; int may_execute; struct list_head list; }; static LIST_HEAD(restriction_list);接着实现关键的file_openHook函数:
static int custom_file_open(struct file *file, const struct cred *cred) { struct file_restriction *entry; const char *pathname = file->f_path.dentry->d_name.name; list_for_each_entry(entry, &restriction_list, list) { if (strncmp(pathname, entry->path_prefix, strlen(entry->path_prefix)) == 0) { if (in_group_p(entry->restricted_gid)) { if ((file->f_flags & O_ACCMODE) == O_RDONLY && !entry->may_read) return -EACCES; if ((file->f_flags & O_ACCMODE) == O_WRONLY && !entry->may_write) return -EACCES; if (file->f_flags & O_EXEC && !entry->may_execute) return -EACCES; } } } return 0; }这个实现展示了如何:
- 遍历预先定义的访问限制规则
- 检查当前进程是否属于受限用户组
- 根据文件打开模式验证相应权限
- 返回-EACCES拒绝未经授权的访问
4. 注册Hook与策略配置
为了使我们的Hook生效,需要将其注册到LSM框架中:
static struct security_hook_list custom_lsm_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(file_open, custom_file_open), }; void __init custom_lsm_add_hooks(void) { security_add_hooks(custom_lsm_hooks, ARRAY_SIZE(custom_lsm_hooks), "custom_lsm"); /* 添加示例策略 */ struct file_restriction *rule = kzalloc(sizeof(*rule), GFP_KERNEL); rule->path_prefix = "/etc/secrets/"; rule->restricted_gid = 1001; // secret-access组 rule->may_read = 1; rule->may_write = 0; rule->may_execute = 0; INIT_LIST_HEAD(&rule->list); list_add_tail(&rule->list, &restriction_list); }在实际产品中,策略配置应该通过更灵活的方式实现,比如:
- 通过/proc或sysfs接口动态加载
- 从配置文件读取规则
- 使用netlink套接字接收策略更新
5. 编译与调试技巧
完成代码编写后,编译模块需要特殊的内核构建系统支持。在模块目录下执行:
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules加载模块前,建议先检查内核日志:
sudo dmesg -wH然后在另一个终端加载模块:
sudo insmod custom_lsm.ko调试时常见的几个问题:
- 权限不足:确保测试用户确实在受限组中(
id -a验证) - 路径匹配失败:检查
dentry->d_name.name是否如预期 - 竞态条件:对策略列表的访问需要适当的同步机制
6. 进阶功能扩展
基础功能实现后,可以考虑以下增强特性:
动态策略更新:
static ssize_t policy_write(struct file *file, const char __user *buf, size_t len, loff_t *ppos) { // 解析用户空间传入的策略规则 // 更新restriction_list return len; }审计日志:
static void log_denied_access(const char *path, uid_t uid, gid_t gid) { printk(KERN_INFO "Denied access to %s by uid=%d gid=%d\n", path, uid, gid); // 也可以集成到内核审计子系统 }性能优化:
- 使用红黑树替代链表存储策略规则
- 对频繁访问的路径实现缓存机制
- 减少字符串比较操作
7. 实际应用场景分析
这种自定义LSM模块特别适合以下场景:
- 多租户环境:隔离不同客户组对特定目录的访问
- 合规需求:确保敏感配置只能被授权进程读取
- 特殊设备管理:控制对/dev下特定设备的操作权限
与完整方案如SELinux相比,我们的模块具有:
- 轻量级:仅包含必要功能,无额外开销
- 易理解:策略规则简单明了
- 可定制:完全掌控安全逻辑
在最近的一个金融项目中,类似方案被用于保护交易系统的密钥存储区,仅允许特定的清算进程访问,而阻止其他所有用户和进程。
