Skip to content

Constrain约束

从 Android mlsconstrain 策略、内核 policydb 表达式栈、AV 决策掩码和多用户 category 追踪运行时约束。

基于android-17.0.0_r1
AndroidSELinuxconstrainmlsconstrainMLSMCS源码阅读

Constrain约束 ​

本文面向已经读过 安全上下文、Type与Attribute 和 Neverallow 的读者。上一篇说明 neverallow 如何在构建期阻止违规 allow;本篇转向运行期:内核已经通过 type enforcement 找到 allow 后,如何继续用 source/target Context 的 user、role、type 与 MLS range 求值,并把不满足 constrain/mlsconstrain 的 permission 从 access vector 中清除。

Android 17 平台策略没有启用普通 constrain、validatetrans 或 mlsvalidatetrans 语句,实际约束全部位于 private/mls 的 mlsconstrain。因此本文不会用不存在的 Android RBAC 案例填充篇幅,而会沿真实 MLS 策略讲清 process、socket、文件、app data、FIFO 和匿名 inode 的条件;同时保留内核对普通 constrain/validatetrans 的通用支持,帮助读者理解数据结构边界。

1. 决策位置 ​

1.1 Allow之后 ​

allow 先根据 source type、target type 与 class 生成候选 permission bits;constraint 随后检查完整 Context。表达式为 false 时,内核不是返回单独的“constraint error”,而是从 avd->allowed 清掉对应 bit。

源码文件:kernel/common/security/selinux/ss/services.c

c
/*
 * Remove any permissions prohibited by a constraint (this includes
 * the MLS policy).
 */
constraint = tclass_datum->constraints;
while (constraint) {
	if ((constraint->permissions & (avd->allowed)) &&
	    !constraint_expr_eval(policydb, scontext, tcontext, NULL,
				  constraint->expr)) {
		avd->allowed &= ~(constraint->permissions);
	}
	constraint = constraint->next;
}

循环先用 permission bitmap 判断当前 constraint 是否与候选 allow 有交集,避免无关表达式求值。若有交集且表达式失败,该 constraint 覆盖的所有 bits 都被清除。

1.2 多条约束叠加 ​

同一 permission 可以出现在多条 constraint 中。后续表达式为 true 不会把已经清除的 bit 加回来,因此所有相关约束都必须通过。

Android process 策略中 ptrace 和 share 同时属于“read operations”与“write operations”:

源码文件:system/sepolicy/private/mls

text
mlsconstrain process { getsched getsession getpgid getcap getattr ptrace share }
	     (l1 dom l2 or t1 == mlstrustedsubject);

mlsconstrain process { sigkill sigstop signal setsched setpgid setcap setrlimit ptrace share }
	     (l1 eq l2 or t1 == mlstrustedsubject);

普通 source 对 ptrace/share 必须同时满足 l1 dom l2 和 l1 eq l2,最终等价于 level equality;受信任 source 才可绕过两条。

1.3 Neverallow差异 ​

对比项NeverallowConstrain/MLSConstrain
检查时机构建期运行时 AV 计算
主要输入type/attribute、class、permissionsource/target Context 全字段
失败结果policy 编译失败permission bit 从 allowed 清除
是否进入 binary policy断言文本不作为运行决策constraint node 与表达式进入 policydb
permissive 影响不适用仍计算 denial,enforcement 可放行

给约束拒绝的操作重复添加相同 allow 不会恢复权限,因为新 allow 仍会在同一位置被 mask。

2. PolicyDB结构 ​

2.1 Class拥有约束 ​

约束按 object class 存储。每个 class_datum 有普通 permissions symbol table、constraints linked list 和 validatetrans linked list。

源码文件:kernel/common/security/selinux/ss/policydb.h

c
struct class_datum {
	u32 value;
	char *comkey;
	struct common_datum *comdatum;
	struct symtab permissions;
	struct constraint_node *constraints; /* constraints on class perms */
	struct constraint_node *validatetrans; /* special transition rules */
};

摘录在 validatetrans 字段后结束;原结构后面还有 object Context 默认 user、role、type 与 range 字段。

一次 AV query 已经知道 target class,因此可以直接从对应 class 找 constraint list,不必扫描整份策略。

2.2 Permission节点 ​

每个 constraint node 保存受约束 permission bitmap、表达式 linked list 和下一条 constraint。

源码文件:kernel/common/security/selinux/ss/constraint.h

