SEAndroid安全机制对Binder IPC的保护分析

时间:2023-03-09 05:41:46
SEAndroid安全机制对Binder IPC的保护分析

在SEAndroid安全机制中,除了文件和属性,还有Binder IPC须要保护。Binder IPC是Android系统的灵魂,使用得相当广泛又频繁。比如,应用程序都是Binder IPC请求訪问系统服务和资源。因此,SEAndroid安全机制必须要为Binder IPC保驾护航,阻止一个进程非法訪问其他进程的服务和资源。本文就具体分析SEAndroid安全机制对Binder IPC提供的支持。

老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!

关于Binder IPC的知识,能够參考Android进程间通信(IPC)机制Binder简要介绍和学习计划这个系列的文章。SEAndroid安全机制对Binder IPC的保护实如今Binder驱动中,如图1所看到的:

SEAndroid安全机制对Binder IPC的保护分析

图1 Binder驱动中的SEAndroid安全检查

从图1能够看到,当Service Manager将自己注冊为Context Manager时,Binder驱动会检查它是否具有设置Context Manager的SEAndroid安全权限。Service Manager将自己注冊为Context Manager的过程,能够參考前面浅谈Service Manager成为Android进程间通信(IPC)机制Binder守护进程之路一文。

此外,当Client通过Binder驱动请求与Server发送通信时,Binder驱动会检查Client是否具有与Server通信的SEAndroid安全权限。假设Client与Server的通信数据带有Binder对象或者文件描写叙述符,那么Binder驱动还会进一步检查源进程是否具有向目标进程传输Binder对象或者文件描写叙述符的SEAndroid权限。Client与Server的通信过程能够參考前面Android系统进程间通信Binder机制在应用程序框架层的Java接口源码分析一文。

以上提到的与Binder IPC相关的SEAndroid安全权限定义在内核的SELinux模块的数组secclass_map中,例如以下所看到的:

struct security_class_mapping secclass_map[] = {
...... { "binder", { "impersonate", "call", "set_context_mgr", "transfer", NULL } }, ......
};

这个数组定义在文件kernel/goldfish/security/selinux/include/classmap.h中。

数组secclass_map列出的Binder IPC相关的SEAndroid权限有四种,各自是impersonate、call、set_context_mgr和transfer。当中,call、set_context_mgr和transfer就相应我们前面说的Client与Server通信、设置Context Manager和传输Binder对象权限。

数组secclass_map没有列出传输文件描写叙述符的权限,是因为传输文件描写叙述符权限实质上是属于文件读写相关的权限,也就是检查目标进程是否具有訪问在通信数据附带的文件描写叙述符所描写叙述的文件的权限,这属于文件类别的SEAndroid安全权限。

同一时候,数组secclass_map多出了一个impersonate权限。当进程1代表进程2与进程3运行Binder IPC时,Binder驱动须要检查进程1是否具有模拟进程2的impersonate权限。眼下,不会发生一个进程代表另外一个进程与目标进程运行Binder IPC的情况,因此,impersonate权限实际上是没有使用到的。

在SEAndroid安全策略中,定义有一个名称为unconfineddomain的domain,它具有上述的call、set_context_mgr和transfer权限,例如以下所看到的:

allow unconfineddomain domain:binder { call transfer set_context_mgr };

上述安全策略定义在文件external/sepolicy/unconfined.te中。

在Android系统中,全部类型的应用程序进程的domain都具有unconfineddomain属性,例如以下所看到的:

#
# Apps signed with the platform key.
#
type platform_app, domain;
......
unconfined_domain(platform_app) # Apps signed with the media key.
type media_app, domain;
......
unconfined_domain(media_app) # Apps signed with the shared key.
type shared_app, domain;
......
unconfined_domain(shared_app) # Apps signed with the release key (testkey in AOSP).
type release_app, domain;
......
unconfined_domain(release_app) # Services with isolatedProcess=true in their manifest.
# In order for isolated_apps to interact with apps that have levelFromUid=true
# set it must be an mlstrustedsubject.
type isolated_app, domain, mlstrustedsubject;
......
unconfined_domain(isolated_app) #
# Untrusted apps.
#
type untrusted_app, domain;
......
unconfined_domain(untrusted_app)

