Skip to content

audit2allow 工具

结合 Android AVC 审计源码和官方调试文档,建立 audit2allow 的输入、候选规则、人工审查与策略验证边界。

基于android-17.0.0_r1
AndroidSELinuxaudit2allowAVC调试源码阅读

audit2allow 工具 ​

本文承接 AVC Denial 日志:上一文已经解释 scontext、tcontext、tclass、permission 和 permissive 如何由内核生成,本文继续回答“拿到 denial 后,audit2allow 到底能帮你做什么”。先说明一个容易误导读者的事实:Android 17 AOSP 源码树没有 audit2allow 的实现,也没有名为 audit2allow 的 Android.bp 模块;该工具属于宿主机 SELinux policy 工具链。因此本文引用的 Android 代码用于定义日志和策略的真实输入契约,工具本身只作为外部候选规则生成器讨论。

audit2allow 的输出是建议,不是授权。它只根据审计记录中看到的 source、target、class 和 permission 生成语法上可能成立的 allow;它不知道你的服务为何访问对象、对象是否应该换标签、Unix DAC 是否先失败、规则应放在 platform 还是 vendor,也不能替你通过 neverallow、Treble mapping 或设备启动验证。正确闭环是“采集 → 解析 → 反查源码与标签 → 缩小规则 → 构建校验 → enforcing 验证”。

1. 输入契约 ​

1.1 内核记录 ​

源码文件:kernel/common/security/selinux/avc.c

c
static void avc_audit_pre_callback(struct audit_buffer *ab, void *a)
{
    struct common_audit_data *ad = a;
    struct selinux_audit_data *sad = ad->selinux_audit_data;
    u32 av = sad->audited, perm;
    const char *const *perms;
    u32 i;

    audit_log_format(ab, "avc:  %s ", sad->denied ? "denied" : "granted");
    if (av == 0) {
        audit_log_format(ab, " null");
        return;
    }

    perms = secclass_map[sad->tclass - 1].perms;
    audit_log_format(ab, " {");
    i = 0;
    perm = 1;
    while (i < (sizeof(av) * 8)) {
        if ((perm & av) && perms[i]) {
            audit_log_format(ab, " %s", perms[i]);
            av &= ~perm;
        }
        i++;
        perm <<= 1;
    }
    if (av)
        audit_log_format(ab, " 0x%x", av);
    audit_log_format(ab, " } for ");
}

工具能解析的 permission 名称来自 secclass_map[sad->tclass - 1].perms。如果日志中出现十六进制 bit,说明 class map 没有对应的可打印名称;把它直接改写成普通 allow 权限名可能丢失语义。avc: granted 也可能被审计,不应把所有输入行都当作 denial。

1.2 上下文字段 ​

源码文件:kernel/common/security/selinux/avc.c

c
rc = security_sid_to_context(sad->ssid, &scontext, &scontext_len);
if (rc)
    audit_log_format(ab, " ssid=%d", sad->ssid);
else
    audit_log_format(ab, " scontext=%s", scontext);

rc = security_sid_to_context(sad->tsid, &tcontext, &tcontext_len);
if (rc)
    audit_log_format(ab, " tsid=%d", sad->tsid);
else
    audit_log_format(ab, " tcontext=%s", tcontext);

tclass = secclass_map[sad->tclass - 1].name;
audit_log_format(ab, " tclass=%s", tclass);
if (sad->denied)
    audit_log_format(ab, " permissive=%u", sad->result ? 0 : 1);

audit2allow 的最小有效输入依赖这些字段:source context 决定候选规则左侧,target context 和 class 决定右侧,permission 决定权限集合。若 SID 转换失败只得到 ssid/tsid,应先修复上下文/策略加载问题,而不是让工具猜类型。

1.3 init转发 ​

源码文件:system/core/init/selinux.cpp

cpp
void SelinuxAvcLog(char* buf) {
    struct NetlinkMessage {
        nlmsghdr hdr;
        char buf[kKlogMessageSize];
    } request = {};

    request.hdr.nlmsg_flags = NLM_F_REQUEST;
    request.hdr.nlmsg_type = AUDIT_USER_AVC;
    request.hdr.nlmsg_len = sizeof(request);
    strlcpy(request.buf, buf, sizeof(request.buf));

    auto fd = unique_fd{socket(PF_NETLINK, SOCK_RAW | SOCK_CLOEXEC,
                               NETLINK_AUDIT)};
    if (!fd.ok())
        return;
    TEMP_FAILURE_RETRY(send(fd.get(), &request, sizeof(request), 0));
}

