三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

Android16 SELinux 关闭方式汇总

Android16 SELinux 关闭方式汇总

Android16 SELinux 关闭方式汇总

文章目录

  • Android16 SELinux 关闭方式汇总
    • @[toc]
      • 一、前言
      • 二、SELinux 基础概念
        • 1、SELinux 三种状态
        • 2、Android 编译版本与 SELinux 的关系
        • 3、常用查询命令
      • 三、运行时关闭方式(临时)
        • 1、setenforce 命令(最常用)
        • 2、init.rc 脚本方式(实际无法修改,不推荐)
        • 3、prop 属性方式(AOSP 标准不支持)
      • 四、编译期/内核级控制方式(永久)
        • 1、源码编译期设置默认 Permissive(推荐)
        • 2、内核级完全关闭:bootargs 添加 selinux=0
        • 3、源码修改 system/core/init/selinux.cpp(简单粗暴)
      • 对比:四种源码层控制方式的优劣
      • 五、源码策略文件修改方式(不改开关)
        • 源码修改 .te 文件编译(推荐)
      • 六、各方式对比汇总
      • 七、user 版本限制说明
        • 1、user 版本为什么 setenforce 0 不行
        • 2、user 版本可行的方案
      • 八、调试常用命令速查
        • 1、SELinux 状态查询
        • 2、AVC 日志排查
      • 九、总结

一、前言

在 Android 系统开发中,SELinux(Security-Enhanced Linux)是强制访问控制(MAC)的安全模块。

开发调试阶段经常遇到 SELinux 权限拦截问题,需要临时或永久关闭 SELinux 来排查。

一般默认值开启selinux的,也就是Enforcing模式;

有些情况是可以通过命令关闭selinux验证,有些情况需要在内核关闭selinux验证;

因为有些系统服务在启动比较早,后期关闭selinux已经不管用了,所以需要修改源码或者在内核关闭selinu进行验证;

如果涉及到selinux权限问题,一般修改.te文件就可以解决。

本文汇总 Android 系统中关闭 SELinux 的各种方式,涵盖运行时临时关闭、内核级永久关闭、策略文件修改等方式,并说明 user 版本的限制。


二、SELinux 基础概念

1、SELinux 三种状态
状态说明getenforce返回值is_selinux_enabled()权限检查行为
Disabled完全未启用Disabled返回 0完全跳过,不做任何检查
Permissive宽容模式Permissive返回 1执行检查但只记录日志不阻止
Enforcing强制模式Enforcing返回 1执行检查,拒绝并记录日志

关键区别

  • DisabledvsPermissive:Disabled 时/sys/fs/selinux不挂载,selinux_check_access()直接返回 0;Permissive 时 SELinux 子系统完整运行,只是不阻止。

    所以你的selinux设置了Permissive ,也是打印一大堆权限问题的日志,是可以不用管的。

  • PermissivevsEnforcing:两者都执行完整的 AVC 策略检查,唯一区别是 Permissive 记录但不阻止,Enforcing 记录且阻止。

2、Android 编译版本与 SELinux 的关系
编译版本默认 SELinux 模式adb rootsetenforce 0适用场景
engPermissive✅ 可以✅ 可以内部开发调试
userdebugEnforcing✅ 可以✅ 可以外部开发调试
userEnforcing❌ 不可以❌ 不可以量产版本
3、常用查询命令
# 查询当前 SELinux 模式getenforce# 输出: Enforcing(开启了selinux,强制模式) / Permissive(关闭selinux,宽容模式) / Disabled(完全未启用)# 查询内核 SELinux 启用状态cat/sys/fs/selinux/enforce# 输出: 1=Enforcing, 0=Permissive#实际没用,什么都没返回# 查看 SELinux 是否完全启用(内核级)cat/proc/cmdline|grepselinux# 如果有 selinux=0 则内核级已关闭#实际没用,什么都没返回//查看selinux最开始的状态,后续setenforce修改后,这个属性也是不会改变的。 console:/# getprop |grep selinux[ro.boot.selinux]:[permissive]

上面的命令就:getenforce 有点用,其他的是没用的。


三、运行时关闭方式(临时)

运行时关闭的特点是重启后失效,适合临时调试。

1、setenforce 命令(最常用)
# 切换到 Permissive 模式(宽容模式),关闭selinuxsetenforce0# 切换回 Enforcing 模式(强制模式),开启selinuxsetenforce1# 通过 adb 执行adb shell setenforce0

