问题

起因是视频播放器播放一个视频没有声音,实际上视频播放的是raw资源自带的默认视频,所有视频资源都放在 /data/link/ac/videocomfort/video 下边

从log来看发现检测文件存在返回结果为false,我们查了系统目录下是有资源的,首先就怀疑权限问题

SELinux关键日志

检查读文件前后的log,发现SELinux警告日志

12-05 10:24:30.386  4845  4845 W cedes.wellbeing: type=1400 audit(0.0:189): avc: denied { search } for name="/" dev="vdw" ino=2 scontext=u:r:platform_app:s0:c523,c768 tcontext=u:object_r:unlabeled:s0 tclass=dir permissive=0

log各个字段的含义

字段

示例值

含义解析

事件标识

type=1400 audit(0.0:189)

SELinux审计事件,事件序列号为189。

核心拒绝

avc: denied { search }

关键操作:此次被拒绝的是 search(搜索/遍历目录)权限。

操作目标

for name="/" dev="vdw" ino=2

关键目标:尝试访问的是根目录 (name="/"),位于 vdw 设备上,inode号为2(通常是文件系统根目录)。

源上下文

scontext=u:r:platform_app:s0:c523,c768

主体:platform_app 域,类别(Category)为 c523,c768

目标上下文

tcontext=u:object_r:unlabeled:s0

客体:标签是 unlabeled在对象(文件)上,角色固定为 object_r

客体类别

tclass=dir

关键类别:客体类别是 dir(目录),另外有 file,link。这与 search 操作和 name="/" 目标是匹配的。

模式标志

permissive=0

表示SELinux当前处于强制模式。访问被实际阻止,而不仅仅是记录日志。

在宽容模式下验证

为了确认的确是SELinux权限导致的问题,我们将SELinux设置为宽容模式

adb shell setenforce 0

然后,我们观察到了一些SELinux警告输出

24249:12-05 11:32:18.797 11038 11038 I ExoPlayer:Loade: type=1400 audit(0.0:1217): avc: denied { search } for name="/" dev="vdw" ino=2 scontext=u:r:platform_app:s0:c522,c768 tcontext=u:object_r:unlabeled:s0 tclass=dir permissive=1
24250:12-05 11:32:18.797 11038 11038 I ExoPlayer:Loade: type=1400 audit(0.0:1218): avc: denied { read } for name="energy_full_screen.mp4" dev="vdw" ino=25 scontext=u:r:platform_app:s0:c522,c768 tcontext=u:object_r:unlabeled:s0 tclass=file permissive=1
24251:12-05 11:32:18.797 11038 11038 I ExoPlayer:Loade: type=1400 audit(0.0:1219): avc: denied { open } for path="/mnt/system_data/video_comfort_a/videocomfort/video/energy_full_screen.mp4" dev="vdw" ino=25 scontext=u:r:platform_app:s0:c522,c768 tcontext=u:object_r:unlabeled:s0 tclass=file permissive=1
24252:12-05 11:32:18.797 11038 11038 I ExoPlayer:Loade: type=1400 audit(0.0:1220): avc: denied { getattr } for path="/mnt/system_data/video_comfort_a/videocomfort/video/energy_full_screen.mp4" dev="vdw" ino=25 scontext=u:r:platform_app:s0:c522,c768 tcontext=u:object_r:unlabeled:s0 tclass=file permissive=1

开启宽容模式后有警告log输出,但是所有访问都会通过,log中permissive=1表示宽容模式,首先输出对根目录执行search操作的denied警告,然后是read,open的警告

检查代码

我们在openVideo传入的是绝对路径为什么在search/根目录呢?因为代码是这样写的

//path=/data/link/ac/videocomfort/xx.mp4
override fun openVideo(path: String) {
    if (isFileExist(path)) {
    ...
    } else {
    ...
    }
}

fun isFileExist(filePath: String?) = filePath?.let { File(filePath).exists() }.orFalse()

isFileExist触发了

  1. 解析符号链接:需要从根目录 / 开始遍历路径, 这个根目录不是Linux根目录而是设备挂载的根目录:/mnt/system_data/video_comfort_a,mount命令可以输出挂载点

  2. 检查权限:需要访问路径上的每一个目录

  3. SELinux检查:访问 vdw 设备的根目录时被拒绝,vdw设备在mount中没有找到,但是根据后面警告日志中的子目录所在的dev也是vdw,所以这个vdw很明显就是挂载点指向的设备

从上面的SELinux log tcontext=u:object_r:unlabeled:s0 tclass=dir 看到目标目录的SELinux上下文是一个unlabeled标签

检查SELinux te规则配置

te规则

在vendor/sepolicy/ac.te中我们的权限规则配置为

