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 日志排查
- 九、总结
文章目录
- 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 root | setenforce 0 | 适用场景 |
|---|---|---|---|---|
eng | Permissive | ✅ 可以 | ✅ 可以 | 内部开发调试 |
userdebug | Enforcing | ✅ 可以 | ✅ 可以 | 外部开发调试 |
user | Enforcing | ❌ 不可以 | ❌ 不可以 | 量产版本 |
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 阶段完成- 时机问题:SELinux 初始化在
SelinuxInitialize()阶段完成,发生在 init.rc 解析执行之前或并行。即使 init.rc 的on boot触发setenforce 0执行了,也可能被系统后续的恢复流程强制改回 Enforcing。 - vendor 定制:部分厂商在
system/core/init/selinux.cpp中强制忽略setenforce的调用,或在on property:sys.boot_completed=1时强制设置为 Enforcing。 - 编译产物问题:如果修改的是 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.img或vendor_boot.img一起编译刷入:
makebootimage vendor_bootimage-j8# fastboot flash boot boot.img3、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.cpp在SelinuxInitialize()调用前或调用后强制执行security_setenforce(0)。
这里修改 BoardConfig.mk 属性编译源码了,并不是简单的prop属性修改。
四、编译期/内核级控制方式(永久)
包含两类方式:
- 源码编译期控制(通过 BOARD_KERNEL_CMDLINE)— 影响默认的 SELinux 模式(Permissive / Enforcing),不关闭内核子系统
- 内核级关闭(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# 预期输出: Permissive2、内核级完全关闭: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.cpp | 1 行 | ✅ 是 | ✅ 是 | 最推荐,简单、无前置条件 |
| 改 StatusFromProperty() | system/core/init/selinux.cpp | 1 行 | ❌ 否(受 ALLOW_ 限制) | ✅ 是 | 简单但受总开关限制 |
| BoardConfig cmdline | device/.../BoardConfig.mk | 1 行 mk | ❌ 否(受 ALLOW_ 限制) | ❌ 否(依赖 cmdline) | 不改 init 源码时用 |
一句话建议:
- 能改 system/core/init/selinux.cpp → 直接改
IsEnforcing()→ 最稳- 不能改 system/core(项目裁剪了)→ 改 BoardConfig
BOARD_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 | ❌ 重启失效 | ❌ 不可用 | ⚠️ Permissive | ⭐ | userdebug 临时调试 |
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 / userdebug:
shell域有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-Z2、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.cpp
IsEnforcing()一行,不行再用 BoardConfig cmdline源码中能真正落地生效、且稳定可预测的方式有两种:
改 selinux.cpp `IsEnforcing()`(最稳)或 `BOARD_KERNEL_CMDLINE += androidboot.selinux=permissive`(需确认总开关)。如果不想编译源码,并且是Debug版本+有串口工具可以修改内核配置关闭selinux。
init.rc 脚本、setprop ro.boot.selinux 都是临时手段或实际无效的方式;
量产解决问题 → 改
.te策略编译