上述安全策略定义在文件external/sepolicy/app.te中。

比如,用户安装的第三方应用程序运行在的进程的domain为untruusted_app,它通过宏unconfined_domain将unconfineddomain设置为自己的属性。宏unconfined_domai的定义例如以下所看到的:

#####################################
# unconfined_domain(domain)
# Allow the specified domain to do anything.
#
define(`unconfined_domain', `
typeattribute $1 mlstrustedsubject;
typeattribute $1 unconfineddomain;
')

这个宏定义在文件external/sepolicy/te_macros中。

这意味着全部应用程序进程都能够与其他进程进行Binder IPC,而且传递Binder对象。关于Android应用程序进程的domain设置过程,能够參考前面SEAndroid安全机制中的进程安全上下文关联分析一文。

此外,Android系统的Service Manager进程的domain也具有unconfined_domain属性,例如以下所看到的:

# servicemanager - the Binder context manager
type servicemanager, domain;
......
unconfined_domain(servicemanager)

上述安全策略定义在文件external/sepolicy/servicemanager.te中。

这说明Service Manager进程具有向Binder驱动设置Context Manager的权限。

了解了Binder IPC相关的SEAndroid安全权限以及安全策略之后,接下来我们就分别分析Binder驱动应用它们的过程。

1. 设置Context Manager过程中的SEAndroid安全检查

从前面浅谈Service Manager成为Android进程间通信(IPC)机制Binder守护进程之路一文能够知道,当Service Manager将自己设置为Binder驱动的Context Manager的时候,Binder驱动里面的函数binder_ioctl就会被调用,例如以下所看到的:

static struct binder_node *binder_context_mgr_node;
...... static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int ret;
struct binder_proc *proc = filp->private_data;
...... switch (cmd) {
......
case BINDER_SET_CONTEXT_MGR:
if (binder_context_mgr_node != NULL) {
......
ret = -EBUSY;
goto err;
}
ret = security_binder_set_context_mgr(proc->tsk);
if (ret < 0)
goto err;
......
binder_context_mgr_node = binder_new_node(proc, NULL, NULL);
......
break;
......
default:
ret = -EINVAL;
goto err;
}
ret = 0;
err:
......
return ret;
}

这个函数定义在文件kernel/goldfish/drivers/staging/android/binder.c中。

binder_context_mgr_node是一个类型为binder_node的全局变量,用来描写叙述注冊在Binder驱动里面的Context Manager。当binder_context_mgr_node的值不等于NULL的时候,就表示已经有进程注冊过Context Manager了,因此,就不再同意反复注冊。否则的话,就会调用函数binder_new_node创建一个binder_node对象,而且保存在全局变量binder_context_mgr_node中。

只是,在调用函数binder_new_node为当前进程创建一个binder_node对象之前,须要调用函数security_binder_set_context_mgr检查当前进程是否具有注冊Context Manager的SEAndroid安全权限。当前进程是通过本地变量proc来描写叙述的,它指向的是一个类型为binder_proc的对象。在结构体binder_proc中,有一个类型为task_struct的成员变量task,它指向的就是内核中用来描写叙述进程的一个控制块。在前面SEAndroid安全机制中的进程安全上下文关联分析一文中提到,进程的安全上下文是保存在内核中用来描写叙述该进程的一个task_struct结构体中的,因此,当知道一个进程的task_struct结构体之后,我们就能够获得它的安全上下文。

接下来,我们就继续分析函数security_binder_set_context_mgr的实现,例如以下所看到的:

