Skip to content

SELinux运行模式

从内核构建、init 启动判定、selinuxfs 和 AVC 决策追踪 Disabled、Permissive、Enforcing 与单域宽容模式。

基于android-17.0.0_r1
AndroidSELinuxEnforcingPermissiveselinuxfs源码阅读

SELinux运行模式 ​

本文面向已经读过 SELinux架构、DAC与MAC 和 类型强制 的读者。本文不把运行模式写成“Disabled、Permissive、Enforcing 三行定义”,而是区分四种不同状态:内核是否编译 SELinux、启动时模块是否启用、全局 enforcing 标志、source domain 是否在 permissive map 中。只有先确定状态 owner,才能解释 getenforce、setenforce、androidboot.selinux 和 permissive domain 各自改变了什么。

全文基于 Android 17 init、system/sepolicy 和 common kernel 源码。读完后,你应能从 ALLOW_PERMISSIVE_SELINUX 追到 init 的目标模式,从 /sys/fs/selinux/enforce 追到 security:setenforce 权限与 AVC reset,从 avc_denied() 解释全局与单域 permissive 的组合,并说明为什么 permissive 不能修复 DAC、错误标签、无效 context、策略编译或加载失败。

1. 四种状态 ​

状态owner何时决定运行期表现
未编译CONFIG_SECURITY_SELINUX=nkernel 构建没有 SELinux LSM 实现
已编译但启动禁用selinux_enabled_boot=0kernel cmdline,且启用 bootparam 支持SELinux 不注册/不完成正常运行主线
全局 permissive/enforcingselinux_state.enforcingkernel 启动、Android init、selinuxfs 写入所有非 strict denial 放行或阻断
单域 permissivepolicydb.permissive_mappolicy 编译与加载仅 source type 对应 domain 的 denial 放行

“Disabled”不是 setenforce 0。运行时 disable 已不支持,selinuxfs 明确要求使用启动参数;permissive 仍加载策略、解析 context、计算 access vector 和生成审计,只改变普通 denial 是否返回 -EACCES。

单域 permissive 不是全局状态转换,图中用分支表示它只影响特定 source type。目标对象 type 是否 permissive 不决定请求是否放行,内核查询的是 source context type。

2. 内核条件 ​

2.1 编译开关 ​

CONFIG_SECURITY_SELINUX 决定内核是否包含 SELinux,依赖 network、audit 和基础 security 框架。CONFIG_SECURITY_SELINUX_BOOTPARAM 才允许 selinux=0 在启动时禁用模块;没有该选项时,命令行参数不会提供这条关闭路径。

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

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

config SECURITY_SELINUX_BOOTPARAM
    bool "SELinux boot parameter"
    depends on SECURITY_SELINUX
    default n

config SECURITY_SELINUX_DEVELOP
    bool "SELinux Development Support"
    depends on SECURITY_SELINUX
    default y

SECURITY_SELINUX_DEVELOP 决定内核 enforcing 状态是否可变。关闭时 enforcing_enabled() 固定返回 true,enforcing_set() 为空函数,selinuxfs 的 enforce 文件也没有 write handler。

2.2 启动参数 ​

开发配置中,kernel 参数 enforcing= 设置 selinux_enforcing_boot;bootparam 配置中,selinux= 设置 selinux_enabled_boot。非开发配置把启动 enforcing 固定为 1。

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

相关位置:boot parameter parsers

c
#ifdef CONFIG_SECURITY_SELINUX_DEVELOP
static int selinux_enforcing_boot __initdata;

static int __init enforcing_setup(char *str)
{
    unsigned long enforcing;
    if (!kstrtoul(str, 0, &enforcing))
        selinux_enforcing_boot = enforcing ? 1 : 0;
    return 1;
}
__setup("enforcing=", enforcing_setup);
#else
#define selinux_enforcing_boot 1
#endif

