File Contexts
本文面向已经读过 安全上下文、Type Transition、M4预处理 和 策略语法工作台 的读者。前文说明 type 如何参与访问控制和新对象 SID 计算,本篇回答路径如何获得 type:多分区 file_contexts 如何被 m4 处理、排序、校验和编译,运行时 lookup 如何得到期望 Context,restorecon 在何时修正 xattr,内核又如何把 xattr 转成 inode SID。
本文首先区分三个经常混为一谈的状态:规则数据库中的期望 Context、文件系统 security.selinux xattr 中持久化的当前 Context、内核 inode security blob 中缓存的 SID。修改 file_contexts 只改变第一项;镜像重建或 restorecon 才会改变第二项;inode 初始化或 xattr 更新再同步第三项。读完后,你应能解释“规则已经改了但 ls -Z 仍旧”“创建成功却标签不对”“restorecon 被拒绝”分别停在哪一层。
1. 三个标签状态
1.1 期望Context
file_contexts 是 path pattern 到安全 Context 的映射数据库。它描述某条路径在特定文件类型下应当使用什么标签,不是 allow rule,也不直接修改磁盘。
源码文件:system/sepolicy/private/file_contexts
# Entries in this file describe the security context associated with a file
# path. They are used when building the device image, to include the security
# context within the extended file attributes of the file system. They are also
# used at runtime when calling restorecon.1.2 持久化Context
支持 SELinux xattr 的 ext4/f2fs 等文件系统通常把当前 Context 字符串存入 security.selinux。镜像构建可以预先写入,运行时 restorecon 可以重新写入。
1.3 Inode SID
内核不在每次访问时解析 path regex。它读取 inode xattr,把 Context 转为 SID,并缓存在 inode security blob;AVC 使用 SID pair 和 class 做决策。
源码文件:kernel/common/security/selinux/hooks.c
static int inode_doinit_use_xattr(struct inode *inode,
struct dentry *dentry,
u32 def_sid, u32 *sid)
{
char *context;
unsigned int len;
int rc;
len = INITCONTEXTLEN;
context = kmalloc(len + 1, GFP_NOFS);
if (!context)
return -ENOMEM;
context[len] = '\0';
rc = __vfs_getxattr(dentry, inode,
XATTR_NAME_SELINUX, context, len);
/* ERANGE重试分支省略。 */
if (rc < 0) {
kfree(context);
if (rc != -ENODATA)
return rc;
*sid = def_sid;
return 0;
}
rc = security_context_to_sid_default(context, rc, sid,
def_sid, GFP_NOFS);
kfree(context);
return 0;
}原函数在初始 buffer 太小时会查询真实长度并重新读取;无 xattr 时采用 filesystem default SID。
2. 规则语法
2.1 Path与Context
常见条目由 path expression、可选的文件类型限制和完整 Context 组成。Android 平台大量使用两字段形式;普通文件限制可用 --。
源码文件:system/sepolicy/private/file_contexts
/ u:object_r:rootfs:s0
/init u:object_r:init_exec:s0
/system/bin/flags_health_check -- u:object_r:flags_health_check_exec:s0
/system/bin/mediatuner u:object_r:mediatuner_exec:s0-- 限制普通文件,避免同路径其他 inode mode 命中同一条目。没有 mode token 的条目可匹配该 path expression 支持的任意文件类型。
2.2 正则示例
源码文件:system/sepolicy/private/file_contexts
/dev(/.*)? u:object_r:device:s0
/dev/block(/.*)? u:object_r:block_device:s0
/dev/block/mapper/.*\.apex u:object_r:apex_dm_device:s0
/dev/block/dm-[0-9]+ u:object_r:dm_device:s0
/mnt/expand/[^/]+/app/vmdl[^/]+\.tmp(/.*)? u:object_r:apk_tmp_file:s0
/data/system/users/[0-9]+/wallpaper u:object_r:wallpaper_file:s0路径表达式要匹配完整规范化路径。.、+ 等 regex metacharacter 需要按字面含义转义;(/.*)? 同时覆盖目录本身和后代。
2.3 匹配规则
平台文件头直接记录了 backend 的匹配约定:静态条目优先,第一个匹配生效,条目从底向上评估。因此源码推荐把较宽规则写在前面、较具体规则写在后面。
源码文件:system/sepolicy/private/file_contexts
# The entries are evaluated by the following rules:
# - Static entries (that is, not using regular expressions) are always
# evaluated first.
# - The first matching entry is used.
# - Entries are evaluated from the bottom to the top.
#
# Based on these rules, it is recommended that the less specific entries appear
# first. For instance:
# /dev(/.*)? u:object_r:device:s0
# /dev/block(/.*)? u:object_r:block_device:s0
# /dev/block/my_dev u:object_r:my_dev:s0这条约定比“最长正则必胜”更准确。构建侧的 fc_sort 会按相同 specificity 方向整理 device entries,但源码文件仍应清楚表达从宽到窄的覆盖关系。
2.4 Type声明边界
Context 中使用的 type 必须已在 policy 中声明,并属于 file contexts 允许的 attribute 集合。下面的 executable type 同时属于 system_file_type、exec_type 和 file_type。
源码文件:system/sepolicy/private/mediatuner.te
type mediatuner_exec, system_file_type, exec_type, file_type;只有 path mapping 没有 type 声明会在 checkfc/checkpolicy 构建阶段失败;只有 type 声明没有 path mapping 则不会让实际 binary 自动获得该标签。
3. 分区输入
3.1 Platform与Device
Android 分别生成 platform、system_ext、product、vendor 和 odm contexts modules。Vendor/ODM 模块显式启用 fc_sort。
源码文件:system/sepolicy/contexts/Android.bp
file_contexts {
name: "plat_file_contexts",
defaults: ["contexts_flags_defaults"],
srcs: [":file_contexts_files{.plat_private}"],
product_variables: {
address_sanitize: {
srcs: [":file_contexts_asan_files{.plat_private}"],
},
debuggable: {
srcs: [":file_contexts_overlayfs_files{.plat_private}"],
},
},
}
file_contexts {
name: "vendor_file_contexts",
defaults: ["contexts_flags_defaults"],
srcs: [
":file_contexts_files{.plat_vendor}",
":file_contexts_files{.vendor}",
],
soc_specific: true,
fc_sort: true,
}3.2 扩展分区
源码文件:system/sepolicy/contexts/Android.bp
file_contexts {
name: "system_ext_file_contexts",
defaults: ["contexts_flags_defaults"],
srcs: [":file_contexts_files{.system_ext_private}"],
system_ext_specific: true,
}
file_contexts {
name: "product_file_contexts",
defaults: ["contexts_flags_defaults"],
srcs: [":file_contexts_files{.product_private}"],
product_specific: true,
}
file_contexts {
name: "odm_file_contexts",
defaults: ["contexts_flags_defaults"],
srcs: [":file_contexts_files{.odm}"],
device_specific: true,
fc_sort: true,
}分区属性控制安装位置和变体,不意味着运行时按五次独立 lookup 决策;Android libselinux file handle 会加载相应分区 contexts 形成查询输入。
3.3 Recovery变体
每个主要分区还有 recovery variant。Recovery contexts 安装到 recovery 根目录,并可能复用 core variant 输出。
4. Module构建
4.1 M4输入
通用 contexts module 在每个 source 后插入 newline,传入 Soong m4 definitions、board API 与 release flags,再生成 m4 output。
源码文件:system/sepolicy/build/soong/selinux_contexts.go
newlineFile := pathForModuleOut(ctx, "newline")
rule.Command().Text("echo").FlagWithOutput("> ", newlineFile)
rule.Temporary(newlineFile)
var inputsWithNewline android.Paths
for _, input := range inputs {
inputsWithNewline = append(inputsWithNewline, input, newlineFile)
}
flags := m.getBuildFlags(ctx)
rule.Command().
Tool(ctx.Config().PrebuiltBuildTool(ctx, "m4")).
Text("--fatal-warnings -s").
FlagForEachArg("-D", ctx.DeviceConfig().SepolicyM4Defs()).
Flag(boardApiLevelToM4Macro(ctx, m.properties.Board_api_level)).
Flags(flagsToM4Macros(flags)).
Inputs(inputsWithNewline).
FlagWithOutput("> ", builtContext)Contexts 支持与 TE policy 相同的 release flag 和 board API 条件;源码中存在的条目不一定进入当前产品输出。
4.2 去注释与排序
File contexts 默认移除注释;开启 fc_sort 时,对 m4 输出再做排序。
源码文件:system/sepolicy/build/soong/selinux_contexts.go
if proptools.Bool(m.properties.Remove_comment) {
rule.Temporary(builtContext)
remove_comment_output :=
pathForModuleOut(ctx, ctx.ModuleName()+"_remove_comment")
rule.Command().
Text("sed -e 's/#.*$//' -e '/^$/d'").
Input(builtContext).
FlagWithOutput("> ", remove_comment_output)
builtContext = remove_comment_output
}
if proptools.Bool(m.properties.Fc_sort) {
rule.Temporary(builtContext)
sorted_output := pathForModuleOut(ctx,
ctx.ModuleName()+"_sorted")
rule.Command().
Tool(ctx.Config().HostToolPath(ctx, "fc_sort")).
FlagWithInput("-i ", builtContext).
FlagWithOutput("-o ", sorted_output)
builtContext = sorted_output
}buildFileContexts() 默认把 Remove_comment 设为 true;是否排序由 module property 决定。
4.3 安装输出
普通系统把 contexts 安装到对应分区的 etc/selinux,recovery 变体安装到 recovery root。文本 contexts 既供镜像工具和测试使用,也可成为合并/编译输入。
5. Binary构建
5.1 五步流水线
顶层构建还生成合并后的 file_contexts.bin。源码注释明确列出五步:local m4、device m4、device check/sort、concat、最终 check/compile。
源码文件:system/sepolicy/Android.bp
// The file_contexts.bin is built in the following way:
// 1. Collect all file_contexts files in THIS repository and process them with
// m4 into a tmp file called file_contexts.local.tmp.
// 2. Collect all device specific file_contexts files and process them with m4
// into a tmp file called file_contexts.device.tmp.
// 3. Run checkfc -e (allow no device fc entries ie empty) and fc_sort on
// file_contexts.device.tmp and output to file_contexts.device.sorted.tmp.
// 4. Concatenate file_contexts.local.tmp and file_contexts.device.sorted.tmp
// into file_contexts.concat.tmp.
// 5. Run checkfc and sefcontext_compile on file_contexts.concat.tmp to produce
// file_contexts.bin.5.2 Local组
Platform、system_ext 和 product 先用 m4 合成 local temporary file。
源码文件:system/sepolicy/Android.bp
java_genrule {
name: "file_contexts.local.tmp",
srcs: [
":plat_file_contexts",
":system_ext_file_contexts",
":product_file_contexts",
],
tools: ["m4"],
out: ["file_contexts.local.tmp"],
cmd: "$(location m4) --fatal-warnings " +
"-s $(in) > $(out)",
}5.3 Device组
Vendor 与 ODM 使用可追加的 device m4 definitions 生成 device temporary file,再由 checkfc -e 和 fc_sort 处理。
源码文件:system/sepolicy/Android.bp
java_genrule {
name: "file_contexts.device.sorted.tmp",
srcs: [
":file_contexts.device.tmp",
":precompiled_sepolicy",
],
tools: [
"checkfc",
"fc_sort",
],
out: ["file_contexts.device.sorted.tmp"],
cmd: "$(location checkfc) " +
"-e $(location :precompiled_sepolicy) " +
"$(location :file_contexts.device.tmp) && " +
"$(location fc_sort) " +
"-i $(location :file_contexts.device.tmp) " +
"-o $(out)",
}5.4 最终校验
Local 与 sorted device contexts 合并后,再用完整 precompiled policy 校验并编译为 binary contexts。
源码文件:system/sepolicy/Android.bp
java_genrule {
name: "file_contexts_bin_gen",
srcs: [
":file_contexts.concat.tmp",
":precompiled_sepolicy",
],
tools: [
"checkfc",
"sefcontext_compile",
],
out: ["file_contexts.bin"],
cmd: "$(location checkfc) " +
"$(location :precompiled_sepolicy) " +
"$(location :file_contexts.concat.tmp) && " +
"$(location sefcontext_compile) " +
"-o $(out) $(location :file_contexts.concat.tmp)",
}Binary contexts 优化运行时加载和 lookup;文本合并结果仍是调试构建问题的重要中间产物。
6. 排序算法
6.1 Specificity字段
Android 测试目录中的 Python fc_sort 与 host fc_sort comparator 保持一致。每个条目记录是否含 regex meta、首个 meta 前 stem 长度、去转义后的表达式长度和可选 file type。
源码文件:system/sepolicy/tests/fc_sort.py
class FileContextsNode(object):
def __init__(self, path, file_type, context,
meta, stem_len, str_len, line):
self.path = path
self.file_type = file_type
self.context = context
self.meta = meta
self.stem_len = stem_len
self.str_len = str_len
self.type = context.split(":")[2]
self.line = line6.2 Comparator
排序从较不具体到较具体:含 meta 的在静态条目前,短 stem 在长 stem 前,短 expression 在长 expression 前,无 file type 限制在有 file type 限制前。
源码文件:system/sepolicy/tests/fc_sort.py
def __lt__(self, other):
if self.meta and not other.meta:
return True
if other.meta and not self.meta:
return False
if self.stem_len < other.stem_len:
return True
if other.stem_len < self.stem_len:
return False
if self.str_len < other.str_len:
return True
if other.str_len < self.str_len:
return False
if self.file_type is None and other.file_type is not None:
return True
if other.file_type is None and self.file_type is not None:
return False
return False更具体条目最终靠后,与 file contexts “从底向上、首个匹配”约定一致。
6.3 单元测试
源码文件:system/sepolicy/tests/fc_sort_test.py
def testLesserThan(self):
n1 = fc_sort.FileContextsNode.create(
"/data u:object_r:rootfs:s0")
n2 = fc_sort.FileContextsNode.create(
"/d u:object_r:rootfs:s0")
n3 = fc_sort.FileContextsNode.create(
"/data/l(/.*)? u:object_r:log:s0")
n4 = fc_sort.FileContextsNode.create(
"/data -- u:object_r:rootfs:s0")
contexts = [n1, n2, n3, n4]
contexts.sort()
self.assertEqual(contexts, [n3, n2, n1, n4])测试输入同时覆盖 meta、stem 长度与 file type;断言是 regex 宽规则最前,带 mode 的静态条目最后。它验证排序,不验证 regex 实际能否匹配某条路径。
6.4 可复现实验
# 从system/sepolicy目录执行,观察从宽到窄的输出。
tmp_fc=$(mktemp)
printf '%s\n' \
'/dev(/.*)? u:object_r:device:s0' \
'/dev/block(/.*)? u:object_r:block_device:s0' \
'/dev/block/my_dev u:object_r:my_dev:s0' \
'/dev/block -- u:object_r:block_dir:s0' > "$tmp_fc"
python3 tests/fc_sort.py -i "$tmp_fc"
rm "$tmp_fc"预期最后两条是 /dev/block -- ... 与 /dev/block/my_dev ... 这类更具体静态规则;同 stem/length 时 file type 限制会更靠后。
7. 构建校验
7.1 Context合法性
checkfc 加载 binary policy,解析每个 Context,并要求 file context type 至少属于 fs_type、dev_type 或 file_type 中的一个。
源码文件:system/sepolicy/tools/checkfc.c
static const char * const CHECK_FC_ASSERT_ATTRS[] = {
"fs_type", "dev_type", "file_type", NULL
};
static const char * const *filemode_to_assert_attrs(filemode mode)
{
switch (mode) {
case filemode_file_contexts:
return CHECK_FC_ASSERT_ATTRS;
case filemode_property_contexts:
return CHECK_PC_ASSERT_ATTRS;
case filemode_service_contexts:
return CHECK_SC_ASSERT_ATTRS;
case filemode_hw_service_contexts:
return CHECK_HW_SC_ASSERT_ATTRS;
case filemode_vendor_service_contexts:
return CHECK_VND_SC_ASSERT_ATTRS;
}
exit(EXIT_FAILURE);
}7.2 Type成员检查
源码文件:system/sepolicy/tools/checkfc.c
const char *type_name = sepol_context_get_type(ctx);
uint32_t len = ebitmap_length(&global_state.assert.set);
if (len > 0) {
res = !is_type_of_attribute_set(global_state.sepolicy.pdb,
type_name, &global_state.assert.set);
if (res) {
fprintf(stderr, "Error: type \"%s\" is not of set: ",
type_name);
dump_char_array(stderr, global_state.assert.attrs);
fprintf(stderr, "\n");
rc = -1;
goto out;
}
}Type 存在但没有任何 file/fs/dev attribute 同样会失败,避免把 process-only type 用作 inode label。
7.3 Path测试
checkfc -t 读取 path expected_type 测试数据,通过真实 selabel backend lookup,并比较解析结果 type。
源码文件:system/sepolicy/contexts/Android.bp
file_contexts_test {
name: "plat_file_contexts_data_test",
srcs: [":file_contexts_files{.plat_private}"],
test_data: "file_contexts_test_data",
}源码文件:system/sepolicy/contexts/file_contexts_test_data
/init init_exec
/dev device
/dev/block block_device
/dev/block/dm-0 dm_device
/system/bin/mediatuner mediatuner_exec测试输入是路径和预期 type;关键断言是 regex precedence 解析到正确 type。它不检查设备上文件当前 xattr。
7.4 Test执行
源码文件:system/sepolicy/build/soong/selinux_contexts.go
if validateWithPolicy {
sepolicy := android.PathForModuleSrc(ctx,
proptools.String(m.properties.Sepolicy))
rule.Command().BuiltTool(tool).
Flags(flags).
Input(sepolicy).
Inputs(srcs)
} else {
test_data := android.PathForModuleSrc(ctx,
proptools.String(m.fileProperties.Test_data))
rule.Command().BuiltTool(tool).
Flags(flags).
Inputs(srcs).
Input(test_data)
}普通 contexts test 验证 Context 与 policy;data test 验证 lookup 结果。两者覆盖面不同。
8. 运行时Lookup
8.1 Handle缓存
Init 启动时创建 file context handle 并交给 Android restorecon 实现复用,避免重复加载 contexts。
源码文件:system/core/init/selabel.cpp
selabel_handle* sehandle = nullptr;
void SelabelInitialize() {
sehandle = selinux_android_file_context_handle();
selinux_android_set_sehandle(sehandle);
}8.2 普通Lookup
源码文件:system/core/init/selabel.cpp
bool SelabelLookupFileContext(const std::string& key,
int type,
std::string* result) {
result->clear();
if (!sehandle) return true;
char* context;
if (selabel_lookup(sehandle, &context,
key.c_str(), type) != 0) {
return false;
}
*result = context;
free(context);
return true;
}Lookup 输入不仅有 path,还有 inode mode/type;带 -- 等 mode restriction 的规则由此参与选择。
8.3 Alias匹配
Init 还封装 selabel_lookup_best_match(),可提供 aliases,在多个可见路径之间选择最佳 Context。
源码文件:system/core/init/selabel.cpp
bool SelabelLookupFileContextBestMatch(
const std::string& key,
const std::vector<std::string>& aliases,
int type, std::string* result) {
result->clear();
if (!sehandle) return true;
std::vector<const char*> c_aliases;
for (const auto& alias : aliases) {
c_aliases.emplace_back(alias.c_str());
}
c_aliases.emplace_back(nullptr);
char* context;
if (selabel_lookup_best_match(sehandle, &context,
key.c_str(), &c_aliases[0],
type) != 0) {
return false;
}
*result = context;
free(context);
return true;
}9. Init Restorecon
9.1 Builtin参数
Init restorecon builtin 支持 recursive、skip-ce、cross-filesystems、force 和 data-data flags,并要求 flags 位于 paths 前。
源码文件:system/core/init/util.cpp
static const flag_type flags[] = {
{"--recursive", SELINUX_ANDROID_RESTORECON_RECURSE},
{"--skip-ce", SELINUX_ANDROID_RESTORECON_SKIPCE},
{"--cross-filesystems",
SELINUX_ANDROID_RESTORECON_CROSS_FILESYSTEMS},
{"--force", SELINUX_ANDROID_RESTORECON_FORCE},
{"--data-data", SELINUX_ANDROID_RESTORECON_DATADATA},
{0, 0}};
int flag = 0;
std::vector<std::string> paths;
bool in_flags = true;
for (size_t i = 1; i < args.size(); ++i) {
if (android::base::StartsWith(args[i], "--")) {
if (!in_flags)
return Error() << "flags must precede paths";
/* flag lookup and OR omitted. */
} else {
in_flags = false;
paths.emplace_back(args[i]);
}
}9.2 Builtin执行
源码文件:system/core/init/builtins.cpp
static Result<void> do_restorecon(const BuiltinArguments& args) {
auto restorecon_info = ParseRestorecon(args.args);
if (!restorecon_info.ok()) {
return restorecon_info.error();
}
const auto& [flag, paths] = *restorecon_info;
int ret = 0;
for (const auto& path : paths) {
if (selinux_android_restorecon(path.c_str(), flag) < 0) {
ret = errno;
}
}
if (ret)
return ErrnoErrorIgnoreEnoent()
<< "selinux_android_restorecon() failed";
return {};
}Builtin 可以一次处理多个 path;任一路径失败会保存 errno,并在循环后返回失败。
9.3 启动阶段
First-stage init 会恢复 /dev、block devices、APEX 目录等关键路径。
源码文件:system/core/init/selinux.cpp
void SelinuxRestoreContext() {
LOG(INFO) << "Running restorecon...";
selinux_android_restorecon("/dev", 0);
selinux_android_restorecon("/dev/console", 0);
selinux_android_restorecon("/dev/kmsg", 0);
selinux_android_restorecon("/dev/null", 0);
selinux_android_restorecon("/dev/socket", 0);
selinux_android_restorecon("/dev/__properties__", 0);
selinux_android_restorecon(
"/dev/block", SELINUX_ANDROID_RESTORECON_RECURSE);
selinux_android_restorecon(
"/dev/dm-user", SELINUX_ANDROID_RESTORECON_RECURSE);
selinux_android_restorecon("/dev/device-mapper", 0);
selinux_android_restorecon("/apex", 0);
selinux_android_restorecon("/bootstrap-apex", 0);
selinux_android_restorecon("/linkerconfig", 0);
}9.4 Init自转换
加载 policy 后,first-stage init 还会 restorecon /system/bin/init,以便 ramdisk 等不持久保存 xattr 的场景获得 init_exec,随后重新 exec 进入 init domain。
源码文件:system/core/init/selinux.cpp
// We're in the kernel domain and want to transition to the init domain.
// File systems that store SELabels in their xattrs, such as ext4 do not
// need an explicit restorecon here, but other file systems do.
if (selinux_android_restorecon("/system/bin/init", 0) == -1) {
PLOG(FATAL) << "restorecon failed of /system/bin/init failed";
}
const char* path = "/system/bin/init";
const char* args[] = {path, "second_stage", nullptr};
execv(path, const_cast<char**>(args));file context、restorecon 和 process transition 在这里形成真实闭环。
10. Ueventd恢复
10.1 Cold Boot策略
Ueventd 注释说明:对每个 child device 重复递归 restorecon 代价很高,因此 cold boot 期间并行处理 uevents,并对 /sys 进行集中递归恢复。
源码文件:system/core/init/ueventd.cpp
// Since many devices have child devices, calling
// selinux_android_restorecon() recursively for each device when its uevent is
// handled, results in multiple restorecon operations being done on a given
// file. It is more efficient to simply do restorecon recursively on /sys
// during cold boot, than to do restorecon on each device as its uevent is
// handled.10.2 并行恢复
源码文件:system/core/init/coldboot.cpp
if (enable_parallel_restorecon_) {
if (parallel_restorecon_queue_.empty()) {
parallel_restorecon_queue_.emplace_back("/sys");
parallel_restorecon_queue_.emplace_back("/sys/devices");
}
for (const auto& dir : parallel_restorecon_queue_) {
selinux_android_restorecon(dir.c_str(), 0);
GenerateRestoreCon(dir);
}
}
runner->StartInBackground();
if (!enable_parallel_restorecon_) {
selinux_android_restorecon(
"/sys", SELINUX_ANDROID_RESTORECON_RECURSE);
}
runner->Wait();Parallel 模式先标注顶层并生成子目录任务;非 parallel 模式由主线程递归 /sys。两条路径最终都在 cold boot 完成前等待。
11. Data恢复
11.1 Init Rc调用
Android init rc 在不同生命周期恢复关键目录。它不会在每次 boot 无条件递归所有路径,而是按 mount、加密和服务准备阶段选择目标。
源码文件:system/core/rootdir/init.rc
on early-init
restorecon /adb_keys
on post-fs
restorecon /metadata
on post-fs-data
restorecon /data
restorecon /data/misc /data/misc/keystore
on late-fs
restorecon_recursive /metadata上面摘录来自多个 action 区段,只保留 restorecon 相关行;实际 action 中还有 mount、mkdir、property 等其他命令。
11.2 Async请求
非 init 进程可通过专用 property 请求 init 异步递归 restorecon,避免阻塞 Property Service 主路径。
源码文件:system/core/init/property_service.cpp
if (name == kRestoreconProperty &&
cr.pid != 1 && !value.empty()) {
[[clang::no_destroy]] static AsyncRestorecon async_restorecon;
async_restorecon.TriggerRestorecon(value);
return {PROP_SUCCESS};
}该 property 是控制入口,不是期望标签来源;实际标签仍由 contexts handle lookup。
11.3 应用数据延迟恢复
Installd 对已有 app data 先恢复顶层并比较 before/after;只有顶层标签变化或上次操作中断时才递归。递归期间用 user.restorecon_in_progress 记录可恢复状态。
源码文件:frameworks/native/cmds/installd/InstalldNativeService.cpp
bool inProgress = getRestoreconInProgress(path);
std::string before, after;
if (!inProgress) {
if (before = lgetfilecon(path); before.empty())
return -1;
if (selinux_android_restorecon_pkgdir(
path.c_str(), seInfo.c_str(), uid, 0) < 0)
return -1;
if (after = lgetfilecon(path); after.empty())
return -1;
}
if (inProgress || before != after) {
auto restorecon = [path, seInfo, uid]() {
RestoreconInProgress fence(path);
return selinux_android_restorecon_pkgdir(
path.c_str(), seInfo.c_str(), uid,
SELINUX_ANDROID_RESTORECON_RECURSE);
};
if (inProgress)
std::thread(restorecon).detach();
else if (int result = restorecon(); result)
return result;
}App data lookup 还使用 seInfo 与 UID,不是单纯 path regex;具体 seapp mapping 由后续专题负责。
12. Xattr提交
12.1 Relabel权限
Restorecon 最终写 security.selinux 时进入 inode setxattr hook。内核检查当前 label 的 relabelfrom、新 label 的 relabelto、validatetrans 与 filesystem associate。
源码文件:kernel/common/security/selinux/hooks.c
if (strcmp(name, XATTR_NAME_SELINUX))
return dentry_has_perm(current_cred(), dentry,
FILE__SETATTR);
if (!(sbsec->flags & SBLABEL_MNT))
return -EOPNOTSUPP;
isec = backing_inode_security(dentry);
rc = avc_has_perm(sid, isec->sid, isec->sclass,
FILE__RELABELFROM, &ad);
if (rc)
return rc;
rc = security_context_to_sid(value, size, &newsid, GFP_KERNEL);
if (rc)
return rc;
rc = avc_has_perm(sid, newsid, isec->sclass,
FILE__RELABELTO, &ad);
if (rc)
return rc;
rc = security_validate_transition(isec->sid, newsid,
sid, isec->sclass);
if (rc)
return rc;
return avc_has_perm(newsid, sbsec->sid,
SECCLASS_FILESYSTEM,
FILESYSTEM__ASSOCIATE, &ad);原函数对 invalid Context 还包含 MAC admin force 与 audit 分支;这里展示正常 relabel 路径。
12.2 更新Inode缓存
Xattr 写成功后,post hook 将 Context 转为 SID,并更新 inode security blob。
源码文件:kernel/common/security/selinux/hooks.c
rc = security_context_to_sid_force(value, size, &newsid);
if (rc) {
pr_err("SELinux: unable to map context to SID"
"for (%s, %lu), rc=%d\n",
inode->i_sb->s_id, inode->i_ino, -rc);
return;
}
isec = backing_inode_security(dentry);
spin_lock(&isec->lock);
isec->sclass = inode_mode_to_security_class(inode->i_mode);
isec->sid = newsid;
isec->initialized = LABEL_INITIALIZED;
spin_unlock(&isec->lock);因此 restorecon 生效后,后续访问不必等待 inode eviction 才看到新 SID。
12.3 不允许移除标签
内核禁止删除 security.selinux xattr;可以重标,但不能让持久化对象重新变为无标签。
源码文件:kernel/common/security/selinux/hooks.c
/* No one is allowed to remove a SELinux security label.
You can change the label, but all data must be labeled. */
return -EACCES;13. 创建与Restorecon
13.1 创建时标签
普通文件创建不会让 kernel 查询 file_contexts path regex。Kernel 根据 creator SID、parent SID、class、object name 与显式 create SID 计算 new SID。
源码文件:kernel/common/security/selinux/hooks.c
return security_transition_sid(crsec->sid,
dsec->sid, tclass,
name, _new_isid);13.2 两条路径
| 场景 | 标签来源 | 生效时机 |
|---|---|---|
| 镜像中的静态文件 | 构建工具按 file contexts 写 xattr | 镜像生成时 |
| Runtime create + type_transition | Creator/parent/class/name | inode 创建时 |
| Runtime create + fscreate SID | Task credentials create_sid | inode 创建时 |
| 已存在对象修正 | file contexts lookup + restorecon | restorecon 执行时 |
| genfs/pseudo fs | genfscon/filesystem behavior | inode 初始化时 |
13.3 MediaTuner闭环
/system/bin/mediatuner 在镜像中获得 mediatuner_exec xattr,init exec 时读取该 inode SID,再匹配 process transition 到 mediatuner。
源码文件:system/sepolicy/private/file_contexts
/system/bin/mediatuner u:object_r:mediatuner_exec:s0源码文件:system/sepolicy/private/mediatuner.te
type mediatuner, domain;
type mediatuner_exec, system_file_type, exec_type, file_type;
init_daemon_domain(mediatuner)如果 binary xattr 错误,增加 process allow 不会让 transition key 自动变成 mediatuner_exec;应修 file contexts/镜像或执行 restorecon。
14. 失败定位
14.1 规则未进入产物
源码有条目但产品无匹配时,检查:
- 条目是否位于正确分区 filegroup;
- M4/release flag 是否裁剪;
- 查看的是 module m4 output、合并文本还是 binary contexts;
- Vendor/ODM 是否经过 fc_sort 后改变了相对顺序;
- Product 是否使用预编译 sepolicy/contexts。
14.2 Lookup错误
| 现象 | 优先检查 |
|---|---|
| 通用规则覆盖具体路径 | 条目排序、static/meta、stem 与 length |
| 文件与目录结果不同 | 可选 inode mode token |
| Path查询无结果 | regex 转义、规范化路径、分区 contexts 是否加载 |
| Test data失败 | 实际 lookup type 与 expected type |
14.3 当前标签未更新
规则改动不会自动遍历已有 userdata。比较 expected 与 current:
# 查看当前xattr对应标签。
adb shell ls -Zd /目标路径
# 查询运行时contexts数据库的期望标签;工具可用性因产品而异。
adb shell matchpathcon /目标路径
# 只读预演或实际restorecon需根据产品工具支持选择。
adb shell restorecon -n -v /目标路径14.4 Restorecon失败
Restorecon 写 xattr 仍受 SELinux 与 filesystem 能力约束。检查 relabelfrom、relabelto、associate、validatetrans、inode owner/capability、mount 是否支持 labels;不要只给目标文件 setattr。
14.5 文件系统边界
无 xattr handler 的 filesystem 会尝试 fallback 到 genfs;mountpoint label、task label 和 transition-based filesystem 也有不同 inode 初始化路径。file_contexts 不是 proc/sysfs 等全部对象的唯一标签来源。
15. 验证方法
15.1 排序测试
# 运行fc_sort单元测试。
cd system/sepolicy
PYTHONPATH=tests python3 -m unittest tests/fc_sort_test.py -v输入覆盖 stem、meta、file type 与读取逻辑;四个测试都通过才说明 comparator 的基本行为没有回归。它不校验 Context type 是否存在。
15.2 M4输出
# 查看平台file_contexts经m4后的实际条目和#line位置。
case "$(uname -s)-$(uname -m)" in
Darwin-*) M4=prebuilts/build-tools/darwin-x86/bin/m4 ;;
Linux-x86_64) M4=prebuilts/build-tools/linux-x86/bin/m4 ;;
Linux-aarch64) M4=prebuilts/build-tools/linux-arm64/bin/m4 ;;
*) printf '%s\n' 'unsupported host'; exit 1 ;;
esac
"$M4" --fatal-warnings -s \
system/sepolicy/private/file_contexts | \
rg '/system/bin/mediatuner|/dev/block\(/\.\*\)\?'15.3 构建测试
# 初始化构建环境并选择产品后,构建文本、data test和binary contexts。
source build/envsetup.sh
lunch PRODUCT-userdebug
m plat_file_contexts \
plat_file_contexts_test \
plat_file_contexts_data_test \
file_contexts.binplat_file_contexts_test 验证 Context 与 binary policy;data test 验证示例 path 的解析 type;binary target 验证合并、排序、checkfc 和 compile 全链路。
15.4 设备闭环
# 以MediaTuner executable为例比较当前和期望标签。
adb shell ls -Z /system/bin/mediatuner
adb shell matchpathcon /system/bin/mediatuner
adb shell ps -AZ | grep mediatuner断言是 executable 当前/期望 type 都为 mediatuner_exec,服务启动后 process domain 为 mediatuner。进程不存在还可能因为 init service disabled/property 未启用,不能直接判断为标签失败。
16. 源码导航
| 问题 | 首选文件 | 关键符号/模块 |
|---|---|---|
| 平台路径规则在哪里 | system/sepolicy/private/file_contexts | 规则头注释与路径条目 |
| 各分区如何生成 | system/sepolicy/contexts/Android.bp | plat/vendor/product/odm_file_contexts |
| Context module如何执行m4 | system/sepolicy/build/soong/selinux_contexts.go | buildGeneralContexts |
| Binary contexts如何合并 | system/sepolicy/Android.bp | file_contexts_bin_gen |
| Device entries如何排序 | system/sepolicy/tests/fc_sort.py | FileContextsNode.__lt__ |
| Context如何校验 | system/sepolicy/tools/checkfc.c | validate、attribute assertion |
| Path解析如何测试 | system/sepolicy/contexts/file_contexts_test_data | path expected_type |
| Init如何缓存handle | system/core/init/selabel.cpp | SelabelInitialize |
| Init builtin如何调用 | system/core/init/builtins.cpp、util.cpp | restorecon parse/execute |
| First-stage标哪些路径 | system/core/init/selinux.cpp | SelinuxRestoreContext |
| Ueventd如何并行恢复 | system/core/init/coldboot.cpp | restorecon queues |
| App data如何延迟递归 | frameworks/native/cmds/installd/InstalldNativeService.cpp | restorecon_app_data_lazy |
| 内核如何读取xattr | kernel/common/security/selinux/hooks.c | inode_doinit_use_xattr |
| 内核如何验证重标 | kernel/common/security/selinux/hooks.c | selinux_inode_setxattr |
从 /system/bin/mediatuner 复述完整路径时,应包括:platform file_contexts 声明期望 Context;构建模块经 m4、校验并让镜像带上 security.selinux xattr;inode 初始化把 Context 转为 mediatuner_exec SID;init 执行该 inode 时,process type transition 计算 mediatuner SID并检查 transition/entrypoint。若规则更新后旧文件 xattr 未变,restorecon 才负责把期望标签提交到文件系统和 inode cache。
能够进一步区分 runtime create 的 type transition、app data 的 seInfo/UID lookup、pseudo filesystem 的 genfs 标签,以及 restorecon 写 xattr 所需的 relabel/associate 权限,才算真正掌握 Android File Contexts。
