Selinux权限怎么加?Android开机脚本必备知识
在Android系统开发中,让一个自定义shell脚本在开机时自动运行,看似简单,实则常被卡在Selinux权限这一关。很多开发者push脚本、修改init.rc、重启设备后发现脚本根本没执行——查logcat却只看到一串avc: denied的拒绝日志,无从下手。这不是脚本写错了,也不是init.rc语法有问题,而是Selinux在默默拦截。
本文不讲抽象理论,不堆砌SELinux术语,而是以“测试开机启动脚本”这个真实镜像为背景,带你从零走通一条可验证、可复现、可落地的完整路径:从写一个最简脚本开始,到定位权限错误、编写te规则、配置file_contexts、最终让脚本稳稳跑在开机阶段。所有步骤均基于Android 8.0+主流平台(含MTK)验证通过,适配预编译镜像环境,无需root手机、不依赖串口,用adb logcat就能完成全部调试。
1. 先写一个能跑起来的脚本
别急着改selinux,第一步永远是:确保脚本本身在手动执行时能正常工作。
1.1 脚本内容与存放位置
新建文件init.test.sh,内容如下:
#!/system/bin/sh # 设置一个测试属性,便于快速验证是否执行成功 setprop sys.test.boot 1 # 可选:写入日志便于追踪(需确保/data有写权限) echo "$(date): init.test.sh executed" >> /data/local/tmp/boot_log.txt注意事项:
- 第一行
#!/system/bin/sh必须严格匹配Android系统实际shell路径。/system/bin/sh是AOSP标准路径;部分厂商(如高通)可能用/system/xbin/sh;绝不能写成/bin/sh,否则init进程加载时会直接失败。 - 脚本中避免创建新文件、修改系统分区等高风险操作。初期仅用
setprop验证执行流,是最安全、最轻量的测试方式。 - 建议将脚本存放在
/system/bin/目录下(需remount system为可写),这是init进程默认信任的可执行路径之一。
1.2 手动验证脚本可用性
通过adb推送并手动执行,确认基础功能:
adb root adb remount adb push init.test.sh /system/bin/init.test.sh adb shell chmod 755 /system/bin/init.test.sh adb shell /system/bin/init.test.sh adb shell getprop sys.test.boot # 应输出 1如果这一步失败,请先检查shell路径、权限、语法错误——Selinux问题永远排在功能验证之后。
2. 在init.rc中注册服务
Android启动流程中,init进程读取init.rc及其包含的.rc文件来启动各类服务。我们要让脚本作为一项“服务”被init管理。
2.1 选择正确的.rc文件位置
不建议直接修改/system/etc/init.rc(易被OTA覆盖,且违反分层设计)。主流方案是:
- 芯片平台侧:在
device/<vendor>/<platform>/sepolicy/对应目录下,查找类似init.<platform>.rc的文件(如init.mt6765.rc) - 项目侧:在
device/<vendor>/<project>/下新建init.<project>.rc - 通用推荐:若镜像已预置
init.vendor.rc或init.custom.rc,优先使用它
在选定的.rc文件末尾添加以下服务定义:
service test_boot /system/bin/init.test.sh class main user root group root oneshot seclabel u:object_r:test_service_exec:s0关键字段说明:
oneshot:表示脚本执行完即退出,不常驻。适合一次性初始化任务。seclabel:此处填写的是目标安全上下文标签,即我们即将为该脚本定义的SELinux类型。现在先占位,后续补全。class main:确保该服务随main类服务一同启动(通常在early-init之后、late-init之前)
2.2 验证init.rc语法
修改后需重新编译bootimage或使用adb shell动态加载(部分设备支持):
adb shell stop adb shell start但更可靠的方式是:重启设备后,立即执行:
adb shell cat /proc/1/cmdline # 确认init进程已重启 adb shell ls -Z /system/bin/init.test.sh # 查看当前文件SELinux上下文(此时应为u:object_r:shell_exec:s0或类似,尚未是我们定义的)3. 定位Selinux拒绝日志
这是最关键的一步。没有日志,就等于在黑暗中调试。
3.1 开机后抓取avc拒绝记录
重启设备,在开机过程中(或开机完成后10秒内),立即执行:
adb logcat -b events | grep avc # 或更精准地过滤 adb logcat | grep -i "avc.*denied"你大概率会看到类似这样的日志:
avc: denied { execute } for path="/system/bin/init.test.sh" dev="mmcblk0p42" ino=123456 scontext=u:r:init:s0 tcontext=u:object_r:shell_exec:s0 tclass=file permissive=0日志解读(逐字段):
scontext=u:r:init:s0:源上下文 —— 发起操作的主体是init进程,类型为inittcontext=u:object_r:shell_exec:s0:目标上下文 —— 被操作对象(脚本文件)当前的类型是shell_exectclass=file:操作对象类别是文件{ execute }:被拒绝的操作是“执行”permissive=0:当前处于enforcing模式(严格模式),不是宽容模式
这条日志明确告诉我们:init进程想以execute方式访问一个类型为shell_exec的文件,但策略不允许。
3.2 理解为什么需要新类型
shell_exec是系统shell的默认类型,用于/system/bin/sh自身。但我们的自定义脚本不应共享此类型,原因有二:
- 安全隔离:防止其他拥有
shell_exec权限的进程误执行该脚本 - 策略可控:为
init访问该脚本单独授权,权限最小化
因此,我们必须为脚本定义一个专属类型(如test_service_exec),并告诉SELinux:“允许init执行这个新类型的文件”。
4. 编写并集成SELinux策略文件
策略文件由三部分组成:类型定义(.te)、文件上下文映射(file_contexts)、策略规则(.te中的allow语句)。缺一不可。
4.1 创建te策略文件
在设备厂商SELinux策略目录中(如device/mediatek/sepolicy/basic/non_plat/),新建文件test_service.te,内容如下:
# 定义服务域类型(domain type) type test_service, domain; # 定义脚本文件类型(file type) type test_service_exec, file_type, vendor_file_type, exec_type; # 声明该服务属于init守护进程域(关键!) init_daemon_domain(test_service); # 允许init域执行该脚本文件 allow init test_service_exec:file { read open execute }; # 允许test_service域读取/写入属性(因为脚本里用了setprop) allow test_service property_type:property_service { set };关键点解析:
init_daemon_domain(test_service):这是核心宏。它自动为test_service域赋予init守护进程所需的基础权限(如访问/dev、/sys等),省去大量手动allow。allow init ...:明确授权init进程对test_service_exec类型文件的read、open、execute权限。注意主语是init(不是test_service),因为init是启动者。allow test_service property_service { set }:因脚本调用setprop,必须授权其向property_service发送set请求。
4.2 配置file_contexts映射
在同目录下的file_contexts文件(如device/mediatek/sepolicy/basic/non_plat/file_contexts)中,添加一行:
/system/bin/init\.test\.sh u:object_r:test_service_exec:s0注意:
- 路径需使用正则转义(
.写成\.),确保精确匹配文件名,避免误匹配其他脚本。 - 上下文格式必须严格为
u:object_r:<type>:s0,其中<type>必须与.te文件中定义的类型名完全一致(test_service_exec)。
4.3 策略编译与生效
- 若使用完整AOSP编译:
m或m sepolicy即可,策略会自动打包进vendor.img或boot.img。 - 若使用预编译镜像(如本文“测试开机启动脚本”镜像):策略文件需提前集成到镜像中。开发者只需确认该镜像的sepolicy目录已包含上述两个文件,并在编译时被正确引用。
验证映射是否生效:
adb shell ls -Z /system/bin/init.test.sh # 正常输出应为:u:object_r:test_service_exec:s05. 验证与常见问题排查
完成以上步骤后,重启设备,用最简方式验证效果。
5.1 快速验证方法
# 1. 检查属性是否设置成功 adb shell getprop sys.test.boot # 应返回 1 # 2. 检查服务是否被init启动(查看进程列表) adb shell ps | grep test_boot # 可能看不到,因为oneshot会退出;可改用start命令临时触发 adb shell start test_boot adb shell getprop sys.test.boot # 再次确认 # 3. 检查日志中是否还有avc拒绝 adb logcat | grep -i "avc.*denied" | grep test # 理想状态:无任何相关拒绝日志5.2 高频问题与解决方案
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
getprop返回空,且logcat无avc日志 | 脚本未被init启动 | 检查.rc文件是否被正确加载(adb shell cat /proc/1/cmdline看init参数)、服务名拼写、class是否匹配当前启动类 |
avc denied { execute }依然存在 | file_contexts未生效或类型名不一致 | adb shell ls -Z确认文件上下文;核对.te和file_contexts中类型名是否完全相同(大小写、下划线) |
avc denied { set }新出现 | 缺少property_service授权 | 在.te中补充allow test_service property_type:property_service { set }; |
脚本执行但/data/local/tmp/boot_log.txt未生成 | /data分区SELinux上下文限制 | 改用log -p i -t TEST "msg"写入logcat,或授权test_service对data_file_type的write权限(不推荐,违背最小权限原则) |
6. 总结:Selinux加权限的本质逻辑
加Selinux权限,从来不是盲目堆砌allow语句,而是一场清晰的“角色-动作-对象”建模:
- 角色(Domain):谁在操作?是
init(启动者)还是test_service(脚本执行后的新进程)?本文中,启动阶段的权限由init发起,故主语是init。 - 动作(Permission):要做什么?
execute(执行文件)、set(设置属性)、read(读取)… 动作必须精确到最小粒度。 - 对象(Type):对什么做?文件类型(
test_service_exec)、属性类型(property_type)、设备节点类型(dev_type)… 类型必须唯一且语义明确。
当你下次再遇到avc denied,请记住这个三步法:
- 看日志:
scontext、tcontext、tclass、{ perm }四要素缺一不可; - 定角色:判断是哪个进程(domain)需要什么权限;
- 写规则:用
allow <domain> <type>:<class> { <perm> };补全,辅以init_daemon_domain()等宏简化。
权限不是障碍,而是系统为你画出的安全边界。理解它,才能真正掌控Android的启动过程。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。