Boolean条件策略
本文面向已经读过 TE规则语法、M4预处理 和 Constrain约束 的读者。前两篇分别介绍构建期条件和运行时 Context 约束,本篇处理另一个容易混淆的机制:标准 SELinux boolean 如何让 true/false 两组规则保留在 binary policy 中并在运行时切换,以及 Android 17 为什么虽然内核保留整套实现,平台策略却没有声明 boolean,并用 neverallow 禁止任何 domain 执行 security setbool。
本文不会用桌面发行版的 httpd_enable_* 列表代替 Android 源码,也不会把 userdebug_or_eng()、is_flag_enabled() 称为 SELinux boolean。读完后,你应能从 selinuxfs boolean 文件追到 pending state、commit、conditional node 重算、policy sequence 与 AVC flush;还能判断一条 Android 条件规则究竟在 m4 构建时消失,还是存在于运行时 conditional avtab。
1. 两种条件
1.1 运行时Boolean
标准 SELinux policy language 可声明 boolean,并把 allow、auditallow、dontaudit 或 type rule 放入条件分支。下面只展示语法形态,不是 Android 平台策略摘录:
bool feature_enabled false;
if (feature_enabled) {
allow client target:file read;
} else {
dontaudit client target:file read;
}编译后 boolean、表达式、true/false rule lists 都进入 binary policy。切换 state 不需要重新运行 m4/checkpolicy,也不会修改 source policy 文件。
1.2 构建期M4
Android 平台实际使用 m4 根据 build variant、Treble、recovery、release flag 与 board API 决定规则是否进入 policy.conf。
源码文件:system/sepolicy/public/te_macros
define(`userdebug_or_eng', ifelse(target_build_variant, `eng', $1, ifelse(target_build_variant, `userdebug', $1,
#
# SUPPRESSED_BY_USERDEBUG_OR_ENG -- this marker is used by CTS -- do not modify
)))
define(`recovery_only', ifelse(target_recovery, `true', $1, ))
define(`not_recovery', ifelse(target_recovery, `true', , $1))这类宏在 m4 结束后已经消失。未选中的分支不进入 binary policy,因此设备运行时没有 state 可切换。
1.3 生命周期对比
| 维度 | SELinux boolean | Android m4条件 |
|---|---|---|
| 条件 owner | policydb boolean state | Soong/m4 -D 参数 |
| 分支存储 | binary policy true/false lists | 只保留被选中的文本 |
| 生效时机 | commit booleans 后立即 | 重新构建并加载策略后 |
| AVC处理 | sequence 增加并 flush | 新 policy load 时 flush |
| Android平台现状 | 内核支持,策略无 boolean | 大量使用 |
2. PolicyDB状态
2.1 Boolean条目
每个 boolean 在 policydb 中有稳定编号与当前 state。编号用于 conditional expression operand,state 是求值输入。
源码文件:kernel/common/security/selinux/ss/policydb.h
struct cond_bool_datum {
u32 value; /* internal type value */
int state;
};注释沿用历史上的 “type value” 措辞,但该字段实际是 boolean symbol 的内部编号。
2.2 Conditional节点
一个 conditional block 保存 postfix expression、当前求值结果,以及 true/false 两组 avtab node 指针。
源码文件:kernel/common/security/selinux/ss/conditional.h
struct cond_expr_node {
#define COND_BOOL 1 /* plain bool */
#define COND_NOT 2 /* !bool */
#define COND_OR 3 /* bool || bool */
#define COND_AND 4 /* bool && bool */
#define COND_XOR 5 /* bool ^ bool */
#define COND_EQ 6 /* bool == bool */
#define COND_NEQ 7 /* bool != bool */
#define COND_LAST COND_NEQ
u32 expr_type;
u32 boolean;
};
struct cond_expr {
struct cond_expr_node *nodes;
u32 len;
};表达式只负责计算 true/false;分支中的规则由另一个索引结构持有。
源码文件:kernel/common/security/selinux/ss/conditional.h
struct cond_av_list {
struct avtab_node **nodes;
u32 len;
};
struct cond_node {
int cur_state;
struct cond_expr expr;
struct cond_av_list true_list;
struct cond_av_list false_list;
};Rule node 真正存储在 te_cond_avtab,true/false lists 保存指向这些 node 的索引,以便切换 AVTAB_ENABLED bit。
2.3 独立AV表
Policydb 将 unconditional 与 conditional TE rules 分开保存。
源码文件:kernel/common/security/selinux/ss/policydb.h
/* bools indexed by (value - 1) */
struct cond_bool_datum **bool_val_to_struct;
/* type enforcement conditional access vectors and transitions */
struct avtab te_cond_avtab;
/* array indexing te_cond_avtab by conditional */
struct cond_node *cond_list;
u32 cond_list_len;这使 AV query 可以先查普通 te_avtab,再只合并 conditional table 中当前 enabled 的节点。
3. 表达式求值
3.1 Reverse Polish
内核使用 postfix expression 和固定 stack 求值。Boolean operand 读取当前 state,逻辑节点修改或归约栈顶。
源码文件:kernel/common/security/selinux/ss/conditional.c
static int cond_evaluate_expr(struct policydb *p, struct cond_expr *expr)
{
u32 i;
int s[COND_EXPR_MAXDEPTH];
int sp = -1;
if (expr->len == 0)
return -1;
for (i = 0; i < expr->len; i++) {
struct cond_expr_node *node = &expr->nodes[i];
switch (node->expr_type) {
case COND_BOOL:
if (sp == (COND_EXPR_MAXDEPTH - 1))
return -1;
sp++;
s[sp] = p->bool_val_to_struct[
node->boolean - 1]->state;
break;
case COND_NOT:
if (sp < 0)
return -1;
s[sp] = !s[sp];
break;
case COND_OR:
if (sp < 1)
return -1;
sp--;
s[sp] |= s[sp + 1];
break;
case COND_AND:
if (sp < 1)
return -1;
sp--;
s[sp] &= s[sp + 1];
break;
/* XOR, EQ, and NEQ cases omitted. */
default:
return -1;
}
}
return s[0];
}原函数在 AND 与 default 之间还有 XOR、EQ、NEQ 分支;摘录保留 operand、NOT、OR、AND 四种基础行为。
3.2 Undefined状态
表达式为空、stack overflow/underflow 或 operator 不合法时返回 -1。Conditional node 将 true 和 false 两组规则全部关闭,采用 fail-closed 行为。
源码文件:kernel/common/security/selinux/ss/conditional.c
new_state = cond_evaluate_expr(p, &node->expr);
if (new_state != node->cur_state) {
node->cur_state = new_state;
if (new_state == -1)
pr_err("SELinux: expression result was undefined - disabling all rules.\n");
for (i = 0; i < node->true_list.len; i++) {
avnode = node->true_list.nodes[i];
if (new_state <= 0)
avnode->key.specified &= ~AVTAB_ENABLED;
else
avnode->key.specified |= AVTAB_ENABLED;
}
for (i = 0; i < node->false_list.len; i++) {
avnode = node->false_list.nodes[i];
if (new_state)
avnode->key.specified &= ~AVTAB_ENABLED;
else
avnode->key.specified |= AVTAB_ENABLED;
}
}new_state == -1 对 true list 满足 <= 0,对 false list 满足非零,双方都清除 enabled。
3.3 全量重算
Boolean commit 后不是只重算引用已变化 boolean 的节点,而是遍历全部 conditional nodes。
源码文件:kernel/common/security/selinux/ss/conditional.c
void evaluate_cond_nodes(struct policydb *p)
{
u32 i;
for (i = 0; i < p->cond_list_len; i++)
evaluate_cond_node(p, &p->cond_list[i]);
}切换频率通常很低,简单全量遍历换取了状态更新逻辑的确定性。
4. AV决策
4.1 Enabled过滤
cond_compute_av() 遍历与 source/target/class 匹配的 conditional nodes,只把同时具有目标 rule flavor 和 AVTAB_ENABLED 的节点并入 decision。
源码文件:kernel/common/security/selinux/ss/conditional.c
void cond_compute_av(struct avtab *ctab, struct avtab_key *key,
struct av_decision *avd,
struct extended_perms *xperms)
{
struct avtab_node *node;
if (!ctab || !key || !avd)
return;
for (node = avtab_search_node(ctab, key); node;
node = avtab_search_node_next(node, key->specified)) {
if ((u16)(AVTAB_ALLOWED | AVTAB_ENABLED) ==
(node->key.specified &
(AVTAB_ALLOWED | AVTAB_ENABLED)))
avd->allowed |= node->datum.u.data;
if ((u16)(AVTAB_AUDITDENY | AVTAB_ENABLED) ==
(node->key.specified &
(AVTAB_AUDITDENY | AVTAB_ENABLED)))
avd->auditdeny &= node->datum.u.data;
if ((u16)(AVTAB_AUDITALLOW | AVTAB_ENABLED) ==
(node->key.specified &
(AVTAB_AUDITALLOW | AVTAB_ENABLED)))
avd->auditallow |= node->datum.u.data;
if (xperms && (node->key.specified & AVTAB_ENABLED) &&
(node->key.specified & AVTAB_XPERMS))
services_compute_xperms_drivers(xperms, node);
}
}Conditional dontaudit 使用 auditdeny &= mask,与普通 dontaudit 的合并语义一致。Xperm 条件规则也受 enabled bit 控制。
4.2 合并顺序
Security server 先合并 unconditional avtab,再调用 cond_compute_av(),随后才执行 MLS/constrain、role transition 与 type bounds。
源码文件:kernel/common/security/selinux/ss/services.c
for (node = avtab_search_node(&policydb->te_avtab, &avkey);
node;
node = avtab_search_node_next(node, avkey.specified)) {
if (node->key.specified == AVTAB_ALLOWED)
avd->allowed |= node->datum.u.data;
else if (node->key.specified == AVTAB_AUDITALLOW)
avd->auditallow |= node->datum.u.data;
else if (node->key.specified == AVTAB_AUDITDENY)
avd->auditdeny &= node->datum.u.data;
else if (xperms &&
(node->key.specified & AVTAB_XPERMS))
services_compute_xperms_drivers(xperms, node);
}
/* Check conditional av table for additional permissions */
cond_compute_av(&policydb->te_cond_avtab, &avkey, avd, xperms);所以 boolean true 只能增加其 conditional branch 中定义的 candidate allow;这些 bits 仍可能被 constrain 或 bounds 清除。
4.3 Type规则
Conditional avtab 也可保存 type transition/change/member rules。读取 policy 时,内核防止同一 key 与 unconditional type rule 冲突,也限制 true/false lists 之外出现多个冲突结果。
源码文件:kernel/common/security/selinux/ss/conditional.c
if (k->specified & AVTAB_TYPE) {
if (avtab_search_node(&p->te_avtab, k)) {
pr_err("SELinux: type rule already exists outside of a conditional.\n");
return -EINVAL;
}
if (other) {
node_ptr = avtab_search_node(&p->te_cond_avtab, k);
if (node_ptr) {
if (avtab_search_node_next(node_ptr,
k->specified)) {
pr_err("SELinux: too many conflicting type rules.\n");
return -EINVAL;
}
/* Check that the existing node belongs to the other list. */
}
} else if (avtab_search_node(&p->te_cond_avtab, k)) {
pr_err("SELinux: conflicting type rules when adding type rule for true.\n");
return -EINVAL;
}
}这里的省略说明位于代码块外:原函数在 false-list 分支中遍历 other->nodes,确认已有相同 key 的 node 确实属于 true list。
4.4 Transition消费者
security_compute_sid() 查不到 unconditional type transition 时,会遍历 conditional avtab,选择第一条带 AVTAB_ENABLED 的 node。
源码文件:kernel/common/security/selinux/ss/services.c
avnode = avtab_search_node(&policydb->te_avtab, &avkey);
/* If no permanent rule, also check for enabled conditional rules */
if (!avnode) {
node = avtab_search_node(&policydb->te_cond_avtab, &avkey);
for (; node; node = avtab_search_node_next(node, specified)) {
if (node->key.specified & AVTAB_ENABLED) {
avnode = node;
break;
}
}
}Android 当前没有 boolean,因此这条 branch 通常为空;但它是 kernel 支持运行时切换 type rule 的真实消费者。
5. Selinuxfs接口
5.1 Boolean文件
Policy load 后,selinuxfs 根据 policy boolean table 创建 /sys/fs/selinux/booleans/<name> 文件。没有 boolean 时循环次数为零,目录存在但没有 boolean entries。
源码文件:kernel/common/security/selinux/selinuxfs.c
ret = security_get_bools(newpolicy, &num, &names,
bool_pending_values);
if (ret)
goto out;
*bool_num = num;
*bool_pending_names = names;
for (i = 0; i < num; i++) {
struct dentry *dentry;
struct inode *inode;
dentry = d_alloc_name(bool_dir, names[i]);
if (!dentry) {
ret = -ENOMEM;
break;
}
inode = sel_make_inode(bool_dir->d_sb,
S_IFREG | S_IRUGO | S_IWUSR);
if (!inode) {
dput(dentry);
ret = -ENOMEM;
break;
}
inode->i_fop = &sel_bool_ops;
inode->i_ino = i | SEL_BOOL_INO_OFFSET;
d_add(dentry, inode);
}原函数还为每个 boolean path 查询 genfs SID 并初始化 inode security blob;摘录聚焦 entry 生命周期。
5.2 Current与Pending
读取 boolean 文件返回两个数:当前 policy state 与尚未 commit 的 pending value。
源码文件:kernel/common/security/selinux/selinuxfs.c
cur_enforcing = security_get_bool_value(index);
if (cur_enforcing < 0) {
ret = cur_enforcing;
goto out_unlock;
}
length = scnprintf(buffer, sizeof(buffer), "%d %d",
!!cur_enforcing,
!!fsi->bool_pending_values[index]);局部变量名 cur_enforcing 是历史命名,这里保存的是 boolean current state,不是全局 enforcing mode。
5.3 写Pending
写单个 boolean 文件只修改 selinuxfs instance 的 pending array,不立即改变 conditional rules。写前先检查 security setbool。
源码文件:kernel/common/security/selinux/selinuxfs.c
length = avc_has_perm(current_sid(), SECINITSID_SECURITY,
SECCLASS_SECURITY, SECURITY__SETBOOL,
NULL);
if (length)
goto out;
length = -EINVAL;
if (index >= fsi->bool_num || strcmp(name,
fsi->bool_pending_names[index]))
goto out;
length = -EINVAL;
if (sscanf(page, "%d", &new_value) != 1)
goto out;
if (new_value)
new_value = 1;
fsi->bool_pending_values[index] = new_value;
length = count;5.4 Commit
向 commit_pending_bools 写非零值才调用 security_set_bools();写 0 只完成一次合法 write,不应用 pending state。
源码文件:kernel/common/security/selinux/selinuxfs.c
length = avc_has_perm(current_sid(), SECINITSID_SECURITY,
SECCLASS_SECURITY, SECURITY__SETBOOL,
NULL);
if (length)
goto out;
length = -EINVAL;
if (sscanf(page, "%d", &new_value) != 1)
goto out;
length = 0;
if (new_value && fsi->bool_pending_values)
length = security_set_bools(fsi->bool_num,
fsi->bool_pending_values);
if (!length)
length = count;下面把单项 pending 更新与整批 commit 的状态关系画在一起。
6. 提交事务
6.1 Copy-on-write
security_set_bools() 不原地修改当前 RCU policy。它先浅复制 policy container,再深复制可能改变的 boolean/conditional 部分。
源码文件:kernel/common/security/selinux/ss/services.c
oldpolicy = rcu_dereference_protected(state->policy,
lockdep_is_held(&state->policy_mutex));
if (WARN_ON(len != oldpolicy->policydb.p_bools.nprim))
return -EINVAL;
newpolicy = kmemdup(oldpolicy, sizeof(*newpolicy), GFP_KERNEL);
if (!newpolicy)
return -ENOMEM;
/*
* Deep copy only the parts of the policydb that might be
* modified as a result of changing booleans.
*/
rc = cond_policydb_dup(&newpolicy->policydb,
&oldpolicy->policydb);
if (rc) {
kfree(newpolicy);
return -ENOMEM;
}SID table、普通 avtab 等未变数据继续共享,conditional-specific data 单独复制后修改。
6.2 Audit与重算
每个 state change 写入 AUDIT_MAC_CONFIG_CHANGE,然后全量重算 conditional nodes。
源码文件:kernel/common/security/selinux/ss/services.c
for (i = 0; i < len; i++) {
int new_state = !!values[i];
int old_state =
newpolicy->policydb.bool_val_to_struct[i]->state;
if (new_state != old_state) {
audit_log(audit_context(), GFP_ATOMIC,
AUDIT_MAC_CONFIG_CHANGE,
"bool=%s val=%d old_val=%d auid=%u ses=%u",
sym_name(&newpolicy->policydb, SYM_BOOLS, i),
new_state, old_state,
from_kuid(&init_user_ns,
audit_get_loginuid(current)),
audit_get_sessionid(current));
newpolicy->policydb.bool_val_to_struct[i]->state =
new_state;
}
}
evaluate_cond_nodes(&newpolicy->policydb);6.3 安装与回收
新 policy 获得递增 granting sequence,通过 RCU pointer 安装;等待 reader 离开后释放旧 conditional copy。
源码文件:kernel/common/security/selinux/ss/services.c
newpolicy->latest_granting = oldpolicy->latest_granting + 1;
seqno = newpolicy->latest_granting;
rcu_assign_pointer(state->policy, newpolicy);
synchronize_rcu();
selinux_policy_cond_free(oldpolicy);
/* Notify others of the policy change */
selinux_notify_policy_change(seqno);
return 0;6.4 AVC失效
Policy change notification 首先 reset AVC,再通知 userspace、status、NetLabel、XFRM 与 IMA。
源码文件:kernel/common/security/selinux/ss/services.c
static void selinux_notify_policy_change(u32 seqno)
{
/* Flush external caches and notify userspace of policy load */
avc_ss_reset(seqno);
selnl_notify_policyload(seqno);
selinux_status_update_policyload(seqno);
selinux_netlbl_cache_invalidate();
selinux_xfrm_notify_policyload();
selinux_ima_measure_state_locked();
}源码文件:kernel/common/security/selinux/avc.c
int avc_ss_reset(u32 seqno)
{
struct avc_callback_node *c;
int rc = 0, tmprc;
avc_flush();
for (c = avc_callbacks; c; c = c->next) {
if (c->events & AVC_CALLBACK_RESET) {
tmprc = c->callback(AVC_CALLBACK_RESET);
if (!rc)
rc = tmprc;
}
}
avc_latest_notif_update(seqno, 0);
return rc;
}旧 cache decision 不能继续复用,否则 boolean 已关闭的 allow 仍可能命中缓存。
7. Policy Reload
7.1 保留State
加载新版 policy 时,内核按 boolean name 将旧 active values 复制到新 policy,再重算条件节点。新 policy 的默认值不会无条件覆盖管理员已经设置的 state。
源码文件:kernel/common/security/selinux/ss/services.c
rc = security_get_bools(oldpolicy, &nbools, &bnames, &bvalues);
if (rc)
goto out;
for (i = 0; i < nbools; i++) {
booldatum = symtab_search(&newpolicy->policydb.p_bools,
bnames[i]);
if (booldatum)
booldatum->state = bvalues[i];
}
evaluate_cond_nodes(&newpolicy->policydb);只有新旧 policy 同名 boolean 才被保留;删除或重命名 boolean 会失去旧 state。
7.2 首次加载
首次 policy load 没有旧 state,直接使用 binary policy 中的 initial boolean values。Android policy boolean count 为零时,这条路径自然退化为空表。
8. Android禁用
8.1 无Boolean声明
Android 17 的 platform/public/private/vendor TE 源中没有 active bool、boolean 声明,也没有 policy-language if (...) 条件块。
# 查找policy-language boolean声明与条件块;预期无输出。
rg -n \
'^[[:space:]]*(bool|boolean)[[:space:]]|^[[:space:]]*if[[:space:]]*\(' \
system/sepolicy/public \
system/sepolicy/private \
system/sepolicy/vendor \
-g '*.te'源码搜索不能代替最终 binary policy 查询,但它确认 AOSP source 没有构建输入。
8.2 禁止Setbool
平台 neverallow 明确说明 AOSP policy 没有 booleans,因此任何 domain 都不需要设置它们。
源码文件:system/sepolicy/private/domain.te
# No booleans in AOSP policy, so no need to ever set them.
neverallow * kernel:security setbool;Selinuxfs 写 boolean 与 commit 两个入口都检查 SECURITY__SETBOOL,所以即使 vendor policy 尝试引入 boolean,也无法通过正常 Android policy 给任何 writer 授权。
8.3 Selinuxfs入口
Kernel 总是创建 selinuxfs booleans directory 和 commit_pending_bools control file,但 boolean entries 来自 policydb->p_bools.nprim。Android policy count 为零时,目录存在不代表存在可切换项。
# 设备只读检查:目录可以存在,但应无boolean entries。
adb shell ls -la /sys/fs/selinux/booleans
# commit控制文件存在也不意味着当前policy有boolean。
adb shell ls -l /sys/fs/selinux/commit_pending_bools8.4 Binary查询
Android 自带 sepolicy-analyze booleans component,直接遍历 binary policy 的 bool symbol table。
源码文件:system/sepolicy/tools/sepolicy-analyze/booleans.c
static int list_booleans(hashtab_key_t k,
__attribute__ ((unused)) hashtab_datum_t d,
__attribute__ ((unused)) void *args)
{
const char *name = k;
printf("%s\n", name);
return 0;
}
int booleans_func(int argc,
__attribute__ ((unused)) char **argv,
policydb_t *policydb)
{
if (argc != 1) {
USAGE_ERROR = true;
return -1;
}
return hashtab_map(policydb->p_bools.table,
list_booleans, NULL);
}对应的最终策略查询命令如下。
# 将PRODUCT替换为实际产品名;Android平台策略预期无输出。
ANDROID_SEPOLICY='out/target/product/PRODUCT/root/sepolicy'
sepolicy-analyze "$ANDROID_SEPOLICY" booleans输入是最终 binary policy。无输出说明 bool table 为空;它比只搜索 .te 更能覆盖 device/vendor 合并结果。
9. Android替代
9.1 Build Variant
调试策略使用 userdebug_or_eng。User 构建根本没有这些 rules,userdebug/eng 才包含。
源码文件:system/sepolicy/private/system_server.te
userdebug_or_eng(`
allow system_server user_profile_data_file:dir rw_dir_perms;
allow system_server user_profile_data_file:file create_file_perms;
')这不是设备启动后的 boolean state。改变 ro.build.type 属性也不会动态生成缺失规则。
9.2 Release Flag
Release flag 使用 is_flag_enabled/is_flag_disabled,缺少 Soong 导出的 target_flag_* 会在 m4 阶段 fatal,而不是在运行时查 property。
源码文件:system/sepolicy/flagging/flagging_macros
define(`is_flag_enabled', `
assert_define_visible(`target_flag_$1')
ifelse(target_flag_$1, `true', `$2')
')
define(`is_flag_disabled', `
assert_define_visible(`target_flag_$1')
ifelse(target_flag_$1, `true', , `$2')
')9.3 Property边界
Android property 可以控制服务行为、feature path 或 daemon 启停,但 property value 本身不会修改 loaded SELinux allow table。若两个行为需要不同权限,策略必须在构建时包含覆盖可能路径的允许规则,或将行为拆到不同 domain/type。
把 property 当成 SELinux boolean 会产生危险误解:业务功能关闭不等于攻击者无法触达其已编译权限。
9.4 选择原则
| 需求 | Android常用做法 |
|---|---|
| User与debug产品差异 | userdebug_or_eng |
| Recovery与正常系统差异 | recovery_only/not_recovery |
| Release feature rollout | is_flag_enabled |
| Board API兼容 | starting_at_board_api/until_board_api |
| 运行时业务行为 | property/config + 固定最小策略 |
| 真正动态权限收缩 | 拆 domain、对象 label、关闭入口;不使用 boolean |
10. 分析工具
10.1 条件表查询
即使 Android 当前 bool table 为空,策略工具仍不能假设 conditional avtab 永远为空。sepolicy-check 先查 unconditional table,未匹配时再查 conditional table。
源码文件:system/sepolicy/tools/sepolicy-check.c
/* Check unconditional rules after attribute expansion. */
match = expand_and_check(s_op, key.source_type,
t_op, key.target_type,
c_op, key.target_class,
perm, policy, &policy->te_avtab);
if (match)
return match;
/* Check conditional rules after attribute expansion. */
return expand_and_check(s_op, key.source_type,
t_op, key.target_type,
c_op, key.target_class,
perm, policy, &policy->te_cond_avtab);这类工具检查“规则是否存在”,未必区分当前 branch 是否 enabled;使用前需确认工具语义。
10.2 测试Policy模型
Android tests/policy.py 初始化普通和 conditional avtab,并把两者的 allow rules 放入同一集合;expanded query 也做同样处理。
源码文件:system/sepolicy/tests/policy.py
def __InitTERules(self):
avtabIterP = self.__libsepolwrap.init_avtab(self.__policydbP)
if (avtabIterP == None):
sys.exit("Failed to initialize avtab")
self.__GetTERules(self.__policydbP, avtabIterP, self.__Rules)
self.__libsepolwrap.destroy_avtab(avtabIterP)
avtabIterP = self.__libsepolwrap.init_cond_avtab(self.__policydbP)
if (avtabIterP == None):
sys.exit("Failed to initialize conditional avtab")
self.__GetTERules(self.__policydbP, avtabIterP, self.__Rules)
self.__libsepolwrap.destroy_avtab(avtabIterP)测试输入来自 binary policy,证明 Android 分析基础设施保留 conditional compatibility;它不表示 AOSP policy 实际定义 boolean。
11. 失败边界
11.1 语法识别
.te 中的 ifelse(...)、is_flag_enabled(...)、userdebug_or_eng(...) 都是 m4。判断 policy-language conditional 时,应匹配 active bool/boolean declaration 和行首 if (,并检查最终 binary bool table。
11.2 启用条件
看到 /sys/fs/selinux/booleans、commit_pending_bools、security_set_bools() 或 te_cond_avtab 只能证明 kernel feature 存在。是否可用由 loaded policy 的 boolean count 和 security setbool allow 决定。
11.3 提交边界
写单个 boolean file 只更新 pending array。只有 commit control file 写非零值才原子应用全部 pending states。调试标准 SELinux 系统时,只修改 pending 而未 commit 不会改变 AV decision。
11.4 约束顺序
Conditional allow 在 constraint loop 之前并入 avd->allowed。即使 boolean true,MLS/constrain、role transition 和 type bounds 仍可清除 permission。
11.5 Type规则冲突
Conditional type transition 不能与 unconditional type rule 使用同一 key 产生另一个结果;true/false 分支也只能按 loader 允许的成对关系存在。错误 policy 会在 load/compile 阶段失败,而不是运行时随机选择。
12. 验证方法
12.1 Source与Binary
# 1. 源码层:AOSP platform/vendor没有active boolean语法。
rg -n \
'^[[:space:]]*(bool|boolean)[[:space:]]|^[[:space:]]*if[[:space:]]*\(' \
system/sepolicy/public \
system/sepolicy/private \
system/sepolicy/vendor \
-g '*.te'
# 2. Binary层:最终产品policy不应列出booleans。
ANDROID_SEPOLICY='out/target/product/PRODUCT/root/sepolicy'
sepolicy-analyze "$ANDROID_SEPOLICY" booleans输入分别是 Android 17 source set 与合并后的 product policy。两项都无输出,才能说明源码和最终产物都没有 boolean。第一项无法覆盖生成 CIL/device extras,第二项无法解释 boolean 来自哪个 source。
12.2 M4对比
# 同一Android宏在构建时产生不同policy文本。
case "$(uname -s)-$(uname -m)" in
Darwin-*) M4=prebuilts/build-tools/darwin-x86/bin/m4 ;;
Linux-x86_64) M4=prebuilts/build-tools/linux-x86/bin/m4 ;;
Linux-aarch64) M4=prebuilts/build-tools/linux-arm64/bin/m4 ;;
*) printf '%s\n' 'unsupported host'; exit 1 ;;
esac
for variant in user userdebug; do
printf 'variant=%s\n' "$variant"
"$M4" \
-D target_build_variant="$variant" \
-D target_full_treble=true \
-D target_exclude_build_test=false \
system/sepolicy/public/global_macros \
system/sepolicy/public/te_macros - <<'EOF' |
userdebug_or_eng(`allow demo debug_file:file read;')
EOF
sed '/^[[:space:]]*$/d'
done关键断言是 user 输出 suppression marker,userdebug 输出 allow。它证明 Android 条件在 binary policy 生成前决定,而非运行时 enabled bit。
12.3 设备只读检查
# 查看是否有boolean entries,不尝试写入系统状态。
adb shell find /sys/fs/selinux/booleans \
-mindepth 1 -maxdepth 1 -type f -print
# 查看Android明确禁止setbool的源码断言。
rg -n 'security setbool' system/sepolicy/private/domain.te设备命令预期无 entry;若 vendor 产品出现 boolean,应立即查询最终 policy、构建来源和兼容性要求,而不是尝试切换它。
13. 源码导航
| 问题 | 首选文件 | 关键符号 |
|---|---|---|
| Boolean与cond node如何存储 | kernel/common/security/selinux/ss/policydb.h、conditional.h | cond_bool_datum、cond_node |
| 表达式如何求值 | kernel/common/security/selinux/ss/conditional.c | cond_evaluate_expr |
| True/false rules如何启停 | kernel/common/security/selinux/ss/conditional.c | evaluate_cond_node |
| Conditional allow何时合并 | kernel/common/security/selinux/ss/conditional.c | cond_compute_av |
| Type transition如何查条件表 | kernel/common/security/selinux/ss/services.c | security_compute_sid |
| Boolean文件如何创建 | kernel/common/security/selinux/selinuxfs.c | sel_make_bools |
| Pending与commit如何处理 | kernel/common/security/selinux/selinuxfs.c | sel_write_bool、sel_commit_bools_write |
| Commit如何替换policy | kernel/common/security/selinux/ss/services.c | security_set_bools |
| AVC为何立即失效 | kernel/common/security/selinux/avc.c | avc_ss_reset |
| Android为何不可写 | system/sepolicy/private/domain.te | neverallow ... setbool |
| Binary中如何列出boolean | system/sepolicy/tools/sepolicy-analyze/booleans.c | booleans_func |
| Android构建条件在哪里 | system/sepolicy/public/te_macros、flagging_macros | m4条件宏 |
从标准 SELinux boolean 切换复述完整路径时,应包括:用户写一个或多个 boolean pending files;commit 文件检查 security setbool 后提交整个 values array;security server 复制 conditional policy data、审计变化、重算 true/false enabled bits、递增 sequence 并通过 RCU 安装;policy change notification flush AVC,使后续请求重新合并 conditional avtab。
在 Android 17 上,这条内核链路仍存在,但 AOSP policy 没有 boolean entries,且 neverallow 阻止任何 domain 获得 setbool。实际条件差异来自构建期 m4/Soong,必须通过不同 policy.conf/CIL 或产品重构建生效。能同时讲清“机制存在”和“平台未使用”,才算真正掌握 Boolean 条件策略。
