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
/*
* 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
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差异
| 对比项 | Neverallow | Constrain/MLSConstrain |
|---|---|---|
| 检查时机 | 构建期 | 运行时 AV 计算 |
| 主要输入 | type/attribute、class、permission | source/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
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
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
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
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
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
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
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
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
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
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
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
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
const (
MlsSens = 1
MlsCats = 1024
PolicyVers = 30
)4.4 声明生成
mls_decl 调用递归 m4 macros 生成 sensitivities、categories 与每个 sensitivity 的完整 category range。
源码文件:system/sepolicy/private/mls_decl
gen_sens(mls_num_sens)
gen_cats(mls_num_cats)
gen_levels(mls_num_sens,mls_num_cats)源码文件:system/sepolicy/private/mls_macros
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,便于查看递归输出:
# 正式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
# 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
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
attribute mlstrustedsubject;
attribute mlstrustedobject;源码文件:system/sepolicy/public/init.te
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
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
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
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
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
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
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
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
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
#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
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
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
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
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
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
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
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
attribute mlsvendorcompat;源码文件:system/sepolicy/private/mls
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
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
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 运行路径。
# 只匹配行首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。
# 查看进程与文件完整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
# 构建真实产品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 无 allow | TE/type/attribute 问题 |
| 有 allow,levels 不满足表达式 | MLS constraint |
| Source 属于 trusted subject 仍 denied | 查看该表达式是否真的有 t1 trusted exemption |
| Target 属于 trusted object 仍 create denied | create/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/mls | 19组 mlsconstrain |
| Sensitivity/category如何生成 | system/sepolicy/private/mls_decl、mls_macros | gen_sens、gen_cats |
| App level输入来自哪里 | system/sepolicy/private/seapp_contexts | levelFrom |
| Trusted属性在哪里声明 | system/sepolicy/public/attributes | mlstrustedsubject/object |
| Constraint如何挂到class | kernel/common/security/selinux/ss/policydb.h | class_datum |
| Binary表达式如何加载 | kernel/common/security/selinux/ss/policydb.c | read_cons_helper |
| 表达式如何求值 | kernel/common/security/selinux/ss/services.c | constraint_expr_eval |
| Permission何时被清除 | kernel/common/security/selinux/ss/services.c | context_struct_compute_av |
| MLS level关系如何定义 | kernel/common/security/selinux/ss/mls_types.h | eq/dom/incomp |
| Validatetrans如何执行 | kernel/common/security/selinux/ss/services.c | security_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 约束。