static struct security_operations *security_ops;
...... int security_binder_set_context_mgr(struct task_struct *mgr)
{
return security_ops->binder_set_context_mgr(mgr);
}

这个函数定义在文件kernel/goldfish/security/security.c中。

security_ops是一个类型为security_operations的全局变量,它的成员变量binder_set_context_mgr是一个函数指针,指向的函数为selinux_binder_set_context_mgr,它的定义例如以下所看到的:

static int selinux_binder_set_context_mgr(struct task_struct *mgr)
{
u32 mysid = current_sid();
u32 mgrsid = task_sid(mgr); return avc_has_perm(mysid, mgrsid, SECCLASS_BINDER, BINDER__SET_CONTEXT_MGR, NULL);
}

这个函数定义在文件kernel/goldfish/security/selinux/hooks.c中。

函数selinux_binder_set_context_mgr首先获得用来描写叙述当前进程的安全上下文的sid,即mysid,接着再获得用来描写叙述要注冊Context Manager的进程mgr的安全上下文的sid,即mgrsid,最后都调用由SELinux模块提供的函数avc_has_perm来检查进程mgr是否具有类型为SECCLASS_BINDER的安全权限BINDER__SET_CONTEXT_MGR。

当前进程即为注冊Context Manager的进程,也就是说前面获得的mysid和mgrsid是一样的。进程的安全上下文本来是用字符串来描写叙述的,可是在内核中,不会直接使用这些字符串形式的安全上下文。我们知道,SELinux是在内核的安全模块LSM的基础上实现的,可是LSM不仅仅是为SELinux服务的。也就是说,我们也能够在内核中创建一个相似SELinux的模块,来实现另外一个安全机制。在这个另外实现的安全机制里面,也许不再使用字符串来描写叙述进程的安全上下文。为了能够提供一种统一方式来计算、应用这些安全策略,LSM模块要求在它的基础上实现的安全机制在内部使用一个不透明的32位无符号数来代表一个安全上下文。这样,LSM模块就能够提供一个统一的avc_has_perm函数计算一个主体是否相应的权限来訪问一个客体。

简单来说,一个SID代表的就是一个安全上下文,不同的SID代表的是不同的安全上下文,依据安全上下文计算出其相应的SID是由SELinux实现的,可是对于LSM来说是不透明的。LSM提供的函数avc_has_perm首先是在内部维护的一个Acess Vector Cache中检查指定的主体mysid是否具有对客体mgrsid类型为SECCLASS_BINDER的安全权限BINDER__SET_CONTEXT_MGR。假设LSM之前假设处理过同样參数的权限检查,那么就能从Acess Vector Cache直接得到结果。否则的话,就须要到从用户空间载入进来的安全策略中去计算结果。计算得到的结果除了返回给调用者之外,还会缓存在Acess Vector Cache中,这样就能够提高下一次同一样的安全检查的效率。

函数avc_has_perm的实现原理就如上所述,它的具体实现我们就不进一步分析了,有兴趣的读者能够自行分析。只是从函数binder_ioctl到函数selinux_binder_set_context_mgr的调用过程,我们就能够知道,函数selinux_binder_set_context_mgr实际上就是由SELinux模块通过LSM模块安插在Binder驱动中的一个Hook,而通过这个Hook就能够验证相应的进程是否具有设置Context Manager的SEAndroid安全权限。

从前面的分析能够知道,Service Manager进程的domain具有unconfined_domain属性,而全部具有unconfined_domain属性的domain都具有设置Context Manager的SEAndroid安全权限,因此,当Service Manager进程将自己注冊为Context Manager的时候,是能成功注冊的。

2. Client调用Server的SEAndroid安全权限检查

从前面Android系统进程间通信(IPC)机制Binder中的Server启动过程源码分析一文能够知道,Client发送给Server的请求须要经过Binder驱动的函数binder_transaction处理,例如以下所看到的:

