Skip to content

sepolicy-analyze 工具

解析 AOSP sepolicy-analyze 的 policydb 加载、组件分派、permissive、attribute、neverallow 和重复规则分析。

基于android-17.0.0_r1
AndroidSELinuxsepolicy-analyzeneverallow策略调试源码阅读

sepolicy-analyze 工具 ​

本文承接 策略查询工具 和 Permissive 域调试。前者介绍 AOSP searchpolicy.py 如何查询已编译 policy,后者解释 permissive domain 的内核和构建语义;本文深入分析 AOSP host 工具 sepolicy-analyze 本身:它如何加载 policy binary、如何按子命令分派,以及各组件的输入、断言和限制。

sepolicy-analyze 不是一个自动发现所有策略问题的黑盒。Android 17 源码把它拆成 dups、neverallow、permissive、typecmp、booleans 和 attribute 组件;每个组件直接操作 libsepol 的 policydb_t。因此某个命令成功只表示该分析器完成了自己的检查,不能外推设备运行时一定允许、所有条件规则都已覆盖,或 platform/vendor 版本兼容。

1. 工具架构 ​

1.1 组件注册 ​

源码文件:system/sepolicy/tools/sepolicy-analyze/sepolicy-analyze.c

c
#define NUM_COMPONENTS (int)(sizeof(analyze_components)/sizeof(analyze_components[0]))

#define COMP(x) { #x, sizeof(#x) - 1, x ##_usage, x ##_func }

static struct {
    const char *key;
    size_t keylen;
    void (*usage)(void);
    int (*func)(int argc, char **argv, policydb_t *policydb);
} analyze_components[] = {
    COMP(dups),
    COMP(neverallow),
    COMP(permissive),
    COMP(typecmp),
    COMP(booleans),
    COMP(attribute)
};

COMP(x) 把命令名、usage 函数和分析函数绑定起来;新增组件必须同时提供这两个函数。表是静态数组,命令名不是插件,也不是 Soong 动态发现。

1.2 生命周期 ​

源码文件:system/sepolicy/tools/sepolicy-analyze/sepolicy-analyze.c

c
int main(int argc, char **argv)
{
    char *policy;
    struct policy_file pf;
    policydb_t policydb;
    int rc;

    if (argc < 3)
        usage(argv[0]);
    policy = argv[1];
    if (!load_policy(policy, &policydb, &pf))
        exit(1);

    for (int i = 0; i < NUM_COMPONENTS; i++) {
        if (!strcmp(analyze_components[i].key, argv[2])) {
            rc = analyze_components[i].func(argc - 2, argv + 2, &policydb);
            if (rc && USAGE_ERROR)
                usage(argv[0]);
            policydb_destroy(&policydb);
            return rc;
        }
    }
    usage(argv[0]);
    exit(0);
}

调用约定是 sepolicy-analyze <policy-file> <component> ...。主函数加载一次 policydb,将组件参数从 argv + 2 传入;组件返回后销毁 policydb。usage 错误和分析失败都通过返回码传播,Soong 或脚本可以据此停止构建。

1.3 binary加载 ​

源码文件:system/sepolicy/tools/sepolicy-analyze/utils.c

c
bool load_policy(char *filename, policydb_t *policydb, struct policy_file *pf)
{
    int fd = -1;
    struct stat sb;
    void *map = MAP_FAILED;
    bool ret = false;

    fd = open(filename, O_RDONLY);
    if (fd < 0) {
        fprintf(stderr, "Can't open '%s':  %s\n", filename, strerror(errno));
        goto cleanup;
    }
    if (fstat(fd, &sb) < 0) {
        fprintf(stderr, "Can't stat '%s':  %s\n", filename, strerror(errno));
        goto cleanup;
    }
    map = mmap(NULL, sb.st_size, PROT_READ | PROT_WRITE,
               MAP_PRIVATE, fd, 0);
    if (map == MAP_FAILED) {
        fprintf(stderr, "Can't mmap '%s':  %s\n", filename, strerror(errno));
        goto cleanup;
    }
    policy_file_init(pf);
    pf->type = PF_USE_MEMORY;
    pf->data = map;
    pf->len = sb.st_size;
    if (policydb_init(policydb))
        goto cleanup;
    if (policydb_read(policydb, pf, 0))
        goto cleanup;
    ret = true;

cleanup:
    if (map != MAP_FAILED)
        munmap(map, sb.st_size);
    if (fd >= 0)
        close(fd);
    return ret;
}