int selinux_enabled_boot __initdata = 1;
#ifdef CONFIG_SECURITY_SELINUX_BOOTPARAM
static int __init selinux_enabled_setup(char *str)
{
    unsigned long enabled;
    if (!kstrtoul(str, 0, &enabled))
        selinux_enabled_boot = enabled ? 1 : 0;
    return 1;
}
__setup("selinux=", selinux_enabled_setup);
#endif

Android bootloader 的 androidboot.selinux 由用户空间 init 读取,和 kernel 的 selinux=、enforcing= 不是同一个参数入口。前者决定 init 想把已启用 SELinux 设置成何种 enforcing 状态,后两者在更早的 kernel 初始化期生效。

2.3 Enforcing状态 ​

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

相关函数:enforcing_enabled、enforcing_set

c
#ifdef CONFIG_SECURITY_SELINUX_DEVELOP
static inline bool enforcing_enabled(void)
{
    return READ_ONCE(selinux_state.enforcing);
}

static inline void enforcing_set(bool value)
{
    WRITE_ONCE(selinux_state.enforcing, value);
}
#else
static inline bool enforcing_enabled(void)
{
    return true;
}

static inline void enforcing_set(bool value)
{
}
#endif

这段代码是 user/production 内核“运行时不能降到 permissive”的最终边界之一:即使用户空间能打开某个接口,没有 development support,内核状态仍固定 enforcing。

3. Android启动 ​

3.1 构建变体 ​

Android init 默认以 ALLOW_PERMISSIVE_SELINUX=0 编译;debuggable 产品覆盖为 1。这个宏属于 init 用户空间程序,不等于 kernel CONFIG_SECURITY_SELINUX_DEVELOP,两侧都允许才可能完成全局切换。

源码文件:system/core/init/Android.bp

make
cflags: [
    "-DALLOW_FIRST_STAGE_CONSOLE=0",
    "-DALLOW_LOCAL_PROP_OVERRIDE=0",
    "-DALLOW_PERMISSIVE_SELINUX=0",
],
product_variables: {
    debuggable: {
        cppflags: [
            "-UALLOW_PERMISSIVE_SELINUX",
            "-DALLOW_PERMISSIVE_SELINUX=1",
        ],
    },
},

因此“userdebug 一定 permissive”是错误的。debuggable 只允许 init 尊重 permissive 启动参数,默认没有该参数时仍选择 enforcing。

3.2 目标模式 ​

init 先查 kernel cmdline,再查 bootconfig 中的 androidboot.selinux;只有值精确等于 permissive 才返回 permissive,其余情况默认 enforcing。user build 直接忽略该结果并返回 true。

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

相关函数:StatusFromProperty、IsEnforcing

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

这里没有读取 ro.debuggable,因为 debuggable 已在编译 init 时转成宏。运行中的属性不能动态改变 ALLOW_PERMISSIVE_SELINUX。

3.3 状态同步 ​

策略读入和加载后,SelinuxSetEnforcement() 比较 kernel 当前状态与 init 目标状态,只在不一致时调用 security_setenforce();失败是 fatal。

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

cpp
void SelinuxSetEnforcement() {
    bool kernel_enforcing =
            (security_getenforce() == 1);
    bool is_enforcing = IsEnforcing();
    if (kernel_enforcing != is_enforcing) {
        if (security_setenforce(is_enforcing)) {
            PLOG(FATAL) << "security_setenforce("
                        << (is_enforcing ? "true" : "false")
                        << ") failed";
        }
    }
}

状态同步发生在 SetupSelinux() 加载 policy 之后、restorecon init 与进入 second stage 之前。策略加载失败不会通过 permissive 降级继续启动;permissive 只处理已加载策略产生的普通 access denial。

4. selinuxfs切换 ​

4.1 读取状态 ​

/sys/fs/selinux/enforce 的 read handler 直接输出 enforcing_enabled()。getenforce 最终依赖这类状态接口,而不是重新读取 Android boot 参数。

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

