App访问串口节点需要解决的权限问题
应用层在开发串口功能时经常需要跟驱动节点打交道,当我们新增一个硬件设备,一般会提供对应的节点给上层(framework,app)访问
访问节点的方式
- java IO
- adb shell cat
以usb节点为例,其路径为/sys/devices/platform/soc/a600000.ssusb/mode
,通常我们使用java标准的IO流
public static final String NODE_PATH = "/sys/devices/platform/soc/a600000.ssusb/mode";
public void readNode(View view) {
String result = FileUtils.readFileByLines(NODE_PATH);
tvShow.setText(result);
Log.d(TAG, "node data:"+result);
}
public static String readFileByLines(String fileName) {
BufferedReader bufferedReader = null;
FileReader fileReader = null;
try {
File file = new File(fileName);
if (!file.exists()) {
return "文件不存在";
}
fileReader = new FileReader(file);
bufferedReader = new BufferedReader(fileReader);
String string;
StringBuffer sb = new StringBuffer();
while ((string = bufferedReader.readLine()) != null) {
sb.append(string);
}
return sb.toString();
} catch (IOException e) {
e.printStackTrace();
return e.getMessage();
} finally {
if (fileReader != null) {
try {
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
或者进入adb shell
,直接cat对应节点名称来访问节点数据
权限问题
但直接访问肯定是不行的,会报权限错误
那么实现上层访问底层节点的核心就在于处理权限问题,主要包括两个方向的权限处理:
- SEAndroid权限
- 节点文件自身的读写权限
首先,app属于application层,驱动节点属于kernel层,由于SEAndroid的限制,application层是无法访问到kernel层的驱动节点文件的,这个我们应该很好理解,如果应用层能随随便便访问到kernel里面的东西,那Android也太不安全了,这个时候需要修改SEAndroid的权限限制,让第三方app也能访问到kernel节点文件。
SEAndroid介绍
在解决这个SEAndroid权限问题之前,我们先了解一下什么是SEAndroid,这里我用通俗易懂的语言简单介绍下:
了解SEAndroid前,需要先了解一下SELinux。SELinux是由美国NSA和 SCC 开发的 Linux的一个扩张强制访问控制安全模块。而SEAndroid相当于定制版SELinux,专门用于Android系统的安全管理。在Android4.4以前,进程的权限与当前用户的权限一致,也就是说,如果我以root用户启动某个进程,那这个进程就拥有了root权限,就可以为所欲为了,这种模式称之为DAC(Discretionary Access Control)。显然,DAC太过于宽松了,于是加入了SEAndroid,任何进程想干任何事情,都必须在《安全策略文件》中赋予权限,这便是MAC(Mandatory Access Control)模式。
实际上现在的Android系统同时采用了DAC和MAC的模式,系统会先做DAC检查,如果不通过,则直接操作失败。通过DAC检查之后再做MAC检查。
关于MAC安全策略文件,SELinux有一套规则来编写,这套规则称之为SELinux Policy语言。关于这套语言的基本规则,可参考此文章《Android系统SELinux(SEAndroid)》。
权限处理
解决权限问题的思路,一般先关闭SELinux验证,同时将目标节点文件的读写权限设到最大,执行
adb root
adb shell setenforce 0
adb shell chmod 777 /sys/devices/platform/soc/a600000.ssusb/mode
看看能否成功访问节点,如果成功了,说明确实是权限的问题,然后分别抓取logcat日志(framework)和dmesg日志(kernel),搜索关键字avc,即可得到跟权限相关的错误日志
执行
# 抓取logcat日志
adb logcat | findstr avc > E://logcat.txt
# 抓取dmesg日志
adb shell dmesg | findstr avc > E://dmesg.txt
抓到日志结果如下
[ 312.961648] type=1400 audit(1684478793.923:22): avc: denied { read } for comm=".jason.nodetest" name="soc" dev="sysfs" ino=10272 scontext=u:r:untrusted_app:s0:c66,c256,c522,c768 tcontext=u:object_r:sysfs:s0 tclass=dir permissive=0
[ 312.962343] type=1400 audit(1684478793.923:23): avc: denied { read } for comm=".jason.nodetest" name="soc" dev="sysfs" ino=10272 scontext=u:r:untrusted_app:s0:c66,c256,c522,c768 tcontext=u:object_r:sysfs:s0 tclass=dir permissive=0
[ 314.145472] type=1400 audit(1684478795.107:24): avc: denied { read } for comm=".jason.nodetest" name="mode" dev="sysfs" ino=29874 scontext=u:r:untrusted_app:s0:c66,c256,c522,c768 tcontext=u:object_r:vendor_sysfs_usb_device:s0 tclass=file permissive=0
[ 314.886338] type=1400 audit(1684478795.847:25): avc: denied { write } for comm=".jason.nodetest" name="mode" dev="sysfs" ino=29874 scontext=u:r:untrusted_app:s0:c66,c256,c522,c768 tcontext=u:object_r:vendor_sysfs_usb_device:s0 tclass=file permissive=0
[ 315.523664] type=1400 audit(1684478796.483:26): avc: denied { write } for comm=".jason.nodetest" name="mode" dev="sysfs" ino=29874 scontext=u:r:untrusted_app:s0:c66,c256,c522,c768 tcontext=u:object_r:vendor_sysfs_usb_device:s0 tclass=file permissive=0
[ 315.945379] type=1400 audit(1684478796.907:27): avc: denied { write } for comm=".jason.nodetest" name="mode" dev="sysfs" ino=29874 scontext=u:r:untrusted_app:s0:c66,c256,c522,c768 tcontext=u:object_r:vendor_sysfs_usb_device:s0 tclass=file permissive=0
[ 316.294456] type=1400 audit(1684478797.255:28): avc: denied { read } for comm=".jason.nodetest" name="mode" dev="sysfs" ino=29874 scontext=u:r:untrusted_app:s0:c66,c256,c522,c768 tcontext=u:object_r:vendor_sysfs_usb_device:s0 tclass=file permissive=0
以其中一条日志来具体分析
[ 314.145472] type=1400 audit(1684478795.107:24): avc: denied { read } for comm=".jason.nodetest" name="mode" dev="sysfs" ino=29874 scontext=u:r:untrusted_app:s0:c66,c256,c522,c768 tcontext=u:object_r:vendor_sysfs_usb_device:s0 tclass=file permissive=0
avc
代表这是SELinux相关的错误denied
{ read } 表示缺少读权限comm=".jason.nodetest"
表示是哪个进程在访问name="mode"
表示目标文件dev="sysfs"
表示.jason.nodetest进程要访问的目标文件的文件类型scontext=u:r:untrusted_app
表示访问进程的类型,这里表示第三方apptcontext=u:object_r:vendor_sysfs_usb_device:s0
表示目标文件的空间名称
翻译过来就是第三方app
要访问vendor_sysfs_usb_device
空间下的mode
文件,需要read
权限
OK,现在报错原因知道了,那我们就给它添加权限。
第一步:找到目标节点文件的声明,在genfs_contexts
文件中
genfscon sysfs /devices/soc/a800000.ssusb/a800000.dwc3/xhci-hcd.0.auto/usb1 u:object_r:vendor_sysfs_usb_device:s0
genfscon sysfs /devices/soc/a800000.ssusb/a800000.dwc3/xhci-hcd.0.auto/usb2 u:object_r:vendor_sysfs_usb_device:s0
genfscon sysfs /devices/platform/soc/a600000.ssusb/mode u:object_r:vendor_sysfs_usb_device:s0
# 目标节点
genfscon sysfs /devices/platform/soc/a800000.ssusb/mode u:object_r:vendor_sysfs_usb_device:s0
发现确实是定义在vendor_sysfs_usb_device
空间下,并且该空间下还定义了其他节点
第二步:找到vendor_sysfs_usb_device
空间的类型定义,在file.te
文件中
type vendor_sysfs_usb_device, sysfs_type, fs_type;
类型为sysfs
和fs
第三步:在untrusted_app_25.te
文件中添加第三方app访问vendor_sysfs_usb_device
空间的权限
allow untrusted_app vendor_sysfs_usb_device:file rw_file_perms;
如果你觉得这样不好记,也可以借助linux提供的audit2allow
工具来自动分析生成
audit2allow -i dmesg.txt
直接将生成的te命令配置到te文件中
配置完后,一定要记得,安全策略不能跟neverallow冲突,我们查看neverallow规则发现,SEAndroid规定了第三方app不允许访问sysfs和fs类型的文件
# Do not allow any write access to files in /sys
# 第三方app不允许访问sysfs类型文件
neverallow all_untrusted_apps sysfs_type:file { no_w_file_perms no_x_file_perms };
# Apps may never access the default sysfs label.
# 第三方app不允许访问sysfs类型文件
neverallow all_untrusted_apps sysfs:file no_rw_file_perms;
neverallow { all_untrusted_apps -mediaprovider } {
# 不允许访问fs类型文件
fs_type
-sdcard_type
file_type
-app_data_file # The apps sandbox itself
-privapp_data_file
-app_exec_data_file # stored within the app sandbox directory
-media_rw_data_file # Internal storage. Known that apps can
# leave artfacts here after uninstall.
-user_profile_data_file # Access to profile files
userdebug_or_eng(`
-method_trace_data_file # only on ro.debuggable=1
-coredump_file # userdebug/eng only
')
}:dir_file_class_set { create unlink };
我们将这些neverallow去掉,注意system/sepolicy/private/app_neverallows.te
和system/sepolicy/prebuilts/api/30.0/private/app_neverallows.te
都要去掉,这两个文件必须保持一致,否则编译报错。
到这里,我们就可以全编验证了,此时会发现读节点没问题,但往节点写数据还存在权限问题,这是因为节点文件本身只有read权限,所以还需要在系统启动之后给节点文件添加write权限,在system/core/rootdir/init.rc
中的on boot条件下添加
chmod 0777 /sys/devices/platform/soc/a600000.ssusb/mode
这时编译运行,仍会报权限错误,那是因为此节点文件的所有者是root
而init进程的所有者是system
,system
所有者直接修改root
所有者的文件权限是不够的,需要给init进程添加访问vendor_sysfs_usb_device
权限,在init.te
文件中添加
allow init vendor_sysfs_usb_device:file setattr;
同时还要将vendor_sysfs_usb_device
定义为mlstrustedobject
类型
type vendor_sysfs_usb_device, sysfs_type, fs_type, mlstrustedobject;
此时再编译验证,第三方app就可以直接读写驱动节点了
读节点
写节点
总结
此文可以让做Android开发的朋友认识到,底层硬件要让上层能够访问,如何去处理权限问题,SELinux相关的知识点对于初学者来说非常的抽象和难以理解,但只要处理几个相关的问题就能马上搞清楚是怎么回事了。
转载自:https://juejin.cn/post/7234795667478003770