Skip to content

策略查询工具

结合 AOSP 的 searchpolicy、libsepolwrap 和真实 policy 产物,解释规则查询、attribute 展开、genfs 查询与结果边界。

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

策略查询工具 ​

本文承接 AVC Denial 日志 和 audit2allow 工具。前者给出 source/target/class/permission 四元组,后者说明候选规则不能直接接受;本文补上中间的验证步骤:如何查询当前 policy 中是否真的存在匹配规则,attribute 是否展开到了目标 type,genfscon 是否给某个文件系统路径提供标签。

需要先划清实现边界:AOSP Android 17 不包含 SETools 的 sesearch 与 seinfo 源码;AOSP 自己提供的是功能较窄的 system/sepolicy/tests/searchpolicy.py,底层通过 libsepolwrap 读取二进制 policy。sesearch/seinfo 是宿主机工具,searchpolicy.py 是 AOSP host 测试工具,三者都消费 policy binary,而不是 .te 或 denial 文本。

查询到规则只能证明“这个 policydb 中存在一条匹配记录”,不能单独证明设备当前加载的是同一份 policy、条件规则在运行时取真、MLS constraint 不会拒绝,或对象实际 label/class 与查询一致。

1. 工具边界 ​

1.1 两套实现 ​

工具来源能查询什么主要消费者
sesearch / seinfo外部 SETools规则、类型、attribute、统计信息主机调试人员
searchpolicy.pyAOSP testsallow、genfscon、attribute 展开CTS/VTS 与 AOSP 测试
sepolicy-analyzeAOSP host toolpermissive、neverallow 等专项分析Soong 构建与测试

1.2 查询数据流 ​

2. AOSP查询器 ​

2.1 参数入口 ​

源码文件:system/sepolicy/tests/searchpolicy.py

python
def do_main(libpath):
    parser = argparse.ArgumentParser(
        description="SELinux policy rule search tool. Intended to have a "
            + "similar API as sesearch, but simplified to use only code "
            + "available in AOSP")
    parser.add_argument("policy", help="Path to the SELinux policy to search.",
                        nargs="?")
    tertypes = parser.add_argument_group("TE Rule Types")
    tertypes.add_argument("-A", "--allow", action="append_const",
                        const="allow", dest="tertypes",
                        help="Search allow rules.")
    genfs = parser.add_argument_group("Genfscon Statements")
    genfs.add_argument("--genfs", action="store_true",
                       help="Display genfscon statements.")
    expr = parser.add_argument_group("Expressions")
    expr.add_argument("-s", "--source")
    expr.add_argument("-t", "--target")
    expr.add_argument("-c", "--class", dest="tclass")
    expr.add_argument("-p", "--perms", metavar="PERMS")
    expr.add_argument("-f", "--fs")

    args = parser.parse_args()
    if not args.tertypes and not args.genfs:
        parser.error("Must specify \"--allow\" or \"--genfs\"")
    if not args.policy:
        parser.error("Must include path to policy")

入口只支持 allow 和 genfscon 两类查询。-s/-t/-c/-p 过滤 source、target、class、permission;-f 只对 genfs 查询文件系统。缺少查询类型或 policy 路径时,解析阶段直接失败。

2.2 查询分派 ​

源码文件:system/sepolicy/tests/searchpolicy.py

python
pol = policy.Policy(args.policy, None, libpath)

if args.genfs:
    for fs, path, context, _ in pol.QueryGenfs(fs=args.fs, target=args.target):
        print("genfscon " + fs + " " + path + " " + context)

if args.tertypes:
    scontext = {args.source} if args.source else set()
    tcontext = {args.target} if args.target else set()
    tclass = set(args.tclass.split(",")) if args.tclass else set()
    perms = set(args.perms.split(",")) if args.perms else set()

    TERules = pol.QueryTERule(scontext=scontext, tcontext=tcontext,
                               tclass=tclass, perms=perms)
    rules = []
    for r in TERules:
        rules.append("allow " + r.sctx + " " + r.tctx + ":" + r.tclass +
                     " { " + " ".join(sorted(r.perms)) + " };")
    for r in sorted(rules):
        print(r)

allow 和 genfs 查询共享一个 policydb 快照。输出阶段只格式化 allow,不显示 neverallow、dontaudit 或 conditional expression;需要这些维度时应使用 SETools 或 AOSP 专项测试。

2.3 host模块 ​

源码文件:system/sepolicy/tests/Android.bp、system/sepolicy/tests/searchpolicy.py

make
python_binary_host {
    name: "searchpolicy",
    srcs: ["searchpolicy.py"],
    libs: ["pysepolwrap"],
    data: [":libsepolwrap"],
}

Blueprint 先声明 host 目标和运行时依赖,脚本入口再负责把 wrapper 解压到临时目录并启动查询;两段代码分别属于构建 owner 和执行 owner。