c
static ssize_t sel_read_enforce(
        struct file *filp, char __user *buf,
        size_t count, loff_t *ppos)
{
    char tmpbuf[TMPBUFLEN];
    ssize_t length;

    length = scnprintf(
            tmpbuf, TMPBUFLEN, "%d",
            enforcing_enabled());
    return simple_read_from_buffer(
            buf, count, ppos,
            tmpbuf, length);
}

4.2 写入权限 ​

写 enforce 文件并非“root 即可”。内核解析 0/1 后,以当前 SID 到 security initial SID 查询 security:setenforce;只有权限通过才更新状态、写 audit、通知 netlink/status page 和 IMA。

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

相关函数:sel_write_enforce

c
new_value = !!scan_value;
old_value = enforcing_enabled();
if (new_value != old_value) {
    length = avc_has_perm(
            current_sid(), SECINITSID_SECURITY,
            SECCLASS_SECURITY,
            SECURITY__SETENFORCE, NULL);
    if (length)
        goto out;

    audit_log(audit_context(), GFP_KERNEL,
              AUDIT_MAC_STATUS,
              "enforcing=%d old_enforcing=%d ...",
              new_value, old_value);

    enforcing_set(new_value);
    if (new_value)
        avc_ss_reset(0);
    selnl_notify_setenforce(new_value);
    selinux_status_update_setenforce(new_value);
    if (!new_value)
        call_blocking_lsm_notifier(
                LSM_POLICY_CHANGE, NULL);
    selinux_ima_measure_state();
}

切回 enforcing 时清空 AVC,防止 permissive 期间临时 grant 的 cache entry 继续影响强制模式;切到 permissive 时通知 LSM policy change 消费者。状态更新不是单一 bool 写入。

4.3 编译条件 ​

sel_write_enforce 只在 CONFIG_SECURITY_SELINUX_DEVELOP 下编译;否则 file operations 的 write 指针为 null。

c
#else
#define sel_write_enforce NULL
#endif

static const struct file_operations sel_enforce_ops = {
    .read = sel_read_enforce,
    .write = sel_write_enforce,
    .llseek = generic_file_llseek,
};

因此 setenforce 失败可能来自没有 write handler、SELinux policy 拒绝 setenforce、文件系统权限或用户空间工具不可用。不能把所有失败都解释为“不是 root”。

4.4 运行时禁用 ​

selinuxfs 的旧 disable 接口只记录错误,明确要求用 kernel cmdline;运行中从 enforcing/permissive 切到 disabled 不被支持。

c
if (new_value) {
    pr_err("SELinux: Runtime disable is not supported, "
           "use selinux=0 on the kernel cmdline.\n");
}

Disabled 会改变 LSM 是否参与对象创建和标签生命周期,无法用运行时 flag 安全撤销。

5. AVC行为 ​

5.1 全局模式 ​

AVC 发现 requested permission 不在 allowed bit 中时进入 avc_denied()。AVC_STRICT 无条件返回 -EACCES;普通请求只有在全局 enforcing 且 source domain 不是 permissive 时拒绝。

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

相关函数:avc_denied

c
static noinline int avc_denied(
        u32 ssid, u32 tsid,
        u16 tclass, u32 requested,
        u8 driver, u8 base_perm, u8 xperm,
        unsigned int flags,
        struct av_decision *avd)
{
    if (flags & AVC_STRICT)
        return -EACCES;

    if (enforcing_enabled() &&
        !(avd->flags & AVD_FLAGS_PERMISSIVE))
        return -EACCES;

    avc_update_node(
            AVC_CALLBACK_GRANT, requested,
            driver, base_perm, xperm,
            ssid, tsid, tclass,
            avd->seqno, NULL, flags);
    return 0;
}

全局 permissive 时普通 denial 会作为临时 grant 更新 AVC node 并返回 0;审计由上层 avc_has_perm() 负责。strict 调用即使全局 permissive 也拒绝,所以“permissive 放行所有 SELinux 查询”并不准确。

