selinux.cpp 详解
本文承接 内核加载流程,但不再按启动时间线复述全部步骤,而是把 system/core/init/selinux.cpp 当作一个由多个函数簇组成的模块来读:策略选择簇、日志簇、策略加载簇、分区补挂簇和标签恢复簇。你需要知道 init 的 first/second stage 和 split policy;如果刚接触这些概念,可先阅读 版本化策略。
selinux.cpp 的特殊之处在于它同时服务三个时机:策略加载前的无策略/内核域、策略安装后的 enforcing 切换,以及 second-stage init 已经运行后的设备标签恢复。函数之间没有共享一个大状态对象,而是通过文件描述符、字符串、环境变量和内核 SELinux 状态传递结果。理解这些隐式边界,比逐行翻译某个主函数更重要。
1. 函数簇
1.1 模块地图
源码文件:system/core/init/selinux.cpp、system/core/init/selinux.h
selinux.h 只导出 SetupSelinux、SelinuxRestoreContext、SelinuxSetupKernelLogging 和 SelinuxGetVendorAndroidVersion;其余函数位于匿名 namespace。这个边界使策略打开、日志转发和版本判断成为 init 内部实现,而不是其他进程可以直接调用的 API。
1.2 公开契约
源码文件:system/core/init/selinux.h
// Initialize SELinux, then exec init to run in the init SELinux context.
int SetupSelinux(char** argv);
// Restore labels before /dev is populated by ueventd.
void SelinuxRestoreContext();
// Set up SELinux logging to be written to kmsg.
void SelinuxSetupKernelLogging();
// Return the Android API level with which vendor SEPolicy was compiled.
int SelinuxGetVendorAndroidVersion();注释本身给出了生命周期契约:SetupSelinux 不承诺返回,而是初始化后 exec;SelinuxRestoreContext 有明确的“必须早于 ueventd”时机;版本查询供其他 init 代码做兼容判断。阅读实现时应先用这些契约约束局部推理。
2. 策略簇
2.1 模式选择
源码文件:system/core/init/selinux.cpp
constexpr const char plat_policy_cil_file[] = "/system/etc/selinux/plat_sepolicy.cil";
bool IsSplitPolicyDevice() {
// Presence of the platform CIL identifies a Treble split-policy device.
return access(plat_policy_cil_file, R_OK) != -1;
}
bool ReadFirstLine(const char* file, std::string* line) {
line->clear();
std::string contents;
if (!android::base::ReadFileToString(file, &contents, true /* follow symlinks */)) {
return false;
}
std::istringstream in(contents);
std::getline(in, *line);
return true;
}IsSplitPolicyDevice 只检查 platform CIL 是否可读,不检查 vendor、mapping 或 hash;它是路径选择器,不是完整健康检查。ReadFirstLine 被版本文件和 hash 检查共用,读取成功但第一行为空仍由调用方决定是否失败。
2.2 预编译检查
源码文件:system/core/init/selinux.cpp
Result<std::string> FindPrecompiledSplitPolicy() {
std::string precompiled_sepolicy;
static constexpr const char vendor_precompiled_sepolicy[] =
"/vendor/etc/selinux/precompiled_sepolicy";
static constexpr const char odm_precompiled_sepolicy[] =
"/odm/etc/selinux/precompiled_sepolicy";
if (access(odm_precompiled_sepolicy, R_OK) == 0) {
precompiled_sepolicy = odm_precompiled_sepolicy;
} else if (access(vendor_precompiled_sepolicy, R_OK) == 0) {
precompiled_sepolicy = vendor_precompiled_sepolicy;
} else {
return ErrnoError() << "No precompiled sepolicy at "
<< vendor_precompiled_sepolicy;
}
// Use precompiled policy only when every corresponding hash matches.
std::vector<std::pair<std::string, std::string>> sepolicy_hashes{
{"/system/etc/selinux/plat_sepolicy_and_mapping.sha256",
precompiled_sepolicy + ".plat_sepolicy_and_mapping.sha256"},
{"/system_ext/etc/selinux/system_ext_sepolicy_and_mapping.sha256",
precompiled_sepolicy + ".system_ext_sepolicy_and_mapping.sha256"},
{"/product/etc/selinux/product_sepolicy_and_mapping.sha256",
precompiled_sepolicy + ".product_sepolicy_and_mapping.sha256"},
};
for (const auto& [actual_id_path, precompiled_id_path] : sepolicy_hashes) {
if (access(actual_id_path.c_str(), R_OK) != 0) {
if (access(precompiled_id_path.c_str(), R_OK) == 0) {
return Error() << precompiled_id_path << " exists but "
<< actual_id_path << " doesn't";
}
continue;
}
std::string actual_id;
std::string precompiled_id;
if (!ReadFirstLine(actual_id_path.c_str(), &actual_id) ||
!ReadFirstLine(precompiled_id_path.c_str(), &precompiled_id)) {
return Error() << "Failed to read policy hash";
}
if (actual_id.empty() || actual_id != precompiled_id) {
return Error() << actual_id_path << " and " << precompiled_id_path
<< " differ";
}
}
return precompiled_sepolicy;
}预编译策略的 owner 是 vendor/odm 分区文件,init 是消费者。三个 hash 分别覆盖 system、system_ext、product 的 policy 与 mapping;任一 pair 不一致就返回错误,让上层选择 runtime compile。注意“hash 文件缺失”有两种结果:两边都缺失时继续,只有一边存在时失败,因为这表示产物集合不完整。为突出循环和返回边界,上面把源码中两次独立的 ReadFirstLine 错误处理压缩成一个条件;真实实现会分别报告实际 hash 文件路径。
2.3 split打开
源码文件:system/core/init/selinux.cpp
bool GetVendorMappingVersion(std::string* plat_vers) {
if (!ReadFirstLine("/vendor/etc/selinux/plat_sepolicy_vers.txt", plat_vers)) {
PLOG(ERROR) << "Failed to read /vendor/etc/selinux/plat_sepolicy_vers.txt";
return false;
}
if (plat_vers->empty()) {
LOG(ERROR) << "No version present in plat_sepolicy_vers.txt";
return false;
}
return true;
}
bool OpenMonolithicPolicy(PolicyFile* policy_file) {
static constexpr char kSepolicyFile[] = "/sepolicy";
policy_file->fd.reset(open(kSepolicyFile, O_RDONLY | O_CLOEXEC | O_NOFOLLOW));
if (policy_file->fd < 0) {
PLOG(ERROR) << "Failed to open monolithic SELinux policy";
return false;
}
policy_file->path = kSepolicyFile;
return true;
}
void ReadPolicy(std::string* policy) {
PolicyFile policy_file;
bool ok = IsSplitPolicyDevice() ? OpenSplitPolicy(&policy_file)
: OpenMonolithicPolicy(&policy_file);
if (!ok) LOG(FATAL) << "Unable to open SELinux policy";
if (!android::base::ReadFdToString(policy_file.fd, policy)) {
PLOG(FATAL) << "Failed to read policy file: " << policy_file.path;
}
}ReadPolicy 把“打开策略”和“读入内存”分成两步,最后只向上层交付字符串。split 路径的 PolicyFile.path 可能是预编译文件,也可能是 /dev/sepolicy.XXXXXX;这个字段只用于失败诊断,但对判断实际消费者很重要。
2.4 组装参数
源码文件:system/core/init/selinux.cpp
std::vector<const char*> compile_args{
"/system/bin/secilc",
use_userdebug_policy ? *userdebug_plat_sepolicy : plat_policy_cil_file,
"-m", "-M", "true", "-G", "-N", "-v",
"-c", version_as_string.c_str(),
plat_mapping_file.c_str(),
"-o", compiled_sepolicy,
// /dev/null is not available this early in boot.
"-f", "/sys/fs/selinux/null",
};
if (!plat_compat_cil_file.empty()) compile_args.push_back(plat_compat_cil_file.c_str());
if (!system_ext_policy_cil_file.empty()) compile_args.push_back(system_ext_policy_cil_file.c_str());
if (!system_ext_mapping_file.empty()) compile_args.push_back(system_ext_mapping_file.c_str());
if (!product_policy_cil_file.empty()) compile_args.push_back(product_policy_cil_file.c_str());
if (!product_mapping_file.empty()) compile_args.push_back(product_mapping_file.c_str());
if (!plat_pub_versioned_cil_file.empty()) compile_args.push_back(plat_pub_versioned_cil_file.c_str());
if (!vendor_policy_cil_file.empty()) compile_args.push_back(vendor_policy_cil_file.c_str());
if (!odm_policy_cil_file.empty()) compile_args.push_back(odm_policy_cil_file.c_str());这里的状态变化是 std::vector<const char*> 逐项追加,而不是 shell 字符串拼接;每个 std::string 在函数作用域内保持有效,直到 ForkExecveAndWaitForCompletion 返回。必需的 vendor 和 platform versioned CIL 在前面已经检查,optional 文件则通过 access 决定是否加入。
3. 日志簇
3.1 回调注册
源码文件:system/core/init/selinux.cpp
void SelinuxSetupKernelLogging() {
selinux_callback cb;
cb.func_log = SelinuxKlogCallback;
selinux_set_callback(SELINUX_CB_LOG, cb);
}exec 会丢失 libselinux 的进程内 callback,因此 first-stage 的 SetupSelinux 和 second-stage init 都会注册。注册动作只改变 libselinux 的回调表,不会改变内核 enforcing 状态。
3.2 消息分类
源码文件:system/core/init/selinux.cpp
int SelinuxKlogCallback(int type, const char* fmt, ...) {
android::base::LogSeverity severity = android::base::ERROR;
if (type == SELINUX_WARNING) severity = android::base::WARNING;
else if (type == SELINUX_INFO) severity = android::base::INFO;
char buf[kKlogMessageSize];
va_list ap;
va_start(ap, fmt);
int length_written = vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
if (length_written <= 0) return 0;
// libselinux may append a newline; Android logger already supplies one.
size_t str_len = strlen(buf);
if (buf[str_len - 1] == '\n') buf[str_len - 1] = '\0';
if (type == SELINUX_AVC) {
SelinuxAvcLog(buf);
} else {
android::base::KernelLogger(android::base::MAIN, severity,
"selinux", nullptr, 0, buf);
}
return 0;
}AVC 不走普通 KernelLogger,而是进入 audit netlink;warning/info/error 才按 severity 写入 kmsg。vsnprintf 的返回值小于等于零时直接返回,避免把无效格式化结果送入下游;缓冲区固定为 1024 字节,超长消息会被截断。
3.3 AVC发送
源码文件:system/core/init/selinux.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));
}unique_fd 保证函数离开时关闭 netlink socket;socket 创建失败只丢弃本条转发,不让日志路径反过来杀死 init。这个函数的消费者是内核 audit 通道,最终日志呈现位置由系统日志链路决定,不应简单等同于 logcat。
4. 辅助簇
4.1 enforcing判定
源码文件:system/core/init/selinux.cpp
enum EnforcingStatus { SELINUX_PERMISSIVE, SELINUX_ENFORCING };
EnforcingStatus StatusFromProperty() {
std::string value;
if (android::fs_mgr::GetKernelCmdline("androidboot.selinux", &value) &&
value == "permissive") return SELINUX_PERMISSIVE;
if (android::fs_mgr::GetBootconfig("androidboot.selinux", &value) &&
value == "permissive") return SELINUX_PERMISSIVE;
return SELINUX_ENFORCING;
}
bool IsEnforcing() {
if (ALLOW_PERMISSIVE_SELINUX) {
return StatusFromProperty() == SELINUX_ENFORCING;
}
return true;
}编译期开关先于命令行属性:没有 ALLOW_PERMISSIVE_SELINUX 时,即使 bootconfig 写了 permissive,也会返回 enforcing。SelinuxSetEnforcement 再把期望值与 security_getenforce() 比较,只在不同的时候调用 security_setenforce。
4.2 vendor版本
源码文件:system/core/init/selinux.cpp
int SelinuxGetVendorAndroidVersion() {
if (IsMicrodroid()) return __ANDROID_API_FUTURE__;
static int vendor_android_version = [] {
if (!IsSplitPolicyDevice()) return __ANDROID_API_FUTURE__;
std::string version;
if (!GetVendorMappingVersion(&version)) {
LOG(FATAL) << "Could not read vendor SELinux version";
}
int major_version;
std::string major_version_str(version, 0, version.find('.'));
if (!ParseInt(major_version_str, &major_version)) {
PLOG(FATAL) << "Failed to parse the vendor sepolicy major version "
<< major_version_str;
}
return major_version;
}();
return vendor_android_version;
}这个函数使用函数内静态变量缓存结果,第一次调用才读取文件和解析版本;后续 property/service parser 等调用不会重复 I/O。它只返回 major version,供兼容分支判断,不会改变当前已加载的 policy。
4.3 分区补挂
源码文件:system/core/init/selinux.cpp
[[clang::no_destroy]] static const std::vector<std::string> kPartitionNames = {
"system_ext", "product"};
for (const auto& name : kPartitionNames) {
if (GetEntryForMountPoint(&mounts, "/"s + name)) {
// The partition is already mounted.
continue;
}
auto system_entries = GetEntriesForMountPoint(&fstab, "/system");
for (auto& system_entry : system_entries) {
if (!system_entry) {
LOG(ERROR) << "Could not find mount entry for /system";
break;
}
if (!system_entry->fs_mgr_flags.logical) {
LOG(INFO) << "Skipping mount of " << name << ", system is not dynamic.";
break;
}
auto entry = *system_entry;
auto partition_name = name + fs_mgr_get_slot_suffix();
auto replace_name = "system"s + fs_mgr_get_slot_suffix();
entry.mount_point = "/"s + name;
entry.blk_device = android::base::StringReplace(
entry.blk_device, replace_name, partition_name, false);
if (!fs_mgr_update_logical_partition(&entry)) {
LOG(ERROR) << "Could not update logical partition";
continue;
}
extra_fstab.emplace_back(std::move(entry));
}
}MountMissingSystemPartitions 只在 system_ext/product 尚未挂载且 system 是 logical partition 时尝试补挂。它为 Android 早期系统镜像 OTA 场景保留旧 vendor 的可启动性;挂载错误记录为 LOG(ERROR) 并继续遍历,和策略打开失败的 FATAL 级别不同。
4.4 标签恢复
源码文件:system/core/init/selinux.cpp、system/core/init/selinux.h
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/ptmx", 0);
selinux_android_restorecon("/dev/socket", 0);
selinux_android_restorecon("/dev/random", 0);
selinux_android_restorecon("/dev/urandom", 0);
selinux_android_restorecon("/dev/__properties__", 0);
selinux_android_restorecon("/dev/block", SELINUX_ANDROID_RESTORECON_RECURSE);
selinux_android_restorecon("/apex", 0);
selinux_android_restorecon("/bootstrap-apex", 0);
selinux_android_restorecon("/linkerconfig", 0);
RestoreconIfExists(SnapshotManager::GetGlobalRollbackIndicatorPath().c_str(), 0);
RestoreconIfExists("/metadata/gsi",
SELINUX_ANDROID_RESTORECON_RECURSE |
SELINUX_ANDROID_RESTORECON_SKIP_SEHASH);
}这批 restorecon 面向策略加载前已经创建的设备节点、目录和 OTA/DSU 文件;函数忽略单个 selinux_android_restorecon 的返回值,调用方承担“继续启动”的策略。与 SetupSelinux 中对 /system/bin/init 的单独 FATAL restorecon 不同,后者是 domain transition 的必要前提。
5. 条件分支
5.1 overlay remount
源码文件:system/core/init/selinux.cpp
#ifdef ALLOW_REMOUNT_OVERLAYS
bool EarlySetupOverlays() {
bool has_overlays = false;
std::string contents;
if (!android::base::ReadFileToString("/proc/mounts", &contents, true)) {
PLOG(ERROR) << "Failed to read /proc/mounts";
return false;
}
for (const auto& line : android::base::Split(contents, "\n")) {
if (android::base::StartsWith(line, "overlay")) {
has_overlays = true;
break;
}
}
if (!has_overlays) return false;
if (mount("tmpfs", kSecondStageRes, "tmpfs",
MS_REMOUNT | MS_NOSUID | MS_NODEV,
"mode=0755,uid=0,gid=0") == -1) {
PLOG(FATAL) << "Failed to remount tmpfs on " << kSecondStageRes;
}
return true;
}
#else
bool EarlySetupOverlays() { return false; }
#endif未定义 ALLOW_REMOUNT_OVERLAYS 时,编译器直接选择恒 false 的实现;这不是运行时读取属性。定义后,SetupSelinux 在策略加载前重挂 ramdisk tmpfs,加载完成后 SetupOverlays 复制并 fexecve overlay_remounter。fexecve 成功时不会回到当前函数,失败则 FATAL。
5.2 Microdroid
源码文件:system/core/init/selinux.cpp
void LoadSelinuxPolicyMicrodroid() {
constexpr const char kMicrodroidPrecompiledSepolicy[] =
"/system/etc/selinux/microdroid_precompiled_sepolicy";
unique_fd policy_fd(open(kMicrodroidPrecompiledSepolicy,
O_RDONLY | O_CLOEXEC | O_NOFOLLOW));
if (policy_fd < 0) {
PLOG(FATAL) << "Failed to open " << kMicrodroidPrecompiledSepolicy;
}
std::string policy;
if (!android::base::ReadFdToString(policy_fd, &policy)) {
PLOG(FATAL) << "Failed to read policy file: "
<< kMicrodroidPrecompiledSepolicy;
}
LoadSelinuxPolicy(policy);
}Microdroid 直接读取 system 内预编译单文件,不走普通设备的 vendor mapping 和 snapuserd 分区组装;SetupSelinux 的 enforcing、restorecon 和 second-stage 逻辑仍然复用。条件分支的消费者是 IsMicrodroid(),不是某个 CIL 文件名。
6. 排查与练习
6.1 日志路径
# Read-only: identify the chosen policy path and version selector.
adb shell 'test -r /system/etc/selinux/plat_sepolicy.cil && echo split || echo monolithic'
adb shell 'cat /vendor/etc/selinux/plat_sepolicy_vers.txt'
# Read-only: inspect policy selection, compiler, label, and AVC messages.
adb shell 'dmesg | grep -E "Opening SELinux policy|Compiling SELinux policy|Could not load policy|restorecon|avc: denied"'没有 Opening SELinux policy 时,应回到 first-stage mount 和 selinux_setup exec;出现 Compiling SELinux policy 只表示预编译未命中;出现 Could not load policy 才说明 binary 被内核拒绝。avc: denied 则发生在 policy 已安装之后,排查对象已经从加载链转为 domain/object/permission。
6.2 读码任务
给定一次启动失败,沿源码回答:
/system/etc/selinux/plat_sepolicy.cil存在但/vendor/etc/selinux/plat_sepolicy_vers.txt为空时,哪一个函数返回 false,哪个上层路径最终 FATAL?- 预编译策略 hash 不匹配时,为什么不会立即启动失败?fallback 生成的临时文件由谁创建、谁删除?
ALLOW_PERMISSIVE_SELINUX未定义时,bootconfig 的 permissive 值为什么不起作用?SelinuxKlogCallback收到 AVC 与 warning 时,分别经过哪个消费者,为什么不能用同一个日志查询命令解释?SelinuxRestoreContext忽略某些 restorecon 返回值,而/system/bin/initrestorecon 却 FATAL,这两个调用的生命周期责任有什么不同?
6.3 源码搜索
# Read-only: navigate internal function clusters and their callers.
rg -n 'SetupSelinux|ReadPolicy|OpenSplitPolicy|FindPrecompiledSplitPolicy|SelinuxSetupKernelLogging|SelinuxKlogCallback|SelinuxRestoreContext|SelinuxGetVendorAndroidVersion' \
system/core/init
# Read-only: inspect consumers of the exported version query.
rg -n 'SelinuxGetVendorAndroidVersion\(' system/core/init --glob '*.cpp'第一条命令用于确认函数是否仍在当前 checkout 中、调用点在哪;第二条能看到 property service、service parser、util 等消费者如何按 vendor API level 选择兼容行为。搜索结果只能建立调用关系,不能代替对具体条件分支和返回值的阅读。
7. 源码导航
system/core/init/selinux.cpp:本篇所有策略、日志、模式、挂载和标签函数。system/core/init/selinux.h:对外导出函数及生命周期注释。system/core/init/main.cpp:selinux_setup/second_stage参数分派。system/core/init/first_stage_init.cpp:first-stage mount 和 exec 前置条件。system/core/init/snapuserd_transition.h:StartTransition/FinishTransition的协作对象。system/core/init/property_service.cpp、service_parser.cpp:SelinuxGetVendorAndroidVersion的实际消费者。
