Skip to content

File Contexts

从 Android 分区 file_contexts 构建、fc_sort、checkfc、运行时 lookup、init/ueventd/installd restorecon 和内核 xattr 标签追踪文件上下文。

基于android-17.0.0_r1
AndroidSELinuxfile_contextsrestoreconxattrcheckfc源码阅读

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

yaml
# 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

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

text
/                   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

text
/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

yaml
# 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

text
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

make
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

make
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

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

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

text
// 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

make
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

make
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

make
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

python
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 = line

6.2 Comparator ​

排序从较不具体到较具体:含 meta 的在静态条目前,短 stem 在长 stem 前,短 expression 在长 expression 前,无 file type 限制在有 file type 限制前。

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

python
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

python
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 可复现实验 ​

bash
# 从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

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

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

make
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

text
/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

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

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

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

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

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

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

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

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

text
// 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

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

bash
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

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

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

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

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

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

c
return security_transition_sid(crsec->sid,
			       dsec->sid, tclass,
			       name, _new_isid);

13.2 两条路径 ​

场景标签来源生效时机
镜像中的静态文件构建工具按 file contexts 写 xattr镜像生成时
Runtime create + type_transitionCreator/parent/class/nameinode 创建时
Runtime create + fscreate SIDTask credentials create_sidinode 创建时
已存在对象修正file contexts lookup + restoreconrestorecon 执行时
genfs/pseudo fsgenfscon/filesystem behaviorinode 初始化时

13.3 MediaTuner闭环 ​

/system/bin/mediatuner 在镜像中获得 mediatuner_exec xattr,init exec 时读取该 inode SID,再匹配 process transition 到 mediatuner。

源码文件:system/sepolicy/private/file_contexts

text
/system/bin/mediatuner	        u:object_r:mediatuner_exec:s0

源码文件:system/sepolicy/private/mediatuner.te

text
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:

bash
# 查看当前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 排序测试 ​

bash
# 运行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输出 ​

bash
# 查看平台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 构建测试 ​

bash
# 初始化构建环境并选择产品后,构建文本、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.bin

plat_file_contexts_test 验证 Context 与 binary policy;data test 验证示例 path 的解析 type;binary target 验证合并、排序、checkfc 和 compile 全链路。

15.4 设备闭环 ​

bash
# 以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.bpplat/vendor/product/odm_file_contexts
Context module如何执行m4system/sepolicy/build/soong/selinux_contexts.gobuildGeneralContexts
Binary contexts如何合并system/sepolicy/Android.bpfile_contexts_bin_gen
Device entries如何排序system/sepolicy/tests/fc_sort.pyFileContextsNode.__lt__
Context如何校验system/sepolicy/tools/checkfc.cvalidate、attribute assertion
Path解析如何测试system/sepolicy/contexts/file_contexts_test_datapath expected_type
Init如何缓存handlesystem/core/init/selabel.cppSelabelInitialize
Init builtin如何调用system/core/init/builtins.cpp、util.cpprestorecon parse/execute
First-stage标哪些路径system/core/init/selinux.cppSelinuxRestoreContext
Ueventd如何并行恢复system/core/init/coldboot.cpprestorecon queues
App data如何延迟递归frameworks/native/cmds/installd/InstalldNativeService.cpprestorecon_app_data_lazy
内核如何读取xattrkernel/common/security/selinux/hooks.cinode_doinit_use_xattr
内核如何验证重标kernel/common/security/selinux/hooks.cselinux_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。