FEATURED · 精选文章

Android SELinux单编快速验证实战指南

发布时间 / 2026/9/17 6:18:07
来源 / 创域科博编辑部
栏目 / 资讯中心
Android SELinux单编快速验证实战指南 1. 项目概述为什么“单编后快速验证”是Android系统工程师的日常生死线在AOSPAndroid Open Source Project开发一线干了十多年我每天最常被喊去救火的场景不是编译失败而是——“刚改完selinux policyrebuild了system.img烧机后发现adb shell进不去、logcat打不开、甚至Settings直接闪退”。这时候老板盯着你测试组等着提测产线等着出货而你手里只有一份刚生成的sepolicy文件和一个黑屏的设备。所谓“单编”就是不全量编译整个Android源码树只针对selinux策略模块通常是external/sepolicy或device/厂商/sepolicy做增量编译生成新的plat_sepolicy.cil、nonplat_sepolicy.cil或vendor_sepolicy.cil等策略文件再打包进system或vendor镜像。它快但风险极高SELinux不是普通代码它是内核级强制访问控制引擎一行allow写错整个服务链就断一个type_transition漏配App连自己创建的文件都读不了。所谓“快速验证”绝不是跑个adb shell getenforce看是不是Enforcing就完事——那等于用体温计量血压。真正要验证的是策略是否精准覆盖所有新引入的domain、type、attribute是否未过度放权导致安全降级是否与现有vendor HAL、HAL service、system_server子系统无冲突是否在不同SELinux模式permissive/enforcing下行为一致。我见过太多团队把“单编fastboot flash system.img”当成验证闭环结果上线后用户反馈“微信无法上传图片”“高德地图定位失败”查到最后发现是allow netd netd_socket:sock_file write漏了一条ioctl权限而这个权限只在Android 12的netd重构中才被引入。所以这篇笔记不讲理论只讲我在小米、OPPO、联发科三家公司量产项目里反复锤炼出来的、能5分钟内定位90%策略问题的实操路径从编译产物提取、策略比对、动态日志抓取到最小化复现验证。关键词就三个Android、Selinux、单编——它们不是孤立概念而是一条必须闭环的工程链路。2. 核心设计思路为什么不能只靠“diff policy文件”或“adb logcat | grep avc”很多人误以为SELinux验证“对比新旧policy文件差异”然后手动检查新增的allow规则。这是典型的安全幻觉。我拿去年一个真实案例说明某次为适配新摄像头HAL我们在device/xxx/sepolicy/camera.te里加了allow camera_service camera_device:chr_file { read write ioctl }diff显示只有这一行。烧机后Camera App能启动但预览画面全是绿屏。logcat里没AVC denialdmesg里也没有。最后用adb shell su -c cat /sys/fs/selinux/policy | wc -l发现新policy比旧版少了234行——原来m4预处理时因camera.te里一处宏定义拼写错误camer_device少了个a导致整个camera.te被m4忽略最终编译进镜像的其实是空策略。这说明单编产物的完整性验证必须穿透到二进制策略文件层而非源码层。另一个常见误区是依赖adb logcat | grep avc。AVC日志默认只记录denial事件且受selinux_enforcing状态影响当系统处于permissive模式时AVC仍会打印但不会阻断操作而某些关键服务如zygote、servicemanager在启动早期若遇到denial会直接abort进程根本来不及输出logcat。我们曾遇到过init进程因allow init sysfs:file write缺失在挂载/sys/fs/selinux时崩溃设备卡在bootanimationlogcat完全为空。因此真正的快速验证必须是三层联动第一层是策略二进制一致性校验确保编译产物没丢第二层是运行时AVC实时捕获绕过logcat缓冲区限制第三层是关键服务状态快照验证核心domain是否正常加载。工具链选择上我坚持不用任何第三方脚本或GUI工具——因为产线环境往往禁用未知apk而adb、getenforce、dmesg、sestatus这些原生命令在所有Android版本从4.4到14都稳定存在。比如dmesg -w命令它能实时监听内核ring buffer而AVC denial日志正是由avc_audit内核函数直接写入ring buffer比logcat早至少200ms。再比如sestatus -b它输出当前策略布尔值状态而很多策略问题恰恰源于某个布尔开关如allow_mtpd被意外关闭。这些命令组合起来构成了一套零依赖、秒级响应的验证骨架。至于为什么不用sepolicy-analyze因为它需要libsepol库支持而该库在Android 8.0以下设备上根本不存在且其输出格式对工程师不友好——它告诉你“policy has 12345 rules”却不告诉你哪条规则让surfaceflinger无法访问/dev/dri/renderD128。所以我的方案永远是用最原始的命令做最确定的事。3. 实操细节拆解从单编产物提取到AVC日志捕获的完整链路3.1 单编产物定位与二进制策略提取别再盲目烧机单编完成后关键不是立刻fastboot flash而是先确认编译产物是否真实生成且完整。以Android 12 AOSP为例m mm -j32 external/sepolicy后产物路径并非直觉上的out/target/product/xxx/obj/ETC/sepolicy_intermediates/而是分散在多个位置平台策略out/target/product/xxx/obj/ETC/plat_sepolicy.cil_intermediates/plat_sepolicy.cil注意后缀是.cil不是.te非平台策略out/target/product/xxx/obj/ETC/nonplat_sepolicy.cil_intermediates/nonplat_sepolicy.cilVendor策略out/target/product/xxx/obj/ETC/vendor_sepolicy.cil_intermediates/vendor_sepolicy.cil提示plat_sepolicy.cil包含AOSP通用策略nonplat_sepolicy.cil是OEM/ODM定制策略vendor_sepolicy.cil则专用于vendor分区。三者在make阶段会被sepolicy-build工具合并为最终的sepolicy二进制文件存于out/target/product/xxx/obj/ETC/sepolicy_intermediates/sepolicy。这个sepolicy文件才是烧机时实际写入system.img的根策略也是验证的黄金标准。验证第一步用sha256sum比对新旧sepolicy文件哈希值。我习惯在单编前先备份旧版adb shell su -c cp /sys/fs/selinux/policy /data/local/tmp/old_policy.bin adb pull /data/local/tmp/old_policy.bin ./backup/单编后将新生成的out/.../sepolicy推送到设备adb push out/target/product/xxx/obj/ETC/sepolicy_intermediates/sepolicy /data/local/tmp/new_policy.bin然后在设备上执行adb shell su -c sha256sum /sys/fs/selinux/policy /data/local/tmp/old_policy.bin /data/local/tmp/new_policy.bin如果三者哈希值两两不同说明策略已更新若/sys/fs/selinux/policy与new_policy.bin相同则证明烧机成功且策略已加载。这是所有后续验证的前提——否则你分析的全是旧策略的日志。3.2 运行时AVC日志捕获绕过logcat陷阱的三种硬核方法adb logcat | grep avc失效的根本原因是logcat有缓冲区和过滤机制。更可靠的方法是直接读取内核日志方法一dmesg实时监听推荐adb shell su -c dmesg -w | grep avc-w参数使dmesg持续监听ring bufferavc关键字匹配内核AVC审计日志。此方法延迟低于50ms且不受logcat级别影响。为防止日志刷屏我常用adb shell su -c dmesg -w | grep -E avc.*denied|avc.*allowed | head -n 20只抓取前20条关键denial/allowed事件。方法二/proc/kmsg直读最底层adb shell su -c cat /proc/kmsg 2/dev/null | grep avc /proc/kmsg是内核消息的原始接口比dmesg更底层。但需注意cat /proc/kmsg会清空ring buffer所以只能开一个实例。我通常将其后台运行再用另一个adb shell窗口触发操作如启动Camera。方法三auditctl动态审计需rootadb shell su -c auditctl -a always,exit -F archb64 -S execve -F path/system/bin/sh -k selinux_debug此命令为sh进程添加审计规则当任何shell命令执行时都会记录到/proc/audit/queue。虽然不直接输出AVC但它能帮你定位哪个进程触发了denial——比如logcat本身因权限不足被deny导致你收不到日志。注意以上所有命令必须在su环境下执行因为/proc/kmsg和auditctl需要root权限。若设备未rootdmesg -w仍是唯一可靠选项但需确保adb root已启用adb root adb remount。3.3 关键服务状态快照用sestatus和ps验证domain加载AVC日志只告诉你“谁被拒绝”但不告诉你“谁该被允许”。这时需验证目标服务的SELinux context是否正确加载。以surfaceflinger为例adb shell su -c ps -Z | grep surfaceflinger输出类似u:r:surfaceflinger:s0-c256,c512,c768,c1023 1234 1 ...。其中u:r:surfaceflinger:s0是domainc256,c512...是MLS类别。若此处显示u:r:untrusted_app:s0说明surfaceflinger进程被错误标记为普通App domain策略必然失效。再用sestatus -b检查布尔值adb shell su -c sestatus -b | grep allow_重点关注与当前功能相关的布尔开关如allow_surfaceflinger_propagate、allow_camera_propagate。若这些布尔值为off即使策略写了allow也会被全局禁用。最后用ls -Z验证关键文件contextadb shell su -c ls -Z /dev/dri/renderD128正常应为u:object_r:graphics_device:s0。若显示u:object_r:device:s0说明graphics_devicetype未正确定义需检查device/xxx/sepolicy/graphic.te是否被正确include。4. 完整实操流程从烧机到问题定位的5分钟标准化动作4.1 烧机前必做三件事建立基线、备份策略、清理缓存烧机不是终点而是验证的起点。在fastboot flash system前务必完成建立AVC基线日志adb shell su -c dmesg -c # 清空ring buffer adb shell su -c dmesg -w | grep avc /data/local/tmp/baseline.log # 后台记录基线然后执行一次完整开机流程从reboot到桌面就绪再kill掉该进程cat /data/local/tmp/baseline.log保存为基线日志。后续所有denial都需与基线对比。备份当前策略二进制adb shell su -c cp /sys/fs/selinux/policy /data/local/tmp/before_flash.bin清理SELinux缓存Android 10引入sepolicy缓存机制位于/data/misc/se/。若不清除新策略可能不生效adb shell su -c rm -rf /data/misc/se/* adb shell su -c reboot实操心得我曾在Pixel 4上遇到过缓存未清除导致新策略完全不加载的问题getenforce返回Enforcing但dmesg里一条AVC都没有——直到删掉/data/misc/se/才恢复正常。这个步骤看似多余却是产线验证的铁律。4.2 烧机后黄金5分钟分步执行、逐层排查烧机重启后按以下顺序执行严格计时超时即停第0-60秒确认基础状态adb wait-for-device adb shell getenforce # 必须为Enforcing adb shell sestatus -v | head -n 5 # 检查policy load时间应为本次启动时间第60-120秒捕获初始AVC风暴adb shell su -c dmesg -c # 清空 adb shell su -c dmesg -w | grep avc /data/local/tmp/first_boot.log # 启动监听 # 等待30秒然后kill adb shell su -c killall dmesg adb pull /data/local/tmp/first_boot.log ./logs/此时first_boot.log里应有大量denial这是系统服务启动时的“策略压力测试”。重点看init、zygote、servicemanager的denial。第120-180秒触发核心功能并抓日志以Camera为例adb shell am start -n com.android.camera/.Camera adb shell su -c dmesg -c adb shell su -c dmesg -w | grep avc /data/local/tmp/camera_test.log # 等待10秒点击拍照按钮 adb shell su -c killall dmesg adb pull /data/local/tmp/camera_test.log ./logs/第180-300秒交叉验证与定位对比baseline.log与first_boot.log找出新增denial用grep -o avc.*denied.*{.*} camera_test.log | sort | uniq -c | sort -nr统计高频denial针对最高频denial如avc: denied { ioctl } for pid1234 commcamera_service path/dev/video0 devtmpfs ino12345 scontextu:r:camera_service:s0 tcontextu:object_r:video_device:s0 tclasschr_file permissive0提取scontext、tcontext、tclass、perm四要素在device/xxx/sepolicy/下搜索对应.te文件检查是否遗漏allow camera_service video_device:chr_file ioctl;。4.3 问题定位速查表90%的单编问题可在此表中找到答案现象可能原因验证命令解决方案adb shell提示Permission deniedshelldomain缺少allow shell system_file:file executeadb shell su -c ps -Z | grep shell检查system/sepolicy/private/shell.teSettings闪退settingsdomain被错误标记为untrusted_appadb shell su -c ps -Z | grep settings检查platform_app.te是否正确includeCamera预览黑屏grallocHAL的allow gralloc_device graphics_device:chr_file ioctl缺失adb shell su -c ls -Z /dev/dri/renderD128在hardware/interfaces/graphics/mapper/2.0/default/sepolicy/gralloc.te中补规则微信无法上传图片media_rw_data_filetype未定义导致/sdcard/DCIMcontext错误adb shell su -c ls -Z /sdcard/DCIM在system/sepolicy/public/media.te中添加type media_rw_data_file, file_type, data_file_type;dmesg无AVC输出但功能异常SELinux处于permissive模式或avc_audit被禁用adb shell su -c getenforce cat /sys/fs/selinux/enforce执行adb shell su -c echo 1 /sys/fs/selinux/enforce实操心得这张表是我从2016年至今踩坑总结的精华。特别提醒/sdcard/DCIM的context问题在Android 11尤为常见因为Google将sdcard挂载点从/mnt/sdcard改为/sdcard而很多OEM的media.te仍沿用旧路径规则。解决方法不是改挂载点而是用type_transition规则重映射type_transition media_rw_data_file sdcard_type:dir media_rw_data_file;。5. 常见问题与独家避坑技巧那些文档里永远不会写的真相5.1 “单编后策略没生效”的五大隐形杀手杀手一BOARD_SEPOLICY_VERS版本不匹配在BoardConfig.mk中BOARD_SEPOLICY_VERS : 30.0必须与AOSP源码的external/sepolicy/version文件一致。若你用Android 13源码但BOARD_SEPOLICY_VERS设为28.0编译器会静默降级策略语法导致typeattributeset等新特性被忽略。验证方法adb shell su -c cat /sys/fs/selinux/mls # 输出1表示MLS启用0则未启用若为0大概率是版本不匹配。杀手二sepolicy_build工具链污染AOSP构建系统会缓存sepolicy_build工具。若你之前编译过旧版AOSPout/host/linux-x86/bin/sepolicy_build可能残留旧二进制。解决方案rm -rf out/host/linux-x86/bin/sepolicy_build m -j32 sepolicy_build杀手三BOARD_PLAT_PRIVATE_SEPOLICY_DIR路径错误此变量指定私有策略目录但若路径末尾多了一个/如device/xxx/sepolicy//m命令会静默跳过该目录。检查方法grep -r BOARD_PLAT_PRIVATE_SEPOLICY_DIR build/make/core/board_config.mk确保路径无尾部斜杠。杀手四genfscon规则未生效genfscon用于为虚拟文件系统如/proc、/sys设置默认context。若genfscon proc / u:object_r:proc:s0写错为genfscon proc /proc u:object_r:proc:s0则/proc/self等路径context为u:object_r:proc:s0但/proc根目录仍为u:object_r:sysfs:s0。验证adb shell su -c ls -Z /proc/self应为u:object_r:proc:s0。杀手五neverallow规则被意外触发neverallow是SELinux的“宪法条款”一旦违反编译直接失败。但某些neverallow规则如neverallow { domain -mlstrustedsubject } self:process { fork clone execmem }在单编时可能因m4宏展开顺序问题被绕过导致编译通过但运行时崩溃。解决方案全量编译一次m clobber m -j32 sepolicy强制检查所有neverallow。5.2 三个被低估的验证技巧让问题暴露得更快技巧一用adb shell su -c setenforce 0临时切permissive模式这不是放弃安全而是隔离问题。若切permissive后功能正常说明100%是SELinux策略问题若仍异常则是代码逻辑或硬件问题。注意切回enforcing前先dmesg -c清空日志再setenforce 1此时所有denial会集中爆发便于捕获。技巧二ps -Z配合grep -v过滤可信进程adb shell su -c ps -Z | grep -v u:r:kernel | grep -v u:r:init | grep -v u:r:zygote此命令列出所有非内核、非init、非zygote的进程快速发现被错误标记的service如u:r:untrusted_app:s0的surfaceflinger。技巧三ls -Z递归检查关键路径adb shell su -c ls -Z -R /dev/block/platform/ | grep -E (video|graphics|audio)-R递归列出所有block设备contextgrep筛选音视频相关设备。很多HAL问题源于/dev/block/platform/xxx/by-name/vendor的context错误而非/dev/video0。5.3 给新手的三条血泪忠告永远不要相信sepolicy文件的字面意思allow规则只是必要条件不是充分条件。type_transition、mlsconstrain、role_allow等规则共同决定最终访问结果。比如allow camera_service video_device:chr_file ioctl写对了但若camera_servicedomain没有mlsconstrain chr_file { ioctl } (u1 u2)ioctl仍会被拒绝。验证必须在目标设备上进行模拟器emulator的SELinux策略与真机差异极大。emulator -selinux permissive启动的模拟器其/sys/fs/selinux/policy是简化版无法复现vendor HAL的denial。记录每次单编的git diff和sha256sum我用一个verify_log.md文件记录## 2024-06-15 camera HAL fix - git diff: device/xxx/sepolicy/camera.te - old_policy.sha256: a1b2c3... - new_policy.sha256: d4e5f6... - first_boot.log denials: 12 (vs baseline 0) - root cause: missing allow camera_service graphics_device:chr_file ioctl这份日志在跨团队协作时价值千金——当测试组说“微信上传失败”你只需查日志30秒定位到是media.te漏了规则而非重新debug。我在小米做Redmi Note系列SELinux加固时这套流程将单编验证平均耗时从47分钟压缩到4分36秒。最后分享一个小技巧把上述所有adb命令写成一个verify.sh脚本放在$PATH里下次只需verify camera它自动执行从基线建立到日志抓取的全流程。真正的效率从来不是更快地写代码而是更快地确认代码没错。
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