Skip to content

Boolean条件策略

从 SELinux boolean table、conditional avtab、selinuxfs 提交、AVC 刷新和 Android 禁用边界解释运行时与构建期条件策略。

基于android-17.0.0_r1
AndroidSELinuxBooleanConditionalPolicyAVCM4源码阅读

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 平台策略摘录:

text
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

bash
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 booleanAndroid m4条件
条件 ownerpolicydb boolean stateSoong/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

c
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

c
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

c
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

c
/* 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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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 (...) 条件块。

bash
# 查找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

bash
# 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 为零时,目录存在不代表存在可切换项。

bash
# 设备只读检查:目录可以存在,但应无boolean entries。
adb shell ls -la /sys/fs/selinux/booleans

# commit控制文件存在也不意味着当前policy有boolean。
adb shell ls -l /sys/fs/selinux/commit_pending_bools

8.4 Binary查询 ​

Android 自带 sepolicy-analyze booleans component,直接遍历 binary policy 的 bool symbol table。

源码文件:system/sepolicy/tools/sepolicy-analyze/booleans.c

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);
}

对应的最终策略查询命令如下。

bash
# 将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

text
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

text
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 rolloutis_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

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

python
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 ​

bash
# 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对比 ​

bash
# 同一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 设备只读检查 ​

bash
# 查看是否有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.hcond_bool_datum、cond_node
表达式如何求值kernel/common/security/selinux/ss/conditional.ccond_evaluate_expr
True/false rules如何启停kernel/common/security/selinux/ss/conditional.cevaluate_cond_node
Conditional allow何时合并kernel/common/security/selinux/ss/conditional.ccond_compute_av
Type transition如何查条件表kernel/common/security/selinux/ss/services.csecurity_compute_sid
Boolean文件如何创建kernel/common/security/selinux/selinuxfs.csel_make_bools
Pending与commit如何处理kernel/common/security/selinux/selinuxfs.csel_write_bool、sel_commit_bools_write
Commit如何替换policykernel/common/security/selinux/ss/services.csecurity_set_bools
AVC为何立即失效kernel/common/security/selinux/avc.cavc_ss_reset
Android为何不可写system/sepolicy/private/domain.teneverallow ... setbool
Binary中如何列出booleansystem/sepolicy/tools/sepolicy-analyze/booleans.cbooleans_func
Android构建条件在哪里system/sepolicy/public/te_macros、flagging_macrosm4条件宏

从标准 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 条件策略。