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 里。我个人习惯在排查安装失败问题时,先看 PackageManagerPackageParser 这两个 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 和签名验证在整个系统里的位置,我画了一张图。

Android 安全日志链路 应用层 安装/访问资源 PackageManager 签名验证 SELinux 权限检查 Logcat 日志输出 avc: denied | verifySignatures | INSTALL_FAILED_* 应用发起请求 → PackageManager 验证签名 → SELinux 检查权限 → 结果全部输出到 Logcat,供开发者排查 应用层 签名验证 权限检查 日志输出

从这张图你可以看到,一个应用从安装到运行,要经过两道安全关卡。第一关是 PackageManager 的签名验证,第二关是 SELinux 的权限检查。任何一关没过,Logcat 里都会有明确的日志。

19.4 总结

安全日志是排查系统问题的利器。我个人建议你养成两个习惯:

  • 遇到功能异常,先过滤 avc: denied,看看是不是 SELinux 在拦截。
  • 遇到安装失败,先过滤 PackageManager,看看签名验证是否通过。

这两个方向排查下来,大部分安全问题都能定位。剩下的,就是根据日志里的上下文信息,去修改策略或者修复签名了。

一句话总结:SELinux 管的是「能不能访问」,签名验证管的是「是不是本人」。两者缺一不可。


公众号:蓝海资料掘金营,微信deep3321