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
#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
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
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
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
// 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
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
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
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 断言”,省略了同一函数中与这些步骤无关的局部声明和错误跳转。
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
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
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
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
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 命令
# 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 输出 foo | binary map 包含 foo | foo 当前正在运行或所有 AVC 来自 foo |
attribute domain 输出若干 type | policydb 的 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. 读码练习
- 用
permissive检查 binary,再回到private/su.te判断声明来源; - 用
attribute domain与attribute -r su做双向查询,验证 type map 的方向; - 用
neverallow -d -w解析一条包含 attribute、否定和 class permission 的规则,观察未知符号如何处理; - 用
dups找到一对重复 allow,回到 source.te和 attribute 定义判断哪条是可删除的冗余; - 将分析结果与设备当前加载 policy、
getenforce、AVC 日志和构建变体对照,写出各自没有证明的部分。
8. 源码导航
system/sepolicy/tools/sepolicy-analyze/sepolicy-analyze.c:组件注册、policydb 生命周期和返回码。system/sepolicy/tools/sepolicy-analyze/utils.c:open、mmap、policydb_read 和资源清理。system/sepolicy/tools/sepolicy-analyze/perm.c:permissive map 遍历。system/sepolicy/tools/sepolicy-analyze/attribute.c:attribute/type 双向 bitmap 查询。system/sepolicy/tools/sepolicy-analyze/neverallow.c:type set、class permission 解析和check_assertions。system/sepolicy/tools/sepolicy-analyze/dups.c:重复 allow 的 attribute 超集判断。system/sepolicy/tools/sepolicy-analyze/booleans.c、typecmp.c:其他组件实现。system/sepolicy/tools/sepolicy-analyze/README:命令输入、输出与分析边界。system/sepolicy/build/soong/policy.go:Soong 如何消费 permissive 分析结果。