static void binder_transaction(struct binder_proc *proc,
struct binder_thread *thread,
struct binder_transaction_data *tr, int reply)
{
...... if (reply) {
......
} else {
if (tr->target.handle) {
struct binder_ref *ref;
ref = binder_get_ref(proc, tr->target.handle);
......
target_node = ref->node;
} else {
target_node = binder_context_mgr_node;
......
}
....... target_proc = target_node->proc;
...... if (security_binder_transaction(proc->tsk, target_proc->tsk) < 0) {
......
goto err_invalid_target_handle;
} ......
} ......
/*Continue to Process Binder Transaction*/
...... err_invalid_target_handle:
.......
/*Error Return*/
.......
}

这个函数定义在文件kernel/goldfish/drivers/staging/android/binder.c中。

參数reply用来差别函数binder_transaction是用来处理Client发送给Server的请求还是Server对Client发送的请求的回应。假设參数reply的值等于false,那么就表示函数binder_transaction是用来处理Client发送给Server的请求。在这样的情况下,函数binder_transaction就须要检查Client是否有权限给Server发送请求。还有一方面,假设參数reply的值等于true,那么就表示函数binder_transaction是用来处理Server对Client发送的请求的回应。在这样的情况下,非常明显就不须要进行权限检查。接下来我们就主要分析Client发送给Server的请求的情况。

參数proc描写叙述的是Client进程,而Server进程由參数tr指向的一个binder_transaction_data结构体描写叙述。该binder_transaction_data结构体是从Client进程的用户空间传递进来的,它的成员变量target.handle是一个Binder句柄,实际上就是一个整数。假设这个Binder句柄不等于0,那么它所引用的Binder实体对象相应的就是运行在Server进程中的一个Binder服务。通过调用函数binder_get_ref能够获得一个Binder句柄所相应的Binder引用对象,而通过一个Binder引用对象的成员变量node能够获得它所引用的Binder实体对象。获得了要通信的Binder实体对象之后,通过它的成员变量proc就能够获得该Binder实体对象相应的Binder服务所运行在的Server进程。

还有一方面,假设上述Binder句柄的值等于0,那么就说明Client进程要发送请求给Service Manager。Service Manager是一个特殊的Binder服务,它会将自己注冊为Binder IPC的Context Manager,而且在Binder驱动中使用一个专门的全局变量binder_context_mgr_node来描写叙述它所相应的Binder实体对象。因此,在这样的情况下,非常easy就能够通过全局变量binder_context_mgr_node的成员变量proc找到要通信的Server进程。

这样,我们就知道了Client进程和Server进程分别由參数proc描写叙述和本地变量arget_proc指向的binder_proc结构体描写叙述。结构体binder_proc的成员变量tsk指向的是内核中用来描写叙述进程的结构体task_struct。依据我们在前面一小节的分析,结构体task_struct包括了进程的安全上下文信息,因此,这时候就能够调用函数security_binder_transaction检查Client进程是否有权限向Server进程发送请求了,也就是Client是否有权限调用Server的功能。假设有权限的话,接下来就会将Client传递进来的參数发送给Server,否则的话,就直接出错返回。

函数security_binder_transaction的实现例如以下所看到的:

static struct security_operations *security_ops;
...... int security_binder_transaction(struct task_struct *from, struct task_struct *to)
{
return security_ops->binder_transaction(from, to);
}

这个函数定义在文件kernel/goldfish/security/security.c中。

前面提到,security_ops是一个类型为security_operations的全局变量,它的成员变量binder_transaction是一个函数指针,指向的函数为selinux_binder_transaction,它的定义例如以下所看到的:

static int selinux_binder_transaction(struct task_struct *from, struct task_struct *to)
{
u32 mysid = current_sid();
u32 fromsid = task_sid(from);
u32 tosid = task_sid(to);
int rc; if (mysid != fromsid) {
rc = avc_has_perm(mysid, fromsid, SECCLASS_BINDER, BINDER__IMPERSONATE, NULL);
if (rc)
return rc;
} return avc_has_perm(fromsid, tosid, SECCLASS_BINDER, BINDER__CALL, NULL);
}

