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 之前。

Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