转自:https://blog.csdn.net/ch853199769/article/details/82501078
https://blog.csdn.net/su749520/article/details/82800050
引言
本文是对SEAndroid方面知识拾人牙慧后的一些总结。
SEAndroid是一套以SeLinux为核心的系统安全机制。
SELinux是一种基于域-类型(domain-type)模型的强制访问控制(MAC)安全系统,其原则是任何进程想在SELinux系统中干任何事,都必须先在安全策略的配置文件中赋予权限。凡是没有在安全策略中配置的权限,进程就没有该项操作的权限。在SELinux出现之前,Linux的安全模型是DAC(DiscretionaryAccess Control),译为自主访问控制。其核心思想是进程理论上所拥有的权限与运行它的用户权限相同。比如,以root用户启动shell,那么shell就有root用户的权限,在Linux系统上能干任何事。这种管理显然比较松散。
在SELinux中,如果需要访问资源,系统会先进行DAC检查,不通过则访问失败,然后再进行MAC权限检查。
SEAndroid框架
回到SEAndroid,SEAndroid的框架图如下:
主要分为两部分:用户空间和内核空间,两者以SELinux文件系统的接口为界。libselinux中封装了访问Security Context、加载资源安全策略和访问SELinux内核文件的接口。
先来看内核空间,在内核空间中,存在一个SELinux LSM模块,这个模块包含有一个访问向量缓冲(Access Vector Cache)和一个安全服务(Security Server)。Security Server负责安全访问控制逻辑,即由它来决定一个主体访问一个客体是否是合法的。这里说的主体一般就是指进程,而客体就是主体要访问的资源,例如文件。
在实际系统中,以/sys/fs/selinux为安装点,安装一个类型为selinuxfs的文件系统,也就是SELinux文件系统,用来与内核空间的SELinux LSM模块通信。
LSM,全称是Linux Security Model。LSM可以说是为了SELinux而设计的,但是它是一个通用的安全模块,SELinux可以使用,其它的模块也同样可以使用。这体现了Linux内核模块的一个重要设计思想,只提供机制实现而不提供策略实现。在我们这个例子中,LSM实现的就是MAC机制,而SELinux就是在这套机制下的一个策略实现。也就是说,你也可以通过LSM来实现自己的一套MAC安全机制。
SELinux、LSM和内核中的子系统是如何交互的呢?首先,SELinux会在LSM中注册相应的回调函数。其次,LSM会在相应的内核对象子系统中会加入一些Hook代码。例如,我们调用系统接口read函数来读取一个文件的时候,就会进入到内核的文件子系统中。在文件子系统中负责读取文件函数vfs_read就会调用LSM加入的Hook代码。这些Hook代码就会调用之前SELinux注册进来的回调函数,以便后者可以进行安全检查。
SELinux在进行安全检查的时候,首先是看一下自己的Access Vector Cache是否已经有缓存。如果有的话,就直接将结果返回给相应的内核子系统就可以了。如果没有的话,就需要到Security Server中去进行检查。检查出来的结果在返回给相应的内核子系统的同时,也会保存在自己的Access Vector Cache中,以便下次可以快速地得到检查结果。
流程图如下:
允许访 |
|
|
|
|
|
|
|
|
|
从图中可以看到,内核中的资源在访问的过程中,一般需要获得三次检查通过:
1. 一般性错误检查,例如访问的对象是否存在、访问参数是否正确等。
2. DAC检查,即基于Linux UID/GID的安全检查。
3. SELinux检查,即基于安全上下文和安全策略的安全检查。
再来看用户空间,分三部分:Security Context、Security Server、SEAndroid Policy。
Security Context里保存着资源的安全上下文,整套SEAndroid系统就是基于这些安全上下文实现的。
Security Server由应用程序安装服务PackageManagerService、应用程序安装守护进程installd、应用程序进程孵化器Zygote进程以及init进程组成。其中,PackageManagerService和installd负责创建App数据目录的安全上下文,Zygote进程负责创建App进程的安全上下文,而init进程负责控制系统属性的安全访问。它有三个任务:1、在开机时将资源安全访问策略SEAndroid Policy加载进内核空间;2、去Security Context中查找安全上下文;3、获取内核空间中安全上下文对应的资源访问权限。
守护进程installd负责创建App数据目录。在创建App数据目录的时候,需要给它设置安全上下文,使得SEAndroid安全机制可以对它进行安全访问控制。Installd根据PackageManagerService传递过来的seinfo,并且调用libselinux库提供的selabel_lookup函数到前面我们分析的seapp_contexts文件中查找到对应的Type。有了这个Type之后,installd就可以给正在安装的App的数据目录设置安全上下文了,这是通过调用libselinux库提供的lsetfilecon函数来实现的。
在Android系统中,Zygote进程负责创建应用程序进程。应用程序进程是SEAndroid安全机制中的主体,因此它们也需要设置安全上下文,这是由Zygote进程来设置的。组件管理服务ActivityManagerService在请求Zygote进程创建应用程序进程之前,会到PackageManagerService中去查询对应的seinfo,并且将这个seinfo传递到Zygote进程。于是,Zygote进程在fork一个应用程序进程之后,就会使用ActivityManagerService传递过来的seinfo,并且调用libselinux库提供的selabel_lookup函数到前面我们分析的seapp_contexts文件中查找到对应的Domain。有了这个Domain之后,Zygote进程就可以给刚才创建的应用程序进程设置安全上下文了,这是通过调用libselinux库提供的lsetcon函数来实现的。
在Android系统中,属性也是一项需要保护的资源。Init进程在启动的时候,会创建一块内存区域来维护系统中的属性,接着还会创建一个Property服务。这个Property服务通过socket提供接口给其它进程访问Android系统中的属性。其它进程通过socket来和Property服务通信时,Property服务可以获得它的安全上下文。有了这个安全上下文之后,Property服务就可以通过libselinux库提供的selabel_lookup函数到前面我们分析的property_contexts去查找要访问的属性的安全上下文了。有了这两个安全上下文之后,Property服务就可以决定是否允许一个进程访问它所指定的属性了。
SEAndroid Policy就是SEAndroid的安全策略,实际是在系统编译时生成的一个sepolicy文件,在init进程中被加载到SELinux内核中。
机制实现
要想理解SEAndroid,就先要了解它的基础——对象。主体通常是进程,是访问者,客体就是指进程被访问的资源,例如文件、系统属性等。
安全上下文实际上是一个附加在对象上的标签(Tag)。这个标签实际上就是一个字符串,它由四部分内容组成,分别是SELinux用户、SELinux角色、类型、安全级别,,每一个部分都通过一个冒号来分隔,格式为“user:role:type:sensitivity”。
例如,在开启了SEAndroid安全机制的设备上执行带-Z和-ef选项的ls命令,就可以看到一个文件的安全上下文:
-rwxr-x---root root u:object_r:rootfs:s0 init.rc
上面的命令列出文件/init.rc的安全上下文为“u:object_r:rootfs:s0”,这表明文件/init.rc的SELinux用户、SELinux角色、类型和安全级别分别为u、object_r、rootfs和s0。
又如,在开启了SEAndroid安全机制的设备上执行带-Z选项的ps命令,就可以看到一个进程的安全上下文:
LABEL USER PID PPID NAME
u:r:init:s0 root 1 0 /init
......
上面的命令列出进程init的安全上下文为“u:r:init:s0”,这表明进程init的SELinux用户、SELinux角色、类型和安全级别分别为u、r、init和s0。
在SEAndroid中,用户、角色和安全级别都分别只有一个,故安全上下文中最重要的是类型这一属性。
对于进程来说,SELinux用户和SELinux角色只是用来限制进程可以标注的类型。而对于文件来说,SELinux用户和SELinux角色就可以完全忽略不计。为了完整地描述一个文件的安全上下文,通常将它的SELinux角色固定为object_r,而将它的SELinux用户设置为创建它的进程的SELinux用户。
在SEAndroid中,只定义了一个SELinux用户u,因此我们通过ps -Z和ls -Z命令看到的所有的进程和文件的安全上下文中的SELinux用户都为u。同时,SEAndroid也只定义了一个SELinux角色r,因此,我们通过ps -Z命令看到的所有进程的安全上下文中的SELinux角色都为r。SELinux的配置文件有:
device/ mediate/common/BoardConfig.mk->TE环境的一些配置,如te文件的路径
system/sepolicy/public/attributes-> 所有定义的attributes都在这个文件
system/sepolicy/private/access_vectors-> 对应了每一个class可以被允许执行的命令
system/sepolicy/public/roles-> Android中只定义了一个role,名字就是r,并将r和attribute domain关联起来
system/sepolicy/private/users-> 其实是将user与roles进行了关联,设置了user的安全级别,s0为最低级是默认的级别,s15是最高的级别
system/sepolicy/private/security_classes->这个class的内容是指在android运行过程中,程序或者系统可能用到的操作的资源类型,它们在*.te文件中会用到。
system/sepolicy/public/te_macros-> 系统定义的宏全在te_macros文件
system/sepolicy/下public目录和private目录、device/sepolicy/mediate/,以及device/sepolicy/mediate/[platformcode]中的te文件 -> 一些配置的文件,包含了各种运行的规则
以上文件路径均是在MTK平台的Android 8.0项目中。以上配置文件,user和roles都比较好理解,BoardConfig.mk配置了te文件的路径:
BOARD_SEPOLICY_DIRS:= \
device/mediatek/sepolicy/basic/non_plat\
device/mediatek/sepolicy/bsp/non_plat \
device/mediatek/sepolicy/full/non_plat
BOARD_PLAT_PUBLIC_SEPOLICY_DIR:= \
device/mediatek/sepolicy/basic/plat_public \
device/mediatek/sepolicy/bsp/plat_public \
device/mediatek/sepolicy/full/plat_public
BOARD_PLAT_PRIVATE_SEPOLICY_DIR:= \
device/mediatek/sepolicy/basic/plat_private \
device/mediatek/sepolicy/bsp/plat_private \
device/mediatek/sepolicy/full/plat_private
BOARD_PREBUILTS_FULL_PUBLIC_PLAT_DIRS:= \
device/mediatek/sepolicy/basic/prebuilts/api/26.0/plat_public \
device/mediatek/sepolicy/bsp/prebuilts/api/26.0/plat_public \
device/mediatek/sepolicy/full/prebuilts/api/26.0/plat_public
BOARD_PREBUILTS_FULL_PRIVATE_PLAT_DIRS:= \
device/mediatek/sepolicy/basic/prebuilts/api/26.0/plat_private \
device/mediatek/sepolicy/bsp/prebuilts/api/26.0/plat_private \
device/mediatek/sepolicy/full/prebuilts/api/26.0/plat_private
BOARD_COMPAT_MAPPING_CIL_DIRS:= \
device/mediatek/sepolicy/full/private/compat/26.0/26.0.cil
BOARD_COMPAT_MAPPING_IGNORE_CIL_DIRS:= \
device/mediatek/sepolicy/full/private/compat/26.0/26.0.ignore.cil
BOARD_26.0_NONPLAT_FILE:= \
device/mediatek/sepolicy/full/prebuilts/api/26.0/nonplat_sepolicy.cil
对应路径下就是各种te文件,当然,BoardConfig.mk文件不止配置了SEAndroid。经试验,系统服务的安全上下文声明要放在BOARD_PLAT_PRIVATE_SEPOLICY_DIR中的目录下,否则不生效,SEAndroid编译生成的文件在手机系统中的system/etc/selinux目录下。
在SEAndroid中,所有的东西都被抽象成类型。进程,抽象成类型;资源,抽象成类型。属性,是类型的集合。所以,TE规则中的最小单位就是类型。一般来说,资源的类型,都定义在了security_classes文件中,句式如下:
class XXX;
type则可由开发者自己定义,可定义在一些te文件中,句式如下:
type xxx
类型又可集合成属性,属性的出现简化了TE规则的配置,例如,如果要配置m个进程对同一组资源(n个)的访问权限,没有属性的话需要一个一个去设置,要m*n行代码,而设置这n个资源为同一个属性的话,只需要m+1行代码,声明X类型有Y和Z属性句式如下:
type X,,Y,Z;
在主体对客体,又有不同的操作类型,如读、写和创建等,这些操作类型定义在access_vectors中,如:
common file
{
ioctl
read
write
create
getattr
setattr
lock
relabelfrom
relabelto
append
map
unlink
link
rename
execute
quotaon
mounton
}
以上的配置文件都是为了服务于te文件中的规则。te文件中除了声明类型,并关联属性之外,主要就是声明一些类型的安全权限。举个例子:
allow zygote{appdomain system_app}:process { getpgid setpgid };
允许zygote类型的进程对appdomain和system_app类型的进程执行getpgid和setpgid操作;
rule_name:allow;
soruce_type:zygote;
target_type:appdomain,system_app;
object(security)class:process;
accessvector:getpgid setpgid;
“{}”可以用以表示一组type或操作集,简化了te语句的书写。除了“{}”还有其他的语法,如“~getpgid”表示process相关操作除了getpgid的操作集;“file_type -system_file”表示拥有file_type属性中除了system_file的类型集;“*”表示所有内容。
之前的例子中还要说明一下rule_name,它不止有一个allow(赋予权限),还有:
neverallow:检查安全策略文件中是否有违反该项操作的allow语句,用来阻止某些操作,allow和neverallowe不可冲突,否则会编译报错,且不可随意更改,否则可能导致CTS测试失败;
allowaudit:audit含义就是记录某项操作,默认情况下SELinux只记录那些权限检查失败的操作。allowaudit则使得权限检查成功的操作也被记录。注意:allowaudit只是允许记录,它和赋予权限没有关系。赋予权限必须且只能使用allow语句。
dontaudit:对那些权限检查失败的操作不做记录;
看到这里,思考一下,绝大多数文件总会归属于某一个目录,即使文件数量庞大,只有声明了目录的权限即可,那进程呢?Android系统中的重要固有进程应该都已经设置了权限,那么应用呢?原生的系统并不知道会有什么其他应用会运行在系统中,那怎么定义这些应用进程的权限呢?
答案是根据它们的特点归类。这个归类方案定义在seapp_context中:
isSystemServer=truedomain=system_server
user=systemseinfo=platform domain=system_app type=system_app_data_file
user=bluetoothseinfo=platform domain=bluetooth type=bluetooth_data_file
user=nfcseinfo=platform domain=nfc type=nfc_data_file
user=radioseinfo=platform domain=radio type=radio_data_file
user=shared_relrodomain=shared_relro
user=shellseinfo=platform domain=shell type=shell_data_file
user=_isolateddomain=isolated_app levelFrom=user
user=_appseinfo=media domain=mediaprovider name=android.process.media type=app_data_filelevelFrom=user
user=_appseinfo=platform domain=platform_app type=app_data_file levelFrom=user
user=_appisV2App=true isEphemeralApp=true domain=ephemeral_app type=app_data_filelevelFrom=user
user=_appisPrivApp=true domain=priv_app type=app_data_file levelFrom=user
user=_appminTargetSdkVersion=26 domain=untrusted_app type=app_data_file levelFrom=user
user=_appdomain=untrusted_app_25 type=app_data_file levelFrom=user
如这一行:
user=_appseinfo=platform domain=platform_app type=app_data_file levelFrom=user
意思是seinfo为platform的app的属性为platform_app,其进程的权限与platform_app相同(权限定义在platform_app.te中),产生的文件类型为app_data_file,其安全权限与app_data_file相同。Seinfo的定义在mac_permissions.xml中,主要是根据app签名区分。
还有一些语法规则如type_transition(声明主体新建某些进程或文件时,这些进程或文件会转变为另一个类型,不与主体相同)、type_change、alias等,使用不多。
了解了SEAnroid的基本原理,接下来看一下如何定制符合项目需求的安全策略,想象一下该情景,某个进程需要访问某个目录,进行操作,发现操作失败,输出log如下:
type=1400audit(1882976.149:5): avc: denied { write } for pid=3194comm="BluetoothAdapte"name="aplog" dev="mmcblk0p22" ino=88 scontext=u:r:bluetooth:s0tcontext=u:object_r:system_data_file:s0tclass=dir
这句log是违反SEAndroid MAC访问策略的一个访问记录。scontext表示进程的SContext,u:r:bluetooth:s0,属于bluetooth域;
tcontext表示目标的SContext,u:r:system_data_file:s0,属于system_data_file类型;
tclass表示进程要操作的ObjectClass,dir表示目录;
mmcblk0p22是userdata分区,write表示写操作。
连起来就是bluetooth域的进程(BluetoothAdapte),对system_data_file类型的dir执行write操作失败。明确了失败原因,我们就可以在安全策略配置文件中定制我们自己的策略了:
在bluetooth.te文件中
allow bluetooth system_data_file:dirw_dir_perms;
w_dir_perms是一个宏,其定义在global_macros中,包含了write相关操作:
define(`w_dir_perms', `{ open search write add_nameremove_name }')
在项目开发中,我们在/dev目录下建立了一个新的设备文件tfa98xx,这是一个音频相关的设备文件,但是在集成framework层的代码后,总是出现下面的访问错误,应该如何处理呢?
type=1400 audit(3635791.670:21): avc: denied{ read write } for pid=273 comm="mediaserver"name="tfa98xx" dev="tmpfs" ino=9770 scontext=u:r:mediaserver:s0tcontext=u:object_r:device:s0 tclass=chr_file
首先,我们先看一下访问失败的原因:从log看,应该是mediaserver域的进程没有权限读写device类型的字符设备文件。那么我们能不能在mediaserver.te中加入访问权限呢?
在domain.te中有如下定义:
[external/sepolicy/domain.te]
neverallow { domain -unconfineddomain -ueventd} device:chr_file { open read write };
也就是说除了unconfineddomain和uevented域外,所有在domain域中的进程都不能对device类型的字符设备文件执行open,read,write操作。
mediaserver也属于domain域,所以肯定不能通过添加策略来设置访问权限,怎么办呢?
在mediaserver.te中,我们发现mediaserver域是可以对audio_device类型的字符设备执行读写的:
allow mediaserver audio_device:chr_filerw_file_perms;
那么,能不能通过打标签的方法,把/dev/tfa98xx设置为audio_device类型呢?答案是肯定的。
在file_context文件中设置/dev/tfa98xx的安全属性,问题解决了:
/dev/tfa9890 u:object_r:audio_device:s0