SELinux 域名哈希表与 setexeccon 查询流程
SELinux 域名哈希表与 setexeccon 查询流程
—— 以示例域 sample_host 为例
一、域名(type)写入哈希表的完整流程
1.1 起点:type 声明与 build_selinux 开关
域名声明位于策略的 type.te(public 侧):
type sample_host, hdfdomain, domain;
build_selinux 开关决定 init 是否编译 selinux 插件(在产品的 config.json 中配置),打开后 init 启动时会执行策略加载,域名才能进入运行时的内核哈希表。整个流程分三段:编译期(checkpolicy 用户态哈希表)→ 序列化(policy.31)→ 运行期(内核 p_types 哈希表)。
1.2 编译期:声明进入 checkpolicy 的用户态哈希表
type.te 经 m4 编译器展开后由 checkpolicy 解析,每个 type 声明被插入用户态 policydb 的符号表,并分配整数 value:
|
步骤 |
动作 |
源码路径 |
|
① |
m4 展开所有 .te 规则生成纯文本 → policy.conf |
base/security/selinux_adapter/scripts/build_policy_api.py |
|
② |
checkpolicy 解析声明,生成 CIL |
build_policy_api.py(checkpolicy -M -C -c 31 -o policy.cil) |
|
③ |
语法规则:define_type 处理 "type xxx, attr, attr;" |
third_party/selinux/checkpolicy/policy_define.c |
|
④ |
declare_type(TRUE, FALSE):TRUE=primary 类型,FALSE=非 attribute |
policy_define.c |
|
⑤ |
create_type:取出名字,声明 SYM_TYPES 符号 |
third_party/selinux/checkpolicy/module_compiler.c |
|
⑥ |
create_symbol:调 libsepol symtab_insert |
module_compiler.c |
|
⑦ |
★ 插表:hashtab_insert(表, "sample_host", datum) |
third_party/selinux/libsepol/src/policydb.c |
|
⑧ |
★ 分配整数 value:*value = ++symtab[SYM_TYPES].nprim |
libsepol/src/policydb.c |
关键点:type 的整数 value 只在编译期分配一次(按声明顺序 nprim 自增),内核运行期不重新分配,只从二进制读回。因此编译期与运行期的 value 一致。
1.3 序列化:用户态哈希表 → policy.31
checkpolicy 内存 symtab --build_policy_api.py (checkpolicy ... -o policy.cil)--> CIL 文本
CIL 文本 --build_policy_api.py (secilc ... -o policy.31)--> 二进制策略
policy.31 中 p_types 段内容:name="sample_host", value=N, attributes 位图
产物位置:/system/etc/selinux/policy/policy.31
1.4 运行期:init 加载策略 → 内核 p_types 哈希表
init 启动早期执行 loadSelinuxPolicy,把 policy.31 写入 selinuxfs,内核构建 p_types 哈希表:
|
步骤 |
动作 |
源码路径 |
|
① |
init 主流程调用加载插件 |
base/startup/init/services/init/init.c |
|
② |
LoadSelinuxPolicy → LoadPolicy |
base/startup/init/services/modules/selinux/selinux_adp.c |
|
③ |
读 policy.31 → security_load_policy |
base/security/selinux_adapter/framework/policycoreutils/src/load_policy.cpp |
|
④ |
写入 selinuxfs |
third_party/selinux/libselinux/src/load_policy.c(write /sys/fs/selinux/load) |
|
⑤ |
内核接收:sel_write_load → security_load_policy |
kernel/linux/linux-5.10/security/selinux/selinuxfs.c |
|
⑥ |
policydb_read 按段解析二进制策略 |
kernel/linux/linux-5.10/security/selinux/ss/services.c |
|
⑦ |
按符号类型分发:read_f[SYM_TYPES] = type_read |
ss/policydb.c |
|
⑧ |
type_read:读回字符串 + 插入 |
ss/policydb.c |
|
⑨ |
str_read 从二进制读回名字 "sample_host" |
ss/policydb.c |
|
⑩ |
★ 入表:symtab_insert(&p_types, "sample_host", typdatum) |
ss/policydb.c |
|
⑪ |
定位桶:hvalue = symhash(key) & (size-1) |
ss/hashtab.h |
|
⑫ |
头插法挂入桶链:newnode->next = *dst |
ss/hashtab.c __hashtab_insert |
1.5 哈希表数据结构与算法
struct hashtab { u32 size; u32 nel; struct hashtab_node **htable; }; // 桶数组(2的幂) + 单向链表
// hash 算法(旋转 XOR)
for (p = keyp; (p - keyp) < size; p++)
val = (val << 4 | (val >> (8*sizeof(unsigned int)-4))) ^ (*p);
// key = 字符串 "sample_host";datum = struct type_datum { value=N; primary=1; attribute位图 }
最终形态:内核 p_types 的某个桶链节点上,存放("sample_host" → type_datum(value=N))。至此,"声明的域名"已作为 key 躺在哈希表里,等待 setexeccon 查询。
二、setexeccon 如何查询这张表
2.1 查询的触发:init 拉起服务前设置执行上下文
hdf_devhost.cfg 中每个服务带 secon 字段(hc_gen 从 device_info.hcs 生成)。init fork 服务子进程后、execv 之前,调用 setexeccon 设置进程的安全上下文:
|
步骤 |
动作 |
源码路径 |
|
① |
ServiceStart fork 后,RunChildProcess 内调 SetServiceContent |
base/startup/init/services/init/init_service_manager.c |
|
② |
selinux 插件执行 setexeccon |
base/startup/init/services/modules/selinux/selinux_adp.c |
|
③ |
写入 /proc/thread-self/attr/exec |
third_party/selinux/libselinux/src/procattr.c |
|
④ |
proc 层转发:proc_pid_attr_write → security_setprocattr |
kernel/linux/linux-5.10/fs/proc/base.c |
|
⑤ |
LSM 分发 |
kernel/linux/linux-5.10/security/security.c |
|
⑥ |
selinux_setprocattr 进入 SELinux 子系统 |
kernel/linux/linux-5.10/security/selinux/hooks.c |
// selinux_adp.c
if (setexeccon(label) < 0) {
// 查询失败 → 进程直接退出,服务起不来
_exit(INIT_EEXEC_CONTENT);
}
2.2 内核查询:字符串 → SID → 查 p_types
|
步骤 |
动作 |
源码路径 |
|
① |
把 "u:r:sample_host:s0" 转成 SID |
security_context_to_sid(state, "u:r:sample_host:s0", ...) |
|
② |
security_context_to_sid → string_to_context_struct |
ss/services.c |
|
③ |
解析用户/角色部分后,进入类型解析 |
ss/services.c |
|
④ |
查询点:symtab_search(&pol->p_types, "sample_host") |
ss/services.c |
|
⑤ |
判空 + 排除 attribute(!typdatum || typdatum->attribute → goto out) |
ss/services.c |
typdatum = symtab_search(&pol->p_types, scontextp);
if (!typdatum || typdatum->attribute)
goto out; // 查不到 / 是 attribute → 返回 -EINVAL
// 命中后:ctx->type = typdatum->value;
2.3 查询算法:与插入完全对称
symtab_search(s, key):
hvalue = symhash(key) & (s->table->size - 1); // 同一哈希函数定位同一桶
for (cur = s->table->htable[hvalue]; cur; cur = cur->next)
if (cmp(key, cur->key) == 0) // strcmp 字符串比较
return cur->datum; // 命中 → 返回 type_datum
return NULL; // 未命中
插入用的是 symhash + 头插桶链(1.5 节),查询用同一个 symhash 算出同一个桶下标,再沿桶链 strcmp 字符串比对。因此 "sample_host" 这个字符串 key 就是插入与查询之间的唯一纽带。
2.4 查询的两种结局
|
结局 |
条件 |
后果 |
|
成功 |
p_types 中存在该字符串且非 attribute |
ctx->type = value;exec_sid 写入 cred → execv 时域转换,进程以 sample_host 域运行 |
|
失败 |
type 未声明(哈希表查不到) |
-EINVAL 原路返回 → setexeccon < 0 → selinux_adp.c _exit(INIT_EEXEC_CONTENT) → 子进程退出,服务起不来 |
三、总结:一条链路,两个哈希表
- 编译期:type.te 声明 → checkpolicy 解析 → 插入用户态 symtab(libsepol policydb.c)→ 序列化为 policy.31。
- 运行期:init 加载 policy.31(load_policy.c → selinuxfs.c)→ 内核 type_read 逐条读回(policydb.c)→ 以 "sample_host" 为 key 插入内核 p_types 哈希表。
- 查询期:setexeccon(selinux_adp.c)→ 写 /proc/thread-self/attr/exec → 内核 hooks.c → string_to_context_struct → symtab_search(&pol->p_types, "sample_host")。
两个哈希表是两份独立实现(libsepol vs 内核),但哈希算法语义相同(旋转 XOR + 头插桶链),中间靠 policy.31 保证 key、value、attribute 位图一致。声明不写进 type.te,运行期就查不到;查不到,服务在 build_selinux 开启时直接死在 exec 之前。
更多推荐
所有评论(0)