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

Android社区 收藏文章

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

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

图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为untrusted_app,它通过宏unconfined_domain将unconfineddomain设置为自己的属性。宏unconfined_domain的定义如下所示:

#####################################
    # 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首先是在内部维护的一个Access Vector Cache中检查指定的主体mysid是否具有对客体mgrsid类型为SECCLASS_BINDER的安全权限BINDER__SET_CONTEXT_MGR。如果LSM之前处理过相同参数的权限检查,那么就能从Access Vector Cache直接得到结果。否则的话,就需要到从用户空间加载进来的安全策略中去计算结果。计算得到的结果除了返回给调用者之外,还会缓存在Access 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实体对象。因此,在这种情况下,很容易就可以通过全局变量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的BINDERIMPERSONATE权限。由于在目前的Binder驱动的实现中,不会出现一个进程代表另外一个进程向第三个进程发起Binder IPC请求的情况,因此,我们在用户空间的SEAndroid安全策略中,是看不到有关BINDERIMPERSONATE的规则的。

最后,函数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

相关标签

扫一扫

在手机上阅读