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 完整构建流程总览

本地策略模块构建与加载流程 ① 编写 myapp.te policy_module(myapp, 1.0.0) ... ② checkmodule 编译 checkmodule -M -m myapp.te -o myapp.mod ③ semodule_package 打包 semodule_package -m myapp.mod -o myapp.pp ④ semodule -i 加载模块 semodule -i myapp.pp ⑤ 验证生效 semodule -l | sesearch | matchpathcon 关键文件类型 .te → 策略源文件 .mod → 二进制中间模块 .pp → 可加载策略包 验证三要素 ✓ 模块列表可见 ✓ 规则被 sesearch 检索到 ✓ 文件上下文匹配预期

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 选项、以及系统策略基础包版本是否匹配。