开机脚本写好了却没执行?可能是SELinux权限问题
你是否遇到过这样的情况:精心编写了开机启动脚本,也按规范添加到了init.rc或init.xxx.rc中,系统重启后却毫无反应?logcat和dmesg里找不到任何执行痕迹,手动执行脚本却一切正常——脚本语法没问题、路径没问题、权限chmod 755也加了……那问题很可能出在 SELinux 上。
在 Android 8.0 及之后的版本中,SELinux 已默认启用并运行于enforcing模式。这意味着,即使脚本本身完全合法,只要它的执行过程违反了 SELinux 策略(比如进程域无权访问该脚本文件、无权设置属性、无权调用某些系统服务),内核就会静默拒绝,脚本根本不会被 fork 和 exec,自然也就“没执行”。
本文不讲抽象理论,只聚焦一个真实可复现的场景:如何让一个简单的init.test.sh在 Android 8.0+ 设备上真正开机自启。我们将从脚本编写、策略定义、上下文标注、服务声明到排错验证,全程实操,每一步都对应实际报错和解决逻辑,帮你绕过那些隐藏极深的 SELinux 坑。
1. 脚本本身:开头、路径、测试三要素缺一不可
很多问题其实在第一步就埋下了伏笔。一个看似普通的 shell 脚本,在 Android init 环境下有严格约束。
1.1 正确的解释器路径是前提
Android 的 init 进程不使用标准 Linux 的/bin/sh,它依赖的是系统分区中的特定 shell:
#!/system/bin/sh # 或 #!/system/xbin/sh # 某些定制 ROM 使用注意:
- 写成
#!/bin/sh或#!/usr/bin/env sh在大多数 Android 设备上会直接失败,init 不会识别该解释器; #后面的注释行不能干扰解释器识别,第一行必须是纯净的#!行;- 如果你把脚本放在
/vendor/bin/下,也要确保该路径下存在对应 shell(通常需 symlink 到/system/bin/sh)。
1.2 脚本内容建议“轻量且可观测”
避免一上来就操作文件、挂载设备或调用复杂命令。初期验证阶段,推荐用最易观测的方式确认执行成功:
#!/system/bin/sh # 设置一个系统属性,重启后可用 getprop 验证 setprop sys.boot.test_script 1 # 记录时间戳到临时日志(需确保 /data/local/tmp 可写) echo "[`date`] test script executed" >> /data/local/tmp/init_test.log # 可选:触发一个 logcat tag,便于快速 grep log -t INIT_TEST "script ran successfully"验证方法(重启前务必做):
adb push init.test.sh /data/local/tmp/ adb shell chmod 755 /data/local/tmp/init.test.sh adb shell /data/local/tmp/init.test.sh adb shell getprop sys.boot.test_script # 应输出 1 adb shell cat /data/local/tmp/init_test.log # 应有时间戳记录 adb logcat -b main -t INIT_TEST # 应看到日志只有手动执行通过,才能进入下一步——否则问题在脚本本身,与 SELinux 无关。
2. SELinux 策略:为什么必须写.te文件?
当脚本被 init 启动时,它不再以shell域运行,而是以你为它单独定义的domain(域)运行。这个新 domain 默认没有任何权限,连读取自己脚本文件都不被允许。这就是为什么“关闭 SELinux 就能跑通”的根本原因——setenforce 0绕过了所有策略检查。
因此,.te文件不是可选项,而是强制要求。它的作用是:明确定义这个新服务进程能做什么、能访问哪些资源。
2.1 定义服务域与可执行文件类型
新建test_service.te(命名建议与服务名一致,便于追踪):
# 定义服务进程的 domain(执行时的上下文类型) type test_service, domain; # 定义脚本文件的 type(文件被加载时的上下文类型) type test_service_exec, file_type, vendor_file_type, exec_type; # 允许 init 以该 domain 启动此服务(关键!) init_daemon_domain(test_service); # 允许 test_service 域读取、打开、获取属性、执行 test_service_exec 类型的文件 allow test_service test_service_exec:file { read open getattr execute }; # 允许 test_service 向 property_service 发送请求(用于 setprop) allow test_service property_service:property_service { set }; # 允许 test_service 向 logd 发送日志(用于 log -t) allow test_service logd:fd use; allow test_service logd:socket { connectto };关键说明:
init_daemon_domain(test_service)是宏,它自动赋予test_service域基本 init 子进程能力(如sys_ptrace,dac_override等),省去大量手动 allow;allow ... set是setprop必需的权限,漏掉则属性设置失败且无提示;logd权限非必需,但强烈建议加上,方便调试;- 不要盲目取消注释
permissive test_service;—— 它会让该域所有拒绝变为警告(avc denied 日志仍会打印),仅用于调试,绝不可合入正式代码。
2.2 将.te文件纳入编译体系
不同平台存放位置略有差异,常见路径如下:
| 平台 | 推荐路径 |
|---|---|
| 高通(QCOM) | device/qcom/sepolicy/vendor/test_service.te |
| 联发科(MTK) | device/mediatek/sepolicy/basic/non_plat/test_service.te |
| 通用 AOSP | device/<vendor>/<product>/sepolicy/vendor/test_service.te |
验证是否生效:编译后检查out/target/product/<product>/root/sepolicy是否包含test_service相关规则(可用sesearch -A -s test_service检查)。
3. 文件上下文标注:让系统知道“这是谁的文件”
SELinux 不仅看进程域,还看文件的security context(安全上下文)。即使策略写了allow test_service test_service_exec:file ...,如果脚本文件本身的 context 不是test_service_exec,这条规则也匹配不上。
3.1 编辑file_contexts
在对应平台的file_contexts文件中添加一行(路径同.te文件所在目录):
/(system|vendor)/bin/init\.test\.sh u:object_r:test_service_exec:s0注意细节:
- 正则表达式需转义点号
\., 否则init.test.sh会被误匹配为initXtestXsh; - 路径前缀
/system/bin/或/vendor/bin/必须与你实际存放脚本的位置一致; s0是 MLS 级别,Android 通常用s0,无需修改;- 即使你
setenforce 0,这行也必须加——因为 init 启动服务时仍会读取file_contexts来设置进程初始上下文。
3.2 验证上下文是否正确
刷机后,用 adb 检查:
adb shell ls -Z /vendor/bin/init.test.sh # 正确输出应类似: # u:object_r:test_service_exec:s0 /vendor/bin/init.test.sh如果显示u:object_r:vendor_file:s0或其他类型,说明file_contexts未生效或路径不匹配,需重新编译或检查正则。
4. init.rc 服务声明:四要素一个都不能少
init.rc中的服务声明,是连接脚本、策略、上下文的最终一环。格式必须严格。
4.1 标准 service 块写法
service test_service /vendor/bin/init.test.sh class main user root group root oneshot seclabel u:object_r:test_service_exec:s0四要素解析:
service <name> <path>:服务名与脚本绝对路径,路径必须与file_contexts中标注的一致;seclabel:明确指定该服务以test_service_exec类型启动,init 会据此查找test_service域策略;user/group root:确保有足够权限执行setprop等操作(非 root 可能因 DAC 权限失败);oneshot:脚本执行完即退出,适合一次性任务;若需常驻,请用disabled+start <name>触发。
4.2 放在哪?别动init.rc主文件!
芯片厂商通常提供扩展入口:
- MTK:
init.mtXXX.rc或init.project.rc - QCOM:
init.qcom.rc或init.vendor.rc - 通用:
init.<product>.rc
正确做法:将 service 块追加到对应扩展 rc 文件末尾。
❌ 错误做法:直接修改system/etc/init.rc(会被 OTA 覆盖,且违反分层设计)。
5. 排错实战:从 avc denied 日志定位问题根源
当脚本仍不执行时,不要猜,要看日志。SELinux 拒绝会生成avc denied记录,它是唯一真相来源。
5.1 抓取并过滤关键日志
# 重启后立即执行(越早越好,避免日志被冲刷) adb shell dmesg | grep avc # 或持续监听 adb shell 'dmesg -w | grep avc'典型错误日志示例及对应修复:
| 日志片段 | 问题原因 | 修复方案 |
|---|---|---|
avc: denied { execute } for path="/vendor/bin/init.test.sh" dev="sda3" ino=123456 scontext=u:r:init:s0 tcontext=u:object_r:vendor_file:s0 tclass=file permissive=0 | 脚本文件 context 错误(应为test_service_exec) | 检查file_contexts正则与路径,重新刷机 |
avc: denied { set } for property="sys.boot.test_script" scontext=u:r:test_service:s0 tcontext=u:object_r:shell_prop:s0 tclass=property_service | 缺少allow test_service property_service:property_service { set }; | 在.te文件中补全该行 |
avc: denied { open } for path="/data/local/tmp/init_test.log" ... tcontext=u:object_r:shell_data_file:s0 | 脚本试图写入/data/local/tmp,但test_service域无权限 | 添加allow test_service shell_data_file:dir { add_name };和allow test_service shell_data_file:file { create write append }; |
avc: denied { getattr } for path="/system/bin/sh" ... tcontext=u:object_r:shell_exec:s0 | 解释器文件 context 不匹配,或缺少对/system/bin/sh的读取权限 | 添加allow test_service shell_exec:file { read open getattr execute }; |
提示:用audit2allow工具可半自动转换日志为策略语句(需在 Linux 主机上运行):
adb shell dmesg | grep avc > avc_log.txt # 传到电脑,执行: audit2allow -i avc_log.txt -p out/target/product/<product>/root/sepolicy但请勿直接复制粘贴结果——理解每条 allow 的含义,再精简加入.te文件。
6. 最终验证清单:五步确认法
完成全部配置后,按顺序执行以下五步,任一环节失败即回溯对应章节:
- 手动执行验证:
adb shell /vendor/bin/init.test.sh→getprop sys.boot.test_script应返回1; - 上下文验证:
adb shell ls -Z /vendor/bin/init.test.sh→ context 必须为test_service_exec; - 策略验证:
adb shell sesearch -A -s test_service | grep -E "(execute|set|property_service)"→ 应看到对应 allow 规则; - 服务状态验证:
adb shell getenforce→ 必须为Enforcing(非Permissive); - 重启后验证:
adb rebootadb wait-for-deviceadb shell getprop sys.boot.test_script→必须为1adb shell cat /data/local/tmp/init_test.log→应有重启后的时间戳
只有全部通过,才算真正解决了 SELinux 权限问题。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。