策略查询工具
本文承接 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.py | AOSP tests | allow、genfscon、attribute 展开 | CTS/VTS 与 AOSP 测试 |
sepolicy-analyze | AOSP host tool | permissive、neverallow 等专项分析 | Soong 构建与测试 |
1.2 查询数据流
2. AOSP查询器
2.1 参数入口
源码文件:system/sepolicy/tests/searchpolicy.py
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
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
python_binary_host {
name: "searchpolicy",
srcs: ["searchpolicy.py"],
libs: ["pysepolwrap"],
data: [":libsepolwrap"],
}Blueprint 先声明 host 目标和运行时依赖,脚本入口再负责把 wrapper 解压到临时目录并启动查询;两段代码分别属于构建 owner 和执行 owner。
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
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
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
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 RuleResolveTypeAttribute 允许 source/target 参数使用 type 或 attribute;查询器先把 attribute 解析为成员集合,再与 TERule 比较。它回答的是“policydb 中有哪些匹配记录”,不是“内核这次访问最终允许了什么”。
4. attribute与genfs
4.1 成员查询
源码文件:system/sepolicy/tests/policy.py
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 TypeAttrIsAttr=True 返回 attribute 的成员 type;IsAttr=False 返回 type 关联的 attributes。不存在的名字在 IgnoreMissing=False 时会终止工具,不能把空集合当作“没有成员”。
4.2 expanded规则
源码文件:system/sepolicy/tests/policy.py
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
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 查询命令
# 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
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 无匹配
- 用 denial 的 source/target type,而不是进程名或路径名。
- 确认 class 和 permission 属于同一 object class。
- 用成员查询展开 source/target attribute。
- 确认查询的是当前构建产物或设备导出的 binary。
- 回到
.te、contexts、M4 variant 和 CIL 产物定位规则在哪一阶段消失。
6.2 有匹配仍拒绝
优先检查对象实际 label、DAC owner/mode、MLS constraint、条件规则状态、是否查询了错误 binary,以及 denial 是否来自另一个 PID。查询工具不执行真实系统调用,不能替代 enforcing 回归。
6.3 查询失败
| 症状 | owner | 含义 |
|---|---|---|
Failed to load policy | libsepolwrap.load_policy | 路径、格式或 binary 无效 |
Failed to initialize avtab | wrapper iterator | policydb 无法建立规则迭代器 |
Must specify --allow or --genfs | argparse | 查询类型缺失 |
| type iterator failure | QueryTypeAttribute | 名字不存在或 type/attribute 角色不符 |
| 输出为空 | TERuleMatch/QueryGenfs | 可能无匹配,也可能过滤条件过窄 |
7. 读码练习
选取一条真实 AVC,完成闭环:
- 从日志取出 source type、target type、class 和 permission;
- 用
searchpolicy查询普通 allow,并用 expanded 查询思路判断 attribute 是否参与; - 若 target 来自 proc/sysfs,改用
--genfs验证标签来源; - 将查询结果与设备
ps -Z、ls -Z和加载日志对照; - 如果规则存在仍拒绝,列出 DAC、MLS、条件规则、binary 来源和 PID 复用等替代解释。
再阅读 policy.py 的 __InitTERules、QueryTypeAttribute 和 QueryExpandedTERule,说明普通 avtab、conditional avtab 和 expanded avtab 为什么不能混为同一个“规则列表”。
8. 源码导航
system/sepolicy/tests/searchpolicy.py:AOSP 查询器参数、allow/genfs 分派和输出格式。system/sepolicy/tests/policy.py:policydb 加载、type/attribute、avtab、expanded rule 和 genfs 查询。system/sepolicy/tests/sepol_wrap.cpp:libsepolwrap的迭代器和 policydb 封装。system/sepolicy/tests/Android.bp:searchpolicy、libsepolwrap和 Python 依赖的 host 构建定义。system/sepolicy/tests/sepolicy_tests.py:AOSP 测试如何消费查询接口。system/sepolicy/docs/validation_and_debugging.md:audit2allow 与查询结果的调试边界。- AVC Denial 日志:四元组字段和内核审计生成。
- policy.conf 生成:查询目标 binary 的构建来源。
