19、Logcat 与安全:SELinux 权限、PackageManager 签名验证日志
说到 Android 安全,很多人第一反应是「应用权限」、「沙箱隔离」。但真正深入到系统底层,你会发现有两样东西是绕不开的:SELinux 和 签名验证。
我个人习惯把 Logcat 里的安全日志分成两类:一类是「系统在防谁」,另一类是「系统在认谁」。前者就是 SELinux,后者就是 PackageManager 的签名校验。今天我们就来聊聊,怎么通过 Logcat 读懂这两类信息。
19.1 SELinux:系统里的「门禁系统」
SELinux,全称 Security-Enhanced Linux。说白了,它就是 Android 系统里的一套「门禁规则」。每个进程、每个文件,都被贴上了标签。你想访问某个资源?先过门禁这一关。
我在项目中遇到过好几次,明明代码逻辑完全正确,但功能就是跑不起来。一查 Logcat,全是 avc: denied 的日志。嗯,这就是 SELinux 在说「不」。
19.1.1 怎么看 SELinux 日志
SELinux 的日志在 Logcat 里通常以 avc: 开头。你想想看,如果系统里某个功能突然失效,第一件事就是过滤 avc 关键字。
// 过滤 SELinux 拒绝日志
adb logcat -b events | grep "avc: denied"
一条典型的 SELinux 拒绝日志长这样:
type=1400 audit(0.0:1234): avc: denied { read } for pid=1234
comm="my_app" name="secret.txt" dev="mmcblk0p25" ino=5678
scontext=u:r:untrusted_app:s0:c512,c768
tcontext=u:object_r:app_data_file:s0 tclass=file permissive=0
我来拆解一下这段日志:
- denied { read }:被拒绝的操作是「读取」。
- comm="my_app":发起请求的进程名。
- scontext:发起方的安全上下文。这里是
untrusted_app,也就是普通第三方应用。 - tcontext:目标资源的安全上下文。这里是
app_data_file。 - permissive=0:当前是强制模式。如果是 1,表示宽容模式,只记录不拦截。
关键点:permissive=0 表示这条规则是「硬拦截」,功能一定会受影响。permissive=1 只是打个日志,不会真的阻止你。
19.1.2 实战:给应用添加 SELinux 权限
假设你的系统服务需要访问一个自定义设备节点 /dev/my_sensor。你写好了代码,但一运行就报错。Logcat 里出现:
avc: denied { open } for pid=5678 comm="system_server"
scontext=u:r:system_server:s0
tcontext=u:object_r:device:s0 tclass=chr_file
这说明 system_server 没有权限打开这个设备文件。怎么办?你需要修改 SELinux 策略文件。
在 system/sepolicy/ 目录下,找到 system_server.te,添加一行:
# 允许 system_server 打开 my_sensor 设备
allow system_server my_sensor_device:chr_file { open read write };
然后在 file_contexts 里声明设备节点的上下文:
/dev/my_sensor u:object_r:my_sensor_device:s0
重新编译烧录,问题解决。我曾经因为漏了 file_contexts 的声明,折腾了整整一个下午。你想想看,策略文件写对了,但上下文没对上,SELinux 照样不认。
注意:不要为了省事直接 setenforce 0 关闭 SELinux。这在开发机上可以临时调试,但正式产品绝对不能这么做。安全不是儿戏。
19.2 PackageManager 签名验证日志
说完了「门禁」,我们再聊聊「身份认证」。Android 系统怎么判断一个 APK 是不是可信的?靠的就是签名验证。
PackageManager 在安装应用时,会做一系列签名检查。这些检查的结果,都会记录在 Logcat 里。我个人习惯在排查安装失败问题时,先看 PackageManager 和 PackageParser 这两个 TAG。
19.2.1 签名验证的关键日志
当你安装一个 APK 时,Logcat 里会出现类似这样的日志:
// 签名验证通过
PackageManager: verifySignatures: checking signatures for com.example.app
PackageManager: verifySignatures: signatures match
// 签名验证失败
PackageManager: verifySignatures: signature does not match
PackageManager: Package com.example.app has no signatures in package manager
第一种情况,签名匹配,安装继续。第二种情况,签名不匹配,安装会被拒绝。
为什么会签名不匹配?最常见的原因是:你尝试升级安装一个应用,但新版本的签名和旧版本不一致。Android 不允许这种情况发生。
小技巧:如果你需要调试签名问题,可以用 adb logcat -s PackageManager 只看 PackageManager 的日志,避免被其他信息干扰。
19.2.2 V1、V2、V3 签名方案
Android 的签名方案经历了多次迭代。从最初的 JAR 签名(V1),到后来的 APK 签名方案 V2、V3。不同版本的 Android 对签名方案的支持也不同。
| 签名方案 | 引入版本 | 特点 |
|---|---|---|
| V1 (JAR签名) | Android 1.0 | 基于 META-INF 目录,不保护 APK 完整性 |
| V2 (APK签名方案v2) | Android 7.0 | 保护整个 APK 文件,签名更快 |
| V3 (APK签名方案v3) | Android 9.0 | 支持密钥轮换,可以更换签名密钥 |
在 Logcat 里,你可以看到 PackageManager 对签名方案的检查:
PackageParser: Checking APK Signature: v2 signature found
PackageParser: v2 signature verified successfully
如果 APK 只用了 V1 签名,而目标设备是 Android 11,系统会提示你缺少 V2 签名。我记得有一次,客户反馈说应用在 Android 10 上装不上。我一看日志,发现 APK 只打了 V1 签名,而 Android 10 默认要求至少 V2 签名。重新打包后问题解决。
19.2.3 签名验证失败的处理
当签名验证失败时,Logcat 里会给出明确的错误码。常见的错误码有:
- INSTALL_FAILED_UPDATE_INCOMPATIBLE:签名不一致,无法升级。
- INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES:APK 内部多个文件签名不一致。
- INSTALL_FAILED_SHARED_USER_INCOMPATIBLE:共享 UID 的应用签名不匹配。
我曾经遇到过一个很隐蔽的问题:两个应用声明了相同的 sharedUserId,但签名密钥不同。结果第二个应用死活装不上。Logcat 里报的就是 INSTALL_FAILED_SHARED_USER_INCOMPATIBLE。排查了半天才发现是签名问题。
核心原则:同一个包名,必须用同一个签名。同一个 sharedUserId,也必须用同一个签名。这是 Android 安全模型的基础。
19.3 安全日志的完整链路
为了让你更直观地理解 SELinux 和签名验证在整个系统里的位置,我画了一张图。
从这张图你可以看到,一个应用从安装到运行,要经过两道安全关卡。第一关是 PackageManager 的签名验证,第二关是 SELinux 的权限检查。任何一关没过,Logcat 里都会有明确的日志。
19.4 总结
安全日志是排查系统问题的利器。我个人建议你养成两个习惯:
- 遇到功能异常,先过滤
avc: denied,看看是不是 SELinux 在拦截。 - 遇到安装失败,先过滤
PackageManager,看看签名验证是否通过。
这两个方向排查下来,大部分安全问题都能定位。剩下的,就是根据日志里的上下文信息,去修改策略或者修复签名了。
一句话总结:SELinux 管的是「能不能访问」,签名验证管的是「是不是本人」。两者缺一不可。
公众号:蓝海资料掘金营,微信deep3321