likes
comments
collection
share

App访问串口节点需要解决的权限问题

作者站长头像
站长
· 阅读数 13

应用层在开发串口功能时经常需要跟驱动节点打交道,当我们新增一个硬件设备,一般会提供对应的节点给上层(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对应节点名称来访问节点数据

App访问串口节点需要解决的权限问题

权限问题

但直接访问肯定是不行的,会报权限错误

App访问串口节点需要解决的权限问题

那么实现上层访问底层节点的核心就在于处理权限问题,主要包括两个方向的权限处理:

  • 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 表示访问进程的类型,这里表示第三方app
  • tcontext=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;

类型为sysfsfs

第三步:在untrusted_app_25.te文件中添加第三方app访问vendor_sysfs_usb_device空间的权限

allow untrusted_app vendor_sysfs_usb_device:file rw_file_perms;

App访问串口节点需要解决的权限问题

如果你觉得这样不好记,也可以借助linux提供的audit2allow工具来自动分析生成

audit2allow -i dmesg.txt

App访问串口节点需要解决的权限问题

直接将生成的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.tesystem/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

App访问串口节点需要解决的权限问题

而init进程的所有者是systemsystem所有者直接修改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就可以直接读写驱动节点了

读节点

App访问串口节点需要解决的权限问题

写节点

App访问串口节点需要解决的权限问题

总结

此文可以让做Android开发的朋友认识到,底层硬件要让上层能够访问,如何去处理权限问题,SELinux相关的知识点对于初学者来说非常的抽象和难以理解,但只要处理几个相关的问题就能马上搞清楚是怎么回事了。