工具将文件映射到内存,再由 policydb_read 反序列化;组件读取的是 libsepol 结构,而不是文本 grep。传入 CIL 文本、空文件或损坏 binary 会在所有分析之前失败。

2. permissive组件 ​

2.1 位图遍历 ​

源码文件:system/sepolicy/tools/sepolicy-analyze/perm.c

c
static int list_permissive(policydb_t *policydb)
{
    struct ebitmap_node *n;
    unsigned int bit;

    /* Iterate over every set bit in the permissive map. */
    ebitmap_for_each_bit(&policydb->permissive_map, n, bit) {
        if (ebitmap_node_get_bit(n, bit)) {
            printf("%s\n", policydb->p_type_val_to_name[bit - 1]);
        }
    }
    return 0;
}

int permissive_func(int argc, __attribute__((unused)) char **argv,
                    policydb_t *policydb)
{
    if (argc != 1) {
        USAGE_ERROR = true;
        return -1;
    }
    return list_permissive(policydb);
}

组件遍历 binary policydb 的 permissive_map,用 bit - 1 映射到 type 名称表。它列出策略中声明的 permissive type,不读取进程表、不读取当前 AVC,也不判断某个 domain 此刻是否运行。

2.2 构建消费者 ​

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

go
// permissive check is performed only in user build (not debuggable).
if !ctx.Config().Debuggable() {
    permissiveDomains := pathForModuleOut(ctx, c.stem()+"_permissive")
    cmd := rule.Command().BuiltTool("sepolicy-analyze").
        Input(bin).
        Text("permissive")
    // Filter domains explicitly allowed by the product.
    for _, d := range c.properties.Permissive_domains_on_user_builds {
        cmd.FlagWithArg("-e ", proptools.ShellEscape(d))
    }
    cmd.Text(" > ").Output(permissiveDomains)
    rule.Temporary(permissiveDomains)
}

Soong 在 non-debuggable 构建中消费该组件的 stdout;过滤 allowlist 后,列表非空会让 se_policy_binary action 失败。debuggable 构建跳过这条产品门槛,但 binary 中的 permissive map 仍然存在。

3. attribute组件 ​

3.1 命令接口 ​

源码文件:system/sepolicy/tools/sepolicy-analyze/attribute.c

c
void attribute_usage() {
    fprintf(stderr, "\tattribute [-l|--list] [-r|--reverse] <name>\n");
}

int attribute_func(int argc, char **argv, policydb_t *policydb)
{
    int list = 0;
    int reverse = 0;
    char ch;
    struct option attribute_options[] = {
        {"list", no_argument, NULL, 'l'},
        {"reverse", no_argument, NULL, 'r'},
        {NULL, 0, NULL, 0}
    };

    while ((ch = getopt_long(argc, argv, "lr", attribute_options, NULL)) != -1) {
        switch (ch) {
        case 'l': list = 1; break;
        case 'r': reverse = 1; break;
        default: USAGE_ERROR = true; return -1;
        }
    }
    if ((argc != 2 && !(reverse && argc == 3)) || (list && reverse)) {
        USAGE_ERROR = true;
        return -1;
    }
    if (list)
        return list_all_attributes(policydb);
    return list_attribute(policydb, argv[optind], reverse);
}

attribute <name> 查询 attribute→type;attribute -r <type> 查询 type→attribute;attribute -l 列出全部 attribute。-l 与 -r 互斥,参数数量不对会走 usage error。

3.2 双向bitmap ​

源码文件:system/sepolicy/tools/sepolicy-analyze/attribute.c

c
static void retrieve_mapping(policydb_t *policydb, struct type_datum *dat,
                             char *name, int reverse)
{
    struct ebitmap_node *n;
    unsigned int bit;

    if (reverse) {
        ebitmap_for_each_bit(&policydb->type_attr_map[dat->s.value - 1], n, bit) {
            if (!ebitmap_node_get_bit(n, bit))
                continue;
            if (!strcmp(policydb->p_type_val_to_name[bit], name))
                continue;
            printf("%s\n", policydb->p_type_val_to_name[bit]);
        }
    } else {
        ebitmap_for_each_bit(&policydb->attr_type_map[dat->s.value - 1], n, bit) {
            if (ebitmap_node_get_bit(n, bit))
                printf("%s\n", policydb->p_type_val_to_name[bit]);
        }
    }
}

正向查询使用 attr_type_map,反向查询使用 type_attr_map。这两个 bitmap 让分析器可以回答“某规则里的 attribute 覆盖哪些具体 type”或“某 domain 具有什么 attributes”;它们描述 policydb 关系,不改变策略。