权限要求

  • eng/userdebug版本:shell 域有setenforce权限,直接可用
  • user版本:shell 域没有setenforce权限,执行会报Permission denied

验证

getenforce# 预期输出: Permissive

注意setenforce 0只是将模式从 Enforcing 改为 Permissive,SELinux 子系统仍然完整运行。对于某些场景(如 wpa_supplicant 访问 keystore2),需要重启相关服务才能让进程重新建立连接:

# setenforce 0 后重启 wpa_supplicantsetenforce0stop wpa_supplicant start wpa_supplicant

连接企业网络就遇到过这个问题,必须要重启wpa才能生效。

2、init.rc 脚本方式(实际无法修改,不推荐)

⚠️实际测试结论:该方式无效。

虽然 init.rc 的语法上支持on boot时调用setenforce 0,但实际测试发现无法改变 SELinux 默认模式

无效原因

init 启动流程: ① FirstStageInit → 挂载文件系统 ② SelinuxInitialize() → 读取 ro.boot.selinux,调用 security_setenforce() ↑ 此处就确定了模式,之后不会被 init.rc 重写覆盖 ③ 解析并执行 init.rc 中的 action → 即使 on boot: setenforce 0 ↑ 执行时间太晚,或被某些 vendor 的 init 定制流程强制改回 Enforcing ④ boot 阶段完成
  1. 时机问题:SELinux 初始化在SelinuxInitialize()阶段完成,发生在 init.rc 解析执行之前或并行。即使 init.rc 的on boot触发setenforce 0执行了,也可能被系统后续的恢复流程强制改回 Enforcing。
  2. vendor 定制:部分厂商在system/core/init/selinux.cpp中强制忽略setenforce的调用,或在on property:sys.boot_completed=1时强制设置为 Enforcing。
  3. 编译产物问题:如果修改的是 device 源码中的 init.rc,需要连同boot.img一起编译,否则修改不会真正打包进 boot 分区。

如果仍想尝试(仅作为记录,不保证有效)

# system/core/rootdir/init.rc 或 device/.../init.rc # 尽量用更早期的触发器,但仍可能无效 on post-fs-data setenforce 0 on boot setenforce 0

编译时必须连同boot.imgvendor_boot.img一起编译刷入:

makebootimage vendor_bootimage-j8# fastboot flash boot boot.img
3、prop 属性方式(AOSP 标准不支持)

⚠️实际测试结论:setprop ro.boot.selinux permissive无效。

ro.boot.selinux 的来源链路

Bootloader kernel cmdline: androidboot.selinux=permissive ↓ 内核传入 /proc/cmdline → init 进程 SystemCore Init 解析 androidboot.* ↓ 自动生成只读属性: ro.boot.selinux = "permissive" ↓ system/core/init/selinux.cpp → SelinuxInitialize() 读取该值后设置为 Permissive

关键点:

  • ro.boot.selinux不是源码中定义的属性,也不能通过setprop动态修改(ro.只读属性,设置无效)
  • 该属性由 init 进程自动从 kernel cmdline 的androidboot.xxx参数生成,有且只有一处来源:bootloader/内核启动参数
  • 源码中修改该属性没有任何意义,因为编译时不会预置这个属性

正确做法(如果想通过属性控制):

# device/<vendor>/<device>/BoardConfig.mk # 在 bootargs 中添加 androidboot.selinux=permissive BOARD_KERNEL_CMDLINE += androidboot.selinux=permissive

这会导致 boot.img 的 cmdline 中包含androidboot.selinux=permissive,启动后自动生成ro.boot.selinux=permissive,进而被SelinuxInitialize()读取并进入 Permissive 模式。

或者直接修改内核 defconfig / bootloader 环境变量添加androidboot.selinux=permissive到 cmdline。

如果源码中没有system/core/init/selinux.cpp(项目裁剪了 system/core),也可以直接修改 init 的main.cppSelinuxInitialize()调用前或调用后强制执行security_setenforce(0)

这里修改 BoardConfig.mk 属性编译源码了,并不是简单的prop属性修改。


四、编译期/内核级控制方式(永久)

包含两类方式:

  1. 源码编译期控制(通过 BOARD_KERNEL_CMDLINE)— 影响默认的 SELinux 模式(Permissive / Enforcing),不关闭内核子系统
  2. 内核级关闭(selinux=0 / CONFIG 关闭)— 完全关闭 SELinux 子系统
1、源码编译期设置默认 Permissive(推荐)

