Skip to content

GKI 与 SELinux

区分 GKI 内核的 SELinux 引擎、用户空间策略、vendor_boot 早期资源和 genfs 标签版本,追踪策略加载与内核兼容边界。

基于android-17.0.0_r1
AndroidSELinuxGKIvendor_bootgenfs源码阅读

GKI 与 SELinux ​

本文承接 内核加载流程、Treble 与 sepolicy 和 APEX 与 SELinux。本文解决 GKI 场景中的三个混淆:GKI kernel 是否携带设备策略、vendor_boot 是否是 sepolicy 的存放分区,以及 vendor kernel 新增 proc/sysfs 节点时为什么需要 genfs label 版本协商。

源码结论是:GKI 内核提供 SELinux LSM 实现、policydb parser、AVC、security server 和 selinuxfs;设备的 type/allow/neverallow/CIL 仍由 system/vendor 等用户空间分区提供。vendor_boot 中的 vendor ramdisk 参与 first-stage mount 和设备准备,但正常 split policy 路径仍从 /system/etc/selinux、/vendor/etc/selinux 等目录读取策略。GKI 的兼容边界不是“把 policy 烧进 kernel”,而是“通用内核能解析并执行用户空间提供的 policy,同时为 vendor kernel 对象选择兼容标签”。

1. 引擎与数据 ​

1.1 内核组成 ​

源码文件:kernel/common/security/selinux/Kconfig、kernel/common/security/selinux/Makefile

text
config SECURITY_SELINUX
    bool "SELinux Support"
    depends on SECURITY_NETWORK && AUDIT && NET && INET
    select NETWORK_SECMARK

config SECURITY_SELINUX_DEVELOP
    bool "SELinux Development Support"
    depends on SECURITY_SELINUX

config SECURITY_SELINUX_AVC_STATS
    bool "SELinux AVC Statistics"
    depends on SECURITY_SELINUX

Kconfig 决定功能是否进入 GKI;Makefile 再列出 SELinux LSM 的具体对象文件,两者分别是配置 owner 和链接 owner。

makefile
obj-$(CONFIG_SECURITY_SELINUX) := selinux.o

selinux-y := avc.o hooks.o selinuxfs.o netlink.o nlmsgtab.o \
             netif.o netnode.o netport.o status.o \
             ss/ebitmap.o ss/hashtab.o ss/symtab.o ss/sidtab.o \
             ss/avtab.o ss/policydb.o ss/services.o \
             ss/conditional.o ss/mls.o ss/context.o

这些对象实现访问 hook、AVC、policydb 反序列化、MLS、context/SID 和 selinuxfs。Kconfig 说明启用 SELinux 仍需要“policy configuration and a labeled filesystem”;内核代码本身没有 Android 的 system_server、HAL 或 app allow 规则。

1.2 用户空间策略 ​

源码文件:system/core/init/selinux.cpp

cpp
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;
    }
}

static void LoadSelinuxPolicy(std::string& policy) {
    set_selinuxmnt("/sys/fs/selinux");
    if (security_load_policy(policy.data(), policy.size()) < 0) {
        PLOG(FATAL) << "SELinux:  Could not load policy";
    }
}

上面的 OpenSplitPolicy 是聚焦职责的注释摘录,完整输入组合见 内核加载流程。init 在用户空间生成/读取一个 monolithic binary,再通过 libselinux 写入内核;GKI kernel 只消费 binary。

2. 三种版本 ​

2.1 policydb格式 ​

源码文件:kernel/common/security/selinux/include/security.h

c
#define POLICYDB_VERSION_XPERMS_IOCTL      30
#define POLICYDB_VERSION_INFINIBAND        31
#define POLICYDB_VERSION_GLBLUB            32
#define POLICYDB_VERSION_COMP_FTRANS       33
#define POLICYDB_VERSION_COND_XPERMS       34
#define POLICYDB_VERSION_NEVERAUDIT        35

#define POLICYDB_VERSION_MIN POLICYDB_VERSION_BASE
#define POLICYDB_VERSION_MAX POLICYDB_VERSION_NEVERAUDIT

policydb version 是 binary 序列化格式能力,不是 Android release 或 Treble sepolicy API。当前内核最大支持 35,其中版本 30 引入 ioctl extended permissions,35 引入 neveraudit type map。

