一、引言

1.1、什么是Selinux

Selinux是一种MAC(强制访问控制)安全机制,是对传统DAC(直接访问控制)机制的加强,而非替换,定义传统进程对文件对象的访问权限,如果不满足则无法执行,不管当前是什么用户。

本文下面讲的内容,有安卓源码的小伙伴可以到类似目录(android/device/softwinner/common/)去检索查看Selinux的相关操作。

1.2、为什么提出Selinux

传统的DAC机制缺点:对权限管理太粗矿,只要获取到root权限就能为所欲为;

Selinux的优点:可以把权限管控做得更加细致,即使有root权限也无能为力。

二、Selinux基本要素

2.1、安全上下文

2.1.1、概念

安全上下文就是描述文件或者进程的一种属性。

2.1.2、查看安全上下文的方法

进程上下文:ps -Z,关注进程的domain。

举例:“u:r:init:s0”,其中的init就是domain。

文件上下文:ls -Z,关注文件的type。

举例:“u:object_r:system_file:s0”,其中的system_file就是type。

2.1.3、安全上下文的定义位置

普通文件在file_contexts文件中定义;虚拟文件在genf_contexts文件中定义;属性文件在property_contexs文件中定义;应用配置在seapp_contexts文件中定义。

因为我是Bsp软件工程师,负责的是SOC的驱动部分,所以对于Selinux,上面四种文件改动得比较多的普通文件,所以这里就简单举一个普通文件的安全上下文是如何定义的。

上图中,总共为两个文件定义了安全上下文,可以看到,定义安全上下文的语法:文件位置   标签。

文件位置很好理解,就是这个文件是哪个地方的文件,这里要用绝对路径。

而什么是标签呢?可以看到2.1.2中举的两个例子,“u:r:init:s0”、“u:object_r:system_file:s0”,他们都是标签。一个是进程的标签,一个是文件的标签。而其中的init是进程的domain,system_file是文件的type,init、system_file也就是我们所说的安全上下文。

Selinux 依靠标签来匹配操作和政策(所谓操作和政策,就是在Selinux,允许某个进程操作某个文件,如果未授权,就算是root权限也不行)。

2.2、组

2.2.1、概念

这个组的概念,大家不需要理解的太复杂,其实就是表面意思,作用是将相同类型的文件或者进程集合到一个组里。

就像是Linux中的group,在同一群组下的用户或者文件,享有相同的权限。

使能Selinux,将Selinux编译进内核后,当我们启动开发板,Linux内核被加载进入内存,Selinux就开始运行起来了。Selinux会根据我们为一些文件和进程定义好的标签,给他们打上标签。而那些我们没有为它们定义标签的文件和进程,Selinux会根据自己的规则,为它们打上默认的标签。为了某些同类型的文件,比如/dev/tty这些串口设备文件,我们知道,一块Linux开发板,串口是有很多的,既然是串口,那么它们肯定就应该有着相同的权限,需要将他们归为一个小组。加入我们把它们归为serial_file组,那么就应该把它们的安全上下文,也就是文件的type设置为serial_file。

现在,大家就应该理解什么是组的概念了。

2.2.2、关联类型和属性

从2.2.1我们知道了,像/dev/tty这种串口文件,是属于serial_file这种类型的,那么它们的标签就应该是这样“u:object_r:serial_file:s0”。

说到这里,问题也就来了。在Selinux中,其实是没有serial_file这种类型的,那我们又想用它怎么办呢?

这时候,我们就需要用到关联类型和属性这个概念了。

经过之前的解释,其实大家都知道,所谓类型就是进程和文件的安全上下文,而前面说过,安全上下文就是描述进程和文件的属性。安全上下文这里涉及到的属性呢,可以自己定义,也可以用Selinux定义好的。而关联类型和属性那里的属性呢?则是Selinux自己的标准属性,不需要,也不能让用户来自己定义。

还是以/dev/tty为例。在Selinux中,对/dev下的文件,安全上下文默认设置的都是device这个type。这个时候你想要将这些串口设备文件都放在serial_file这一组,让他们与其他设备文件区分开来,而Selinux中又没有这种类型,那么就需要你将它给定义出来并将它与device这个Selinux自带的属性关联起来了。

自定义类型且关联属性,必须在te文件中,命令:“type type_id, [attribute_id]”,示例:“type serial_file, device;”。

既然可以将自定义类型与属性关联起来,那么非自定义的类型自然也可以和属性关联。比如,你想要让一个文件或者进程拥有多个属性时,就可以将它的安全上下文和一些属性关联起来。命令:“typeattribute type_id atrribute_id”。

2.3、transition(切换)

2.3.1、为什么会有这个

背景:在linux中,子进程会默认继承父进程的属性,子目录会默认继承父目录的属性。

问题:基于以上事实,在某些场景直接继承父对象的属性明显是不合适的,容易造成权限过大、不安全。

为了让新起的进程或新建的文件,归属到它应有的那个组或者type,避免子对象继承父对象的属性而造成的不安全,所以就有了切换这种概念。

注意:系统关键进程或长时间运行的进程,必须进行domain切换,比如netd、installd、vold、zygote等。