在设备的BoardConfig.mk中通过BOARD_KERNEL_CMDLINE添加androidboot.selinux=permissive。这是目前在源码层修改 SELinux 默认值最可靠的方式,编译进 boot.img 后每次开机自动生效。

生效链路

BoardConfig.mk 配置 BOARD_KERNEL_CMDLINE += androidboot.selinux=permissive ↓ 编译进 boot.img 的 cmdline /proc/cmdline 含 androidboot.selinux=permissive ↓ init 进程启动,解析 androidboot.* 自动生成属性 ro.boot.selinux = "permissive" ↓ system/core/init/selinux.cpp: SelinuxInitialize() if (property_get("ro.boot.selinux") == "permissive") { security_setenforce(0); // 设置为 Permissive } ↓ 开机后 getenforce 返回 Permissive ✅

修改方式

# device/<vendor>/<device>/BoardConfig.mk # 在现有的 BOARD_KERNEL_CMDLINE 后追加: BOARD_KERNEL_CMDLINE += androidboot.selinux=permissive

如果 device 目录中有多个 BoardConfig(如 common、variant),需要在实际编译时生效的那个文件里添加。

比如AML方案的目录是:release/device/amlogic/t7_an400/BoardConfig.mk

编译

sourcebuild/envsetup.sh lunch<your_target>makebootimage-j8# 必须编 boot.img,因为 cmdline 在 boot.img 里# fastboot flash boot boot.img//如果不想单独替换镜像整编也是有效的。

验证

getenforce# 预期输出: Permissive
2、内核级完全关闭:bootargs 添加 selinux=0

这个不需要重新编译源码,但是需要有串口设备设置内核。

在内核参数中添加selinux=0,内核启动时检测到该参数后不初始化 SELinux 子系统。

注意:selinux=0内核级完全关闭androidboot.selinux=permissive用户空间设置为 Permissive,两者效果不同,前者更彻底。

直接修改 bootloader 环境变量(U-Boot)

# 进入 U-Boot 命令行后# 关闭selinuxsetenv EnableSelinux permissive;saveenv;reset#开启selinuxsetenv EnableSelinux enforcing;saveenv;reset

验证

getenforce# 预期输出: Permissive 或 Enforcing

注意:此方式与编译版本无关,user 版本也可用(需解锁 bootloader)。

3、源码修改 system/core/init/selinux.cpp(简单粗暴)

直接修改 Android init 的 SELinux 初始化逻辑,这是最可靠、最直接的源码级修改方式。

基于 init 实际代码如下:

// system/core/init/selinux.cpp (核心逻辑)EnforcingStatusStatusFromProperty(){std::string value;// 门 1:从 kernel cmdline 读取 androidboot.selinux=permissiveif(android::fs_mgr::GetKernelCmdline("androidboot.selinux",&value)&&value=="permissive"){returnSELINUX_PERMISSIVE;}// 门 2:从 bootconfig 读取 androidboot.selinux=permissiveif(android::fs_mgr::GetBootconfig("androidboot.selinux",&value)&&value=="permissive"){returnSELINUX_PERMISSIVE;}returnSELINUX_ENFORCING;// 默认值}boolIsEnforcing(){// 总开关:ALLOW_PERMISSIVE_SELINUX 编译常量// true → 允许通过 cmdline/bootconfig 切换为 Permissive// false → 永远返回 Enforcing,上层怎么改都没用if(ALLOW_PERMISSIVE_SELINUX){returnStatusFromProperty()==SELINUX_ENFORCING;}returntrue;}

两道门槛说明

门槛控制者作用
ALLOW_PERMISSIVE_SELINUX编译时常量(在 BuildConfig.generated.h 或类似文件中)总开关。如果为 false,StatusFromProperty() 无论返回什么都被忽略,永远返回 true (Enforcing)
StatusFromProperty()kernel cmdline / bootconfig 的androidboot.selinux具体值。只有总开关为 true 时才会被读取

修改方式:任选其一,不需要同时修改。

方式 A:直接改IsEnforcing()返回 false(最推荐,一行搞定)

// system/core/init/selinux.cppboolIsEnforcing(){// 原代码:// if (ALLOW_PERMISSIVE_SELINUX) {// return StatusFromProperty() == SELINUX_ENFORCING;// }// return true;// 修改为:永远返回 false,强制 Permissive 模式// 优点:// 1. 绕过 ALLOW_PERMISSIVE_SELINUX 总开关限制// 2. 不需要改 kernel cmdline// 3. 不需要改 bootconfig// 4. 代码最简单,可预测性最高returnfalse;}

修改上面这个需要把 StatusFromProperty() 函数注释了,因为系统编译检测无用会报错;

要么就是在返回前加上: (void)StatusFromProperty(); // 欺骗编译器:标记函数被引用,消除unused报错

方式 B:改StatusFromProperty()永远返回 PERMISSIVE

// system/core/init/selinux.cppEnforcingStatusStatusFromProperty(){// 原代码:读 cmdline 和 bootconfig// 修改为:直接返回 PERMISSIVE,不读任何配置returnSELINUX_PERMISSIVE;}// 注意:前提是 ALLOW_PERMISSIVE_SELINUX == true,否则仍然无效// 如果 ALLOW_PERMISSIVE_SELINUX 是 false,还是走方式 A 最稳

编译

sourcebuild/envsetup.sh lunch<your_target># init 属于 boot 分区的可执行文件,需要连同 boot.img 一起编译makeinit-j8# 先单独编译 init,确认无错误makebootimage-j8# 生成 boot.img# 刷入# fastboot flash boot boot.img

验证

getenforce# 预期输出: Permissive127|console:/# getprop | grep selinux //虽然prop属性值是enforcing,这个不用管[ro.boot.selinux]:[enforcing]# 注意:即使 /proc/cmdline 中没有 androidboot.selinux=permissive,也应该是 Permissive# 因为改的是代码逻辑,不依赖 cmdline

对比:四种源码层控制方式的优劣

修改方式修改文件代码行数绕过总开关?不依赖 cmdline?适用场景
直接改 IsEnforcing()system/core/init/selinux.cpp1 行✅ 是✅ 是最推荐,简单、无前置条件
改 StatusFromProperty()system/core/init/selinux.cpp1 行❌ 否(受 ALLOW_ 限制)✅ 是简单但受总开关限制
BoardConfig cmdlinedevice/.../BoardConfig.mk1 行 mk❌ 否(受 ALLOW_ 限制)❌ 否(依赖 cmdline)不改 init 源码时用

一句话建议

  • 能改 system/core/init/selinux.cpp → 直接改IsEnforcing()→ 最稳
  • 不能改 system/core(项目裁剪了)→ 改 BoardConfigBOARD_KERNEL_CMDLINE += androidboot.selinux=permissive
  • 想临时切换(userdebug 场景)→setenforce 0
  • 生产环境解决特定权限问题 → 改.te策略,不关闭 SELinux

五、源码策略文件修改方式(不改开关)

策略文件修改方式不关闭 SELinux,而是修改 SELinux 策略规则,让特定域获得所需权限。
这是Google 推荐的做法,不影响整体安全性。

这个主要是根据logcat 日志中查看类似的日志:

SELinux : avc: denied { grant } for scontext=u:r:hal_wifi_supplicant_default:s0 tcontext=u:object_r:wifi_key:s0 tclass=keystore2_key permissive=0
源码修改 .te 文件编译(推荐)

在 Android 源码中修改.te策略文件,重新编译后刷入镜像。

示例:为 wpa_supplicant 添加 keystore2 密钥访问权限

# system/sepolicy/vendor/hal_wifi_supplicant_default.te # 追加:允许 wpa_supplicant 访问 wifi_key 命名空间的密钥 allow hal_wifi_supplicant_default wifi_key:keystore2_key { get_info use manage_blob grant rebind update delete convert_storage_key_to_ephemeral req_forced_op }; # 追加:恢复 setuid/setgid 能力 allow hal_wifi_supplicant_default self:global_capability_class_set { setuid setgid };

编译步骤

# 1. 清除 sepolicy 编译缓存(关键!)rm-rfout/soong/.intermediates/system/sepolicy/rm-rfout/target/product/*/obj/ETC/*sepolicy*# 2. 设置编译环境sourcebuild/envsetup.sh lunch<your_target># 3. 先单独编译策略,确认无报错makesepolicy-j8# 4. 编译完整镜像makebootimage vendorimage-j8

验证

# 刷入后在 Enforcing 模式下验证getenforce# 预期: Enforcing# 查看是否有 AVC 拒绝dmesg|grepavc|grepwpa_supplicant# 预期: 无输出(权限已配置)

selinux denied 权限报错具体如何分析修改,后续再做总结。


六、各方式对比汇总

方式持久性user 版本可用安全性难度适用场景
setenforce 0❌ 重启失效❌ 不可用⚠️ Permissiveuserdebug 临时调试
init.rcsetenforce 0实际无效❌ 不可用-不推荐(测试无法修改默认模式)
prop 属性方式实际无效❌ 不可用-不推荐(ro. 只读,需改 cmdline)
BoardConfigBOARD_KERNEL_CMDLINE✅ 持久✅ 需编镜像⚠️ Permissive⭐⭐源码层 Permissive 推荐方式
bootargsselinux=0✅ 持久✅ 需解锁 BL❌ 完全关闭⭐⭐⭐内核级调试
内核 CONFIG 关闭✅ 持久✅ 需编内核❌ 完全关闭⭐⭐⭐⭐永久关闭
源码改 .te 编译✅ 持久✅ 可用✅ 最小权限⭐⭐⭐量产推荐

七、user 版本限制说明

1、user 版本为什么 setenforce 0 不行

关键在 SELinux 策略中的userdebug_or_eng()宏:

# system/sepolicy/private/shell.te userdebug_or_eng(` # 只有 eng/userdebug 版本才编译这段规则 allow shell self:capability { setenforce }; ')
  • eng / userdebugshell域有setenforce权限 →adb shell setenforce 0生效
  • user:这段策略在编译时被直接移除shell域没有setenforce权限

执行结果:

console:/ $ setenforce0setenforce: Permission denied

同时 user 版本还有以下限制:

  • adb root不可用
  • 分区只读(无法 remount)
  • 无法修改 init.rc 或 prop 文件
2、user 版本可行的方案
方式是否可用前提条件
源码改 .te 编译✅ 可用有源码编译环境
修改 selinux.cppIsEnforcing()返回 false✅ 可用有源码,能编译 boot.img(最推荐,不受任何开关限制)
BoardConfigandroidboot.selinux=permissive✅ 可用有源码编译环境,且ALLOW_PERMISSIVE_SELINUX == true
bootargsselinux=0✅ 可用需解锁 bootloader
内核 CONFIG 关闭✅ 可用需能编译刷入内核
setenforce 0❌ 不可用-
init.rc 修改实际无效SelinuxInitialize() 早于 init.rc 执行
setprop ro.boot.selinux❌ 不可用ro. 只读属性,设置无效

量产推荐:源码改 .te 编译,不关闭 SELinux,只修改策略规则解决权限问题,通过 CTS/VTS 认证不受影响。


八、调试常用命令速查

1、SELinux 状态查询
# 查询当前模式getenforce# 查询进程安全上下文ps-Z# 查询文件安全上下文ls-Z
2、AVC 日志排查
# 实时监控 AVC 日志adb shellcat/proc/kmsg|grepavc# 通过 logcat 查看adb logcat-bkernel|grepavc# 如果有 dontaudit 隐藏日志,临时关闭 dontaudit,Wifi 企业网络连接失败# (需要 SELinux 策略支持 auditallow)adb shell setenforce0# 临时切 permissive 让所有拒绝都记录adb shell stop wpa_supplicant adb shell start wpa_supplicant# 再查 dmesg# 用 audit2allow 自动生成策略(如果有该工具)adb shelldmesg|grepavc|grep-iE"wpa|keystore"|audit2allow

九、总结

场景推荐方式说明
userdebug 临时调试setenforce 0最快,有些情况无作用,重启失效
源码层设置 Permissive(编译期,推荐)修改 selinux.cppIsEnforcing()返回 false最简单、最可靠,不受ALLOW_PERMISSIVE_SELINUX开关和 cmdline 限制
源码层不改 init 源码BoardConfigBOARD_KERNEL_CMDLINE += androidboot.selinux=permissive编译进 boot.img,每次开机自动 Permissive(要求ALLOW_PERMISSIVE_SELINUX == true
内核级调试bootargsselinux=0完全关闭 SELinux 子系统
量产版本解决问题源码改 .te 编译不关闭 SELinux,最小权限原则,通过 CTS/VTS
init.rc 脚本 / setprop ro.boot.selinux不推荐实际测试无效

一句话总结

  • 临时调试 →setenforce 0

  • 编译期默认 Permissive →优先改 selinux.cppIsEnforcing()一行,不行再用 BoardConfig cmdline

    源码中能真正落地生效、且稳定可预测的方式有两种:

改 selinux.cpp `IsEnforcing()`(最稳)或 `BOARD_KERNEL_CMDLINE += androidboot.selinux=permissive`(需确认总开关)。
  • 如果不想编译源码,并且是Debug版本+有串口工具可以修改内核配置关闭selinux。

  • init.rc 脚本、setprop ro.boot.selinux 都是临时手段或实际无效的方式;

  • 量产解决问题 → 改.te策略编译

← 返回列表