这个函数定义在文件kernel/goldfish/security/selinux/hooks.c中。

这里有3个sid,各自是mysid、fromsid和tosid。当中,mysid描写叙述的是当前进程的安全上下文,fromsid描写叙述的是Client进程的安全上下文,tosid描写叙述的是Server进程的安全上下文。在眼下的Binder驱动的实现中,当前正在调用函数selinux_binder_transaction进程即为Client进程。因此,mysid和fromsid的值总是相等的。可是,selinux模块在设计函数selinux_binder_transaction的时候,也考虑到了一个可能出现的特殊情景,那就是当前进程即不是真正的进程,而是代表參数from描写叙述的Client进程向參数to描写叙述的Server进程发送Binder通信请求。在这样的情况下,假设当前进程和Client进程具有不同的安全上下文,即mysid和fromsid的值不相等,那么就要求当前进程具有模拟Client进程的权限,也就是具有类别为SECCLASS_BINDER的BINDER__IMPERSONATE权限。因为在眼下的Binder驱动的实现中,不会出现一个进程代表另外一个进程向第三个进程发起Binder IPC请求的情况,因此,我们在用户空间的SEAndroid安全策略中,是看不到有关BINDER__IMPERSONATE的规则的。

最后,函数selinux_binder_transaction调用LSM模块提供的通用函数avc_has_perm检查Client进程对Server进程是否具有类别为SECCLASS_BINDER的BINDER__CALL的权限,也就是检查Client进程是否有权限向Server进程发送Binder IPC请求,而且将结果返回给调用者。

从前面的分析能够知道,在Android系统中,全部类型的应用程序进程的domain都具有unconfined_domain属性,也就是说它们之间是能够互相进行Binder IPC的。而且,Android系统的服务进程,比如Service Manager进程、Zygote进程和System Server进程等,它们的domain也是具有unconfined_domain属性的,因此,应用程序进程和它们也能够互相进行Binder IPC的。当然,这仅仅是默认的情况,假设有须要,我们是能够在SEAndroid安全策略中定义一个规则,禁止某一个应用程序进程向某一个服务进程发送Binder IPC请求的,这样就能够起到保护系统服务或者资源的目的。

3. 传递Binder对象的SEAndroid安全权限检查

Client与Server在运行Binder IPC的过程中,可能会将Binder对象传递给对方,使得对方能够获得这些Binder对象的代理接口,进而使用这些Binder对象提供的服务。想象这样的一个情景。一个进程从还有一个进程获得了一个有权限使用的Binder对象,接着将这个Binder对象通过Binder IPC传递给第三个进程,可是第三个进程没有使权限使用这个Binder对象。这时候就须要从源头上杜绝第三个进程获得它没有权限使用的Binder对象。

确保一个进程不会通过Binder IPC获得它没有权限使用的Binder对象也是由Binder驱动在函数binder_transaction中进行的,例如以下所看到的:

static void binder_transaction(struct binder_proc *proc,
struct binder_thread *thread,
struct binder_transaction_data *tr, int reply)
{
struct binder_transaction *t;
......
struct binder_proc *target_proc;
...... t = kzalloc(sizeof(*t), GFP_KERNEL);
...... t->buffer = binder_alloc_buf(target_proc, tr->data_size,
tr->offsets_size, !reply && (t->flags & TF_ONE_WAY));
...... if (copy_from_user(t->buffer->data, tr->data.ptr.buffer, tr->data_size)) {
......
goto err_copy_data_failed;
}
if (copy_from_user(offp, tr->data.ptr.offsets, tr->offsets_size)) {
......
goto err_copy_data_failed;
}
...... off_end = (void *)offp + tr->offsets_size;
for (; offp < off_end; offp++) {
struct flat_binder_object *fp;
......
fp = (struct flat_binder_object *)(t->buffer->data + *offp);
switch (fp->type) {
case BINDER_TYPE_BINDER:
case BINDER_TYPE_WEAK_BINDER: {
......
struct binder_node *node = binder_get_node(proc, fp->binder);
...... if (security_binder_transfer_binder(proc->tsk, target_proc->tsk)) {
......
goto err_binder_get_ref_for_node_failed;
}
......
} break;
case BINDER_TYPE_HANDLE:
case BINDER_TYPE_WEAK_HANDLE: {
struct binder_ref *ref = binder_get_ref(proc, fp->handle);
......
if (security_binder_transfer_binder(proc->tsk, target_proc->tsk)) {
......
goto err_binder_get_ref_failed;
}
......
} break;
......
} ......
} ...... err_binder_get_ref_for_node_failed:
err_binder_get_ref_failed:
......
}