init 的转发只复制字符串并送入 audit netlink,不对日志做规则推导。socket 创建失败时静默丢弃转发,原始内核判定不受影响;因此采集前要保留 dmesg、kernel log 和 logcat 的完整行,避免只给 audit2allow 一个被截断的片段。

2. 工具边界 ​

2.1 AOSP是否提供 ​

Android 17 checkout 中没有 external/selinux/policycoreutils/audit2allow/,也没有 AOSP Android.bp 注册 audit2allow host binary。不要在设备上执行“编译 AOSP audit2allow”的假想步骤;实际使用通常是在安装了 SELinux policycoreutils 的 Linux 主机上运行,再把日志文件和生成的候选规则带回 AOSP 策略工程。

可用的 Android 侧事实来源是官方调试文档:

源码文件:system/sepolicy/docs/validation_and_debugging.md

text
Never blindly copy audit2allow output into policy. It suggests rules to make
the denial go away, not to make the system secure.

If a denial shows tcontext=u:object_r:device:s0 (or tmpfs,
system_data_file), do not write an allow rule for device. The correct fix is to
label the specific file or node in file_contexts.

If a dac_override denial occurs, do NOT grant the capability. Fix the underlying
Unix permissions of the file or process.

这不是泛化的安全口号,而是工具输入不足的直接后果:日志里有一次访问,却没有业务意图、资源 owner、生命周期和最小权限设计。generic target type 和 dac_override 正是最容易被工具生成过宽规则的两类输入。

2.2 候选规则 ​

在宿主机上,常见调用形态是:

sh
# Read-only host analysis; the input file is copied from device logs.
audit2allow -i denials.txt

# Read-only host analysis with a reference binary policy.
audit2allow -p device_policy -i denials.txt

-i 让工具读取保存的 audit 文本,-p 提供参考 policy 供工具分析;两者都不会修改 AOSP 源码或设备。不同发行版的 audit2allow 参数和输出格式可能不同,使用前应运行主机上的 audit2allow --help,不能把某个 Linux 发行版的选项表当作 Android 源码事实。

2.3 参考policy ​

-p 的作用是让候选生成器知道当前 policy 中已有的声明和规则,但它仍不等价于 Android 设备当前加载的全部策略。参考文件可能来自构建输出、设备 /sys/fs/selinux/policy 或导出的 binary;使用时必须记录来源和设备状态。若参考 policy 与 denial 来自不同版本,工具可能重复建议已有规则或遗漏版本化/attribute 条件。

3. 逐步审查 ​

3.1 先校验上下文 ​

sh
# Read-only: verify the process domain and target label independently.
adb shell 'ps -AZ | grep my_daemon'
adb shell 'ls -Zd /vendor/etc/my_config /vendor/etc'

# Read-only: retain complete denial lines for later parsing.
adb shell 'dmesg | grep -E "avc:  denied|avc: denied"' > denials.txt

如果 ps -Z 与 scontext 不一致,可能是 PID 已复用、domain transition 未按预期发生或日志来自旧启动;如果 ls -Z 与 tcontext 不一致,可能是对象重标记、路径不同或日志来自另一个 inode。上下文未确认前运行工具只会放大误判。

3.2 再校验DAC ​

dac_override 表示 Linux DAC 检查路径出现问题,不能直接生成 capability allow。对文件/目录 denial,先保存 owner、group、mode 和路径组件:

sh
# Read-only: inspect Unix DAC information for the target and its parents.
adb shell 'namei -l /vendor/etc/my_config'
adb shell 'stat -c "%U %G %a %n" /vendor/etc/my_config /vendor/etc'

这一步的消费者是文件 owner 和 init/service 配置,而不是 SELinux policy compiler。若 Unix 权限不符合设计,先改 owner/mode 或服务运行身份;否则增加 SELinux allow 只会把 DAC 问题隐藏在更宽的安全域中。

3.3 缩小权限 ​

工具可能给出一组 { getattr open read write }。逐项审查时要回到调用方:

候选权限需要回答的问题
getattr服务是否需要读取 metadata,还是库函数顺带触发?
open目标是否被正确标记,是否只需要目录搜索?
read/write哪个业务阶段真正读写,是否应拆成只读文件?
create/add_name服务是否真的拥有创建对象的职责?
ioctl是否需要具体 ioctl 白名单,而非宽泛 device 权限?