3.3 类型校验 ​

源码文件:system/sepolicy/tools/sepolicy-analyze/attribute.c

c
static int list_attribute(policydb_t *policydb, char *name, int reverse)
{
    struct type_datum *dat = hashtab_search(policydb->p_types.table, name);
    if (!dat) {
        fprintf(stderr, "%s is not defined in this policy.\n", name);
        return -1;
    }
    if (reverse) {
        if (dat->flavor != TYPE_TYPE) {
            fprintf(stderr, "%s is an attribute not a type in this policy.\n", name);
            return -1;
        }
    } else if (dat->flavor != TYPE_ATTRIB) {
        fprintf(stderr, "%s is a type not an attribute in this policy.\n", name);
        return -1;
    }
    retrieve_mapping(policydb, dat, name, reverse);
    return 0;
}

工具不会把同名 type 和 attribute 模糊处理:正向必须传 attribute,反向必须传 type。这个失败边界能快速发现查询命令把 domain 当成 attribute,或把 attribute 名写成不存在的 type。

4. neverallow组件 ​

4.1 输入分派 ​

源码文件:system/sepolicy/tools/sepolicy-analyze/neverallow.c

下面是 check_neverallows 的真实关键片段;为突出“注释清理 → 规则解析 → libsepol 断言”,省略了同一函数中与这些步骤无关的局部声明和错误跳转。

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 ((file == NULL) == (rules == NULL)) {
        USAGE_ERROR = true;
        return -1;
    }
    return file ? check_neverallows_file(policydb, file)
                : check_neverallows_string(policydb, rules, strlen(rules));
}

输入必须二选一:-f 读取已展开的 neverallow 文本文件,或 -n 直接读取规则字符串;两者同时给出或都缺失都会返回 usage error。-d 打印解析过程,-w 对 policydb 中找不到的 type/class/permission 发 warning。

4.2 type set ​

源码文件:system/sepolicy/tools/sepolicy-analyze/neverallow.c

c
if (*p == '*') {
    typeset->flags = TYPE_STAR;
    p++;
    continue;
}
if (*p == '-') {
    negate = true;
    p++;
    continue;
}

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) {
    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);
}

解析器支持 *、-type、attribute 和普通 type。attribute 会通过 attr_type_map 展开,否定项先进入 negset,之后从 typeset 中移除;neverallow 的集合语义不是简单字符串匹配。未知 type 是否输出 warning 取决于 -w,这在跨版本检查中既可能是预期,也可能暴露版本错配。

4.3 断言检查 ​

源码文件:system/sepolicy/tools/sepolicy-analyze/neverallow.c

c
static int check_neverallows(policydb_t *policydb, char *text, char *end)
{
    /* Comments are removed before parsing expanded rules. */
    char *cur_non_comment_text = calloc(1, (end - text) + 1);
    char *p = text;
    bool in_comment = false;
    while (p < end) {
        if (*p == '#') in_comment = true;
        if (!in_comment || *p == '\n') *cur_non_comment_text++ = *p;
        if (*p == '\n') in_comment = false;
        ++p;
    }
    /* read_typeset/read_classperms build avrule nodes here. */
    result = check_assertions(NULL, policydb, neverallows);
    avrule_list_destroy(neverallows);
    free(non_comment_text);
    return result;
}

真实函数会继续调用 read_typeset、read_classperms 并检查分号;此处省略与核心关系无关的局部解析语句。关键是最后由 libsepol check_assertions 把 neverallow 节点与目标 policydb 的 allow/avtab 交叉比对。返回 0 且无输出表示给定输入未发现违规;非零需要结合 stderr 判断是解析失败还是断言冲突。

5. 其他组件 ​

5.1 duplicate allow ​

源码文件:system/sepolicy/tools/sepolicy-analyze/dups.c

c
if (!(k->specified & AVTAB_ALLOWED))
    return 0;
if (k->source_type == k->target_type)
    return 0; /* self rule */

/* An attribute-based rule that is a superset of a concrete rule is a dup. */
if ((attrib1 && perms == node->datum.data) ||
    (attrib2 && perms == d->data)) {
    printf("Duplicate allow rule found:\n");
    display_allow(policydb, k, i, d->data);
    display_allow(policydb, &node->key, i, node->datum.data);
}