c
struct constraint_node {
	u32 permissions; /* constrained permissions */
	struct constraint_expr *expr; /* constraint on permissions */
	struct constraint_node *next; /* next constraint */
};

一个 mlsconstrain dir { read getattr search } (...) 会成为 dir class 下的一个 node,permissions 位包含三项,expr 指向已经编译的表达式序列。

2.3 表达式节点 ​

表达式节点有五种形态:NOT、AND、OR、Context 字段比较和字段对 type/user/role names 的成员测试。

源码文件:kernel/common/security/selinux/ss/constraint.h

c
struct constraint_expr {
#define CEXPR_NOT   1
#define CEXPR_AND   2
#define CEXPR_OR    3
#define CEXPR_ATTR  4
#define CEXPR_NAMES 5
	u32 expr_type;

#define CEXPR_USER    1
#define CEXPR_ROLE    2
#define CEXPR_TYPE    4
#define CEXPR_TARGET  8
#define CEXPR_XTARGET 16
#define CEXPR_L1L2    32
#define CEXPR_L1H2    64
#define CEXPR_H1L2    128
#define CEXPR_H1H2    256
#define CEXPR_L1H1    512
#define CEXPR_L2H2    1024
	u32 attr;

#define CEXPR_EQ     1
#define CEXPR_NEQ    2
#define CEXPR_DOM    3
#define CEXPR_DOMBY  4
#define CEXPR_INCOMP 5
	u32 op;

	struct ebitmap names;
	struct type_set *type_names;
	struct constraint_expr *next;
};

CEXPR_TARGET 选择 target Context;未设置时选择 source。CEXPR_XTARGET 只供 validatetrans 第三个 task Context 使用。MLS pair constants 对应 l1/l2/h1/h2 的不同组合。

2.4 Binary加载 ​

Policy loader 从 binary policy 读取 constraint permission bitmap 和 postfix expression sequence,并验证 stack depth。

源码文件:kernel/common/security/selinux/ss/policydb.c

c
rc = next_entry(buf, fp, (sizeof(u32) * 2));
if (rc)
	return rc;
c->permissions = le32_to_cpu(buf[0]);
nexpr = le32_to_cpu(buf[1]);
le = NULL;
depth = -1;
for (j = 0; j < nexpr; j++) {
	e = kzalloc(sizeof(*e), GFP_KERNEL);
	if (!e)
		return -ENOMEM;

	rc = next_entry(buf, fp, (sizeof(u32) * 3));
	if (rc)
		return rc;
	e->expr_type = le32_to_cpu(buf[0]);
	e->attr = le32_to_cpu(buf[1]);
	e->op = le32_to_cpu(buf[2]);

	switch (e->expr_type) {
	case CEXPR_NOT:
		if (depth < 0)
			return -EINVAL;
		break;
	case CEXPR_AND:
	case CEXPR_OR:
		if (depth < 1)
			return -EINVAL;
		depth--;
		break;
	case CEXPR_ATTR:
		if (depth == (CEXPR_MAXDEPTH - 1))
			return -EINVAL;
		depth++;
		break;
	default:
		return -EINVAL;
	}
}
if (depth != 0)
	return -EINVAL;

原函数在 CEXPR_ATTR 与 default 之间还有 CEXPR_NAMES 分支,用于读取 names bitmap 与保留 type set 信息;这里专注展示 postfix depth 校验。

内核不解析 infix policy text。checkpolicy 已把表达式编译为 postfix nodes;loader 只验证序列能归约为一个布尔值。

3. 表达式求值 ​

3.1 Postfix栈 ​

constraint_expr_eval() 使用固定大小 s[CEXPR_MAXDEPTH] 作为 boolean stack。字段比较把结果 push,AND/OR pop 两个值并写回,NOT 修改栈顶。

源码文件:kernel/common/security/selinux/ss/services.c

c
struct constraint_expr *e;
int s[CEXPR_MAXDEPTH];
int sp = -1;

for (e = cexpr; e; e = e->next) {
	switch (e->expr_type) {
	case CEXPR_NOT:
		BUG_ON(sp < 0);
		s[sp] = !s[sp];
		break;
	case CEXPR_AND:
		BUG_ON(sp < 1);
		sp--;
		s[sp] &= s[sp + 1];
		break;
	case CEXPR_OR:
		BUG_ON(sp < 1);
		sp--;
		s[sp] |= s[sp + 1];
		break;
	}
}

BUG_ON(sp != 0);
return s[0];