5.2 单域模式 ​

security server 计算 access vector 时读取 source context 的 type,并查询 policydb.permissive_map。命中后在 decision 上设置 AVD_FLAGS_PERMISSIVE。

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

相关位置:security_compute_av

c
scontext = sidtab_search(sidtab, ssid);
if (!scontext) {
    pr_err("SELinux: unrecognized SID %d\n", ssid);
    goto out;
}

if (ebitmap_get_bit(
        &policydb->permissive_map,
        scontext->type)) {
    avd->flags |= AVD_FLAGS_PERMISSIVE;
}

if (ebitmap_get_bit(
        &policydb->neveraudit_map,
        scontext->type)) {
    avd->flags |= AVD_FLAGS_NEVERAUDIT;
}

查询 key 是 source type,不是 target type。把文件 type 标记为 permissive 没有“让所有人访问该文件”的语义;policy permissive 声明适用于 domain/source type。

5.3 neveraudit组合 ​

当 decision 同时标记 permissive 与 neveraudit,security server 直接把 allowed 设为全 bit,并清空审计向量。

c
if (avd->flags ==
        (AVD_FLAGS_PERMISSIVE |
         AVD_FLAGS_NEVERAUDIT)) {
    goto allow;
}

// ... 正常计算TE与constraints。

if (avd->flags & AVD_FLAGS_NEVERAUDIT)
    avd->auditallow = avd->auditdeny = 0;

allow:
avd->allowed = 0xffffffff;

这说明“permissive 一定有所有 denial 日志”也不成立。dontaudit/neveraudit 可以抑制审计,日志采样、rate limit 和请求是否到达 SELinux hook也会影响可见性。

6. 构建约束 ​

6.1 permissive_map ​

policy source 中的 permissive <domain>; 会进入 binary policy 的 permissive bitmap。Android 平台源码当前不依赖一组普遍 permissive domain;调试宏或设备策略可能在特定构建中引入。

Permissive_domains_on_user_builds 是 Soong module 属性,用于极少数明确豁免;它不是运行期 allowlist。

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

相关结构:policyBinaryProperties

go
type policyBinaryProperties struct {
    Stem *string
    Srcs []string `android:"path"`
    Ignore_neverallow *bool
    Installable *bool

    // List of domains that are allowed to be in permissive mode on user builds.
    Permissive_domains_on_user_builds []string
}

6.2 User构建检查 ​

非 debuggable 构建完成 secilc 后,Soong 用 sepolicy-analyze permissive 提取 permissive domains,过滤显式豁免后要求文件为空。

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

相关函数:policyBinary.GenerateAndroidBuildActions

go
if !ctx.Config().Debuggable() {
    permissiveDomains := pathForModuleOut(
            ctx, c.stem()+"_permissive")
    cmd := rule.Command().BuiltTool("sepolicy-analyze").
        Input(bin).
        Text("permissive")

    allowedDomains :=
            c.properties.Permissive_domains_on_user_builds
    if len(allowedDomains) != 0 {
        cmd.Text("| { grep -Fxv")
        for _, d := range allowedDomains {
            cmd.FlagWithArg(
                    "-e ", proptools.ShellEscape(d))
        }
        cmd.Text(" || true; }")
    }
    cmd.Text(" > ").Output(permissiveDomains)

    rule.Command().Text("if test").
        FlagWithInput("-s ", permissiveDomains).
        Text("; then echo").
        Flag("-e").
        Text("ERROR: permissive domains not allowed in user builds").
        Text("&& cat ").
        Input(permissiveDomains).
        Text("; exit 1; fi")
}

失败发生在构建期,设备不会加载这份策略。它验证“user policy 没有未豁免 permissive domain”,不验证设备当前全局 enforcing 状态,也不验证 bootloader 是否传了正确参数。

7. 模式矩阵 ​