2.3.2、如何切换

我们知道,安卓系统所有的进程都是由init派生而来的,所以为了让这些子进程不继承init的权限,就需要让他们进行domain切换,domain切换的意思也就是让他们切换到自己的组,这样它们就会使用自己的权限,而非init进程的权限。示例看下图:

可以看到,图中的vecentekseservice是自定义出来的,并且还和domain这个Selinux自己的标准属性关联起来,然后又用init_daemon_domain这个宏函数,将vecentekseservice这个进程从init的子进程的权限切换到了自己的domain。看到这里,可能大家会有疑惑,这个vecentekseservice不是一个自定义的类型嘛?怎么你又在说进程?其实,上图中的代码,都是在vecentekseservice.te这个文件中的。而在Selinux中,定义相关进程的类型和规则,就是在以它的进程名命名的te文件中进行。

2.4、sepolicy

作用:制定规则,确认进程对文件的访问权限,同样在以它的进程名命名的te文件中定义。

2.4.1、分类

AOSP(安卓开源项目)所定义的通用规则:

  • 所在路径为android/system/sepolicy/。里面的public为公共所有,而private对SOC/OEM不可见。
  • 警告:不要试图修改它,否则会违反Goole CTS测试,连编译都通不过。

SOC/OEM所定义的规则目录:

  • 在android/device/softwinner/common/sepolicy/vendor。

2.4.2、该怎么定义sepolicy规则

  • 语法

模板:rule_name source_type target_type:class per_set。

rule,规则,allow, neverallow, allowaudit(record), dontaudit(don't record);

source_type,进程的组,也就是进程的domain(可以通过ps -Z查看);

target_type进程所要操作的文件的type(可以通过ls -Z查看);

class为target_type的类型,system/sepolicy/private/security_classes中有class的定义,常见的有file,dir等;

per_set,定义的权限,常见的有read , write, ioctrol, create, getattr, getattr等。

  • 练习

假如有这么一条报错信息:“avc: denied { call } for pid=2162 comm="AudioOut_D" scontext=u:r:audioserver:s0 tcontext=u:r:hal_audiocontrol_default:s0 tclass=binder permissive=1”。

安卓系统打开了Selinux之后,遇到权限报错,就是上面这样的格式。

我们先来按照语法,来拆分一下上面的log信息。

rule:allow;

source_type:audioserver;

target_type:hal_audiocontrol_default;

class:binder;

per_set:call。

所以,将它们组合起来,一条完整的规则应该是:“allow audioserver hal_audiocontrol_default:binder call”。

然后我们通过ps -Z找到“audioserver”所属的进程,将这条规则添加到以它进程名命名的te文件中去。

三、sepolicy实战

3.1、两种工作模式

Enforcing:强制模式,遇到权限问题立即返回无法继续执行;

Permissive:宽容模式,遇到权限问题,允许继续执行,并将错误信息打印出来,一般用于调试。

3.2、sepolicy的启动和关闭

3.2.1、编译阶段

在编译阶段启动或者关闭sepolicy,属于静态配置。

要想在编译阶段启动sepolicy,需要在config文件中这么配置:“CONFIG_SECURITY_SELINUX=y”;并且在BoardConfig.mk文件中配置:“BOARD_KERNEL_CMDLINE := androidboot.selinux=permissive”。

3.2.2、启动过程中

在启动过程中启动或者关闭sepolicy,属于动态配置。

要想在这个时候启动或者关闭sepolicy,如果不想重新编译,可以直接修改cmdline。方法:fastboot boot.img -c androidboot.selinux=permissive。

3.2.3、启动完成后

在启动完成后启用或者关闭sepolicy,也属于动态配置。

setenforce [Enforcing|Permissive|1|0]设置模式。

getenforce:查看模式。

注意:此方法会在重启后失效。

3.3、如何判断是否是selinux引起的问题

设置为Permissive模式,如果问题依旧存在则说明是其他问题引起的,如果问题不存在,那就说明是selinux引起的。

3.4、如何抓取selinux的log

如果是安卓系统,我这里一般是使用“logcat | grep avc”。

3.5、如何修改

Selinux导致的问题,十有八九是权限问题,所以,只要你将权限被限制的文件或者进程的权限解开即可。这里,有两种组合规则的方式:

自动:audit2allow -i deny.log。

手动:根据错误日志拆分关键字,然后组合成规则。

这里建议手动解决好一些。

3.5.1、常见问题的处理原则

不要试图修改谷歌定义的规则;

如果遇到跟goole规则冲突的地方,只能选择绕道或者修改设计。

3.5.2、常见问题的问题类型

  • 权限被拒绝,解决方法:补齐该权限。
  • 跟AOSP的规则冲突,解决方法:修改进程的domain、修改文件的type、修改设计。

3.5.3、常见问题之新增设备

为新增设备定义具体的标签类型;

为进程添加访问该文件的权限。

3.5.4、新增进程或服务

新建te文件,以进程或服务名字来命名,并将te文件加入编译系统;定义新的domain/type,若有必要需添加transition,增加selinux规则;新建安全上下文,将文件打上标签。

Logo

更多推荐