这段摘录只保留逻辑操作节点;同一 switch 的 CEXPR_ATTR 与 CEXPR_NAMES 分支在后续小节分别展开。

CEXPR_MAXDEPTH 当前为 5。Loader 已验证深度,运行时 evaluator 可以使用固定数组而不分配表达式栈。

3.2 User/Role/Type ​

普通 constrain 可比较 source/target user、role 和 type。Role dominance 读取 role datum 的 dominates bitmap;user/type 的基本比较只有 eq/neq。

源码文件:kernel/common/security/selinux/ss/services.c

c
case CEXPR_USER:
	val1 = scontext->user;
	val2 = tcontext->user;
	break;
case CEXPR_TYPE:
	val1 = scontext->type;
	val2 = tcontext->type;
	break;
case CEXPR_ROLE:
	val1 = scontext->role;
	val2 = tcontext->role;
	r1 = policydb->role_val_to_struct[val1 - 1];
	r2 = policydb->role_val_to_struct[val2 - 1];
	switch (e->op) {
	case CEXPR_DOM:
		s[++sp] = ebitmap_get_bit(&r1->dominates, val2 - 1);
		continue;
	case CEXPR_DOMBY:
		s[++sp] = ebitmap_get_bit(&r2->dominates, val1 - 1);
		continue;
	case CEXPR_INCOMP:
		s[++sp] =
			(!ebitmap_get_bit(&r1->dominates, val2 - 1) &&
			 !ebitmap_get_bit(&r2->dominates, val1 - 1));
		continue;
	}
	break;

Android 当前 private/mls 没有使用 u1/u2/r1/r2,但内核数据结构保留通用 SELinux constraint 能力。

3.3 MLS字段 ​

Context range 有 low/high 两个 mls_level。Evaluator 根据 expression attr 选择 source/target 的 low/high,再调用 level relation。

源码文件:kernel/common/security/selinux/ss/services.c

c
case CEXPR_L1L2:
	l1 = &(scontext->range.level[0]);
	l2 = &(tcontext->range.level[0]);
	goto mls_ops;
case CEXPR_L1H2:
	l1 = &(scontext->range.level[0]);
	l2 = &(tcontext->range.level[1]);
	goto mls_ops;
case CEXPR_H1H2:
	l1 = &(scontext->range.level[1]);
	l2 = &(tcontext->range.level[1]);
	goto mls_ops;
case CEXPR_L2H2:
	l1 = &(tcontext->range.level[0]);
	l2 = &(tcontext->range.level[1]);
	goto mls_ops;

源码文件:kernel/common/security/selinux/ss/services.c

c
mls_ops:
	switch (e->op) {
	case CEXPR_EQ:
		s[++sp] = mls_level_eq(l1, l2);
		continue;
	case CEXPR_NEQ:
		s[++sp] = !mls_level_eq(l1, l2);
		continue;
	case CEXPR_DOM:
		s[++sp] = mls_level_dom(l1, l2);
		continue;
	case CEXPR_DOMBY:
		s[++sp] = mls_level_dom(l2, l1);
		continue;
	case CEXPR_INCOMP:
		s[++sp] = mls_level_incomp(l2, l1);
		continue;
	default:
		BUG();
		return 0;
	}

3.4 Type成员测试 ​

t1 == mlstrustedsubject、t2 != app_data_file_type 会编译为 CEXPR_NAMES,运行时在 names bitmap 中检查当前 Context type。

源码文件:kernel/common/security/selinux/ss/services.c

c
case CEXPR_NAMES:
	if (sp == (CEXPR_MAXDEPTH-1))
		return 0;
	c = scontext;
	if (e->attr & CEXPR_TARGET)
		c = tcontext;
	else if (e->attr & CEXPR_XTARGET) {
		c = xcontext;
		if (!c) {
			BUG();
			return 0;
		}
	}

	if (e->attr & CEXPR_USER)
		val1 = c->user;
	else if (e->attr & CEXPR_ROLE)
		val1 = c->role;
	else if (e->attr & CEXPR_TYPE)
		val1 = c->type;

	switch (e->op) {
	case CEXPR_EQ:
		s[++sp] = ebitmap_get_bit(&e->names, val1 - 1);
		break;
	case CEXPR_NEQ:
		s[++sp] = !ebitmap_get_bit(&e->names, val1 - 1);
		break;
	}
	break;

Attribute membership 在编译时已转换为 names bitmap,运行时不按名字搜索 policy text。

4. MLS关系 ​

4.1 Level结构 ​

