Neverallow
本文面向已经读过 TE规则语法、Type与Attribute 和 M4预处理 的读者。前文已经说明 allow 与 attribute 如何进入最终策略、m4 如何裁剪规则;本篇继续追踪 neverallow:编译器如何把 source/target type set、class set 与 permission set 解析成断言,为什么 allow 与断言存在交集就会失败,Android 又为何同时运行 checkpolicy 和 sepolicy-analyze neverallow -w 两条检查路径。
本文不把 neverallow 描述成“运行时优先级更高的 deny”。它不会让 AVC 在运行时多做一次拒绝判断,而是在构建阶段阻止违规 allow 进入可加载策略。读完后,你应能从一条冲突定位到具体 source/target type、attribute 成员、class 与 permission;能区分普通 neverallow、build_test_only 断言、CTS marker 和 neverallowxperm;也能判断应该删除错误 allow、修正 type/attribute 建模,还是在架构允许的前提下调整断言例外。
1. 断言模型
1.1 四元组交集
一条普通 TE allow 与 neverallow 都可以归一化为四个集合:source types、target types、classes 和 permissions。只有四个维度同时有交集时才构成冲突。
allow S_allow T_allow : C_allow P_allow;
neverallow S_never T_never : C_never P_never;冲突条件可写成:
(S_allow ∩ S_never) != empty
and (T_allow ∩ T_never) != empty
and (C_allow ∩ C_never) != empty
and (P_allow ∩ P_never) != empty这不是源码中的字符串比较。type 与 attribute 会解析成 policydb 编号和 bitmap,class/permission 也会转换为 access vector bit。
1.2 运行边界
最终 binary policy 保存 allow、type transition、constraints 等运行时数据;neverallow 的用途是检查构建输入。Android 的 sepolicy-analyze 必须从 -f 文件或 -n 字符串重新读取 neverallow,再与已加载 binary policy 比较,正说明 binary policy 本身不能提供完整断言文本。
源码文件:system/sepolicy/tools/sepolicy-analyze/README
NEVERALLOW CHECKING (neverallow)
sepolicy-analyze out/target/product/<board>/root/sepolicy neverallow \
[-w] [-d] [-f neverallows.conf] | [-n "neverallow string"]
Check whether the sepolicy file violates any of the neverallow rules
from the neverallows.conf file or a given string, which contain neverallow
statements in the same format as the SELinux policy.conf file, i.e. after
m4 macro expansion of the rules from a .te file.运行期 AVC denial 来自“没有允许路径”或 constraints;neverallow 只保证某类允许路径无法通过正常构建产生。
1.3 默认拒绝的差异
| 机制 | 发生阶段 | 输入 | 结果 |
|---|---|---|---|
| 无 allow | 运行时 | source/target/class/permission | AVC deny |
| constraint | 运行时 policy decision | Context 字段与 operation | 即使有 allow 也可拒绝 |
| neverallow | 构建期 | allow 集合与断言集合 | 编译/分析失败 |
| neverallowxperm | 构建期 | ioctl 基础权限与 command bitmap | 编译失败 |
后续 constraint 专题会展开运行时 Context 条件;本篇只负责构建期断言。
2. 集合解析
2.1 Type集合
sepolicy-analyze 的 neverallow parser 展示了 external assertion 如何解析 type set。它支持 ~ complement、*、花括号、-type 排除和 self。
源码文件:system/sepolicy/tools/sepolicy-analyze/neverallow.c
if (*p == '~') {
if (debug)
printf(" ~");
typeset->flags = TYPE_COMP;
p++;
}
if (*p == '*') {
if (debug)
printf(" *");
typeset->flags = TYPE_STAR;
p++;
continue;
}
if (*p == '-') {
if (debug)
printf(" -");
negate = true;
p++;
continue;
}self 不进入普通 type bitmap,而是设置 RULE_SELF,表示每个 source 与自身组成 target pair。
源码文件:system/sepolicy/tools/sepolicy-analyze/neverallow.c
if (len == keyword_size && !strncmp(start, keyword, keyword_size)) {
if (debug)
printf(" self");
*flags |= RULE_SELF;
continue;
}2.2 Attribute展开
解析器在遇到 attribute 时直接把 policydb->attr_type_map 中的成员并入 types bitmap;具体 type 则设置自己的编号位。
源码文件:system/sepolicy/tools/sepolicy-analyze/neverallow.c
type = hashtab_search(policydb->p_types.table, id);
if (!type) {
if (warn)
fprintf(stderr,
"Warning! Type or attribute %s used in neverallow "
"undefined in policy being checked.\n", id);
negate = false;
continue;
}
if (type->flavor == TYPE_ATTRIB) {
if (negate)
rc = ebitmap_union(&typeset->negset,
&policydb->attr_type_map[type->s.value - 1]);
else
rc = ebitmap_union(&typeset->types,
&policydb->attr_type_map[type->s.value - 1]);
} else if (negate) {
rc = ebitmap_set_bit(&typeset->negset, type->s.value - 1, 1);
} else {
rc = ebitmap_set_bit(&typeset->types, type->s.value - 1, 1);
}因此下面的断言并不是把字符串 domain 与 allow source 比较,而是覆盖所有属于 domain attribute 的具体 type,再移除例外。
源码文件:system/sepolicy/private/domain.te
neverallow { domain -init -recovery } unlabeled:dir_file_class_set create;新增 daemon 一旦加入 domain,会自动进入 source set。除非它是 init 或 recovery,任何对 unlabeled 对象的 create allow 都会发生交集。
2.3 Star与Complement
* 会枚举 policydb 中所有具体 type,跳过 attribute;随后排除 negset。~set 则对全部具体 type 求补集。
源码文件:system/sepolicy/tools/sepolicy-analyze/neverallow.c
if (typeset->flags & TYPE_STAR) {
for (bit = 0; bit < policydb->p_types.nprim; bit++) {
if (ebitmap_get_bit(&typeset->negset, bit))
continue;
if (policydb->type_val_to_struct[bit] &&
policydb->type_val_to_struct[bit]->flavor == TYPE_ATTRIB)
continue;
if (ebitmap_set_bit(&typeset->types, bit, 1))
goto err;
}
}
if (typeset->flags & TYPE_COMP) {
for (bit = 0; bit < policydb->p_types.nprim; bit++) {
if (policydb->type_val_to_struct[bit] &&
policydb->type_val_to_struct[bit]->flavor == TYPE_ATTRIB)
continue;
if (ebitmap_get_bit(&typeset->types, bit))
ebitmap_set_bit(&typeset->types, bit, 0);
else if (ebitmap_set_bit(&typeset->types, bit, 1))
goto err;
}
}neverallow * ~{ allowed_types }:file read; 的 target 会随新 type 增长。它适合表达封闭 allowlist,但新增 type 可能意外进入禁止集合,必须在版本升级时重新评估。
2.4 Permission补集
Class 与 permission parser 先解析 class set,再为每个 class 查找 permission bit。~{ ... } 对每个 class 的 32 位 permission mask 求补。
源码文件:system/sepolicy/tools/sepolicy-analyze/neverallow.c
if (!strcmp(id, "*")) {
for (node = classperms; node; node = node->next)
node->data = ~0;
free(id);
id = NULL;
continue;
}
for (node = classperms; node; node = node->next) {
cls = policydb->class_val_to_struct[node->tclass-1];
perm = hashtab_search(cls->permissions.table, id);
if (cls->comdatum && !perm)
perm = hashtab_search(cls->comdatum->permissions.table, id);
if (!perm) {
if (warn)
fprintf(stderr,
"Warning! Permission %s used in neverallow undefined "
"in class %s in policy being checked.\n",
id, policydb->p_class_val_to_name[node->tclass-1]);
continue;
}
node->data |= 1U << (perm->s.value - 1);
}
if (complement) {
for (node = classperms; node; node = node->next)
node->data = ~node->data;
}真实策略常用 complement 表达“只允许列出的权限,其余全部禁止”。
源码文件:system/sepolicy/private/shell.te
neverallow shell {dev_type -apex_dm_device}:blk_file ~getattr;
neverallow shell self:perf_event ~{ open read write kernel };第一条允许 shell 对大多数 block device type 只有 getattr;第二条限定 perf_event 权限只能落在四个 allowlisted operation 中。
3. Analyze路径
3.1 读取外部断言
sepolicy-analyze 加载 binary policy 后,从 -f 文件或 -n 字符串读取已完成 m4 展开的 neverallow。
源码文件:system/sepolicy/tools/sepolicy-analyze/neverallow.c
int neverallow_func(int argc, char **argv, policydb_t *policydb) {
char *rules = 0, *file = 0;
char ch;
struct option neverallow_options[] = {
{"debug", no_argument, NULL, 'd'},
{"file_input", required_argument, NULL, 'f'},
{"neverallow", required_argument, NULL, 'n'},
{"warn", no_argument, NULL, 'w'},
{NULL, 0, NULL, 0}
};
while ((ch = getopt_long(argc, argv, "df:n:w",
neverallow_options, NULL)) != -1) {
switch (ch) {
case 'd': debug = 1; break;
case 'f': file = optarg; break;
case 'n': rules = optarg; break;
case 'w': warn = 1; break;
default:
USAGE_ERROR = true;
return -1;
}
}
if (!(rules || file) || (rules && file)) {
USAGE_ERROR = true;
return -1;
}
if (file)
return check_neverallows_file(policydb, file);
return check_neverallows_string(policydb, rules, strlen(rules));
}-f 与 -n 互斥;-d 输出 parser 看到的断言;-w 对被测 policy 中不存在的 type、class 或 permission 给 warning。
3.2 精确关键字
Parser 会跳过空白,读取下一个 token,只有长度和内容都精确等于 neverallow 才创建 assertion。
源码文件:system/sepolicy/tools/sepolicy-analyze/neverallow.c
const char *keyword = "neverallow";
size_t keyword_size = strlen(keyword), len;
while (p < end) {
while (p < end && isspace(*p))
p++;
start = p;
while (p < end && !isspace(*p))
p++;
len = p - start;
if (len != keyword_size || strncmp(start, keyword, keyword_size))
continue;
avrule = calloc(1, sizeof *avrule);
if (!avrule)
goto err;
avrule->specified = AVRULE_NEVERALLOW;
/* source、target、class/permission解析继续。 */
}这意味着当前 sepolicy-analyze neverallow 路径不会把关键字 neverallowxperm 当作普通 neverallow 解析。扩展 ioctl 断言仍必须依靠包含源码断言的 checkpolicy/secilc 编译路径,不能只运行 external analyzer。
3.3 调用引擎
解析完成后,工具把 assertion linked list 交给 libsepol 的 check_assertions(),被测 allow 数据来自已经加载的 policydb。
源码文件:system/sepolicy/tools/sepolicy-analyze/neverallow.c
if (!neverallows)
goto err;
result = check_assertions(NULL, policydb, neverallows);
avrule_list_destroy(neverallows);
free(non_comment_text);
return result;Analyzer 在这里调用 libsepol 的 assertion 检查入口。它的价值在于:断言文本可以来自平台通用 conf,而 allow policy 可以来自另一份已编译产品策略;这与 Soong 直接把完整源码 conf 交给 checkpolicy 的检查方式不同。
3.4 Warning边界
-w 对无法解析的 symbol 发出 warning,然后忽略该 symbol,而不是立即判违规。这对跨 release 或不同产品 policy 检查是必要的:平台断言可能提到被测 policy 中不存在的新 type。
但 warning 也意味着 assertion coverage 变窄。看到 unknown type/class/permission 时,必须判断它是正常版本差异,还是构建输入漏掉了本应存在的 public symbol。
4. Soong双路径
4.1 完整输入
sepolicy_neverallows 汇总 platform、system_ext、product、vendor 与 odm policy,测试产品最终合并边界,而不是只检查 platform private 文件。
源码文件:system/sepolicy/Android.bp
se_neverallow_test {
name: "sepolicy_neverallows",
defaults: ["se_policy_conf_flags_defaults"],
srcs: plat_public_policy +
plat_private_policy +
system_ext_public_policy +
system_ext_private_policy +
product_public_policy +
product_private_policy +
vendor_policy +
[":se_build_files{.odm}"],
}一个 vendor allow 可以撞上 platform neverallow,正是因为这份 module 把双方放入同一分析边界。
4.2 两份Conf
Load hook 动态创建两份 user policy.conf。第一份保留 build_test_only,第二份设置 Exclude_build_test=true。
源码文件:system/sepolicy/build/soong/sepolicy_neverallow.go
checkpolicyConf := n.checkpolicyConfModuleName()
ctx.CreateModule(policyConfFactory, &nameProperties{
Name: proptools.StringPtr(checkpolicyConf),
}, &policyConfProperties{
Srcs: n.properties.Srcs,
Build_variant: proptools.StringPtr("user"),
Installable: proptools.BoolPtr(false),
}, &struct {
Defaults []string
}{
Defaults: n.properties.Defaults,
})
sepolicyAnalyzeConf := n.sepolicyAnalyzeConfModuleName()
ctx.CreateModule(policyConfFactory, &nameProperties{
Name: proptools.StringPtr(sepolicyAnalyzeConf),
}, &policyConfProperties{
Srcs: n.properties.Srcs,
Build_variant: proptools.StringPtr("user"),
Exclude_build_test: proptools.BoolPtr(true),
Installable: proptools.BoolPtr(false),
}, &struct {
Defaults []string
}{
Defaults: n.properties.Defaults,
})第一份回答“源码中的全部 build assertions 能否与 allow 共存”;第二份回答“从 binary policy 外部重新解析的普通断言能否检查展开后的产品 allow”。
4.3 Checkpolicy步骤
第一步直接用保留 build tests 的 conf 生成 binary policy。若普通 neverallow、build-test neverallow 或 neverallowxperm 冲突,这里失败。
源码文件:system/sepolicy/build/soong/sepolicy_neverallow.go
binaryPolicy := pathForModuleOut(ctx, "policy")
rule.Command().BuiltTool("checkpolicy").
Flag("-M").
FlagWithArg("-c ", strconv.Itoa(PolicyVers)).
FlagWithOutput("-o ", binaryPolicy).
Input(checkpolicyConfPath)
rule.Build("neverallow_checkpolicy",
"Neverallow check: "+ctx.ModuleName())4.4 Analyze步骤
第二步使用第一步得到的 binary policy,读取排除 build tests 的 conf,并加 -w 检查普通 neverallow。
源码文件:system/sepolicy/build/soong/sepolicy_neverallow.go
rule.Command().BuiltTool("sepolicy-analyze").
Input(binaryPolicy).
Text("neverallow").
Flag("-w").
FlagWithInput("-f ", sepolicyAnalyzeConfPath).
Text("|| (echo").
Flag("-e").
Text(`"` + msg + `"`).
Text("; exit 1)")失败提示明确指出常见原因是 expanded attribute 被用于 analyzer 无法重建的 neverallow。这也是两条路径同时存在的原因。
5. 展开属性
5.1 缺口原因
部分 attribute 为性能或兼容原因在策略编译时展开。Binary policy 中可能不再保留 analyzer 需要的原始 attribute 结构,因此外部 neverallow parser 无法完整解析这些断言。
hal_attribute 对 client attribute 设置 expandattribute true,对 server 设置 false,并在注释中明确:针对 expanded attribute 的 neverallow 无法由 CTS 验证。
源码文件:system/sepolicy/public/te_macros
define(`hal_attribute', `
attribute hal_$1;
expandattribute hal_$1 true;
attribute hal_$1_client;
expandattribute hal_$1_client true;
attribute hal_$1_server;
expandattribute hal_$1_server false;
neverallow { hal_$1_server -halserverdomain } domain:process fork;
build_test_only(`
neverallow { hal_$1_server -hal_$1 } domain:process fork;
neverallow { hal_$1_client -halclientdomain } domain:process fork;
')
')第一条 server assertion 可以保留为普通 neverallow;后两条涉及基础/client expanded attribute,因此只在 build-time conf 中检查。
5.2 展开实验
对同一宏分别设置 target_exclude_build_test=false/true,可以看到两份 conf 的真实差异。
# 从AOSP根目录选择host m4后执行。
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 exclude in false true; do
printf 'exclude_build_test=%s\n' "$exclude"
"$M4" \
-D target_build_variant=user \
-D target_full_treble=true \
-D target_exclude_build_test="$exclude" \
system/sepolicy/public/global_macros \
system/sepolicy/public/te_macros - <<'EOF' |
hal_attribute(demo)
EOF
sed '/^#/d;/^[[:space:]]*$/d'
donefalse 输出三条 neverallow;true 只剩 hal_demo_server -halserverdomain。实验验证 m4 裁剪边界,不验证 type membership 是否正确。
5.3 设计要求
新增针对 expanded attribute 的架构断言时,不能只放普通 neverallow 并期待 CTS external analyzer 覆盖。应遵循现有模式,将无法在 binary policy 中重建的断言纳入 build_test_only,同时保留可在最终 policy 上检查的非 expanded 边界。
6. 安全边界
6.1 策略封印
系统策略加载后,不允许任何 domain 再加载新策略或改变 enforcing/boolean 状态。注释说明 init 在切换 domain 前仍处于 kernel domain,早期 setenforce 特例不需要为运行期开放。
源码文件:system/sepolicy/private/domain.te
neverallow * kernel:security load_policy;
neverallow * kernel:security setenforce;
neverallow { domain -kernel } kernel:security setcheckreqprot;
neverallow * kernel:security setbool;
neverallow { domain -init } kernel:security setsecparam;这些规则表达架构不变量,不是为了处理某一条当前 denial。给普通 daemon 增加 security load_policy 应被视为设计错误。
6.2 EntryPoint标注
所有 file entrypoint 必须属于 exec_type 或特殊 postinstall_file。这把“可执行文件”与“可以作为 domain 入口的 executable”区分开。
源码文件:system/sepolicy/private/domain.te
neverallow * { file_type -exec_type -postinstall_file }:file entrypoint;若自定义 daemon 的 executable 只声明 file_type,init_daemon_domain 展开的 entrypoint allow 会撞上这条断言。正确修复是把 executable type 加入 exec_type 并配置文件标签,而不是为具体 type 增加 neverallow 例外。
6.3 Unlabeled对象
除 init/recovery 外,普通 domain 不应创建 unlabeled 文件系统对象。
源码文件:system/sepolicy/private/domain.te
neverallow { domain -init -recovery } unlabeled:dir_file_class_set create;如果某个 daemon 遇到 unlabeled create denial,应优先修复 filesystem labeling、file contexts 或 transition,而不是放开 unlabeled。
6.4 Binder边界
init 与 vendor_init 不应参与 Binder transaction。策略注释指出,触发此断言通常意味着 init service 没有独立 SELinux domain。
源码文件:system/sepolicy/private/domain.te
neverallow * init:binder *;
neverallow * vendor_init:binder *;正确修复是为服务定义 domain 与 exec transition,使业务进程离开 init/vendor_init,而不是允许调用方直接与 PID 1 domain 通信。
6.5 Capability边界
mac_override 对 SELinux 未使用,全部禁止;mac_admin 只有 system_server 因 safesetid 管理获得例外。
源码文件:system/sepolicy/private/domain.te
neverallow * self:global_capability2_class_set mac_override;
neverallow { domain -system_server } self:global_capability2_class_set mac_admin;这里使用 capability class set 同时覆盖 init 与非 init user namespace class,避免通过 user namespace 绕过断言。
7. 宏内断言
7.1 Service所有权
add_service(domain, service) 同时授予 add/find,并生成“除 owner 外任何 domain 不得 add”的 neverallow。
源码文件:system/sepolicy/public/te_macros
define(`add_service', `
allow $1 $2:service_manager { add find };
neverallow { domain -$1 } $2:service_manager add;
userdebug_or_eng(`
allow $1 su:tcp_socket { accept getopt read write };
')
')调用这个宏不仅获得权限,也声明 service type 的唯一注册 owner。另一个 domain 手写 add allow 会在构建期冲突,不存在“后写规则覆盖断言”。
7.2 App隔离
app_domain 生成 allow、type transition 与多条 neverallow,保证新 app domain 不会只获得通用能力而缺少隔离约束。
源码文件:system/sepolicy/public/te_macros
neverallow { $1 -runas_app -shell -simpleperf }
{ domain -$1 }:file no_rw_file_perms;
neverallow { appdomain -runas_app -shell -simpleperf -$1 }
$1:file no_rw_file_perms;
neverallow {
domain
-$1
-crash_dump
userdebug_or_eng(`-llkd')
-runas_app
-simpleperf
} $1:process ptrace;宏参数 $1 同时进入 allow 与 neverallow source/target set。因此修改宏调用的具体 domain 会改变约束 bitmap。
7.3 Socket通信
源码文件:system/sepolicy/public/neverallow_macros
define(`neverallow_establish_socket_comms', `
neverallow $1 $2:socket_class_set { connect sendto };
neverallow $1 $2:unix_stream_socket connectto;
')该宏覆盖普通 socket connect/sendto 与 Unix stream domain-to-domain connectto。Vendor init 用它阻止绝大多数跨域主动连接。
源码文件:system/sepolicy/private/vendor_init.te
neverallow_establish_socket_comms(vendor_init, {
domain -init -logd -prng_seeder -su -vendor_init });已建立 socket fd 的 read/write 不在这个宏的禁止集合中。断言限制主动建立通信,不等于禁止通过稳定 API 接收现成 fd 后交换数据。
8. Xperm断言
8.1 Command位图
普通 neverallow ... ioctl 只能约束基础 ioctl permission;neverallowxperm 针对具体 ioctl command 或 command set。
源码文件:system/sepolicy/private/domain.te
neverallowxperm * *:{ dir notdevfile_class_set socket_class_set blk_file }
ioctl { 0 };
neverallowxperm domain domain:socket_class_set ioctl { SIOCATMARK };
neverallowxperm * devpts:chr_file ioctl TIOCSTI;TIOCSTI 可向终端输入队列注入字符,Android 在全局断言中禁止任何 source 对 devpts 使用该 command。
8.2 宏生成专用Type
create_pty(domain) 为 domain 创建专用 devpts type,允许 unprivileged tty ioctls,再重复声明 TIOCSTI 禁止项,保证每个派生 pty type 也受约束。
源码文件:system/sepolicy/public/te_macros
define(`create_pty', `
type $1_devpts, fs_type;
type_transition $1 devpts:chr_file $1_devpts;
allow $1 $1_devpts:chr_file { open getattr read write ioctl };
allowxperm $1 $1_devpts:chr_file ioctl unpriv_tty_ioctls;
neverallowxperm * $1_devpts:chr_file ioctl TIOCSTI;
')8.3 Analyzer覆盖
sepolicy_filter_neverallow.py 的 startswith("neverallow") 会把 neverallowxperm 文本保留在 CTS conf,但当前 sepolicy-analyze parser 只识别精确 token neverallow。因此不能用 analyzer 无输出证明 xperm 断言通过;完整 checkpolicy/secilc 构建才是这类规则的必要证据。
9. CTS规则集
9.1 General Conf
Android 生成一份嵌入 CTS 的 platform neverallow conf。它固定 user build、CTS 条件、排除 build-test assertions,并只保留 neverallow 规则。
源码文件:system/sepolicy/Android.bp
se_policy_conf {
name: "general_sepolicy.conf",
defaults: ["se_policy_conf_flags_defaults"],
srcs: plat_public_policy +
plat_private_policy,
build_variant: "user",
cts: true,
exclude_build_test: true,
only_neverallow_rules: true,
dist: {
targets: ["sepolicy_finalize"],
},
}它与本地 sepolicy_neverallows 不同:前者是 platform 通用、带 CTS marker 的外部断言输入;后者在产品构建中合并 platform/vendor/odm,并运行双路径检查。
9.2 Marker过滤
过滤器只保留带 CTS 注释的 marker 和 neverallow statement;没有断言内容的相邻 BEGIN/END marker 会同时移除。
源码文件:system/sepolicy/tools/sepolicy_filter_neverallow.py
if re.search(r"#.*-- this marker is used by CTS --", line):
if len(outputs) > 0 and markers_match(outputs[-1], line):
outputs.pop()
else:
outputs.append(line.rstrip())
continue
line = re.sub(r"#.*", "", line)
if line.strip() == "":
continue
if line.lstrip().startswith("neverallow"):
in_neverallow_rule = True
if in_neverallow_rule:
outputs.append(line.rstrip())
if line.endswith(';'):
in_neverallow_rule = False9.3 过滤实验
# allow被删除,neverallow和包裹它的CTS marker被保留。
python3 system/sepolicy/tools/sepolicy_filter_neverallow.py \
/dev/stdin /dev/stdout <<'EOF'
# BEGIN_TREBLE_ONLY -- this marker is used by CTS -- do not modify
allow demo target:file read;
neverallow demo target:file write;
# END_TREBLE_ONLY -- this marker is used by CTS -- do not modify
allow demo target:file getattr;
EOF预期输出:
# BEGIN_TREBLE_ONLY -- this marker is used by CTS -- do not modify
neverallow demo target:file write;
# END_TREBLE_ONLY -- this marker is used by CTS -- do not modify实验输入包含一条 allow、一条 neverallow 和 marker。断言是普通 allow 被移除,而有断言内容的条件边界保留。它验证过滤算法,不验证 binary policy 是否违反断言。
10. 冲突定位
10.1 先展开规则
错误信息中的 allow 可能来自高层宏、attribute rule 或 m4 条件。第一步应在实际产品 policy.conf 中找到冲突 allow 与 neverallow 的展开形式,而不是只搜索某个 .te 文件。
# 构建真实产品的neverallow测试与平台conf。
source build/envsetup.sh
lunch PRODUCT-userdebug
m sepolicy_neverallows plat_sepolicy.conf
# 搜索冲突type、class或permission。
rg -n 'source_type|target_type|permission' \
out/soong/.intermediates/system/sepolicy10.2 展开Attribute
若错误涉及 attribute,必须列出双方成员。最终 binary policy 可用 sepolicy-analyze attribute 查询。
# 将PRODUCT替换为产品名。
ANDROID_SEPOLICY='out/target/product/PRODUCT/root/sepolicy'
# 列出attribute成员。
sepolicy-analyze "$ANDROID_SEPOLICY" attribute domain
sepolicy-analyze "$ANDROID_SEPOLICY" attribute exec_type
# 反向查询具体type所属attribute。
sepolicy-analyze "$ANDROID_SEPOLICY" attribute -r my_daemon_exec如果 my_daemon_exec 缺少 exec_type,entrypoint neverallow 冲突就来自建模错误,而不是 permission 本身。
10.3 单条断言
-n 可以针对已编译 policy 快速测试一条已展开断言;-d 同时输出 parser 看到的规则。
# 检查是否存在任何非system_server domain的mac_admin allow。
sepolicy-analyze "$ANDROID_SEPOLICY" neverallow -w -d \
-n 'neverallow { domain -system_server } self:global_capability2_class_set mac_admin;'输入是 binary policy 和一条 policy.conf 形式的 assertion。断言是命令退出 0 且无 violation;warning 需要人工判断是否削弱了检查范围。
10.4 修复方向
| 冲突原因 | 常见错误修复 | 正确方向 |
|---|---|---|
executable 缺 exec_type | 给 domain.te 加例外 | 修正 type declaration/file label |
| 服务在 init domain 运行 | 允许 init Binder | 建独立 domain 与 transition |
| 对 unlabeled 创建对象 | 放开 unlabeled create | 修复 filesystem/context 标注 |
| 非 owner 注册 service | 删除 add_service neverallow | 重新确定唯一 owner 或 service type |
| Expanded HAL attribute 关系错误 | 排除 build test | 使用 hal_client_domain/hal_server_domain 正确标记 |
| 设备 ioctl 被 xperm 阻止 | 删除全局断言 | 核对 command 风险与专用 allowlist |
10.5 条件例外
Neverallow 允许在 source/target set 中显式排除合法例外,但例外是架构承诺。添加 -my_domain 前应回答:为什么现有 owner/attribute 不能表达职责、是否只需更具体 target type、该访问是否会扩散到 vendor 或 app domain、CTS marker 对旧设备是否适用。
11. 反向验证
11.1 宏展开测试
hal_attribute 实验的输入是同一宏调用和两个 target_exclude_build_test 值。关键断言是普通 server invariant 在两份输出中都存在,而涉及 expanded attributes 的两条 assertion 只出现在完整 build-test 输出。它证明双 conf 的差异来自 m4 条件,不是 analyzer 随机忽略规则。
11.2 Parser范围
sepolicy-analyze 源码的精确 keyword 比较提供了反向边界:输入文件可以包含普通 policy.conf 文本,但只有 token neverallow 被转成 AVRULE_NEVERALLOW。这解释了为什么 external analyzer 不应作为 neverallowxperm 的唯一验证。
11.3 双路径结果
Soong 只有在 checkpolicy 和 sepolicy-analyze 两个 rule 都成功后才 touch test timestamp。
源码文件:system/sepolicy/build/soong/sepolicy_neverallow.go
rule.Command().Text("touch").Output(n.testTimestamp)
rule.Build("neverallow_sepolicy-analyze",
"Neverallow check: "+ctx.ModuleName())
ctx.CheckbuildFile(n.testTimestamp)测试输入是完整产品策略文件组;关键断言是 timestamp 依赖两步构建均完成。它覆盖产品构建时的普通、build-test 与 xperm 断言,但不等同于 CTS 在另一版本通用规则集上检查设备策略。
12. 源码导航
| 问题 | 首选文件 | 关键符号 |
|---|---|---|
| 断言如何解析type set | system/sepolicy/tools/sepolicy-analyze/neverallow.c | read_typeset |
| Class/permission如何解析 | system/sepolicy/tools/sepolicy-analyze/neverallow.c | read_classperms |
| 外部规则如何执行 | system/sepolicy/tools/sepolicy-analyze/neverallow.c | check_neverallows |
| 双conf如何创建 | system/sepolicy/build/soong/sepolicy_neverallow.go | loadHook |
| Checkpolicy/analyzer如何串联 | system/sepolicy/build/soong/sepolicy_neverallow.go | GenerateAndroidBuildActions |
| 产品测试输入有哪些 | system/sepolicy/Android.bp | sepolicy_neverallows |
| CTS通用规则如何生成 | system/sepolicy/Android.bp | general_sepolicy.conf |
| CTS marker如何过滤 | system/sepolicy/tools/sepolicy_filter_neverallow.py | markers_match、状态循环 |
| 权限否定集合在哪 | system/sepolicy/public/neverallow_macros | no_*_perms |
| 全局架构断言在哪 | system/sepolicy/private/domain.te | policy seal、entrypoint、IPC |
| 宏如何捆绑约束 | system/sepolicy/public/te_macros | add_service、app_domain、HAL macros |
面对 neverallow ... violated by allow ...,完整复述应是:m4 已根据产品条件生成一组具体断言与 allow;type/attribute 被解析成成员 bitmap,class 与 permission 变成 bit;四个集合出现共同元素,因此 checkpolicy 或 external analyzer 调用 assertion engine 失败。修复必须改变交集本身,而不是依赖规则顺序、permissive mode 或 dontaudit。
能够进一步判断断言是否属于 build-test expanded attribute、CTS marker 或 xperm command,并选择对应验证路径,才算真正掌握 Android Neverallow。