条件Policy加载SELinux检查普通denialStrict检查日志
SELinux未编译/启动禁用否否或不完整不适用不适用无SELinux AVC
全局permissive是是返回0仍拒绝按audit策略,可能被静默
全局enforcing,source domain permissive是是该domain返回0仍拒绝按audit策略
全局enforcing,普通domain是是返回-EACCES拒绝按audit策略

这里的 “返回0” 只描述 SELinux hook 的普通 denial。VFS DAC、read-only filesystem、capability、seccomp、Binder UID 检查和其他 LSM 仍可拒绝同一操作。

8. 故障边界 ​

现象第一检查点不应直接下的结论
getenforce 输出 Enforcing/sys/fs/selinux/enforce不说明每个domain都非permissive
setenforce 0 Permission deniedkernel develop、write handler、security:setenforce、身份不只是root问题
permissive 下仍返回 EACCESDAC、strict AVC、其他LSM、只读/immutable、应用层检查不说明SELinux仍在强制普通denial
没有 AVC 日志dontaudit/neveraudit、提前失败、未到hook、rate limit不说明policy允许
policy加载失败CIL/binary/version/class映射permissive不会绕过加载错误
标签错误contexts/restorecon/seappsetenforce不会修复标签
user build出现permissive domain构建输入、豁免列表、实际binary来源不能只看源码里有无permissive字符串
重启后模式恢复bootconfig/cmdline与init构建宏运行时setenforce不持久化启动配置

9. 验证实验 ​

9.1 只读状态实验 ​

下面命令不修改设备,分别观察模块是否启用、全局 enforcing、活动 LSM、进程 context 和 policy 状态页。

bash
# 收集模式、LSM列表和当前context。
adb shell getenforce
adb shell 'cat /sys/fs/selinux/enforce 2>/dev/null || true'
adb shell 'cat /sys/kernel/security/lsm 2>/dev/null || true'
adb shell 'id -Z; cat /proc/self/attr/current'

# 查看selinuxfs与AVC统计是否存在。
adb shell 'mount | grep selinuxfs || true'
adb shell 'cat /sys/fs/selinux/avc/cache_stats 2>/dev/null || true'

实验输入是不修改状态的运行设备。关键断言是:getenforce 与 enforce 文件应一致;活动 LSM 应包含 selinux;当前 context 应能读取。缺少某一项时应区分工具缺失、securityfs 未挂载、SELinux 未启用和权限不足。该实验不能证明单 domain permissive 列表,也不能证明 policy 来自当前源码 checkout。

9.2 Build变体实验 ​

可在已初始化的 AOSP 构建环境比较 user 与 userdebug 的 init 编译命令或中间 flags,确认 ALLOW_PERMISSIVE_SELINUX 的差异。

bash
# 选择对应产品后查看init编译命令中的宏;具体目标名随产品变化。
m init -n 2>&1 | rg 'ALLOW_PERMISSIVE_SELINUX'

# 查询生成的binary policy中是否存在permissive domain。
sepolicy-analyze \
  out/target/product/<product>/vendor/etc/selinux/precompiled_sepolicy \
  permissive

关键断言是 user init 命令含 ALLOW_PERMISSIVE_SELINUX=0,debuggable 产品覆盖为 1;user binary policy 的 permissive 输出应为空或只包含明确豁免。此实验依赖产品实际输出路径,不能把示例路径当成所有设备固定位置。

9.3 Enforcing回归 ​

在允许修改模式的隔离测试设备上,切换实验必须记录原状态并恢复;生产设备不应执行。

bash
# 仅限可丢弃的userdebug/eng测试设备;先记录原值并注册自动恢复。
original_mode=$(adb shell getenforce | tr -d '\r')
restore_selinux_mode() {
  if [ "$original_mode" = Enforcing ]; then
    adb shell su 0 setenforce 1
  elif [ "$original_mode" = Permissive ]; then
    adb shell su 0 setenforce 0
  fi
}
trap restore_selinux_mode EXIT INT TERM

adb shell su 0 setenforce 0
adb shell getenforce
adb shell su 0 setenforce 1
adb shell getenforce