一个 level 包含 sensitivity 编号与 category bitmap;一个 range 包含 low 和 high 两个 level。

源码文件:kernel/common/security/selinux/ss/mls_types.h

c
struct mls_level {
	u32 sens; /* sensitivity */
	struct ebitmap cat; /* category set */
};

struct mls_range {
	struct mls_level level[2]; /* low == level[0], high == level[1] */
};

单级 Context 的 low/high 相等,字符串只显示一个 level;范围 Context 则显示 low-high。

4.2 Equality ​

两个 level 只有 sensitivity 相等且 category bitmap 完全相等才满足 eq。

源码文件:kernel/common/security/selinux/ss/mls_types.h

c
static inline int mls_level_eq(const struct mls_level *l1,
			       const struct mls_level *l2)
{
	return ((l1->sens == l2->sens) &&
		ebitmap_equal(&l1->cat, &l2->cat));
}

4.3 Dominance ​

l1 dom l2 要求 sensitivity 不低于 l2,并且 l1 category set 包含 l2 全部 categories。

源码文件:kernel/common/security/selinux/ss/mls_types.h

c
static inline int mls_level_dom(const struct mls_level *l1,
				const struct mls_level *l2)
{
	return ((l1->sens >= l2->sens) &&
		ebitmap_contains(&l1->cat, &l2->cat, 0));
}

#define mls_level_incomp(l1, l2) \
	(!mls_level_dom((l1), (l2)) && !mls_level_dom((l2), (l1)))

Android build 固定生成 1 个 sensitivity 和默认 1024 个 categories;因此实际隔离主要由 category inclusion/equality 决定,而不是多 sensitivity 层级。

源码文件:system/sepolicy/build/soong/policy.go

go
const (
	MlsSens    = 1
	MlsCats    = 1024
	PolicyVers = 30
)

4.4 声明生成 ​

mls_decl 调用递归 m4 macros 生成 sensitivities、categories 与每个 sensitivity 的完整 category range。

源码文件:system/sepolicy/private/mls_decl

text
gen_sens(mls_num_sens)
gen_cats(mls_num_cats)
gen_levels(mls_num_sens,mls_num_cats)

源码文件:system/sepolicy/private/mls_macros