2.2 Android编译版本 ​

源码文件:system/sepolicy/build/soong/policy.go

go
const (
    MlsSens    = 1
    MlsCats    = 1024
    PolicyVers = 30
)

secilcCmd := rule.Command().BuiltTool("secilc").
    Flag("-m").
    FlagWithArg("-M ", "true").
    Flag("-G").
    FlagWithArg("-c ", strconv.Itoa(PolicyVers))

Android 17 构建固定让 secilc 输出 policy version 30,即使 GKI 内核 parser 支持到 35。这是 userspace 选择的兼容格式,不代表内核能力只有 30。修改 PolicyVers 会影响 binary 格式和所有加载端,不能与 PLATFORM_SEPOLICY_VERSION=202604 混为一谈。

2.3 版本坐标 ​

名称示例owner用途
policydb version30secilc/kernel parserbinary 格式能力
platform sepolicy version202604system/vendor Treblepublic API mapping
genfs labels version202504 等vendor boardproc/sysfs 标签集合

三者解决不同问题。policydb version 不决定 public type API;platform version 不决定 kernel parser 格式;genfs version 只选择内核伪文件系统的兼容标签补丁。

3. 内核加载契约 ​

3.1 版本检查 ​

源码文件:kernel/common/security/selinux/ss/policydb.c

c
p->policyvers = le32_to_cpu(buf[0]);
if (p->policyvers < POLICYDB_VERSION_MIN ||
    p->policyvers > POLICYDB_VERSION_MAX) {
    pr_err("SELinux: policydb version %d does not match my version range %d-%d\n",
           le32_to_cpu(buf[0]), POLICYDB_VERSION_MIN,
           POLICYDB_VERSION_MAX);
    goto bad;
}

if (le32_to_cpu(buf[1]) & POLICYDB_CONFIG_MLS) {
    p->mls_enabled = 1;
    if (p->policyvers < POLICYDB_VERSION_MLS) {
        pr_err("SELinux: security policydb version %d (MLS) not backwards compatible\n",
               p->policyvers);
        goto bad;
    }
}

内核首先验证版本范围,再检查配置位与格式版本是否兼容。格式过新、过旧或 MLS 标志与旧格式冲突都会让 policydb 读取失败,最终表现为 init security_load_policy FATAL,而不是某条 AVC。

3.2 selinuxfs加载 ​

源码文件:kernel/common/security/selinux/selinuxfs.c

c
static ssize_t sel_write_load(struct file *file, const char __user *buf,
                              size_t count, loff_t *ppos)
{
    struct selinux_load_state load_state;
    ssize_t length;
    void *data = NULL;

    if (*ppos || !count)
        return -EINVAL;

    mutex_lock(&selinux_state.policy_mutex);
    length = avc_has_perm(current_sid(), SECINITSID_SECURITY,
                          SECCLASS_SECURITY, SECURITY__LOAD_POLICY, NULL);
    if (length)
        goto out;

    data = vmalloc(count);
    if (!data) {
        length = -ENOMEM;
        goto out;
    }
    if (copy_from_user(data, buf, count) != 0) {
        length = -EFAULT;
        goto out;
    }

    length = security_load_policy(data, count, &load_state);
    if (length)
        goto out;
    selinux_policy_commit(&load_state);
    length = count;
out:
    mutex_unlock(&selinux_state.policy_mutex);
    return length;
}

selinuxfs 拒绝 partial/empty write,以 mutex 序列化 policy 更新,并要求 caller 有 security:load_policy。新 policy 先构建到 load_state,selinuxfs 节点初始化成功后才 commit;失败路径不会把半解析 policy 替换为当前状态。

3.3 policy能力 ​

源码文件:kernel/common/security/selinux/include/policycap_names.h、kernel/common/security/selinux/ss/services.c

c
const char *const selinux_policycap_names[__POLICYDB_CAP_MAX] = {
    "network_peer_controls",
    "open_perms",
    "extended_socket_class",
    "always_check_network",
    "cgroup_seclabel",
    "nnp_nosuid_transition",
    "genfs_seclabel_symlinks",
    "ioctl_skip_cloexec",
    "userspace_initial_context",
    "netlink_xperm",
    "netif_wildcard",
    "genfs_seclabel_wildcard",
    "functionfs_seclabel",
    "memfd_class",
};