allow platform_app software_update_link_data_file:dir { read getattr search open  };
allow platform_app software_update_link_data_file:file { read getattr open };
allow platform_app software_update_link_data_file:lnk_file { read getattr open };

allow platform_app system_data_dir:dir { read getattr search open  };
allow platform_app system_data_dir:file { read getattr open };
allow platform_app system_data_dir:lnk_file { read getattr open };

allow platform_app apk_data_file:dir { read getattr search open  };
allow platform_app apk_data_file:file { read getattr open };

上面得规则表明platform_app 对label为software_update_link_data_file的目录/文件具有搜索读写权限

file_contexts配置

目标目录配置 vendor/sepolicy/file_contexts为

/mnt/system_data/video_comfort_a(/.*)?       u:object_r:software_update_link_data_file:s0
/mnt/system_data/video_comfort_b(/.*)?       u:object_r:software_update_link_data_file:s0

表明目标目录的tcontext应该是 u:object_r:software_update_link_data_file:s0 ,而log中显示为 tcontext=u:object_r:unlabeled:s0

检查文件的context

用命令行显示文件SELinux上下文

ls -laZ /mnt/system_data/
#正常输出
drwxr-xr-x 2 root system u:object_r:software_update_link_data_file:s0  40 1970-01-01 08:00 video_comfort_a
异常输出
drwxr-xr-x 2 root system u:object_r:unlabeled:s0  40 1970-01-01 08:00 video_comfort_a

上边显示了两种情况下的context,第5个字段为SELinux context

从上面的分析来看,权限拒绝的原因不是SELinux权限规则配置错误,而是文件夹的SELinux context 丢失,变成了unlabeled,导致权限校验失败

验证

为了验证这个猜想,我们尝试在正常可以访问的设备上来修改文件夹的context为unlabeled

adb root
adb remount
// 将dev下面的设备重新挂载到system_data目录下,挂载方式为读写
adb shell mount -o remount,rw /dev/block/video_comfort_a /mnt/system_data/video_comfort_a
//修改SELinux context
chcon -R u:object_r:unlabeled:s0 /mnt/system_data/video_comfort_a/
//查看context
ls -lZ /mnt/system_data/
//查看SELinux拒绝访问的log
logcat |grep avc
//恢复context
chcon -R u:object_r:software_update_link_data_file:s0 /mnt/system_data/video_comfort_a/

上面步骤里的重新挂载需要确认挂载点,因为直接remount之后依旧提示

chcon: 'video_comfort_a' to u:object_r:system_file:s0: Read-only file system

我们通过 mount |grep video_comfort_a 输出了挂载点的消息

/dev/block/video_comfort_a on /mnt/system_data/video_comfort_a type ext4 (ro,seclabel,nosuid,nodev,noexec,relatime)

将上述的context改为unlabeled之后,logcat立即就有avc : denied输出

这个验证可以明确我们SELinux 规则是配置正确的,根本原因是因为刷机方式不同导致设备挂载后的SELinux context变更为unlabled.

检查挂载点的配置文件

查找挂载点配置文件

cd /vendor/etc/
find ./ -name  "*fstab*"
#output 
./fstab.gen4.qcom
./fstab.qcom

查看fstab.qcom文件,是否包含分区的挂载配置,经过检查,我们发现/dev/block/video_comfort_a挂载行被注释

这个文件的在aosp中的配置位于 /device/vendor/qcom或者/platform/vendor/qcom

检查SELinux label规则是否生效

cd /vendor/etc/
grep -rin 'video_comfort'
#output
./fstab.qcom:54:#/dev/block/video_comfort_a                       /mnt/system_data/video_comfort_a  ext4    rw,noatime,noexec                     defaults
./selinux/vendor_file_contexts:720:/mnt/system_data/video_comfort_a(/.*)?       u:object_r:software_update_link_data_file:s0
./selinux/vendor_file_contexts:1031:/dev/block/video_comfort  u:object_r:userdata_block_device:s0

上面的输出表示,在file_context文件中配置的label: software_update_link_data_file是生效的

尝试修复label

在上面的输出中看到了挂载信息被注释掉的行,所以问题的原因很可能是跟挂载方式有关系,尝试将挂载的注释放开,重启设备label恢复

进一步排查发现,被注释掉的原因是挂载的功能由OTA或者应用自己负责挂载,另外了解到两种不同的刷机方式

第一种是低层次的初始刷机,会刷掉系统所有信息,此时我们挂载的路径是没有SELinux label的

第二种是full upgrade全量刷机,所有需要升级的地方都会升级,刷完之后有label

所以最终来看这个应该还是和挂载方式有关,另外从bsp开发了解到,两套刷机方式有不同的SELinux策略,位于不同的代码库,所以两种刷机的结果表现不一致,放开上面的注释是一种解决办法

春风花气馥,秋月寒江湛