python
if __name__ == "__main__":
    with tempfile.TemporaryDirectory(ignore_cleanup_errors=True) as temp_dir:
        # Extract the wrapper for the host Python process.
        lib_path = policy.ReadLibsepolwrap(temp_dir)
        do_main(lib_path)

Soong 把脚本、Python library 和 wrapper 作为 host 工具打包;它不是 device binary,不能在设备 shell 中假定存在。

3. policydb查询 ​

3.1 加载与释放 ​

源码文件:system/sepolicy/tests/policy.py

python
def __InitPolicy(self, PolicyPath):
    cPolicyPath = create_string_buffer(PolicyPath.encode("ascii"))
    self.__policydbP = self.__libsepolwrap.load_policy(cPolicyPath)
    if self.__policydbP is None:
        sys.exit("Failed to load policy")

def __del__(self):
    if self.__policydbP is not None:
        self.__libsepolwrap.destroy_policy(self.__policydbP)

输入必须是 libsepol 可读取的 policy binary;CIL 文本或损坏文件会在这里失败。析构时销毁 policydb,说明查询对象拥有这份快照的生命周期。

3.2 avtab遍历 ​

源码文件:system/sepolicy/tests/policy.py

python
def __InitTERules(self):
    avtabIterP = self.__libsepolwrap.init_avtab(self.__policydbP)
    if avtabIterP is None:
        sys.exit("Failed to initialize avtab")
    self.__GetTERules(self.__policydbP, avtabIterP, self.__Rules)
    self.__libsepolwrap.destroy_avtab(avtabIterP)

    avtabIterP = self.__libsepolwrap.init_cond_avtab(self.__policydbP)
    if avtabIterP is None:
        sys.exit("Failed to initialize conditional avtab")
    self.__GetTERules(self.__policydbP, avtabIterP, self.__Rules)
    self.__libsepolwrap.destroy_avtab(avtabIterP)

普通 avtab 与 conditional avtab 会合并到 __Rules。查询能看到条件表中的记录,但没有把条件当前值附加到输出,因此它仍是 policydb 事实,不是一次运行时决策。

3.3 类型解析 ​

源码文件:system/sepolicy/tests/policy.py

python
def QueryTERule(self, **kwargs):
    if len(self.__Rules) == 0:
        self.__InitTERules()
    if "scontext" in kwargs and len(kwargs["scontext"]) > 0:
        kwargs["scontext"] = self.ResolveTypeAttribute(kwargs["scontext"])
    if "tcontext" in kwargs and len(kwargs["tcontext"]) > 0:
        tcontext = set()
        for tctx in kwargs["tcontext"]:
            tcontext |= self.ResolveTypeAttribute(tctx)
        kwargs["tcontext"] = tcontext
    for Rule in self.__Rules:
        if self.__TERuleMatch(Rule, **kwargs):
            yield Rule

ResolveTypeAttribute 允许 source/target 参数使用 type 或 attribute;查询器先把 attribute 解析为成员集合,再与 TERule 比较。它回答的是“policydb 中有哪些匹配记录”,不是“内核这次访问最终允许了什么”。

4. attribute与genfs ​

4.1 成员查询 ​

源码文件:system/sepolicy/tests/policy.py

python
def QueryTypeAttribute(self, Type, IsAttr, IgnoreMissing=False):
    TypeIterP = self.__libsepolwrap.init_type_iter(
        self.__policydbP, create_string_buffer(Type.encode("ascii")), IsAttr)
    if TypeIterP is None:
        if IgnoreMissing:
            return {}
        sys.exit("Failed to initialize type iterator")
    buf = create_string_buffer(self.__BUFSIZE)
    TypeAttr = set()
    while True:
        ret = self.__libsepolwrap.get_type(
            buf, self.__BUFSIZE, self.__policydbP, TypeIterP)
        if ret == 0:
            TypeAttr.add(buf.value.decode("ascii"))
            continue
        if ret == 1:
            break
        sys.exit("Failed to import policy")
    self.__libsepolwrap.destroy_type_iter(TypeIterP)
    return TypeAttr

IsAttr=True 返回 attribute 的成员 type;IsAttr=False 返回 type 关联的 attributes。不存在的名字在 IgnoreMissing=False 时会终止工具,不能把空集合当作“没有成员”。

4.2 expanded规则 ​

源码文件:system/sepolicy/tests/policy.py

python
def QueryExpandedTERule(self, **kwargs):
    # Expanded tables materialize attribute members into concrete types.
    if len(self.__ExpandedRules) == 0:
        self.__InitExpandedTERules()
    for Rule in self.__ExpandedRules:
        if self.__TERuleMatch(Rule, **kwargs):
            yield Rule

普通规则保留 policydb 表示;expanded 规则遍历 expanded avtab 和 expanded conditional avtab,把 attribute 组合展开为具体 type。前者适合确认原始规则形状,后者适合解释具体 domain 命中哪条 attribute 规则。

4.3 genfs查询 ​

源码文件:system/sepolicy/tests/searchpolicy.py、system/sepolicy/tests/policy.py