名称表定义可观察的 capability;加载新 policy 时,security server 再从 policydb bitmap 把每一位复制到全局运行状态并打印值。

c
for (i = 0; i < ARRAY_SIZE(selinux_state.policycap); i++)
    WRITE_ONCE(selinux_state.policycap[i],
               ebitmap_get_bit(&p->policycaps, i));

for (i = 0; i < ARRAY_SIZE(selinux_policycap_names); i++)
    pr_info("SELinux: policy capability %s=%d\n",
            selinux_policycap_names[i],
            ebitmap_get_bit(&p->policycaps, i));

policy capability 是 binary policy 与 kernel hook 之间的功能开关,例如 open permission、genfs symlink label、memfd class。它不同于 Kconfig:内核代码可能支持某 capability,但只有 policydb 设置对应 bit 后 hook 才采用新语义。

4. vendor_boot边界 ​

4.1 vendor ramdisk ​

源码文件:build/soong/etc/prebuilt_etc.go、build/soong/android/paths.go

go
// Make this module available when building for vendor ramdisk.
// On devices without a dedicated recovery partition, the module is only
// available after switching root into /first_stage_ramdisk.
Vendor_ramdisk_available *bool

这个 property 决定模块是否产生 vendor ramdisk 变体;路径层再根据设备是否把 recovery 资源移入 vendor_boot,选择普通 vendor ramdisk 或 first_stage_ramdisk 子目录。

go
} else if ctx.InstallInVendorRamdisk() {
    if ctx.DeviceConfig().BoardMoveRecoveryResourcesToVendorBoot() &&
       !ctx.InstallPathSkipFirstStageRamdisk() {
        partition = "vendor_ramdisk/first_stage_ramdisk"
    } else {
        partition = "vendor_ramdisk"
    }
}

vendor_boot 承载 vendor ramdisk 变体和 first-stage 可用资源。它影响 first-stage init 能看到哪些配置、fstab、模块/工具,但正常 split sepolicy 的 CIL/binary 安装路径仍是 system/vendor/odm 的 etc/selinux。不能因为 init 从 vendor ramdisk 启动,就推断 policy 存储在 vendor_boot。

4.2 first-stage交接 ​

源码文件:system/core/init/first_stage_init.cpp

cpp
if (!fsm->DoFirstStageMount()) {
    LOG(FATAL) << "Failed to mount required partitions early ...";
}

const char* path = "/system/bin/init";
const char* args[] = {path, "selinux_setup", nullptr};
execv(path, const_cast<char**>(args));
PLOG(FATAL) << "execv failed";

first-stage owner 负责使用 ramdisk/fstab/设备节点挂载 /system、/vendor 等,之后才执行 system init 的 selinux_setup。策略加载依赖挂载结果,但不由 vendor_boot ramdisk 直接向内核注入规则。

4.3 OTA文件类型 ​

源码文件:system/sepolicy/public/file.te、system/sepolicy/private/file_contexts

text
type vendor_boot_ota_file, vendor_file_type, file_type;

/vendor/boot_otas(/.*)?  u:object_r:vendor_boot_ota_file:s0

vendor_boot_ota_file 标注的是 /vendor/boot_otas 下 OTA 资源文件,不是 vendor_boot 分区整体的 mount label,也不是内核 sepolicy。update_engine/system_app 获得读取权限,说明 SELinux 控制的是用户空间 OTA 文件工作流。

5. genfs版本 ​

5.1 vendor声明 ​

源码文件:build/make/core/config.mk、system/sepolicy/Android.bp

makefile
BOARD_GENFS_LABELS_VERSION ?= $(BOARD_API_LEVEL)
ifeq ($(call math_gt,$(BOARD_API_LEVEL),$(BOARD_GENFS_LABELS_VERSION)),true)
  $(error BOARD_GENFS_LABELS_VERSION must be greater than or equal to BOARD_API_LEVEL)
endif

Make 层确定并校验 board 选择,Soong 层再把这个值物化为 vendor 分区中的版本文件。

make
genrule {
    name: "genfs_labels_version.txt.gen",
    out: ["genfs_labels_version.txt"],
    cmd: select(soong_config_variable("ANDROID", "BOARD_GENFS_LABELS_VERSION"), {
        any @ value: "echo " + value + " > $(out)",
        default: "echo > $(out)",
    }),
}