# 退出脚本或中断时,trap会恢复实验前状态。

输入是支持 development mode、具备 security:setenforce 权限的测试设备。关键断言是 0/1 写入分别反映为 Permissive/Enforcing,切回 enforcing 后正常业务仍通过。它没有验证任何具体 denial,也不授权在用户数据设备上制造安全窗口。

10. 源码导航 ​

先区分 kernel 编译、启动 enable 和 enforcing 三层开关:

bash
# 查找Kconfig、boot参数和enforcing状态实现。
rg -n 'SECURITY_SELINUX|SECURITY_SELINUX_BOOTPARAM|SECURITY_SELINUX_DEVELOP|selinux_enabled_boot|selinux_enforcing_boot|enforcing_enabled' \
  kernel/common/security/selinux/Kconfig \
  kernel/common/security/selinux/hooks.c \
  kernel/common/security/selinux/include/security.h

再检查 Android init 如何把 build variant 和 bootconfig 转成目标模式:

bash
# 查看init编译宏、androidboot.selinux和状态同步。
rg -n 'ALLOW_PERMISSIVE_SELINUX|StatusFromProperty|IsEnforcing|SelinuxSetEnforcement' \
  system/core/init/Android.bp \
  system/core/init/selinux.cpp

运行时切换问题要进入 selinuxfs,而不是只读用户空间工具源码:

bash
# 查看enforce文件的read/write handler及安全权限检查。
rg -n 'sel_read_enforce|sel_write_enforce|SECURITY__SETENFORCE|enforcing_set|avc_ss_reset' \
  kernel/common/security/selinux/selinuxfs.c

单 domain permissive 与普通 denial 的最终行为位于 security server 和 AVC:

bash
# 追踪permissive_map、decision flag和avc_denied返回值。
rg -n 'permissive_map|AVD_FLAGS_PERMISSIVE|AVD_FLAGS_NEVERAUDIT|avc_denied|AVC_STRICT' \
  kernel/common/security/selinux/ss/services.c \
  kernel/common/security/selinux/avc.c \
  kernel/common/security/selinux/include/security.h

user build 的 permissive domain 检查属于策略构建链:

bash
# 查看binary policy生成后的permissive分析与失败条件。
rg -n 'Permissive_domains_on_user_builds|sepolicy-analyze|permissive domains not allowed' \
  system/sepolicy/build/soong/policy.go

11. 闭环复述 ​

  1. 分别解释 CONFIG_SECURITY_SELINUX=n、selinux=0、enforcing=0、androidboot.selinux=permissive 和 policy permissive domain 的 owner 与生效时机。
  2. 从 ALLOW_PERMISSIVE_SELINUX 开始,追到 StatusFromProperty()、IsEnforcing()、SelinuxSetEnforcement() 和 kernel enforce state,说明为什么 userdebug 仍默认 enforcing。
  3. 从写 /sys/fs/selinux/enforce 开始,复述 security:setenforce 检查、audit、状态写入、AVC reset、netlink/status 通知与 IMA measurement。
  4. 从 policydb.permissive_map 追到 AVD_FLAGS_PERMISSIVE 和 avc_denied(),说明为什么判断的是 source domain,以及 global enforcing 下单域 denial 如何返回 0。
  5. 给出 permissive 下操作仍失败的至少四个非 SELinux 普通 denial 原因,并说明没有 AVC 日志为什么也不能证明 allow。
  6. 用 Soong 的 sepolicy-analyze permissive 路径解释 user build 如何在设备启动前阻断未豁免 permissive domain。

SELinux 运行模式不是一个三值枚举,而是一组分层状态:kernel 是否包含并启用模块,Android init 期望何种全局 enforcing,policy 是否把 source domain 标为 permissive,以及具体检查是否 strict。只有把问题放回正确层次,getenforce、boot 参数、setenforce、AVC 日志和构建检查才能形成可验证的因果链。