1. 概述
官方文档:
https://source.android.com/docs/security/features/selinux
https://source.android.com/docs/security/features/selinux/images/SELinux_Treble.pdf
Your visual how-to guide for SELinux policy enforcement | Opensource.com
SeLinux(Security-Enhanced Linux)是一个标签系统(labeling system)。每个进程都有一个label(称为process label),每个文件系统所涵盖的文件/目录、网络端口、设备等对象也有一个lable(称为Object label)。SeLinux通过编写规则来控制一个process label对一个Object label的访问,这个规则称之为策略(Policy)。SeLinux的这种安全机制称为Mandatory Access Control (MAC),是在内核层实现的。
在标准的Linux上,也就是未实施SeLinux的Linux上,传统的访问控制是通过owner/group + permission flags(比如rwx)实现的,称之为Discretionary Access Control (DAC).
SeLinux和传统的DAC不是替代的关系,而是并行的关系。两者同时在起作用。之所以出现SeLinux,是因为传统DAC的安全机制过于粗粝,而Selinux提供了更为细致和安全的访问控制。简言之,传统DAC机制下,一旦你获得了root权限,将无所不能,但在SeLinux的机制下,即使获得了root权限,也仍然需要遵循已经设置好的访问策略,只有指定的进程才可以访问指定的文件。
SeLinux有两种运行模式:
- Permissive mode:当访问并未授权时,不会阻止访问,但会打印log
- Enforcing mode:当访问未被授权时,会阻止访问,并且会打印log
log将出现在dmesg和logcat。
除了可以对整体进行模式设置,也可以针对某个进程单独设置某个模式,除此之外的进程使用全局模式。
设计
SeLinux在Android 4~7和Android 8以后采用了不同的设计
Android 4~7上,SeLinux的策略是作为一个整体来编译和更新的;
Android 8及以后,SeLinux采用了模块化、动态化设计,Platform(可以理解为AOSP的部分)、Non-Platform(vendor、odm、oem的部分,这里总体称为vendor部分)的SeLinux策略分别独立编译、升级。
附一张Android设备的架构图:
编译后会生成对应的img文件
● system.img. Contains mainly Android framework.
● boot.img. (kernel/ramdisk) Contains Linux kernel + Android patches.
● vendor.img. Contains SoC-specific code and configurations.
● odm.img. Contains device-specific code and configurations.
● oem.img. Contains OEM/carrier-related configurations and customizations.
● bootloader. Brings up the kernel (vendor-proprietary).
● radio. Modem (proprietary).
Android 8以后,SeLinux的策略文件可以伴随相应的img独立编译或者OTA。
2. 概念
什么是标签(Label)?怎么基于Label对访问进行控制?
先抛开Label这个概念不说。所谓SeLinux里的访问控制,就是判定一个Source有没有权限去访问Target。这里的Source一般就是进程,Target最长见的就是文件系统(比如文件、目录、socket、设备等等),当然还有其他类型的Target。换句话说,SeLinux的机制就是通过读取一个“规则”,来控制一个进程有没有权限去访问一个文件(或其他类型)。
上面说的“规则”,在SeLinux里的术语叫做Policy(策略)或叫Access Vector Rule。是可以由AOSP和厂商、供应商来编写的。
上面的Source,还叫做Subject(主体)
上面的Target,还叫做Object(对象或客体)
Label、Source、Target、Subject、Object,这些都不重要, 在实际语法中并没有相关关键词,只要各种资料里出现这些词的时候知道其所指就可以。
2.1 规则Policy Rule(或叫Access Vector Rule)
策略规则的语法为:
allow source target:class permissions;
- Source - The type (or attribute) of the subject of the rule. Who is requesting the access?(是谁在请求访问一个资源)
- Target - The type (or attribute) of the object. To what is the access requested?(被访问的对象)
- Class - The kind of object (e.g. file, socket) being accessed.(被访问者的类型)
- Permissions - The operation (or set of operations) (e.g. read, write) being performed.(对Target具体要做什么操作?比如对被访问者是文件来说,是要读、写它还是其他操作?)
具体例子如下:
allow sample_app app_data_file:file { read write };
这个例子是说,允许sample_app这个进程去访问app_data_file(它是一个file类型,也就是文件),允许的操作是read和write。
而其实这里的sample_app并不是一个真正的具体的进程名,而是在系统编译阶段就定义好的一个标签(Label),一些真正的进程被映射到sample_app这个标签上,那么在执行上面规则的时候,其实生效的、有权限访问app_data_file的是所有映射了sample_app标签的那些进程。同样的,app_data_file也不是一个具体的文件名。它也是一个提前声明了的标签,一些真正的文件被映射到这个标签上,sample_app有权访问的是所映射的这些文件。
从这里看出来,有别于传统DAC的Owner、Group、Permissions 的控制方式,所谓的“基于标签系统”的SeLinux,就是这种通过声明标签的方式来表述访问规则的。
标签只是一种概念性的东西,具体体现在策略文件里,则是抽象成了Type、Attribute、Class、Permissions这些具体关键字。
2.2 规则里的关键字说明
以下面这个规则举例
allow sample_app app_data_file:file { read write };
Rule Name
上面规则示例中,allow就是Rule Name的一种。SeLinux有多种RuleName:
- allow:表示允许Source对Target执行许可的操作
- neverallow:表示不允许Source对Target执行制定的操作
- auditallow:表示允许操作并记录访问决策信息
- dontaudit:表示不记录违反规则的决策信息,切违反规则不影响运行。
其中allow是用的最多的。
Type
上面的示例中,sample_app、app_data_file都是一个Type。简单理解就是将一个或几个进程声明为Type A, 将一个或多个文件声明为Type B。那么在控制这个进程有没有权限访问这个文件的时候,只用A和B来表示就可以了。
这样做有什么好处?“一类进程”总比具体的“一个进程”要灵活的多。把多个进程声明为同一个Type,那么在写规则的时候只要描述这个Type,那么这个Type对应的所有进程都会生效。文件或其他对象也是同样的。
也就是说,在规则中描述Source能不能访问Target,是通过Type来表述的。
Type是厂商或供应商可以自定义的
Attribute
将多个Type归为一组,就是一个Attribute。
通俗的说,把一些进程声明为Type,但是多个Type有某种共通的特性,就可以把这些Type声明为同一个Attribute。在描述规则的时候,可以将Source或者Target指定为一个Attribute而不是Type,这样所有属于这个Attribute的Type都生效。
AOSP本身内置了一些Attribute,而这些Attribute很多都是约定俗称的固定含义。比如:
domain
所有进程的type必须归属于domain。domain因此成了进程type的常见表述。
file_type
所有文件type都归属于file_type
...
AOSP内置的Attribute见
platform/system/sepolicy/public/attributes
platform/system/sepolicy/private/attributes
platform/system/sepolicy/prebuilts/api/[version]/public/attributes
platform/system/sepolicy/prebuilts/api/[version]/private/attributes
Class
上面示例中,“:file”就是一个Class。简单说就是将某些被访问对象归为一种Class,比如说被访问的是文件,Class一般就是file,如果是目录,Class就是dir,如果是socket,Class就是socket等等。Class是AOSP内定义好的,一般不需要自定义
具体有那些Class,可见源码platform/system/sepolicy/private/security_classes
Class是用来做什么的?其实是与Permissions相关的。
Permissions
即示例中的 { read write }。表示具体可以做什么操作。不同的Class有不同的Permissions集合。罗列一些Class对应的Permission(非完整):
Class | Permission |
file | ioctl read write create getattr setattr lock relabelfrom relabelto append unlink link rename execute swapon quotaon mounton |
directory | add_name remove_name reparent search rmdir open audit_access execmod |
socket | ioctl read write create getattr setattr lock relabelfrom relabelto append bind connect listen accept getopt setopt shutdown recvfrom sendto recv_msg send_msg name_bind |
filesystem | mount remount unmount getattr relabelfrom relabelto transition associate quotamod quotaget |
process | fork transition sigchld sigkill sigstop signull signal ptrace getsched setsched getsession getpgid setpgid getcap setcap share getattr setexec setfscreate noatsecure siginh setrlimit rlimitinh dyntransition setcurrent execmem execstack execheap setkeycreate setsockcreate |
security | compute_av compute_create compute_member check_context load_policy compute_relabel compute_user setenforce setbool setsecparam setcheckreqprot read_policy |
capability | chown dac_override dac_read_search fowner fsetid kill setgid setuid setpcap linux_immutable net_bind_service net_broadcast net_admin net_raw ipc_lock ipc_owner sys_module sys_rawio sys_chroot sys_ptrace sys_pacct sys_admin sys_boot sys_nice sys_resource sys_time sys_tty_config mknod lease audit_write audit_control setfcap |
MORE | AND MORE |
可以从对应的Class中选取任意个Permission
Class与Perimssion的完整映射具体见源码:kernel/arm64/security/selinux/include/classmap.h
示例
以控制狗是否有权吃猫粮狗粮为例。
有一个狗叫小黄, 一种狗粮叫“狗粮A”。
现在声明一个Type叫dog,一个Type叫dog_chow,系统本身内置了Class叫food,food对应的permissions里包含了eat这个权限。
随后将小黄映射到dog这个type,将狗粮A映射到dog_chow这个type
这样,在添加了这样一条规则后,小黄就有权限去吃狗粮A了:
allow dog dog_chow:food eat;
此时如果还有一只狗小白,那么可以将小白映射到dog这个type,这样小白也可以有权限吃狗粮A。
2.3 Context
上面所说的“映射”,即将一个进程关联到一个Type,或者将一个文件关联到一个Type,是通过context来完成的。
进程Context(seapp_contexts)
一个进程的Context条目可能如下:
user=_app isPrivApp=true name=com.android.vzwomatrigger domain=vzwomatrigger_app type=privapp_data_file levelFrom=all
user=_app代表这是一个常规的app;
isPrivApp=true代表这是一个预置app;
name=com.android.vzwomatrigger 指定了进程名
domain=vzwomatrigger_app 将进程名关联到一个type
type=privapp_data_file 这是一个文件type,指定了app的data directory目录所属的type
levelFrom=all MLS/MCS的level相关
AOSP内置进程Context见platform/system/sepolicy/private/seapp_contexts
供应商或厂商要定义自己的seapp_context,将在/vender下相应目录添加
文件Context
一个文件的Context可能这样:
/system/bin/bcc u:object_r:rs_exec:s0
/system/bin/bcc 指的是具体文件
u:object_r:rs_exec:s0是一个security context。其中的rs_exec为type。这样文件和type就进行了关联
Security Context
其格式为:
user:role:type:sensitivity[:categories]
在Android中,
user是固定的,永远为u
role是固定的,访问者(称subject或source)永远为r;被访问者(称object或target)永远为object_r。一般情况下,进程一方就是subject,文件一方就是object,所以一般进程的role
type为与文件关联的type
sensitivity是固定的,永远为s0
categories是Multi-Level Security (MLS) 协议,用来隔离一个app的data,防止被另一个app访问,或者隔离不同用户间对同一个app data的访问。
AOSP内置的文件Context见platform/system/sepolicy/private/file_contexts
seinfo
seapp_context里除了可以将一个具体应用映射到一个domain,也可以将seinf映射到domain。mac_permissions.xml里定义了seinfo。其会根据app所属的signature来为其分配一个seinfo。
比如:
platform/system/sepolicy/private/mac_permissions.xml
<!-- Platform dev key in AOSP -->
<signer signature="@PLATFORM" >
<seinfo value="platform" />
</signer>
<!-- Sdk Sandbox key -->
<signer signature="@SDK_SANDBOX" >
<seinfo value="sdk_sandbox" />
</signer>
<!-- Bluetooth key in AOSP -->
<signer signature="@BLUETOOTH" >
<seinfo value="bluetooth" />
</signer>
<!-- Media key in AOSP -->
<signer signature="@MEDIA" >
<seinfo value="media" />
</signer>
<signer signature="@NETWORK_STACK" >
<seinfo value="network_stack" />
</signer>
也就是说所有拥有PLATFORM signature的app,将拥有platform这个seinfo。在seapp_context中可以如下配置:
user=radio seinfo=platform domain=radio type=radio_data_file
所有拥有platform签名的,并且未配置具体context的app,将遵循其seinfo platform的规则。
untrusted_app
既为配置seinfo,又未配置seapp_context的app,将默认为untrusted_app。各级seapp_context中也有对untrusted_app的权限配置。
查看当前Context
要看进程的Context,使用ps -Z
emu64xa:/ $ ps -AZ | grep google
u:r:hal_camera_default:s0 system 362 1 10891800 3272 0 0 S android.hardware.camera.provider@2.7-service-google
u:r:permissioncontroller_app:s0:c179,c256,c512,c768 u0_a179 976 354 13918328 83660 0 0 S com.google.android.permissioncontroller
u:r:bluetooth:s0 bluetooth 1033 354 14050488 73628 0 0 S com.google.android.bluetooth
u:r:priv_app:s0:c512,c768 u0_a167 1090 354 14016372 119796 0 0 S com.google.android.apps.nexuslauncher
u:r:priv_app:s0:c512,c768 u0_a170 1157 354 13873728 68724 0 0 S com.google.android.ext.services
u:r:untrusted_app_32:s0:c142,c256,c512,c768 u0_a142 1393 354 14070168 104520 0 0 S com.google.android.inputmethod.latin
查看文件的Context,使用
emu64xa:/ $ ls -Z
u:object_r:cgroup:s0 acct u:object_r:tmpfs:s0 debug_ramdisk u:object_r:vendor_file:s0 odm u:object_r:sysfs:s0 sys
u:object_r:apex_mnt_dir:s0 apex u:object_r:device:s0 dev u:object_r:vendor_file:s0 odm_dlkm u:object_r:system_file:s0 system
u:object_r:rootfs:s0 bin u:object_r:rootfs:s0 etc u:object_r:oemfs:s0 oem ?
...
3. SeLinux的配置
参见https://android.googlesource.com/platform/system/sepolicy/
3.1 SeLinux文件体系
之前提到Android架构中大致包含AOSP、厂商、Vendor等部分。在Android 8以上的系统中,AOSP和厂商、供应商的部分是独立配置的,方便OTA更新。
针对这种架构,SeLinux的Policy文件体系包含以下目录:
system/sepolicy/public:这部分是AOSP公开给vender使用的,作为其基础api。比如声明了domain的attribute就在这下面:platform/system/sepolicy/public/attributes。这部分Policy需要做兼容处理,因为Vender引用了这里policy,如果OTA单独升级了AOSP,需要做向后兼容处理。详见https://source.android.com/docs/security/features/selinux/compatibility
system/sepolicy/private:AOSP内部使用的,即system image内部用的policy。vender不应该感知到这部分policy
system/sepolicy/vendor:vender部分的policy
device/manufacturer/device-name/sepolicy:特定设备的policy,比如三星设备:device/samsung/tuna/sepolicy
BoardConfig.mk makefile
AOSP的SeLinux Policy文件一般不需要更改,或少量更改。厂商侧主要修改和定制的Policy在/device/manufacturer/device-name/下,/device/manufacturer/device-name/BoardConfig.mk用于指定Policy文件的具体路径,通过BOARD_VENDOR_SEPOLICY_DIRS,比如:
device/samsung/tuna/BoardConfig.mk
BOARD_VENDOR_SEPOLICY_DIRS += device/samsung/tuna/sepolicy
system_ext 和 product分区
在Android 11及以上系统中,system_ext 和 product分区也独立出单独的policy,并且区分了public和private。public部分同样共vender引用。system_ext和product的版本兼容处理由各partner自己负责,AOSP不负责。
SYSTEM_EXT_PUBLIC_SEPOLICY_DIRS:指定system_ext的public的目录,安装到system_ext分区。
SYSTEM_EXT_PRIVATE_SEPOLICY_DIRS:指定system_ext的private目录。system_ext内部使用。安装到system_ext分区
PRODUCT_PUBLIC_SEPOLICY_DIRS:指定product分区的public目录。安装到product分区
PRODUCT_PRIVATE_SEPOLICY_DIRS:指定product的private目录。安装到product分区。
3.2 Policy文件
把SeLinux策略配置相关的文件统称为Policy文件
Policy文件一般包含:
TE文件
就是一堆.te文件。TE里定义了Type、访问规则等。对进程和文件Type的定义、访问规则的定制就只在TE里完成的。一般每个进程有一个独立的te文件,而所有文件的te统一声明在一个file.te文件中。详见3.3节
Context files
file_contexts:前面说过,file_contexts里面定义了文件的context,用于将文件目录和文件的type关联起来。
genfs_contexts:用于将文件系统(比如/proc和vfat)与type关联起来。
property_contexts:用于将Android系统properties与type关联起来
service_contexts:用于将service进程与type关联
seapp_contexts:用于将app与type关联
mac_permissions.xml:根据app的signature或包名,为app分配一个seinfo。seinfo用于在app没有明确关联一个type时归属一个默认type。
keystore2_key_contexts :为Keystore 2.0 namespaces分配一个label。
Attribute
用来声明Attribute
security_classes
用来声明Class
3.3 TE(Type Enforcement)文件
所有的规则配置,或称Policy,都写在.te文件中。编写完TE文件后,将TE文件放在对应目录下,Android系统编译后.te文件将被编译成.cil文件。在init进程启动阶段,会将.cil文件汇总统一编译成一个命名为policy的文件。cil文件是可读的,policy文件是二进制的。在系统运行时最终加载使用的是policy文件。
policy文件一般在/sys/fs/selinux/policy
一般一个进程单独声明一个TE文件。比如一个dhcp进程单独有一个te文件叫dhcp.te。而文件的te统一整合在file.te(比如platform/system/sepolicy/public/file.te)中。
针对Platform(AOSP的部分)、Non-Platform(厂商、供应商的部分),TE会有不同的放置目录。
下面是TE文件的一个例子:
platform/system/sepolicy/public/dhcp.te
type dhcp, domain;
permissive dhcp;
type dhcp_exec, exec_type, file_type;
type dhcp_data_file, file_type, data_file_type;
init_daemon_domain(dhcp)
net_domain(dhcp)
allow dhcp self:capability { setgid setuid net_admin net_raw net_bind_service
};
allow dhcp self:packet_socket create_socket_perms;
allow dhcp self:netlink_route_socket { create_socket_perms nlmsg_write };
allow dhcp shell_exec:file rx_file_perms;
allow dhcp system_file:file rx_file_perms;
# For /proc/sys/net/ipv4/conf/*/promote_secondaries
allow dhcp proc_net:file write;
allow dhcp system_prop:property_service set ;
unix_socket_connect(dhcp, property, init)
type_transition dhcp system_data_file:{ dir file } dhcp_data_file;
allow dhcp dhcp_data_file:dir create_dir_perms;
allow dhcp dhcp_data_file:file create_file_perms;
allow dhcp netd:fd use;
allow dhcp netd:fifo_file rw_file_perms;
allow dhcp netd:{ dgram_socket_class_set unix_stream_socket } { read write };
allow dhcp netd:{ netlink_kobject_uevent_socket netlink_route_socket
netlink_nflog_socket } { read write };
TE文件语法解析
type dhcp, domain; -- 声明一个type名为dhcp,其继承domain这个Attribute,也就是说在声明时便继承了domain所拥有的权限。domain属性是进程专用的,很显然这是一个进程的te文件。
permissive dhcp; -- 将dhcp标识为permissive模式。之前说到,SeLinux开启模式可以全局设置,也可以针对进程单独设置。这里是单独设置该进程的模式。
type dhcp_exec, exec_type, file_type; -- 声明一个type名为dhcp_exec, 从属于exec_type和file_type两个Attribute。也就是说同时继承两个Attribute所代表的文件。exec_type用于代表可执行文件,也就是进程的可执行文件入口;file_type代表通用文件。从这里我们知道dhcp_exec代表dhcp可执行文件的入口。
init_daemon_domain(dhcp) -- 这是一个宏,代表这是一个从init启动的进程,并且可以同init通信。宏的源码见platform/system/sepolicy/public/te_macros
net_domain -- 也是一个宏,可以进行通用的网络操作,比如读写TCP包,操作socket等。
allow dhcp self:capability { setgid setuid net_admin net_raw net_bind_service}; -- 这个就是具体的规则描述了。dhcp是source方, self是target方,capability是一个Class名,其后是可以做的操作。这句话是说允许dhcp这个进程对自己做setgid等操作。self关键字用来表示source可以对自己做什么操作。
除了上面例子里出现的关键字,有时我们还会看到typeattribute这个关键字:它代表将之前已经声明过的Type关联到一个之前声明过的Attribute。
更多语法见TypeStatements - SELinux Wiki
4. SeLinux的编译
4.1 编译SeLinux Policy
Android 8以上编译过程中将把/system 和 /vendor下的policy合并。这个逻辑体现在/platform/system/sepolicy/Android.mk。
具体policy位置:
Location | Contains |
system/sepolicy/public | The platform's sepolicy API |
system/sepolicy/private | Platform implementation details (vendors can ignore) |
system/sepolicy/vendor | Policy and context files that vendors can use (vendors can ignore if desired) |
BOARD_SEPOLICY_DIRS | Vendor sepolicy |
BOARD_ODM_SEPOLICY_DIRS (Android 9 and higher) | Odm sepolicy |
SYSTEM_EXT_PUBLIC_SEPOLICY_DIRS (Android 11 and higher) | System_ext's sepolicy API |
SYSTEM_EXT_PRIVATE_SEPOLICY_DIRS (Android 11 and higher) | System_ext implementation details (vendors can ignore) |
PRODUCT_PUBLIC_SEPOLICY_DIRS (Android 11 and higher) | Product's sepolicy API |
PRODUCT_PRIVATE_SEPOLICY_DIRS (Android 11 and higher) | Product implementation details (vendors can ignore) |
编译过程:
- 将Policy转化成CIL( Common Intermediate Language )文件,包括
- system + system_ext + product的public部分的policy
- 合并private + public policy
- 合并public + vendor and BOARD_SEPOLICY_DIRS policy
- 将public部分的policy进行整理成对应版本policy,作为vendor policy的一部分。然后将public的policy传给合并后的public + vendor and BOARD_SEPOLICY_DIRS policy,告知其哪部分可以连接到platform的attribute
- 创建一个mapping文件,连接platform和vendor的部分。
- 合并所有的policy文件,整合mapping、platform和vender的policy,最终输出一个二进制的名为policy的文件。一般这个文件的位置在/sys/fs/sepolicy/policy。这个policy最终会被kernel加载。这个工作是在init进程初始化时进行的。也就是说是在运行时进行的。
4.2 预编译
在SeLinux正式开启之前,init进程会收集所有的cil文件,将其编译成policy,这个过程需要花费1-2秒时间。为了更快完成此过程,会将CIL在编一阶段预编译,放在/vendor/etc/selinux/precompiled_sepolicy和/odm/etc/selinux/precompiled_sepolicy下,并且附有sha256 摘要。运行时会检查sha256是否有变化,如果没有变化就直接使用预编译的文件。