4. 编写第一个本地策略模块:使用 checkmodule、semodule_package 构建模块、编译加载并验证生效
4.1 模块化策略原理概述
SELinux 安全策略采用模块化设计。系统原生策略由多个基础模块(如 kernel、corecommands)组成,管理员或开发者可以通过编写本地策略模块(Local Policy Module)来扩展或定制安全规则,而无需修改系统原有策略。本地策略模块的典型生命周期包含四个阶段:编写(Write)→ 编译(Compile)→ 打包(Package)→ 加载(Load)。
为什么要用本地模块? 直接修改
policy.conf 或手动加载二进制策略会破坏系统策略的原子性,升级时会被覆盖。模块化方式让自定义规则独立、可装卸、可追溯。
4.2 核心工具:checkmodule 与 semodule_package
| 工具 | 阶段 | 输入 | 输出 | 关键选项 |
|---|---|---|---|---|
| checkmodule | 编译 | .te(策略源文件) |
.mod(中间二进制模块) |
-M(启用模块化)、-m(生成可加载模块) |
| semodule_package | 打包 | .mod + 可选 .fc .if |
.pp(策略包,可直接加载) |
-m(指定 .mod)、-o(输出 .pp) |
4.3 编写第一个 .te 策略文件
以下示例为一个名为 myapp 的自定义守护进程编写策略,允许其读取 /var/myapp 目录下的文件,并绑定 TCP 端口 18080。
policy_module(myapp, 1.0.0)
# 声明需要的系统类型和属性
gen_require(`
type httpd_t;
type var_t;
type unreserved_port_t;
')
# 定义本地类型
type myapp_t;
type myapp_data_t;
files_type(myapp_data_t)
# 允许 myapp_t 管理自己的数据目录
allow myapp_t myapp_data_t:dir create_dir_perms;
allow myapp_t myapp_data_t:file create_file_perms;
# 允许 myapp_t 绑定 18080 端口
allow myapp_t unreserved_port_t:tcp_socket name_bind;
# 允许 myapp_t 与网络通信
corenet_tcp_connect_http_port(myapp_t)
corenet_tcp_sendrecv_http_port(myapp_t)
# 文件上下文(可内嵌在 .te 中,也可单独写 .fc)
filecon "/var/myapp(/.*)?", system_u:object_r:myapp_data_t:s0
⚠ 命名规范: 模块名、类型名、属性名均不得超过 64 字节;类型名必须以
_t 结尾;模块名应使用小写字母、数字和下划线,首字符不能为数字。
4.4 编译:从 .te 到 .mod
使用 checkmodule 将可读的策略源文件编译为二进制中间模块。
checkmodule -M -m myapp.te -o myapp.mod
| 选项 | 含义 |
|---|---|
-M |
启用模块化策略语法(必须与 -m 同时使用) |
-m |
生成可加载模块(而非基础内核策略) |
-o |
指定输出文件路径 |
编译成功时不输出任何信息;若出现语法错误或类型未定义,则会终止并报错。可使用 echo $? 确认返回值为 0。
4.5 打包:从 .mod 到 .pp
semodule_package 将中间模块与可选的 .fc(文件上下文)和 .if(接口定义)打包成可加载的策略包。
semodule_package -m myapp.mod -o myapp.pp
| 选项 | 含义 |
|---|---|
-m |
指定输入的 .mod 文件 |
-o |
输出的 .pp 策略包文件 |
-f |
(可选)指定 .fc 文件上下文文件 |
-i |
(可选)指定 .if 接口定义文件 |
文件上下文合并: 如果
.te 中已使用 filecon 语句定义了文件上下文,则打包时无需单独指定 .fc 文件。两种方式等价,但建议 .fc 独立以保持结构清晰。
4.6 加载模块并验证生效
使用 semodule 命令将 .pp 包安装到系统策略中。
semodule -i myapp.pp
验证模块是否已加载:
semodule -l | grep myapp
# 输出示例:myapp 1.0.0
使用 sesearch 确认规则已生效:
sesearch -A -s myapp_t -t myapp_data_t -c file -p read
# 输出示例:allow myapp_t myapp_data_t : file { ioctl read write create ... }
检查文件上下文是否正确关联:
matchpathcon /var/myapp/data.txt
# 输出示例:/var/myapp/data.txt system_u:object_r:myapp_data_t:s0
4.7 完整构建流程总览
4.8 调试与常见错误
| 错误现象 | 根因 | 解决方案 |
|---|---|---|
ERROR: 'type myapp_t is not defined' |
.te 中使用了未声明的类型 |
添加 type myapp_t; 声明,或通过 gen_require 引入系统类型 |
ERROR: 'policy_module' not found |
未使用 -M 启用模块化模式 |
在 checkmodule 命令中加入 -M 选项 |
libsepol.print_missing_requirements: myapp's global requirements were not met |
模块引用了当前策略中不存在的类型或属性 | 检查 gen_require 中声明的系统类型是否拼写正确,必要时升级策略基础包 |
semodule -i 成功但 semodule -l 看不到 |
模块优先级被更高优先级的相同模块覆盖 | 使用 semodule -l | grep myapp 并观察优先级数字,或先 semodule -r myapp 再重新安装 |
AVC 依旧拒绝:avc: denied |
规则遗漏或类型未正确关联到进程 | 使用 ausearch -m avc -ts recent 查看拒绝详情,补充缺失的 allow 规则 |
🔍 调试快速命令清单:
semodule -l | grep myapp — 列出已加载模块sesearch -A -s myapp_t -t myapp_data_t -c file -p read — 检索具体规则matchpathcon /var/myapp/data.txt — 查看文件上下文ausearch -m avc -ts recent | audit2allow — 从拒绝日志生成建议规则
4.9 卸载与重载
当模块需要更新或移除时,使用以下命令:
# 卸载模块
semodule -r myapp
# 重新加载(先卸载再安装,或使用 -u 选项)
semodule -u myapp.pp
⚠ 注意: 卸载正在被进程使用的策略模块不会导致进程立即崩溃,但该进程后续的访问请求将不再被自定义规则允许,可能触发 AVC 拒绝。建议在卸载前确保相关进程已停止或已转入其他安全域。
4.10 流程总结
- .te → .mod → .pp → 加载 → 验证 是本地策略模块的标准生命周期,五步缺一不可。
- checkmodule 负责语法分析和模块化编译;semodule_package 负责组装与打包;semodule 负责与内核策略交互。
- 验证必须覆盖模块可见性、规则存在性和文件上下文一致性三个维度。
- 遇到错误时,优先检查
gen_require是否正确、是否启用了-M选项、以及系统策略基础包版本是否匹配。