这个函数定义在文件kernel/goldfish/drivers/staging/android/binder.c中。

參数tr指向的是一个从用户空间传递进来的binder_transaction_data结构体,里面包括了源进程proc发送给目标进程target_proc的通信数据。在这些通信数据里面,可能包括有Binder对象。

函数binder_transaction通过创建一个类型为binder_transaction的结构体t来描写叙述当前这一次的Binder通信,而且通过函数binder_alloc_buf给这个结构体在内核空间分配了一块缓冲区,用来保存从用户空间传递进来的通信数据。用户空间传统进来的数据包括有两部分内容。第一部分是通信数据内容,第二部分是一个偏移数组,用来描写叙述第一部分哪些位置包括有Binder对象。因此,函数binder_transaction须要通过两个copy_from_user函数调用将它们复制到刚才分配得到的内核缓冲区中。

将Binder通信数据从用户空间复制到内核空间后,函数binder_transaction就通过上述的偏移数组对包括在通信数据里面的Binder对象进行处理,当中就包括SEAndroid安全检查。首先,每个包括在通信数据里面的Binder对象都是用一个flat_binder_object结构体来描写叙述。结构体flat_binder_object有一个成员变量type,当它的值等于BINDER_TYPE_BINDER、BINDER_TYPE_WEAK_BINDER、BINDER_TYPE_HANDLE和BINDER_TYPE_WEAK_HANDLE的时候,都表示它描写叙述的是一个Binder对象。当中,前两者表示结构体flat_binder_object描写叙述的是一个Binder实体对象,而后两者表示结构体flat_binder_object描写叙述的是一个Binder引用对象。在Binder驱动中,Binder实体对象使用结构体binder_node来描写叙述,而Binder引用对象使用结构体binder_ref来描写叙述。不管是哪一种情况,都须要通过调用函数security_binder_transfer_binder来检查源进程proc是否具有向目标进程target_proc传递Binder对象的权限。

函数security_binder_transfer_binder的实现例如以下所看到的:

int security_binder_transfer_binder(struct task_struct *from, struct task_struct *to)
{
return security_ops->binder_transfer_binder(from, to);
}

这个函数定义在文件kernel/goldfish/security/security.c中。

前面提到,security_ops是一个类型为security_operations的全局变量,它的成员变量binder_transfer_binder是一个函数指针,指向的函数为selinux_binder_transfer_binder,它的定义例如以下所看到的:

static int selinux_binder_transfer_binder(struct task_struct *from, struct task_struct *to)
{
u32 fromsid = task_sid(from);
u32 tosid = task_sid(to);
return avc_has_perm(fromsid, tosid, SECCLASS_BINDER, BINDER__TRANSFER, NULL);
}

这个函数定义在文件kernel/goldfish/security/selinux/hooks.c中。

參数from和to描写叙述的各自是源进程和目标进程,通过函数task_sid能够获得用来描写叙述它们的安全上下文的sid。有了源进程和目标进程的安全上下文之后,就能够调用LSM模块提供的函数avc_has_perm来检查源进程是否具有向目标进程传递Binder对象的SEAndroid安全权限了,也就是类型为SECCLASS_BINDER的BINDER__TRANSFER权限。