prebuilt_etc {
    name: "genfs_labels_version.txt",
    src: ":genfs_labels_version.txt.gen",
    relative_install_path: "selinux",
    vendor: true,
}

vendor image 安装 /vendor/etc/selinux/genfs_labels_version.txt,声明它期望的 genfs 标签版本。新 board 构建不能选择低于 board API 的版本;旧设备没有文件时由读取库提供兼容默认值。

5.2 读取与默认 ​

源码文件:system/sepolicy/compat/libgenfslabelsversion/src/lib.rs

rust
const GENFS_LABELS_VERSION_TXT_PATH: &str =
    "/vendor/etc/selinux/genfs_labels_version.txt";
const DEFAULT_GENFS_LABELS_VERSION: i32 = 202404;

#[no_mangle]
pub extern "C" fn get_genfs_labels_version() -> i32 {
    match fs::read_to_string(GENFS_LABELS_VERSION_TXT_PATH) {
        Ok(contents) => match contents.trim().parse::<i32>() {
            Ok(version) => version,
            Err(_) => DEFAULT_GENFS_LABELS_VERSION,
        },
        Err(_) => DEFAULT_GENFS_LABELS_VERSION,
    }
}

读取失败和解析失败都回退到 202404,而不是当前 platform version。这个 fallback 服务旧 vendor 镜像;调试时若文件内容错误,init 日志可能仍显示使用默认版本,不能只检查文件是否存在。

5.3 init选择 ​

源码文件:system/core/init/selinux.cpp

cpp
int vendor_genfs_version = get_genfs_labels_version();
std::string genfs_cil_file = std::format(
        "/system/etc/selinux/plat_sepolicy_genfs_{}.cil",
        vendor_genfs_version);

if (access(genfs_cil_file.c_str(), F_OK) != 0) {
    if (vendor_genfs_version >= 202504) {
        LOG(ERROR) << "Missing " << genfs_cil_file;
        return false;
    }
    LOG(INFO) << "Missing " << genfs_cil_file << "; skipping";
    genfs_cil_file.clear();
} else {
    LOG(INFO) << "Using " << genfs_cil_file << " for genfs labels";
}

版本 202504 及以上缺少对应 CIL 是启动错误;更旧版本缺文件可以跳过。选中的 genfs CIL 被追加到 secilc 输入,最终成为单一 binary 的 genfs table。

5.4 兼容标签 ​

源码文件:system/sepolicy/compat/plat_sepolicy_genfs_202504.cil、plat_sepolicy_genfs_202604.cil

text
(genfscon sysfs "/class/udc" (u object_r sysfs_udc ((s0) (s0))))
(genfscon sysfs "/power/mem_sleep" (u object_r sysfs_mem_sleep ((s0) (s0))))

202504 层提供 UDC 和 suspend mode 标签;后续版本在此基础上追加新的内核对象标签,而不是替换整个 genfs 表。

text
(genfscon sysfs "/class/typec" (u object_r sysfs_typec ((s0) (s0))))
(genfscon proc "/sys/kernel/firmware_config"
    (u object_r proc_firmware_config ((s0) (s0))))

版本文件是增量兼容标签集合。vendor kernel/module 暴露新 sysfs/proc 路径时,platform policy 需要在适当 genfs version 引入 label;旧 vendor 选择旧版本时不会看到超出其契约的新标签变化。

6. GKI APEX边界 ​

6.1 安装钩子 ​

源码文件:system/sepolicy/apex/com.android.gki-file_contexts、system/sepolicy/private/gki_apex_prepostinstall.te

text
(/.*)?       u:object_r:system_file:s0
/bin/(.*)?   u:object_r:gki_apex_prepostinstall_exec:s0

payload 标签只选择 exec type;真正的进程 domain、Binder 能力和工具权限必须提前写入 platform policy。

yaml
type gki_apex_prepostinstall, domain, coredomain;
type gki_apex_prepostinstall_exec, system_file_type, exec_type, file_type;

allow gki_apex_prepostinstall shell_exec:file rx_file_perms;
allow gki_apex_prepostinstall toolbox_exec:file rx_file_perms;
binder_use(gki_apex_prepostinstall)
allow gki_apex_prepostinstall
    update_engine_stable_service:service_manager find;
binder_call(gki_apex_prepostinstall, update_engine)
allow gki_apex_prepostinstall apexd:fd use;