python
def QueryGenfs(self, **kwargs):
    for fs in self.__GenfsDict:
        if "fs" in kwargs and kwargs["fs"] and kwargs["fs"] != fs:
            continue
        for path, context, Type in self.__GenfsDict[fs]:
            if "target" in kwargs and kwargs["target"] and kwargs["target"] != Type:
                continue
            yield (fs, path, context, Type)

QueryGenfs 查询 binary policy 的 genfs 表;它的返回值包含文件系统、路径、context 和 type。对于 proc/sysfs denial,先确认这里的 type,再回到 genfs_contexts 源文件,避免把伪文件系统误判成普通 file_contexts 问题。

5. 联合验证 ​

5.1 查询命令 ​

sh
# Read-only: query AOSP allow rules in a policy binary.
searchpolicy <policy> --allow -s my_daemon -t vendor_configs_file -c file -p read

# Read-only: query a genfs label for one filesystem and type.
searchpolicy <policy> --genfs -f proc -t proc_my_node

# Read-only: use SETools on a host when installed.
sesearch -A -s my_daemon -t vendor_configs_file -c file -p read <policy>
seinfo -a domain -x <policy>

searchpolicy 只支持 AOSP 源码实现的 allow/genfs 参数;sesearch/seinfo 是否可用取决于宿主机。三个命令都只读 policy 文件,输出应与 denial 四元组逐字段比较。

5.2 查询结论 ​

若查询到 allow my_daemon vendor_configs_file:file read;,可以得出“查询的 binary policy 中存在这条 allow”;不能得出设备内核加载的是该 binary、条件表达式当前取真、MLS constraint 不会拒绝、DAC 已通过,或对象当前 label/class 与 denial 相同。

5.3 测试消费者 ​

源码文件:system/sepolicy/tests/sepolicy_tests.py

python
for typeName in test_policy.pol.QueryTypeAttribute("domain", True):
    # Tests consume wrapper queries to validate policy invariants.
    if typeName in exempted_types:
        continue
    check_domain(typeName, test_policy)

AOSP policy tests 直接消费 wrapper 的 type/attribute 和规则查询,把 policydb 事实组合成约束;单独运行查询工具只能得到局部事实。

6. 故障排查 ​

6.1 无匹配 ​

  1. 用 denial 的 source/target type,而不是进程名或路径名。
  2. 确认 class 和 permission 属于同一 object class。
  3. 用成员查询展开 source/target attribute。
  4. 确认查询的是当前构建产物或设备导出的 binary。
  5. 回到 .te、contexts、M4 variant 和 CIL 产物定位规则在哪一阶段消失。

6.2 有匹配仍拒绝 ​

优先检查对象实际 label、DAC owner/mode、MLS constraint、条件规则状态、是否查询了错误 binary,以及 denial 是否来自另一个 PID。查询工具不执行真实系统调用,不能替代 enforcing 回归。

6.3 查询失败 ​

症状owner含义
Failed to load policylibsepolwrap.load_policy路径、格式或 binary 无效
Failed to initialize avtabwrapper iteratorpolicydb 无法建立规则迭代器
Must specify --allow or --genfsargparse查询类型缺失
type iterator failureQueryTypeAttribute名字不存在或 type/attribute 角色不符
输出为空TERuleMatch/QueryGenfs可能无匹配,也可能过滤条件过窄

7. 读码练习 ​

选取一条真实 AVC,完成闭环:

  1. 从日志取出 source type、target type、class 和 permission;
  2. 用 searchpolicy 查询普通 allow,并用 expanded 查询思路判断 attribute 是否参与;
  3. 若 target 来自 proc/sysfs,改用 --genfs 验证标签来源;
  4. 将查询结果与设备 ps -Z、ls -Z 和加载日志对照;
  5. 如果规则存在仍拒绝,列出 DAC、MLS、条件规则、binary 来源和 PID 复用等替代解释。

再阅读 policy.py 的 __InitTERules、QueryTypeAttribute 和 QueryExpandedTERule,说明普通 avtab、conditional avtab 和 expanded avtab 为什么不能混为同一个“规则列表”。

8. 源码导航 ​

  1. system/sepolicy/tests/searchpolicy.py:AOSP 查询器参数、allow/genfs 分派和输出格式。
  2. system/sepolicy/tests/policy.py:policydb 加载、type/attribute、avtab、expanded rule 和 genfs 查询。
  3. system/sepolicy/tests/sepol_wrap.cpp:libsepolwrap 的迭代器和 policydb 封装。
  4. system/sepolicy/tests/Android.bp:searchpolicy、libsepolwrap 和 Python 依赖的 host 构建定义。
  5. system/sepolicy/tests/sepolicy_tests.py:AOSP 测试如何消费查询接口。
  6. system/sepolicy/docs/validation_and_debugging.md:audit2allow 与查询结果的调试边界。
  7. AVC Denial 日志:四元组字段和内核审计生成。
  8. policy.conf 生成:查询目标 binary 的构建来源。