欢迎来到 黑吧安全网 聚焦网络安全前沿资讯,精华内容,交流技术心得!

CVE-2019-13272 'PTRACE_TRACEME' 本地提权漏洞分析(一)

来源:本站整理 作者:佚名 时间:2019-08-07 TAG: 我要投稿

漏洞信息
这个漏洞是 jannh 在今年 7月份发现的漏洞来源
影响版本 Linux Kernel 5.1.17
本地提权
 
漏洞补丁
漏洞补丁
diff --git a/kernel/ptrace.c b/kernel/ptrace.c
index 8456b6e..705887f 100644
--- a/kernel/ptrace.c
+++ b/kernel/ptrace.c
@@ -79,9 +79,7 @@ void __ptrace_link(struct task_struct *child, struct task_struct *new_parent,
  */
 static void ptrace_link(struct task_struct *child, struct task_struct *new_parent)
 {
-    rcu_read_lock();
-    __ptrace_link(child, new_parent, __task_cred(new_parent));
-    rcu_read_unlock();
+    __ptrace_link(child, new_parent, current_cred());
 }
漏洞的补丁十分的简单,不使用 rcu 机制,__task_cred(new_parent)) 变成current_cred()
jannh 发布的信息中提出了两个问题,在这里我们先对第一个问题做分析
 
前置知识
这里是代码分析的时候涉及的一些知识点, 只做简单的描述,不做太详细的介绍
linux rcu 机制
rcu (Read-Copy Update) 是linux 中用的比较多的同步机制,它保护的是指针rcu 使用在 读多写少的情况下,允许多个读者和写者读者之间不需要同步,但如果存在多个写者时,在写者把更新后的“副本”覆盖到原数据时,写者与写者之间需要利用其他同步机制保证同步。
常用函数rcu_read_lock()rcu_read_unlock()
在读取rcu指针的时候需要加上,例如前面的
    rcu_read_lock();
    __ptrace_link(child, new_parent, __task_cred(new_parent));
    rcu_read_unlock();
读者是可以嵌套的,也就是说rcu_read_lock()可以嵌套调用.rcu_read_lock()和rcu_read_unlock()用来保持一个读者的RCU临界区. 该临界区上不允许上下文切换(禁止和启用抢占)
rcu_dereference()有 rcu 标签的指针不能直接使用,读者要调用rcu_dereference来获得一个被RCU保护的指针.
synchronize_rcu()用来等待之前的读者全部退出,读端临界区的检查是全局的,系统中有任何的代码处于读端临界区,synchronize_rcu() 都会阻塞,直到所有读端临界区结束才会返回,用在可睡眠的环境下.
call_rcu()用在不可睡眠的条件中,如果中断环境,禁止抢占环境等.
linux ptrace 机制
这个应该都是比较熟悉的,gdb的实现就是用的ptrace系统调用它的实现原型如下
#include 
int ptrace(int request, int pid, int addr, int data);
它提供了父进程控制子进程的方法,用户可以借助它实现断点和调试request参数决定了系统调用的功能它只能调试属于自己用户的进程,种显而易见的限制就是普通用户的ptrace不能调试root进程
PTRACE_ATTACH trace指定 pid 对应的进程PTRACE_DETACH 结束 tracePTRACE_TRACEME 本进程将被父进程trace(告诉别人,来trace我呀)PTRACE_SYSCALL, PTRACE_CONT 重新运行。PTRACE_PEEKTEXT, PTRACE_PEEKDATA 从内存地址中读取一个字节,内存地址由addr给出。PTRACE_PEEKUSR从USER区域中读取一个字节,偏移量为addr。PTRACE_POKEUSR 往USER区域中写入一个字节。偏移量为addr。
setresuid 函数实现
setresuid 函数原型int setresuid(uid_t ruid, uid_t euid, uid_t suid);设置程序运行的 ruid, euid 和 suid,执行的条件有
1.当前进程的euid是root
2. 三个参数,每一个等于原来某个id中的一个
满足以上条件的任意一个,setresuid()都可以正常调用,并执行例如,如果原来 ruid = 100,euid =300, suid =200那么 setresuid(200,300,100) 可以成功,但是setresuid(100,200,400) 就会失败,当然 euid = 0 的时候就是 root 权限了,要设置成其他的用户id 也是没有问题的
setresuid 函数实现在 kernel/sys.c主要逻辑如下, 省略了一些代码
 long __sys_setresuid(uid_t ruid, uid_t euid, uid_t suid)
{
    struct user_namespace *ns = current_user_ns();
    const struct cred *old;
    struct cred *new;
....
    new = prepare_creds();
...
    old = current_cred();
...// some check
    return commit_creds(new);
...
}
可以看到,它会调用 prepare_creds 生成一个新的 cred, 然后 根据old cred和检查传进来的参数是否合法,合法机会运行 commit_creds 替换成新的commit cred 在 kernel/cred.c
int commit_creds(struct cred *new)
{
    struct task_struct *task = current;
    const struct cred *old = task->real_cred;
...
    BUG_ON(task->cred != old);
...
    BUG_ON(atomic_read(&new->usage) 1);
    get_cred(new); /* we will require a ref for the subj creds too */
...
rcu_assign_pointer(task->real_cred, new);
    rcu_assign_pointer(task->cred, new);
...//some check
    /* release the old obj and subj refs both */
    put_cred(old);
    put_cred(old);
    return 0;
}
EXPORT_SYMBOL(commit_creds);
commit_creds 主要将传入的 new_cred 替换原来的 task->real_cred 和 task->cred, 然后调用了 两次 put_cred 解除 old cred 的引用
put_cred 实现在 kernel/cred.c
static inline void put_cred(const struct cred *_cred)
{
    struct cred *cred = (struct cred *) _cred;
    if (cred) {
        validate_creds(cred);

[1] [2] [3] [4]  下一页

【声明】:黑吧安全网(http://www.myhack58.com)登载此文出于传递更多信息之目的,并不代表本站赞同其观点和对其真实性负责,仅适于网络安全技术爱好者学习研究使用,学习中请遵循国家相关法律法规。如有问题请联系我们,联系邮箱admin@myhack58.com,我们会在最短的时间内进行处理。
  • 最新更新
    • 相关阅读
      • 本类热门
        • 最近下载