dups 只处理 AVTAB_ALLOWED,跳过 self rule,并在 attribute rule 覆盖 concrete rule 时报告重复。它是策略精简提示,不是安全违规;宽 attribute 可能自然产生重复,删除前必须回到 source .te 和 attribute 设计。

5.2 boolean与typecmp ​

源码文件:system/sepolicy/tools/sepolicy-analyze/booleans.c、system/sepolicy/tools/sepolicy-analyze/README

c
int booleans_func(int argc, __attribute__((unused)) char **argv,
                  policydb_t *policydb)
{
    if (argc != 1) {
        USAGE_ERROR = true;
        return -1;
    }
    return hashtab_map(policydb->p_bools.table, list_booleans, NULL);
}

booleans 枚举 policydb 中的 boolean 名称;Android policy 禁止 policy booleans 时,任何输出都应回到构建/CTS 约束。typecmp 比较 type 的 allow 等价/差异,README 明确指出它不覆盖所有约束、默认关闭条件、audit 规则和 transition,不能据此直接合并两个 domain。

6. 构建与诊断 ​

6.1 命令 ​

sh
# Read-only: list permissive domains in a built binary policy.
sepolicy-analyze out/target/product/<board>/root/sepolicy permissive

# Read-only: list all attributes or inspect type/attribute direction.
sepolicy-analyze out/target/product/<board>/root/sepolicy attribute -l
sepolicy-analyze out/target/product/<board>/root/sepolicy attribute domain
sepolicy-analyze out/target/product/<board>/root/sepolicy attribute -r my_daemon

# Read-only: check expanded neverallow text against a binary policy.
sepolicy-analyze out/target/product/<board>/root/sepolicy neverallow \
  -f out/target/product/<board>/obj/ETC/general_sepolicy.conf_intermediates/general_sepolicy.conf

这些命令分别消费同一份 binary 的不同结构;路径必须替换为实际 product 输出。neverallow -f 的输入应是 M4 展开后的文本,不能直接传 .te 宏源码。

6.2 结果边界 ​

结果可以得出不能得出
permissive 输出 foobinary map 包含 foofoo 当前正在运行或所有 AVC 来自 foo
attribute domain 输出若干 typepolicydb 的 domain 成员这些 type 的每条运行时访问都允许
neverallow 返回 0给定文本与 policydb 未发现违规设备加载的是这份 binary
dups 输出规则对存在可疑重复 allow一定可以删除其中一条
typecmp -e 输出 type 对分析范围内 allow 行为等价约束、transition、audit 全部等价

6.3 错误路径 ​

policy 文件打不开、mmap 失败、policydb 反序列化失败时,错误发生在所有组件之前;组件参数错误会设置 USAGE_ERROR 并打印 usage。neverallow 的未知符号是否报 warning 取决于 -w,而 attribute 对 type/attribute 角色错误会返回非零。排查时先区分“工具没读到 policy”“参数不合法”和“分析发现违规”。

7. 读码练习 ​

  1. 用 permissive 检查 binary,再回到 private/su.te 判断声明来源;
  2. 用 attribute domain 与 attribute -r su 做双向查询,验证 type map 的方向;
  3. 用 neverallow -d -w 解析一条包含 attribute、否定和 class permission 的规则,观察未知符号如何处理;
  4. 用 dups 找到一对重复 allow,回到 source .te 和 attribute 定义判断哪条是可删除的冗余;
  5. 将分析结果与设备当前加载 policy、getenforce、AVC 日志和构建变体对照,写出各自没有证明的部分。

8. 源码导航 ​

  1. system/sepolicy/tools/sepolicy-analyze/sepolicy-analyze.c:组件注册、policydb 生命周期和返回码。
  2. system/sepolicy/tools/sepolicy-analyze/utils.c:open、mmap、policydb_read 和资源清理。
  3. system/sepolicy/tools/sepolicy-analyze/perm.c:permissive map 遍历。
  4. system/sepolicy/tools/sepolicy-analyze/attribute.c:attribute/type 双向 bitmap 查询。
  5. system/sepolicy/tools/sepolicy-analyze/neverallow.c:type set、class permission 解析和 check_assertions。
  6. system/sepolicy/tools/sepolicy-analyze/dups.c:重复 allow 的 attribute 超集判断。
  7. system/sepolicy/tools/sepolicy-analyze/booleans.c、typecmp.c:其他组件实现。
  8. system/sepolicy/tools/sepolicy-analyze/README:命令输入、输出与分析边界。
  9. system/sepolicy/build/soong/policy.go:Soong 如何消费 permissive 分析结果。