text
define(`gen_cats',`decl_cats(0,decr($1))')
define(`gen_sens',`
decl_sens(0,decr($1))
dominance { gen_dominance(0,decr($1)) }
')
define(`gen_levels',`decl_levels(0,decr($1),decr($2))')

最小实验把 categories 缩到 4,便于查看递归输出:

bash
# 正式Android构建使用1个sensitivity和1024个categories;此处仅缩小展示。
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

"$M4" \
  -D mls_num_sens=1 \
  -D mls_num_cats=4 \
  system/sepolicy/private/mls_macros \
  system/sepolicy/private/mls_decl | \
  sed '/^#/d;/^[[:space:]]*$/d'

预期包含 sensitivity s0、category c0 到 c3 和 level s0:c0.c3。实验验证声明生成,不验证 app UID 到具体 category 的分配。

5. Android输入 ​

5.1 LevelFrom ​

App process 与 app data 的 MLS level 来自 seapp contexts 输出。levelFrom 可选择 none、app、user 或 all。

源码文件:system/sepolicy/private/seapp_contexts

bash
# levelFrom and level are used to determine the level (sensitivity + categories)
# for MLS/MCS.
# levelFrom=none omits the level.
# levelFrom=app determines the level from the process UID.
# levelFrom=user determines the level from the user ID.
# levelFrom=all determines the level from both UID and user ID.

当前 app 规则按兼容性和职责选择不同 level source。

源码文件:system/sepolicy/private/seapp_contexts

text
user=_isolated domain=isolated_app levelFrom=user
user=_sdksandbox domain=sdk_sandbox_34 type=sdk_sandbox_data_file levelFrom=all
user=_app seinfo=app_zygote domain=app_zygote levelFrom=user
user=_app minTargetSdkVersion=37 domain=untrusted_app type=app_data_file levelFrom=all
user=_app minTargetSdkVersion=26 domain=untrusted_app_27 type=app_data_file levelFrom=user

不要把某个 Android 版本的 category 计算公式硬编码进 constraint 解释。策略只看到最终 Context range;实际设备应通过 ps -AZ、ls -Z 观察 source/target level。

5.2 Trusted Subject ​

mlstrustedsubject 是 type attribute,允许特定系统进程绕过多条 source-side MLS 约束。

源码文件:system/sepolicy/public/attributes

text
attribute mlstrustedsubject;
attribute mlstrustedobject;

源码文件:system/sepolicy/public/init.te

text
type init, domain, mlstrustedsubject;
type init_exec, system_file_type, exec_type, file_type;
type init_tmpfs, file_type;

源码文件:system/sepolicy/private/system_server.te

text
typeattribute system_server coredomain;
typeattribute system_server mlstrustedsubject;

这个 attribute 权限面极大,不能作为一般 denial 的快捷修复。

5.3 Trusted Object ​

共享系统对象可加入 mlstrustedobject,允许低 level source 读取或高 level source 写入某些非 app data 对象。

源码文件:system/sepolicy/public/file.te

text
type anr_data_file, file_type, data_file_type, core_data_file_type, mlstrustedobject;
type tombstone_data_file, file_type, data_file_type, core_data_file_type, mlstrustedobject;
type apk_tmp_file, file_type, data_file_type, core_data_file_type, mlstrustedobject;
type ota_package_file, file_type, data_file_type, core_data_file_type, mlstrustedobject;

但 file create/relabel constraint 明确不豁免 trusted object:新对象仍必须 single-level,并与 creator level 相等或由 trusted subject 创建。

5.4 反向约束 ​

Android 用 neverallow 防止普通 app 通过加入 trusted attribute 绕过多用户隔离。

源码文件:system/sepolicy/private/app_neverallows.te

text
neverallow all_untrusted_apps mlstrustedsubject:process fork;

由于 policy language 没有直接对 attribute assignment 写 neverallow,这条规则利用 app self-fork allow 与 attribute target 形成冲突,间接禁止成员关系。

6. Process约束 ​

6.1 Transition ​

Process transition 要求 source 与 target 的 high/low level 都相等,或 source type 属于 mlstrustedsubject。

源码文件:system/sepolicy/private/mls

text
mlsconstrain process { transition dyntransition }
	     ((h1 eq h2 and l1 eq l2) or t1 == mlstrustedsubject);

Type transition 规则只计算新 domain/type;如果新 process Context 的 range 不满足这里,PROCESS__TRANSITION 会被从候选 allow 中清除。

6.2 Read与Write ​

Process metadata read 要求 source low level dominates target low level;signal、setrlimit 等写操作要求 equality。

源码文件:system/sepolicy/private/mls

text
mlsconstrain process { getsched getsession getpgid getcap getattr ptrace share }
	     (l1 dom l2 or t1 == mlstrustedsubject);

mlsconstrain process { sigkill sigstop signal setsched setpgid setcap setrlimit ptrace share }
	     (l1 eq l2 or t1 == mlstrustedsubject);

ptrace/share 两次出现不是重复文本错误。它们既读取目标状态也形成强交互,普通 source 最终必须 level equal。

6.3 条件路径 ​

只有 candidate allow 中包含相关 process permission 时才求值。没有 allow source target:process signal 时,请求已在 TE 层拒绝;约束不会产生额外工作,也不能单独授予权限。

7. Socket约束 ​

7.1 创建与重标 ​

Socket create/relabel 要求 source 与 socket range 完全一致,或 source trusted。Socket 通常继承 creator range,因此正常 self socket 创建可满足 equality。

源码文件:system/sepolicy/private/mls

text
mlsconstrain socket_class_set { create relabelfrom relabelto }
	     ((h1 eq h2 and l1 eq l2) or t1 == mlstrustedsubject);

7.2 Unix通信 ​

Unix datagram send 与 stream connect 要求双方 low level 相等,或任一方是 trusted subject。

源码文件:system/sepolicy/private/mls

text
mlsconstrain unix_dgram_socket { sendto }
	     (l1 eq l2 or t1 == mlstrustedsubject or t2 == mlstrustedsubject);

mlsconstrain unix_stream_socket { connectto }
	     (l1 eq l2 or t1 == mlstrustedsubject or t2 == mlstrustedsubject);

这里的 t2 membership 针对 target socket Context type。受信任服务端可以接收不同 app levels 的连接;普通 app 之间仍要求 equality。

7.3 Vsock差异 ​

Vsock 约束覆盖 create、bind、connect、listen、accept 与数据操作,但只有 source trusted exemption,没有 target trusted exemption。

源码文件:system/sepolicy/private/mls

text
mlsconstrain vsock_socket {
   ioctl read write create getattr setattr lock relabelfrom relabelto append map
   bind connect listen accept getopt setopt shutdown recvfrom sendto name_bind
} (l1 eq l2 or t1 == mlstrustedsubject);

因此“目标是 system_server”不能自动豁免 vsock source;必须按这一条实际表达式分析。

7.4 Binder未约束 ​

Android 17 的 Binder MLS constraint 仍被注释,因为 app categories 按 app/user 隔离,而 apps 预期可以通过 Binder 互调。

源码文件:system/sepolicy/private/mls

bash
#mlsconstrain binder call
#	(l1 eq l2 or t1 == mlstrustedsubject or t2 == mlstrustedsubject);

Binder 访问当前主要依赖 type enforcement、service contexts 与 app-domain policy,不能声称 Binder call 受这条 MLS constraint 保护。

8. 文件约束 ​

8.1 Create与Relabel ​

新目录/文件必须是 single-level,即 target low 等于 target high;source low 还要等于 target low,除非 source trusted。

源码文件:system/sepolicy/private/mls

text
mlsconstrain dir_file_class_set { create relabelfrom relabelto }
	     (l2 eq h2 and (l1 eq l2 or t1 == mlstrustedsubject));

注释明确要求不要为 mlstrustedobject 加豁免。Trusted object 允许后续跨 level 访问,不代表可以创建任意 range 的对象。

8.2 Anonymous Inode ​

匿名 inode 所有列出的操作要求 level equality,或 target type 属于 mlstrustedobject。

源码文件:system/sepolicy/private/mls

text
mlsconstrain anon_inode {
    ioctl read write create getattr setattr lock relabelfrom relabelto append
    map unlink link rename execute open execmod
}
	     (l1 eq l2 or t2 == mlstrustedobject);

当前策略并非“匿名 inode 无任何豁免”;真实豁免在 target trusted object,而不是 source trusted subject。

8.3 App Data只卡Open ​

App data 的普通 file/sock_file 约束只限制 open 和 metadata/lifecycle 操作,不限制已有 fd 上的 read/write。策略注释明确说明这是为了允许使用已打开 fd。

源码文件:system/sepolicy/private/mls

text
mlsconstrain dir { open search getattr setattr rename add_name remove_name reparent rmdir }
	     (t2 != app_data_file_type or l1 dom l2 or t1 == mlstrustedsubject);
mlsconstrain { file sock_file } { open setattr unlink link rename }
	     ((t2 != app_data_file_type and t2 != appdomain_tmpfs)
	      or l1 dom l2 or t1 == mlstrustedsubject);

这条设计把“谁能按路径打开 app private data”与“谁能使用合法传递的已有 fd”分开。仅观察 read/write allow 无法判断是否能自行 open 文件。

8.4 Symlink双规则 ​

普通 app data symlink 要求 equality;privapp data 与 appdomain tmpfs 为兼容性继续使用 dominance。两条 constraint 同时存在,但各自第一个 type 条件把不相关 target 快速放行。

源码文件:system/sepolicy/private/mls

text
mlsconstrain lnk_file { open setattr unlink link rename read }
	     ((t2 != app_data_file_type or t2 == privapp_data_file)
	      or l1 eq l2 or t1 == mlstrustedsubject);

mlsconstrain lnk_file { open setattr unlink link rename read }
	     ((t2 != privapp_data_file and t2 != appdomain_tmpfs)
	      or l1 dom l2 or t1 == mlstrustedsubject);

8.5 非App对象 ​

非 app data 的 read 要求 source dominates object,或 source/object trusted;write 要求 equality,或 source/object trusted。

源码文件:system/sepolicy/private/mls

text
mlsconstrain { file lnk_file sock_file chr_file blk_file } { read getattr execute }
	     (t2 == app_data_file_type or t2 == appdomain_tmpfs
	      or l1 dom l2 or t1 == mlstrustedsubject or t2 == mlstrustedobject);

mlsconstrain { file lnk_file sock_file chr_file blk_file }
    { write setattr append unlink link rename }
	     (t2 == app_data_file_type or t2 == appdomain_tmpfs
	      or l1 eq l2 or t1 == mlstrustedsubject or t2 == mlstrustedobject);

开头的 app data/type 条件不是豁免整个安全模型,而是把这些 target 留给前面的专用 open/manipulation constraints。

8.6 FIFO例外 ​

Unnamed pipe 常以创建进程 domain type 标注,并通过 Binder 或 local socket 传递。FIFO constraint 因此允许 target type 属于 domain。

源码文件:system/sepolicy/private/mls

text
mlsconstrain fifo_file { read getattr }
	     (l1 dom l2 or t1 == mlstrustedsubject
	      or t2 == mlstrustedobject or t2 == domain);

mlsconstrain fifo_file { write setattr append unlink link rename }
	     (l1 eq l2 or t1 == mlstrustedsubject
	      or t2 == mlstrustedobject or t2 == domain);

9. Trusted边界 ​

9.1 主体豁免 ​

即使 domain 属于 mlstrustedsubject,Android 仍用 neverallow 限制其访问 private app data,防止 MLS override 变成通用数据访问能力。

源码文件:system/sepolicy/private/mlstrustedsubject.te

text
neverallow {
  mlstrustedsubject
  -artd
  -installd
} {
  app_data_file
  privapp_data_file
}:file ~{ read write map getattr ioctl lock append };

后续规则还只给 system_server、adbd、runas、zygote 等少数 trusted domains 搜索 app data directories。Constraint exemption 与 TE/neverallow 边界共同组成最终权限。

9.2 目标豁免 ​

把 file type 加入 mlstrustedobject 会豁免非 app read/write MLS checks,但不会自动授予任何 TE allow,也不会豁免 create/relabel constraint。修改前必须搜索所有 t2 == mlstrustedobject 消费者。

9.3 Vendor兼容 ​

Android 还保留 mlsvendorcompat,只用于旧 vendor image 上的 app 兼容,允许其读取部分 platform data directories。

源码文件:system/sepolicy/private/attributes

text
attribute mlsvendorcompat;

源码文件:system/sepolicy/private/mls

text
mlsconstrain dir { read getattr search }
	     (t2 == app_data_file_type or l1 dom l2
	      or t1 == mlstrustedsubject or t2 == mlstrustedobject
	      or (t1 == mlsvendorcompat
	          and (t2 == system_data_file or t2 == user_profile_root_file)));

Domain policy 用 neverallow 防止新 domain 随意获得该兼容 attribute。

源码文件:system/sepolicy/private/domain.te

text
neverallow domain mlsvendorcompat:process fork;

10. 转换校验 ​

10.1 第三个Context ​

内核支持 validatetrans:old Context、new Context 和执行 transition 的 task Context 分别传给 evaluator,第三个 Context 通过 CEXPR_XTARGET 引用。

源码文件:kernel/common/security/selinux/ss/services.c

c
constraint = tclass_datum->validatetrans;
while (constraint) {
	if (!constraint_expr_eval(policydb, &oentry->context,
				  &nentry->context, &tentry->context,
				  constraint->expr)) {
		if (user)
			rc = -EPERM;
		else
			rc = security_validtrans_handle_fail(policy,
							oentry,
							nentry,
							tentry,
							tclass);
		goto out;
	}
	constraint = constraint->next;
}

失败时 enforcing 返回 -EPERM,permissive 可以记录后放行。

10.2 Android未启用 ​

Android 17 的 system/sepolicy/{public,private,vendor} 中没有 active validatetrans、mlsvalidatetrans 或普通 constrain 语句。理解内核支持有助于读源码,但不能把它写成当前 Android 运行路径。

bash
# 只匹配行首active语句,排除注释中的Binder示例。
rg -n '^[[:space:]]*(constrain|validatetrans|mlsvalidatetrans)[[:space:]]' \
  system/sepolicy/public \
  system/sepolicy/private \
  system/sepolicy/vendor

预期无输出。Active runtime constraints 来自 private/mls 的 mlsconstrain。

11. 排错路径 ​

11.1 普通AVC外观 ​

Constraint loop 只清除 avd->allowed,没有像 type bounds 路径那样调用 security_dump_masked_av(..., "bounds")。因此普通 constraint denial 通常仍表现为标准 AVC denied,不直接注明具体表达式。

遇到“源码与 binary policy 已有 allow,但设备仍 denied”时,应把 constrain 纳入检查,而不是继续增加重复 allow。

11.2 Context四元组 ​

先记录 source/target 完整 Context,尤其是 low/high categories。

bash
# 查看进程与文件完整SELinux Context。
adb shell ps -AZ | grep '目标进程名'
adb shell ls -Zd /data/user/0/目标路径
adb shell ls -Zd /data/user/10/目标路径

# 保留对应AVC中的scontext、tcontext、tclass和permission。
adb shell dmesg | grep 'avc:  denied' | tail -30

输入是运行时 process/file labels 与 denial。关键断言是 type allow 存在时,source/target level 是否满足相关 class/permission 的 MLS expression。它不能单独证明 type allow 存在,需要结合 binary policy 查询。

11.3 查询Policy ​

bash
# 构建真实产品platform policy和CIL。
source build/envsetup.sh
lunch PRODUCT-userdebug
m plat_sepolicy.conf plat_sepolicy.cil

# 查看m4后的active约束和CIL中的约束节点。
rg -n '^mlsconstrain' \
  out/soong/.intermediates/system/sepolicy -g '*plat_sepolicy.conf*'
rg -n 'mlsconstrain|constrain' \
  out/soong/.intermediates/system/sepolicy -g '*.cil'

11.4 判断顺序 ​

现象优先判断
Binary policy 无 allowTE/type/attribute 问题
有 allow,levels 不满足表达式MLS constraint
Source 属于 trusted subject 仍 denied查看该表达式是否真的有 t1 trusted exemption
Target 属于 trusted object 仍 create deniedcreate/relabel 明确不豁免 trusted object
App data fd 已传入但 read 成功、按路径 open 失败app data 只约束 open 的设计
Binder 跨 level 成功当前 Binder MLS constraint 被注释

11.5 修复原则 ​

不要为了单个 denial 直接添加 mlstrustedsubject 或 mlstrustedobject。正确修复通常属于以下之一:

  • Source/target level 标注错误:修正 seapp contexts、创建标签或 relabel 流程;
  • 本应传递 fd:避免让消费者自行按路径 open private app data;
  • Type 被误归入 app data/trusted attribute:修正 attribute 建模;
  • 真正的跨用户系统服务:证明职责后使用已有 trusted domain 或最小化新的 trusted subject,并补 neverallow 边界;
  • 仅缺 type allow:添加最窄 allow,但仍验证 constraint 能通过。

12. 反向验证 ​

12.1 声明实验 ​

缩小版 m4 实验的输入是 1 个 sensitivity 与 4 个 categories。断言是宏生成 s0、c0..c3 和完整 level range。它证明 Android 的 MLS declarations 由构建变量驱动,不证明设备 app Context 使用哪些 category bits。

12.2 运行标签 ​

设备实验应选择同一 app 在两个 Android user 下的 process/data Context,比较 category sets,再尝试由一个 user 的进程访问另一个 user 的路径。关键断言是 type 可能相同,但 levels 不满足 app-data open constraint;实验还受 DAC、mount namespace 和路径可见性影响,不能把所有失败归因于 MLS。

12.3 源码闭环 ​

内核闭环由四段源码组成:policydb loader 把 constraint 读入 class,context_struct_compute_av() 先生成 type allow,constraint loop 清除 bits,constraint_expr_eval() 根据 Context 与 MLS bitmap 返回 boolean。四段共同说明约束是可加载、可缓存的运行时决策,而不是构建注释。

13. 源码导航 ​

问题首选文件关键符号
Android有哪些active约束system/sepolicy/private/mls19组 mlsconstrain
Sensitivity/category如何生成system/sepolicy/private/mls_decl、mls_macrosgen_sens、gen_cats
App level输入来自哪里system/sepolicy/private/seapp_contextslevelFrom
Trusted属性在哪里声明system/sepolicy/public/attributesmlstrustedsubject/object
Constraint如何挂到classkernel/common/security/selinux/ss/policydb.hclass_datum
Binary表达式如何加载kernel/common/security/selinux/ss/policydb.cread_cons_helper
表达式如何求值kernel/common/security/selinux/ss/services.cconstraint_expr_eval
Permission何时被清除kernel/common/security/selinux/ss/services.ccontext_struct_compute_av
MLS level关系如何定义kernel/common/security/selinux/ss/mls_types.heq/dom/incomp
Validatetrans如何执行kernel/common/security/selinux/ss/services.csecurity_compute_validatetrans

从一条 app data open denial 复述完整路径时,应包括:seapp contexts 为进程和数据目录产生最终 levels;type allow 先进入 avd->allowed;file/app-data mlsconstrain 检查 target type 与 l1 dom l2;若 source 不 trusted 且 level 不支配,内核清除 open bit;AVC 因最终 allowed 缺少 open 而拒绝。已有 fd 上的 read 不属于这条 app-data constraint,因此可能得到不同结果。

能够继续解释为什么 create 不豁免 trusted object、vsock 只豁免 trusted source、Binder constraint 当前未启用,以及多条 constraint 对同一 permission 必须全部通过,才算真正掌握 Android Constrain 约束。