在Java层和native守护进程socket通信 一文中,新建了一个名为myguard的socket。然后修改了5个权限相关的文件,
1,在device/qcom/sepolicy/common/ 路径下添加myguard.te文件,内容如下,
#myguard
allow myguard_socket myguard_socket:sock_file create_file_perms;
type_transition myguard_socket socket_device:sock_file myguard_socket;
2,在device/qcom/sepolicy/common/的file_contexts文件中添加
/dev/socket/myguard u:object_r:myguard_socket:s0
3,在device/qcom/sepolicy/common/file.te中添加
type myguard_socket, file_type;
4,在device/qcom/sepolicy/common/system_app.te 中添加
allow system_app myguard_socket:sock_file rw_file_perms;
5,在device/qcom/sepolicy/common/system_server.te中添加
allow system_server myguard_socket:sock_file rw_file_perms;
编译刷入版本开机启动之后,在dev/ socket路径下出现myguard
2个进程读写一个socket为什么非要搞得这么艰难?这是为啥呢?
还不是SEAndroid安全机制!
在引进SEAndroid安全机制之前,Android系统的安全机制分为应用程序和内核两个级别。
应用程序级别的安全机制就是通常说的Permission机制。一个应用如果需要访问一些系统敏感或者特权资源,
那么就必须要在AndroidManifest.xml配置文件中进行申请,并且在安装的时候由用户决定是否赋予相应的权限(android 5.0),
或者在apk运行的时候由用户决定是否赋予相应的权限(android 6.0)。
内核级别的安全机制就是传统的Linux UID/GID机制。在Linux中,每一个用户都拥有一个用户ID,并且也有一个用户组ID,
分别简称为UID和GID。此外,Linux系统的进程和文件也有UID和GID的概念。Linux就是通过用户、进程、
文件的UID/GID属性来进行权限管理的。文件的权限控制在所有者的手中。因此,这种权限控制方式就称为自主式的,
正式的英文名称为Discretionary Access Control,简称为DAC。
例如, myguard设置为660(二进制110 110 000) 对应 rw- rw----
对于root用户,该socket可读可写;对于同组的其他用户, 该socket可读可写;对于其他用户, 该socket不可读也不可写。
为什么会有这2个机制呢,因为一些别有用心的为达目的不择手段;为什么还要有SEAndroid安全机制呢?
因为前面2个机制已经无法阻止一些素丧心病狂的开发人员了。
SEAndroid安全机制与传统的Linux UID/GID安全机制是并存关系的,也就是说,它们同时用来约束进程的权限。
当一个进程访问一个文件的时候,首先要通过基于UID/GID的DAC安全检查,接着才有资格进入到基于SEAndroid的MAC安全检查。
只要其中的一个检查不通过,那么进程访问文件的请求就会被拒绝。上述的安全检查过程如图所示:
SEAndroid安全机制的整体框架如下,
以SELinux文件系统接口为边界,SEAndroid安全机制包含有内核空间和用户空间两部分支持。
在内核空间,主要涉及到一个称为SELinux LSM的模块。而在用户空间中,涉及到Security Context、Security Server
和SEAndroid Policy等模块。这些内核空间模块和用户空间模块的作用以及交互如下所示:
1. 内核空间的SELinux LSM模块负责内核资源的安全访问控制。
2. 用户空间的SEAndroid Policy描述的是资源安全访问策略。系统在启动的时候,用户空间的Security Server
需要将这些安全访问策略加载内核空间的SELinux LSM模块中去。这是通过SELinux文件系统接口实现的。
3. 用户空间的Security Context描述的是资源安全上下文。SEAndroid的安全访问策略就是在资源的安全上下文基础上实现的。
4. 用户空间的Security Server一方面需要到用户空间的Security Context去检索对象的安全上下文,另一方面也需要到内核空间去操作对象的安全上下文。
5. 用户空间的selinux库封装了对SELinux文件系统接口的读写操作。用户空间的Security Server访问内核空间的SELinux LSM模块时,
都是间接地通过selinux进行的。这样可以将对SELinux文件系统接口的读写操作封装成更有意义的函数调用。
6. 用户空间的Security Server到用户空间的Security Context去检索对象的安全上下文时,同样也是通过selinux库来进行的。
内核中的资源在访问的过程中,一般需要获得三次检查通过:
1. 一般性错误检查,例如访问的对象是否存在、访问参数是否正确等。
2. DAC检查,即基于Linux UID/GID的安全检查。
3. SELinux检查,即基于安全上下文和安全策略的安全检查。
在用户空间中,SEAndroid包含有三个主要的模块,分别是安全上下文(Security Context)、
安全策略(SEAndroid Policy)和安全服务(Security Server)。
SEAndroid是一种基于安全策略的MAC安全机制。这种安全策略又是建立在对象的安全上下文的基础上的。
这里所说的对象分为两种类型,一种称主体(Subject),一种称为客体(Object)。主体通常就是指进程,
而客观就是指进程所要访问的资源,例如文件、系统属性等。
安全上下文实际上就是一个附加在对象上的标签(Tag)。这个标签实际上就是一个字符串,它由四部
type init, domain, mlstrustedsubject;
分别是SELinux用户、SELinux角色、类型、安全级别,每一个部分都通过一个冒号来分隔,格式为“user:role:type:sensitivity”。
例如,
myguard socket的安全上下文是 u:object_r: myguard_socket:s0
这表明文件myguard的SELinux用户、SELinux角色、类型和安全级别分别为u、object_r、myguard_socket和s0。
其他三个不重要,主要看SELinux类型。
资源的SELinux类型和进程的SELinux类型就像映射关系,当然并不止一一映射。
只有符合资源规定的SELinux类型的进程才可以访问资源。
例如下面这段话的意思是只有系统服务才可以读写myguard_socket类型的文件,即是读写myguard socket。
allow system_server myguard_socket:sock_file rw_file_perms;
在android系统中, 安全上下文的SELinux类型声明一般有2种,描述进程的是domain,描述文件的是file_type。
但是自己写的守护进程myguardservice的SELinux类型怎么是init? myguard socket怎么是myguard_socket类型?
external/sepolicy/init.te文件的init类型定义如下,
type init, domain, mlstrustedsubject;
原来init 还是domain类型的,就像狗是动物一样。
同样的, 在device/qcom/sepolicy/common/file.te中myguard_socket也是file_type类型的。
type myguard_socket, file_type;
类似于继承关系,一方面维护独立性,另一方面又维护统一性。
了解了SEAndroid安全机制的安全上下文之后,就可以继续分析Android系统中的对象的安全上下文是如何定义。
这里只讨论四种类型的对象的安全上下文,分别是系统进程、数据文件、系统文件和系统属性。
这些描述安全上下文的文件一般位于external/sepolicy目录中,当然各大厂商可能不一样。
反正就是一条, 安全上下文的SELinux类型规定了资源文件只能由特定进程访问。
上面分析了SEAndroid安全机制中的对象安全上下文,接下来就继续分析SEAndroid安全机制中的安全策略。
SEAndroid安全机制中的安全策略是在安全上下文的基础上进行描述的,也就是说,它通过主体和客体的安全上下文,定义主体是否有权限访问客体。
在external/sepolicy目录中(或者其他目录),所有以.te为后缀的文件经过编译之后,就会生成一个sepolicy文件。
这个sepolicy文件会打包在ROM中,并且保存在设备上的根目录下,即它在设备上的路径为/sepolicy。
保存在这个sepolicy文件中的安全策略是不会自动加载到内核空间的SELinux LSM模块(内核空间)去的。
它需要在系统启动的过程中进行加载。
系统中第一个启动的进程是init进程。知道,Init进程在启动的过程中,执行了很多的系统初始化工作,
其中就包括加载SEAndroid安全策略的工作,init.cpp文件中的main方法有关selinux代码如下,
selinux_initialize(is_first_stage); //加载selinx策略
selinux_initialize方法如下,
static void selinux_initialize(bool in_kernel_domain) {
Timer t;
selinux_callback cb;
cb.func_log = selinux_klog_callback;
selinux_set_callback(SELINUX_CB_LOG, cb);
cb.func_audit = audit_callback;
selinux_set_callback(SELINUX_CB_AUDIT, cb);
if (selinux_is_disabled()) {
return;
}
if (in_kernel_domain) {
INFO("Loading SELinux policy...\n");
if (selinux_android_load_policy() < 0) {
ERROR("failed to load policy: %s\n", strerror(errno));
security_failure();
}
bool is_enforcing = selinux_is_enforcing();
security_setenforce(is_enforcing);
if (write_file("/sys/fs/selinux/checkreqprot", "0") == -1) {
security_failure();
}
NOTICE("(Initializing SELinux %s took %.2fs.)\n",
is_enforcing ? "enforcing" : "non-enforcing", t.duration());
} else {
selinux_init_all_handles();
}
}
selinux_set_callback设置SEAndroid日志和审计回调方法,
selinux_android_load_policy加载安全策略到内核空间的SELinux LSM模块;
selinux_init_all_handles打开前面分析file_contexts和property_contexts文件,以便可以用来查询系统文件和系统属性的安全上下文。
selinux_android_load_policy方法在libselinux.so库中继续执行。
android.c的selinux_android_load_policy方法如下,
int selinux_android_load_policy(void)
{
const char *mnt = SELINUXMNT;
int rc;
rc = mount(SELINUXFS, mnt, SELINUXFS, 0, NULL);
if (rc < 0) {
if (errno == ENODEV) {
/* SELinux not enabled in kernel */
return -1;
}
if (errno == ENOENT) {
/* Fall back to legacy mountpoint. */
mnt = OLDSELINUXMNT;
rc = mkdir(mnt, 0755);
if (rc == -1 && errno != EEXIST) {
selinux_log(SELINUX_ERROR,"SELinux: Could not mkdir: %s\n",
strerror(errno));
return -1;
}
rc = mount(SELINUXFS, mnt, SELINUXFS, 0, NULL);
}
}
if (rc < 0) {
selinux_log(SELINUX_ERROR,"SELinux: Could not mount selinuxfs: %s\n",
strerror(errno));
return -1;
}
set_selinuxmnt(mnt);
return selinux_android_load_policy_helper(false);
}
SELINUXMNT、OLDSELINUXMNT和SELINUXFS是三个宏,它们定义在文件external/libselinux/src/policy.h文件中,如下所示:
/* Preferred selinuxfs mount point directory paths. */
#define SELINUXMNT "/sys/fs/selinux"
#define OLDSELINUXMNT "/selinux"
/* selinuxfs filesystem type string. */
#define SELINUXFS "selinuxfs"
selinux_android_load_policy方法主要逻辑如下,
A. 以/sys/fs/selinux为安装点,安装一个类型为selinuxfs的文件系统,也就是SELinux文件系统,
用来与内核空间的SELinux LSM模块通信。
B. 如果不能在/sys/fs/selinux这个安装点安装SELinux文件系统,那么再以/selinux为安装点,安装SELinux文件系统。
C. 成功安装SELinux文件系统之后,接下来就调用另外一个函数selinux_android_load_policy_helper来将
SEAndroid安全策略加载到内核空间的SELinux LSM模块中去。
security_load_policy的实现很简单,它首先打开/sys/fs/selinux/load文件,然后将参数data所描述的安全策略
写入到这个文件中去。由于/sys/fs/selinux是由内核空间的SELinux LSM模块导出来的文件系统接口,
因此当将安全策略写入到位于该文件系统中的load文件时,就相当于是将安全策略从用户空间加载到SELinux LSM模块中去了。
以后SELinux LSM模块中的Security Server就可以通过它来进行安全检查。
用户空间的Security Server主要是用来保护用户空间资源的,以及用来操作内核空间对象的安全上下文的,
它由应用程序安装服务PackageManagerService、应用程序安装守护进程installd、应用程序进程孵化器
Zygote进程以及init进程组成。其中,PackageManagerService和installd负责创建App数据目录的安全上下文,
Zygote进程负责创建App进程的安全上下文,而init进程负责控制系统属性的安全访问。