最小规则必须使用真实 source/target/class;不要把工具输出中的 attribute 展开成 *,也不要把具体 target type 替换成 device、tmpfs 等 generic type。

text
allow my_daemon vendor_configs_file:file { getattr open read };

上面的规则只是语法形状示例,不能替代对 my_daemon 调用方和 vendor_configs_file 标签来源的核对。真正修改前还要确认该 source/target 是否属于 platform public/private 或 vendor scope。

3.4 构建验证 ​

sh
# Read-only source search: locate the owning policy and constraints.
rg -n 'my_daemon|vendor_configs_file|neverallow' \
  system/sepolicy device vendor --glob '*.te' --glob '*.cil'

# Build validation: compile the policy modules that consume the change.
m selinux_policy

构建阶段会重新经过 M4、checkpolicy、CIL 过滤/版本化和 secilc;候选规则可能因为 undefined type、neverallow、分区 scope 或 mapping 约束失败。audit2allow 成功只证明它能写出一段文本,不证明该文本适合 AOSP 分区边界。

4. 误用与失败 ​

4.1 permissive误读 ​

permissive=1 表示请求被记录但没有阻断;它适合收集候选访问,不证明 enforcing 下业务可用。Android 调试文档建议仅在 bring-up 阶段使用 permissive,并尽快恢复 enforcing。一次 permissive 日志应先转成“需要验证的访问”,而不是直接转成永久 allow。

4.2 dontaudit缺口 ​

dontaudit 可能让某个拒绝不产生日志;没有 audit2allow 输出不等于没有安全策略缺口。此时要用 sesearch、策略源码和调用链判断访问是否发生。相反,auditallow 可能记录成功访问,工具若把 granted 当成 denial 也会产生错误规则。

4.3 generic label ​

如果 target 是 device、tmpfs 或 system_data_file,先追标签生产者:file_contexts、ueventd、mount 规则或对象创建方。对具体路径重新标记后,同一个 source 可能只需要访问专用 type;直接允许 generic type 会扩大到大量不相关对象。

4.4 构建约束 ​

候选规则进入 AOSP 后仍要通过 neverallow。vendor 规则还要经过 public API 与 mapping 约束;即使本地 secilc 能合并,也可能在 Treble compatibility test 或 freeze test 中失败。修复方向应是缩小 source/target、调整标签或把规则放入正确分区,而不是先关闭 neverallow。

5. 可复现闭环 ​

5.1 采集与候选 ​

sh
# Read-only: save a fresh denial set while reproducing one operation.
adb shell 'dmesg -c >/dev/null; <trigger-command>; dmesg' > denials.txt

# Read-only host analysis: generate a proposal for review.
audit2allow -i denials.txt > candidate.te

第一条命令中的 <trigger-command> 代表实际业务操作,必须替换成可重复的触发动作;清空 dmesg 属于会改变设备日志缓冲的调试操作,应在专用调试设备执行。第二条只写主机文件,不修改设备。

5.2 构建与回归 ​

sh
# Read-only: inspect the candidate before copying its minimal rule.
sed -n '1,160p' candidate.te

# Build validation: run the policy target selected by the product.
m selinux_policy

# Device validation: reboot into enforcing and reproduce the same operation.
adb reboot
adb wait-for-device
adb shell 'getenforce'
adb shell '<trigger-command>'
adb shell 'dmesg | grep -E "avc:  denied|avc: denied"'

回归断言不是“日志完全为空”这么简单:要确认原 denial 的 source/target/class/permission 消失,同时没有新增更宽泛的 target denial、DAC 错误、服务启动失败或其他路径的 AVC。若设备仍处于 permissive,先修复测试前置条件,否则结果不能代表生产行为。

6. 源码导航 ​

  1. kernel/common/security/selinux/avc.c:AVC 判定、审计位图和 denial 字段生成。
  2. kernel/common/security/selinux/hooks.c:文件/inode LSM hook 如何发起权限检查。
  3. system/core/init/selinux.cpp:AVC 日志转发、kmsg 和 audit netlink。
  4. system/sepolicy/docs/validation_and_debugging.md:audit2allow 的安全边界、generic label、DAC 和 simpleperf。
  5. AVC Denial 日志:字段含义、permissive 结果和策略查询。
  6. policy.conf 生成:规则进入哪个 scope 和构建变体。
  7. 版本化策略:vendor/public mapping 和跨分区合并。