GKI APEX 安装脚本运行在预定义 domain,通过 update_engine 执行更新;它不是把内核 policy 或新 domain 动态注入设备。exec type、Binder 和工具权限都必须在 system policy 中预先存在。

6.2 分区边界 ​

GKI APEX 是用户空间更新容器;vendor_boot 是启动分区/ramdisk;GKI kernel 是通用内核 binary;SELinux binary policy 又来自 system/vendor CIL 合并。四者的签名、更新和生命周期不同,不能用“GKI 策略”一词将它们合并成一个产物。

7. 排查方法 ​

7.1 版本与能力 ​

sh
# Read-only: inspect kernel policy format support and loaded policy capability nodes.
adb shell 'cat /sys/fs/selinux/policyvers'
adb shell 'find /sys/fs/selinux/policy_capabilities -maxdepth 1 -type f -print -exec cat {} \;'

# Read-only: inspect Android and vendor compatibility selectors.
adb shell 'cat /vendor/etc/selinux/plat_sepolicy_vers.txt'
adb shell 'cat /vendor/etc/selinux/genfs_labels_version.txt 2>/dev/null || true'

policyvers 显示内核最大 parser version,不等于当前 binary 一定使用该版本;Android 构建常量需从 Soong/构建日志确认。policy capability 节点显示已加载 policy 启用的功能,而不是 Kconfig 是否编译代码。

7.2 启动日志 ​

sh
# Read-only: inspect policy and genfs selection during early boot.
adb shell 'dmesg | grep -E "policydb version|policy capability|genfs labels|SELinux|sepolicy"'

# Read-only: verify a kernel-generated object label.
adb shell 'ls -Zd /sys/class/typec /proc/sys/kernel/firmware_config 2>/dev/null'

policydb version mismatch 会在内核 parser 阶段失败;genfs CIL 缺失会在 init 组装阶段失败;某个 sysfs 节点标签不符合预期则应检查 vendor genfs version 和对应 compatibility CIL。三者是不同 owner。

7.3 新增内核对象 ​

vendor kernel module 新增 sysfs/proc 路径时,先确定旧 vendor/system 组合是否需要保持 generic label,再选择引入 genfs version 的时机。若直接修改当前 genfs_contexts 而不维护版本 CIL,system OTA 可能改变旧 vendor 观察到的标签,导致已有 allow 失效。

8. 读码练习 ​

  1. 对比 PolicyVers=30 与 /sys/fs/selinux/policyvers,说明两者为何可以不同;
  2. 从 sel_write_load 复述 binary policy 的权限检查、解析、节点初始化和 commit 顺序;
  3. 找到一个 policy capability,说明 Kconfig 支持、binary bit 和 hook 分支三者的关系;
  4. 追踪 vendor genfs version 文件从 BoardConfig/Soong 到 Rust reader,再到 init CIL 路径;
  5. 假设 vendor module 新增 /sys/class/foo,设计一个不破坏旧 vendor system-only OTA 的标签演进方案;
  6. 解释 vendor_boot、GKI APEX、GKI kernel 和 sepolicy binary 分别由谁加载、何时生效。

9. 源码导航 ​

  1. kernel/common/security/selinux/Kconfig、Makefile:GKI 内核中的 SELinux 引擎组成。
  2. kernel/common/security/selinux/include/security.h:policydb 格式版本范围。
  3. kernel/common/security/selinux/ss/policydb.c:binary 版本和 MLS 配置校验。
  4. kernel/common/security/selinux/selinuxfs.c:load policy 事务和 commit。
  5. kernel/common/security/selinux/include/policycap_names.h、ss/services.c:policy capability。
  6. system/core/init/first_stage_init.cpp、selinux.cpp:first-stage mount、策略读取和 genfs CIL 选择。
  7. build/soong/etc/prebuilt_etc.go、build/soong/android/paths.go:vendor ramdisk 安装变体。
  8. system/sepolicy/compat/libgenfslabelsversion/:vendor genfs version 读取和默认值。
  9. system/sepolicy/compat/plat_sepolicy_genfs_*.cil:版本化 genfs 标签。
  10. system/sepolicy/apex/com.android.gki-file_contexts、private/gki_apex_prepostinstall.te:GKI APEX 更新钩子的预定义策略。