Skip to content

Neverallow

从 Android neverallow 集合解析、attribute 展开、checkpolicy 与 sepolicy-analyze 双路径、CTS marker 和真实安全边界解释编译期约束。

基于android-17.0.0_r1
AndroidSELinuxneverallowneverallowxpermcheckpolicysepolicy-analyze源码阅读

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。只有四个维度同时有交集时才构成冲突。

text
allow       S_allow T_allow : C_allow P_allow;
neverallow  S_never T_never : C_never P_never;

冲突条件可写成:

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

text
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/permissionAVC deny
constraint运行时 policy decisionContext 字段与 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

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

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

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

text
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

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

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

text
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

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

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

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

make
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

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

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

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

text
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 的真实差异。

bash
# 从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'
done

false 输出三条 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

text
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

text
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

text
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

text
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

text
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

text
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

text
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

text
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

text
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

text
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

text
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

make
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

python
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 = False

9.3 过滤实验 ​

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

预期输出:

bash
# 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 文件。

bash
# 构建真实产品的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/sepolicy

10.2 展开Attribute ​

若错误涉及 attribute,必须列出双方成员。最终 binary policy 可用 sepolicy-analyze attribute 查询。

bash
# 将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 看到的规则。

bash
# 检查是否存在任何非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

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 setsystem/sepolicy/tools/sepolicy-analyze/neverallow.cread_typeset
Class/permission如何解析system/sepolicy/tools/sepolicy-analyze/neverallow.cread_classperms
外部规则如何执行system/sepolicy/tools/sepolicy-analyze/neverallow.ccheck_neverallows
双conf如何创建system/sepolicy/build/soong/sepolicy_neverallow.goloadHook
Checkpolicy/analyzer如何串联system/sepolicy/build/soong/sepolicy_neverallow.goGenerateAndroidBuildActions
产品测试输入有哪些system/sepolicy/Android.bpsepolicy_neverallows
CTS通用规则如何生成system/sepolicy/Android.bpgeneral_sepolicy.conf
CTS marker如何过滤system/sepolicy/tools/sepolicy_filter_neverallow.pymarkers_match、状态循环
权限否定集合在哪system/sepolicy/public/neverallow_macrosno_*_perms
全局架构断言在哪system/sepolicy/private/domain.tepolicy seal、entrypoint、IPC
宏如何捆绑约束system/sepolicy/public/te_macrosadd_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。