从前面的分析能够知道,在Android系统中,全部类型的应用程序进程,以及系统服务进程,比如Service Manager进程、Zygote进程和System Server进程,它们的domain都具有unconfined_domain属性,也就是说,在默认情况下,它们之间是能够在Binder IPC中,互相传递Binder对象。当然。我们也能够在SEAndroid安全策略中定义相应的规则,来阻止一个进程向另外一个进程传递Binder对象。

4. 传递文件描写叙述符的SEAndroid安全权限检查

Client与Server在运行Binder IPC的过程中,除了能够相互传递Binder对象之外,还能够传递文件描写叙述符。也就是说,一个进程能够打开一个有权限訪问的文件,得到一个文件描写叙述符,然后再通过Binder IPC将这个文件描写叙述符传递给另外一个进程,使得另外的这个进程也能够訪问这个文件。因为文件是属于系统的敏感资源,因此,我们须要禁止一个进程将一个文件描写叙述符传递给一个没有权限訪问的进程。

与传递Binder对象的安全检查一样,传递文件描写叙述符的安全检查也是在Binder驱动的函数binder_transaction进行的,例如以下所看到的:

static void binder_transaction(struct binder_proc *proc,
struct binder_thread *thread,
struct binder_transaction_data *tr, int reply)
{
struct binder_transaction *t;
......
struct binder_proc *target_proc;
...... t = kzalloc(sizeof(*t), GFP_KERNEL);
...... t->buffer = binder_alloc_buf(target_proc, tr->data_size,
tr->offsets_size, !reply && (t->flags & TF_ONE_WAY));
...... if (copy_from_user(t->buffer->data, tr->data.ptr.buffer, tr->data_size)) {
......
goto err_copy_data_failed;
}
if (copy_from_user(offp, tr->data.ptr.offsets, tr->offsets_size)) {
......
goto err_copy_data_failed;
}
...... off_end = (void *)offp + tr->offsets_size;
for (; offp < off_end; offp++) {
struct flat_binder_object *fp;
......
fp = (struct flat_binder_object *)(t->buffer->data + *offp);
switch (fp->type) {
......
case BINDER_TYPE_FD: {
......
struct file *file;
...... file = fget(fp->handle);
...... if (security_binder_transfer_file(proc->tsk, target_proc->tsk, file) < 0) {
......
goto err_get_unused_fd_failed;
} ......
} break;
......
} ......
} ...... err_get_unused_fd_failed:
......
}

这个函数定义在文件kernel/goldfish/drivers/staging/android/binder.c中。

与Binder对象一样,包括在Binder IPC通信数据里面的文件描写叙述符也是使用一个类型为flat_binder_object的结构体来描写叙述的,只是这时候flat_binder_object结构体的成员变量type的值为BINDER_TYPE_FD。此外,这时候flat_binder_object结构体的成员变量handle描写叙述的就是要传递的文件描写叙述符。有了这个文件描写叙述符之后,就能够调用函数fget获得一个file结构体,该file结构体就描写叙述要传递的文件的全部信息,包括它的安全上下文信息。因此,接下来就能够调用函数security_binder_transfer_file来检查源进程proc是否具有向目标进程target_proc传递指定的文件file了。

函数security_binder_transfer_file的实现例如以下所看到的:

int security_binder_transfer_file(struct task_struct *from, struct task_struct *to, struct file *file)
{
return security_ops->binder_transfer_file(from, to, file);
}

这个函数定义在文件kernel/goldfish/security/security.c中。

前面提到,security_ops是一个类型为security_operations的全局变量,它的成员变量binder_transfer_file是一个函数指针,指向的函数为selinux_binder_transfer_file,它的定义例如以下所看到的:

static int selinux_binder_transfer_file(struct task_struct *from, struct task_struct *to, struct file *file)
{
u32 sid = task_sid(to);
struct file_security_struct *fsec = file->f_security;
struct inode *inode = file->f_path.dentry->d_inode;
struct inode_security_struct *isec = inode->i_security;
struct common_audit_data ad;
struct selinux_audit_data sad = {0,};
int rc; COMMON_AUDIT_DATA_INIT(&ad, PATH);
ad.u.path = file->f_path;
ad.selinux_audit_data = &sad; if (sid != fsec->sid) {
rc = avc_has_perm(sid, fsec->sid,
SECCLASS_FD,
FD__USE,
&ad);
if (rc)
return rc;
} if (unlikely(IS_PRIVATE(inode)))
return 0; return avc_has_perm(sid, isec->sid, isec->sclass, file_to_av(file),
&ad);
}

这个函数定义在文件kernel/goldfish/security/selinux/hooks.c中。

參数from和to描写叙述的各自是源进程和目标进程,而參数file描写叙述的是要传递的文件。函数security_binder_transfer_file实际上是检查目标进程是否具有訪问文件file的权限。假设有的话,那么就同意源进程将它传递给目标进程訪问。

因为源进程是以文件描写叙述符的形式传递文件给目标进程訪问,因此,函数security_binder_transfer_file还会要求目标进程拥有使用文件file的文件描写叙述符的权限。也就是说。能够訪问文件的内容是一种权限,而能够使用文件的描写叙述符又是还有一种权限。仅仅有目标进程对这两种权限都具有的情况下,才同意源进程将一个文件描写叙述符传递给目标进程使用。

通过函数task_sid能够获得目标进程的sid,也就是获得目标进程的安全上下文,而通过文件file的成员变量f_security能够获得文件内核对象的安全上下文,也就是文件描写叙述符相关的安全上下文。在Linux内核中,每个文件都关联一个inode对象,通过该inode对象的成员变量i_security能够获得inode内核对象的安全上下文,也就是文件内容相关的安全上下文。

当目标进程的sid不等于要传递的文件内核对象的sid的时候,要求目标进程能够使用要传递的文件的文件描写叙述符,这时候能够调用LSM模块提供的函数avc_has_perm检查目标进程对要传递的文件是否具有类型为SECCLASS_FD的FD__USE的权限。

当目标进程具有使用要传递的文件的描写叙述符的权限之后,函数security_binder_transfer_file就继续检查目标进程是否具有訪问要传递的文件的内容的权限。当中,isec->sclass描写叙述的是文件的类型,比如,有可能是文件夹,也有可能是普通文件,函数file_to_av获得的是要传递的文件的訪问方式,比如,是读还是写。举个样例,假设要传递的文件描写叙述符指向的是一个普通文件,而且以仅仅读方式打开,那么函数security_binder_transfer_file就会调用LSM提供的函数avc_has_perm检查目标进程对要传递的文件是否具有类型为SECCLASS_FILE的FILE__READ的权限。

从前面的分析能够知道,在Android系统中,全部类型的应用程序进程,以及系统服务进程,比如Service Manager进程、Zygote进程和System Server进程,它们能不能传递文件描写叙述符给对方,不是取决于它们的domain有没有unconfined_domain属性,而是取决于目标进程是否具有使用传递的文件的描写叙述符以及訪问传递的文件的内容的权限。

至此,我们就分析完毕SEAndroid安全机制对Binder IPC的保护了。要想非常好地理解这篇文章的内容,须要我们对Android系统的Binder IPC有一定的了解,具体能够參考Android进程间通信(IPC)机制Binder简要介绍和学习计划这个系列的文章。同一时候,我们也分析完毕整个SEAndroid安全机制的内容了。又一次学习SEAndroid安全机制,能够从SEAndroid安全机制简要介绍和学习计划这篇文章開始。很多其他信息能够关注老罗的新浪微博:http://weibo.com/shengyangluo