RBAC角色
本文面向已经读过 安全上下文 和 类型强制 的读者。前文说明 u:r:system_server:s0 中的 type/domain 是 TE 的主要输入,但 role 并不是一个只为显示四元组而存在的字符串。本文从 Android 17 的 roles_decl、roles、users 出发,追到内核 policydb_context_isvalid()、context_struct_compute_av() 和 security_compute_sid(),回答三个问题:user 是否允许进入某个 role,role 是否允许关联某个 type,进程转换改变 role 时为什么还要额外的 role allow。
Android 平台策略的角色配置非常精简:常见进程 context 使用 u:r:<domain>:...,对象 context 使用 u:object_r:<type>:...。但“配置精简”不等于“内核忽略 RBAC”。读完后,你应能解释 object_r 在 context validation 中为何走特殊分支,指出 role r types domain; 对新增 domain 的影响,并从一次 process transition 的 access vector 中找到 role 变化被清除的具体代码。
1. 角色关系
Android 17 平台策略把 user、role 和 type 的关系压缩为三条核心声明:
源码文件:
system/sepolicy/private/roles_declsystem/sepolicy/public/rolessystem/sepolicy/private/users
role r;
role r types domain;
user u roles { r } level s0 range s0 - mls_systemhigh;三条语句分别拥有不同状态:role r 创建角色符号;role r types domain 把所有属于 domain attribute 的 type 放入角色授权集合;user u roles { r } 把角色加入 SELinux user 的授权位图,并设置默认 MLS level/range。它们不是同一条声明的三种写法。
| 关系 | 策略表达 | 内核结构 | 失败后果 |
|---|---|---|---|
| user → role | user u roles { r } | user_datum.roles | context 无效 |
| role → type | role r types domain | role_datum.types | context 无效 |
| role → new role | allow old_role new_role; | role_allow 链 | process transition 权限被清除 |
| role transition | role_transition ... | policydb.role_tr | 新 context 沿用默认 role |
Android 当前平台策略通常不会在进程间切换多个业务角色,所以第三、第四类关系很少出现在 Android sepolicy 源码中;内核仍实现它们,以支持完整 SELinux policy 语义。
图中的 domain 是 attribute 集合,不是一个具体运行 domain。把新 type 加入 domain 后,role r types domain 会使它成为 r 可用的 type;但该进程能否通过 exec 进入这个 domain,仍由 TE transition、entrypoint、MLS 和其他约束决定。
2. 上下文结构
2.1 四字段
内核 security server 使用 struct context 保存 user、role、type 和 MLS range。role 与 type 是两个独立整数 ID,不能从 type 名反推出 role。
源码文件:kernel/common/security/selinux/ss/context.h
相关结构:context
struct context {
u32 user;
u32 role;
u32 type;
u32 len;
struct mls_range range;
char *str;
};当 context 能映射到当前 policy 时,user/role/type/range 是权威结构化状态;str 只用于无法映射时保存原始字符串。AVC 和 constraint evaluator 消费整数/bitmap,不会在每次访问时重新拆分冒号字符串。
2.2 role与user结构
policydb 为 role 保存内部值、bounds、支配集合和允许的 type 位图;为 user 保存允许的 role 位图和 MLS 范围。
源码文件:kernel/common/security/selinux/ss/policydb.h
相关结构:role_datum、user_datum
struct role_datum {
u32 value;
u32 bounds;
struct ebitmap dominates;
struct ebitmap types;
};
struct user_datum {
u32 value;
u32 bounds;
struct ebitmap roles;
struct mls_range range;
struct mls_level dfltlevel;
};role_datum.types 是 role r types domain 的运行期结果,user_datum.roles 是 user u roles { r } 的结果。dominates 和 bounds 支持更完整的角色关系,即使 Android 当前策略没有建立复杂角色层级,内核数据结构也不会因此删除这些字段。
2.3 transition结构
角色转换与 TE type transition 分开存储。role transition key 由当前 role、可执行文件或新对象 type、target class 构成;结果是 new role。role allow 则只描述当前 role 到 new role 是否允许。
源码文件:kernel/common/security/selinux/ss/policydb.h
相关结构:role_trans_key、role_trans_datum、role_allow
struct role_trans_key {
u32 role;
u32 type;
u32 tclass;
};
struct role_trans_datum {
u32 new_role;
};
struct role_allow {
u32 role;
u32 new_role;
struct role_allow *next;
};一个 role_transition 规则可以为新 context 选择 role,但 process transition 真正获准时,若新旧 role 不同,还必须命中 role_allow。选择和授权是两个阶段,与 TE 中 type_transition 和 allow ... process transition 的分工相似。
3. Context校验
3.1 基本范围
字符串解析得到 user/role/type 编号后,policydb_context_isvalid() 先检查三个编号是否在符号表范围内,再检查关系集合。
源码文件:kernel/common/security/selinux/ss/policydb.c
相关函数:policydb_context_isvalid
int policydb_context_isvalid(
struct policydb *p, struct context *c)
{
struct role_datum *role;
struct user_datum *usrdatum;
if (!c->role || c->role > p->p_roles.nprim)
return 0;
if (!c->user || c->user > p->p_users.nprim)
return 0;
if (!c->type || c->type > p->p_types.nprim)
return 0;
// ... role/type、user/role 和 MLS 校验。
return 1;
}因此一个形状正确的 u:r:missing_type:s0 仍会失败:type 不在 policy symbol table 中。反过来,user、role、type 都存在也不够,它们还必须形成被授权的组合。
3.2 role到type
对非 object_r context,内核从 role_val_to_struct 取角色,再检查 role->types 是否包含当前 type。
源码文件:kernel/common/security/selinux/ss/policydb.c
相关位置:role/type validation
if (c->role != OBJECT_R_VAL) {
role = p->role_val_to_struct[c->role - 1];
if (!role ||
!ebitmap_get_bit(&role->types, c->type - 1))
return 0;
usrdatum = p->user_val_to_struct[c->user - 1];
if (!usrdatum)
return 0;
if (!ebitmap_get_bit(
&usrdatum->roles, c->role - 1))
return 0;
}Android 的 role r types domain; 使所有 domain 成员通过第一项关系;user u roles { r } 使 u:r:<domain> 通过第二项关系。若新进程 type 忘记加入 domain,即使 TE allow 看似完整,context 仍可能因 role/type 组合无效而无法建立。
3.3 MLS校验
RBAC 关系通过后,函数还调用 mls_context_isvalid()。所以 u:r:domain:s0:bad_category 不能因为 role/type 合法就被接受。
if (!mls_context_isvalid(p, c))
return 0;
return 1;Context validation 是 user、role、type、MLS 的串联约束,不是 RBAC 单独决定最终访问。TE allow 只在 context 已经成为合法 SID 后参与 access vector 计算。
4. object_r
4.1 特殊值
policydb 明确定义 object_r 的名称和值,它不是 Android 私有的普通 role 声明。
源码文件:kernel/common/security/selinux/ss/policydb.h
相关定义:OBJECT_R、OBJECT_R_VAL
#define OBJECT_R "object_r"
#define OBJECT_R_VAL 1policydb_context_isvalid() 对 OBJECT_R_VAL 跳过 role→type 和 user→role 位图校验,随后仍执行 MLS validation。这是对象 role 可以搭配大量 file/service/property type 的内核依据。
4.2 默认选择
创建新 context 时,security server 根据 class 的 default_role 决定沿用 source/target role;如果没有显式默认,process 与 socket 使用 source role,其他对象默认使用 OBJECT_R_VAL。
源码文件:kernel/common/security/selinux/ss/services.c
相关位置:security_compute_sid 的 role 选择
if (cladatum && cladatum->default_role == DEFAULT_SOURCE) {
newcontext.role = scontext->role;
} else if (cladatum &&
cladatum->default_role == DEFAULT_TARGET) {
newcontext.role = tcontext->role;
} else {
if ((tclass == policydb->process_class) || sock)
newcontext.role = scontext->role;
else
newcontext.role = OBJECT_R_VAL;
}所以 object_r 不只是输出格式的占位符:它是 security server 为大多数非进程、非 socket 新对象选择的具体内部 role。访问判断主要依赖 type/class,但 context 合法性与转换计算仍认识这个特殊 role。
4.3 初始SID
Android 的 initial SID contexts 也展示了这种分工:kernel task 使用 r,security、unlabeled、fs、file、port、netif 等对象使用 object_r。
源码文件:system/sepolicy/private/initial_sid_contexts
sid kernel u:r:kernel:s0
sid security u:object_r:kernel:s0
sid unlabeled u:object_r:unlabeled:s0
sid fs u:object_r:labeledfs:s0
sid file u:object_r:unlabeled:s0
sid port u:object_r:port:s0
sid netif u:object_r:netif:s0
sid node u:object_r:node:s0这里 security 对象的 type 也叫 kernel,但 role 是 object_r;这说明同名 type 在不同 class/context 中不能仅凭 type 名判断主体或客体身份。
5. 访问决策
5.1 TE先合并
context_struct_compute_av() 先根据 source/target type 及其 attribute 集合查询 TE avtab,把 allow、auditallow、auditdeny 与 conditional rules 合并到 av_decision。
源码文件:kernel/common/security/selinux/ss/services.c
相关函数:context_struct_compute_av
avd->allowed = 0;
avd->auditallow = 0;
avd->auditdeny = 0xffffffff;
avkey.target_class = tclass;
avkey.specified = AVTAB_AV | AVTAB_XPERMS;
sattr = &policydb->type_attr_map_array[
scontext->type - 1];
tattr = &policydb->type_attr_map_array[
tcontext->type - 1];
ebitmap_for_each_positive_bit(sattr, snode, i) {
ebitmap_for_each_positive_bit(tattr, tnode, j) {
avkey.source_type = i + 1;
avkey.target_type = j + 1;
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;
}
cond_compute_av(
&policydb->te_cond_avtab,
&avkey, avd, xperms);
}
}这一步说明 Android 日常访问决策主要由 TE type/attribute 驱动。role 并没有替代 TE;它在后续 constraint 和 process role change 检查中收紧已经得到的 allowed bit。
5.2 Constraint收紧
TE 合并完成后,class constraints 会检查 user、role、type 或 MLS 表达式;失败的 constraint 会从 avd->allowed 清除对应 permission。
源码文件:kernel/common/security/selinux/ss/services.c
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;
}因此“存在 allow”不保证 permission 最终留在 access vector 中。RBAC/MLS constraint 可以在 TE 授权之后把 bit 清除,AVC 最终缓存的是约束处理后的决策。
5.3 role变化
若当前检查是 process transition,TE 已允许 transition 类 permission,且 source role 与 target role 不同,内核还会搜索 policydb->role_allow。没有匹配时会删除全部 process transition permissions。
源码文件:kernel/common/security/selinux/ss/services.c
相关位置:process role change check
if (tclass == policydb->process_class &&
(avd->allowed & policydb->process_trans_perms) &&
scontext->role != tcontext->role) {
for (ra = policydb->role_allow; ra; ra = ra->next) {
if (scontext->role == ra->role &&
tcontext->role == ra->new_role)
break;
}
if (!ra)
avd->allowed &= ~policydb->process_trans_perms;
}Android 常见转换都是 r→r,这个分支通常不会改变结果;这只是当前策略数据带来的事实,不是内核特判 Android。若未来策略引入第二个进程角色,必须同时维护 role/type、user/role 和 role allow 三类关系。
6. 新Context
6.1 默认字段
security server 计算 transition/member/change context 时,user、role、type、MLS 各有独立默认和规则查找过程。role 在 type 之前确定,随后 type transition 和 role transition 可以分别覆盖默认值。
源码文件:kernel/common/security/selinux/ss/services.c
相关位置:security_compute_sid 的 context 构造
if (cladatum && cladatum->default_role == DEFAULT_SOURCE) {
newcontext.role = scontext->role;
} else if (cladatum &&
cladatum->default_role == DEFAULT_TARGET) {
newcontext.role = tcontext->role;
} else {
if ((tclass == policydb->process_class) || sock)
newcontext.role = scontext->role;
else
newcontext.role = OBJECT_R_VAL;
}
avkey.source_type = scontext->type;
avkey.target_type = tcontext->type;
avkey.target_class = tclass;
avkey.specified = specified;
avnode = avtab_search_node(
&policydb->te_avtab, &avkey);
if (avnode)
newcontext.type = avnode->datum.u.data;
else if ((tclass == policydb->process_class) || sock)
newcontext.type = scontext->type;
else
newcontext.type = tcontext->type;没有 type transition 时,process/socket 默认沿用 source type,普通对象默认沿用 related target type;role 的默认规则类似但独立。不能把“新文件继承父目录 type”当成适用于所有 class 的唯一规则。
6.2 role_transition
当 operation 是 transition,内核用 source role、target executable/object type 和 class 查询 role_tr;命中时覆盖 new context role。
if (specified & AVTAB_TRANSITION) {
struct role_trans_datum *rtd;
struct role_trans_key rtk = {
.role = scontext->role,
.type = tcontext->type,
.tclass = tclass,
};
rtd = policydb_roletr_search(policydb, &rtk);
if (rtd)
newcontext.role = rtd->new_role;
}Android 当前平台策略没有复杂 role_transition 主线,但这段代码说明角色变化并不是通过 type_transition 隐式完成。两种规则分别修改 newcontext.role 和 newcontext.type。
6.3 最终验证
user、role、type 和 MLS 都计算完成后,内核再次调用 policydb_context_isvalid();失败不会把一个部分合法的 context 塞入 sidtab。
if (!policydb_context_isvalid(
policydb, &newcontext)) {
rc = compute_sid_handle_invalid_context(
policy, sentry, tentry,
tclass, &newcontext);
if (rc)
goto out_unlock;
}
if (context_equal(tcontext, &newcontext))
*out_sid = tsid;
else
rc = sidtab_context_to_sid(
sidtab, &newcontext, out_sid);最终 SID 分配是 consumer boundary:在此之前只是候选结构;通过 validity 后,sidtab 才返回可供 inode/task hook 使用的 SID。
7. 构建顺序
Soong 显式规定策略输入顺序:attributes 与 .te 在前,随后是 roles_decl、roles、users,最后才是 initial contexts。这样 role 可以引用已声明的 domain attribute,user 可以引用已声明的 role。
源码文件:system/sepolicy/build/soong/policy.go
相关变量:policyConfOrder
var policyConfOrder = []string{
"security_classes",
"initial_sids",
"access_vectors",
"global_macros",
"neverallow_macros",
"mls_macros",
"mls_decl",
"mls",
"policy_capabilities",
"te_macros",
"attributes|*.te",
"roles_decl",
"roles",
"users",
"initial_sid_contexts",
"fs_use",
"genfs_contexts",
"port_contexts",
}构建器再按该序列稳定排序输入并交给 m4。缺少角色、重复符号、无效 role/type 组合会在 checkpolicy/secilc 阶段阻断构建,而不是留到设备启动后产生 AVC denial。
8. 失败边界
| 现象 | 第一检查点 | 状态 owner | 不能直接推导 |
|---|---|---|---|
| context 中 role 名不存在 | p_roles symbol table | policydb | 不是 TE allow 缺失 |
u:r:new_type:s0 无效 | role r types ... 与 type attribute | role_datum.types | type 已声明不代表 role 已授权 |
| SELinux user 不能使用 role | user ... roles | user_datum.roles | Linux UID 不能替代 SELinux user |
对象 context 使用 object_r | OBJECT_R_VAL 特殊分支 | policydb context validation | 不代表 role 字段完全被所有路径忽略 |
| process transition 有 allow 仍失败 | role change、constraint、MLS、entrypoint | security server/AVC | 一条 TE allow 不保证最终 transition bit 保留 |
| 新对象 role 与预期不同 | class default_role、role_transition | security_compute_sid() | type_transition 只改变 type |
id -Z 显示 r | proc/LSM attribute 输出 | task cred SID | 不代表 role 独立授予任何 file permission |
RBAC 问题通常发生在 context 创建、转换或验证阶段。普通 file read denial 中若 source/target context 都已经合法,最先检查的仍是 TE class/permission 和 MLS constraint,而不是凭 role 字段猜权限。
9. 反向测试
9.1 Context格式测试
APEX sepolicy 测试加载真实 precompiled_sepolicy,再把 file context 行交给检查器。完整的 u:object_r:vendor_file:s0 应通过,缺少 level 或使用未知 type 应失败。
源码文件:system/sepolicy/tests/apex_sepolicy_tests_test.py
相关测试:test_parse_lines、test_unknown_label
def test_parse_lines(self):
self.assert_error(
'./path1 invalid_contexts',
r'Error: invalid file_contexts: .*')
self.assert_error(
'./path1 u:object_r:vendor_file',
r'Error: invalid file_contexts: .*')
self.assert_ok(
'./path1 u:object_r:vendor_file:s0')
def test_unknown_label(self):
self.assert_error(
'./bin/hw/foo u:object_r:foo_exec:s0',
r'Error: \./bin/hw/foo: tcontext\(foo_exec\) is unknown')输入分别覆盖完全非法字符串、缺少 MLS level、合法 object_r context 和未知 type。关键断言是检查器既验证四字段结构,也用编译策略确认 type 存在。它没有单独构造一个“role 不允许 type”的进程 context,因此不能替代内核 policydb_context_isvalid() 的关系校验分析。
9.2 LSM属性一致性
内核 LSM selftest 读取 LSM_ATTR_CURRENT,并与 /proc/self/attr/current 返回的第一个 context 比较。SELinux 启用时,两条 ABI 应暴露相同当前 context 字符串。
源码文件:kernel/common/tools/testing/selftests/lsm/lsm_get_self_attr_test.c
相关测试:basic_lsm_get_self_attr
if (cnt_current) {
size = page_size;
count = lsm_get_self_attr(
LSM_ATTR_CURRENT, ctx, &size, 0);
ASSERT_EQ(cnt_current, count);
tctx = ctx;
ASSERT_EQ(0, read_proc_attr(
"current", attr, page_size));
ASSERT_EQ(0, strcmp(
(char *)tctx->ctx, attr));
for (i = 1; i < count; i++) {
tctx = next_ctx(tctx);
ASSERT_NE(0, strcmp(
(char *)tctx->ctx, attr));
}
}测试先根据活动 LSM 计算支持 current 属性的模块数,再断言 syscall 返回数量匹配;第一个 context 必须与传统 proc 属性相同,后续模块 context 不应重复。它验证 context 的用户可见输出一致性,不证明 role r types domain 的策略内容正确。
9.3 Buffer边界
同一 selftest 还验证缓冲区过小时,支持属性的 LSM 返回 E2BIG 并更新所需大小;没有支持模块时返回 EOPNOTSUPP。
__u32 size = 1;
ASSERT_EQ(-1, lsm_get_self_attr(
LSM_ATTR_CURRENT, ctx, &size, 0));
if (attr_lsm_count()) {
ASSERT_EQ(E2BIG, errno);
} else {
ASSERT_EQ(EOPNOTSUPP, errno);
}
ASSERT_NE(1, size);这项测试证明调用者必须先处理活动模块和容量协商,不能假定 context 永远是一个固定长度的 u:r:type:s0 字符串。
10. 源码导航
先从 Android 策略的三条关系确认当前平台角色模型:
# 查看角色声明、role→domain集合和user→role关系。
sed -n '1,80p' system/sepolicy/private/roles_decl
sed -n '1,80p' system/sepolicy/public/roles
sed -n '1,80p' system/sepolicy/private/users再从内核 context validity 找到 role/type 与 user/role 的真实消费者:
# 查看context结构、role/user位图和合法性检查。
rg -n 'struct context|struct role_datum|struct user_datum|policydb_context_isvalid' \
kernel/common/security/selinux/ss/context.h \
kernel/common/security/selinux/ss/policydb.h \
kernel/common/security/selinux/ss/policydb.c若问题发生在 process transition,继续查看 TE、constraint 与 role allow 的执行顺序:
# 找到访问向量计算、constraint和role_allow收紧路径。
rg -n 'context_struct_compute_av|role_allow|process_trans_perms|constraint_expr_eval' \
kernel/common/security/selinux/ss/services.c若问题是新对象或新进程 role 选择,定位 context 构造与 role transition:
# 查看默认role、OBJECT_R_VAL和role_transition覆盖点。
rg -n 'default_role|OBJECT_R_VAL|role_tr|newcontext.role|policydb_context_isvalid' \
kernel/common/security/selinux/ss/services.c \
kernel/common/security/selinux/ss/policydb.h设备侧只读检查用于确认实际 context 与活动 LSM:
# 对比当前进程、关键系统进程和对象context。
adb shell 'id -Z; cat /proc/self/attr/current'
adb shell 'ps -AZ | grep -E "init|zygote|system_server|servicemanager"'
adb shell 'ls -lZ /system/bin/init /system/bin/servicemanager'
adb shell 'cat /sys/kernel/security/lsm'运行构建与内核测试时,需要对应 AOSP 和 kernel 构建环境:
# 验证上下文与策略关系,并构建LSM selftests。
m sepolicy_test
atest apex_sepolicy_tests_test
make -C kernel/common/tools/testing/selftests/lsm11. 闭环复述
- 从
user u roles { r }、role r types domain和一个具体typeattribute system_server coredomain开始,说明 user、role、type 三张集合如何组成合法进程 context。 - 给定
u:r:new_daemon:s0,指出 type 已声明但未加入domain时,policydb_context_isvalid()在哪一行拒绝;再说明补一条 TE allow 为什么不能修复 context validity。 - 从
context_struct_compute_av()复述 TE avtab、conditional rules、constraints 和 role allow 的执行顺序,并解释为什么存在 process transition allow 仍可能被后续步骤清除。 - 从新文件创建路径说明为什么非 process/socket context 默认选择
OBJECT_R_VAL,以及object_r为什么不是一个普通业务角色,也不是完全没有内核语义的占位字符串。 - 使用
basic_lsm_get_self_attr说明/proc/self/attr/current与 LSM syscall 的一致性证明范围,并指出它没有验证哪些 RBAC policy 关系。
Android 的角色模型之所以看起来简单,是因为平台策略把绝大多数进程 domain 放入同一个 r 角色,并把大多数对象交给特殊 object_r。但内核仍完整执行 user→role、role→type、role transition 和 role allow 校验。读策略时应把 RBAC 看作 context 合法性与角色变化边界,把 TE 看作日常访问向量主体;两者共同作用,却不能互相替代。
