PTRACE_TRACEME CVE-2019-13272 本地提权漏洞解析

来源:岁月联盟 编辑:猪蛋儿 时间:2020-01-29


 
PTRACE_TRACEME 漏洞 是 Jann Horn 201907 月发现的内核提权漏洞, 漏洞发现和利用的思路有很多值得学习的地方, 本文记录了个人的学习过程
author: Gengjia Chen (chengjia4574@gmail.com) of IceSwordLab, qihoo 360
 
漏洞补丁
我们从漏洞补丁 ptrace: Fix ->ptracer_cred handling for PTRACE_TRACEME 入手分析
Fix two issues:
// 第一个问题,是 cred 的 rcu reference 问题
When called for PTRACE_TRACEME, ptrace_link() would obtain an RCU  
reference to the parent's objective credentials, then give that pointer
to get_cred().  However, the object lifetime rules for things like
struct cred do not permit unconditionally turning an RCU reference into
a stable reference.
// 第二个问题,tracee 记录的 tracer 的 cred 的问题
PTRACE_TRACEME records the parent's credentials as if the parent was
acting as the subject, but that's not the case.  If a malicious
unprivileged child uses PTRACE_TRACEME and the parent is privileged, and
at a later point, the parent process becomes attacker-controlled
(because it drops privileges and calls execve()), the attacker ends up
with control over two processes with a privileged ptrace relationship,
which can be abused to ptrace a suid binary and obtain root privileges.
Fix both of these by always recording the credentials of the process
that is requesting the creation of the ptrace relationship:
current_cred() can't change under us, and current is the proper subject
for access control.
以上是补丁的描述,以下是补丁的代码
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());
 }
从补丁的描述来看,一共修复了 2 个问题
1 是 rcu reference 的问题,对应的代码是删除了 rcu 锁;
2 是 tracee 记录 tracer 进程的 cred 引发的问题
本文不关心第一个问题,只分析可以用于本地提权的第二个问题
从补丁描述看第二个问题比较复杂,我们后面再分析,补丁对应的代码倒是非常简单,将 ‘__task_cred(new_parent)’ 换成了 ‘current_cred()’, 也就是说记录的 cred 从 tracer 进程的 cred 换成了当前进程的 cred
 
漏洞分析
ptrace 是一个系统调用,它提供了一种方法来让进程 (tracer) 可以观察和控制其它进程 (tracee) 的执行,检查和改变其核心映像以及寄存器, 主要用来实现断点调试和系统调用跟踪
   1    396  kernel/ptrace.c
             ptrace_link(task, current);  // link 的双方分别是要 trace 的目标进程 'task'
                      //  和发动 trace 的当前进程 'current'
   2    469  kernel/ptrace.c
             ptrace_link(current, current->real_parent);  // link 的双方分别是发动 trace 的
                              // 当前进程 ‘current’ 和当前进程的
                              // 父进程 ' current->real_parent'
trace 关系的建立有 2 种方式
1 是进程调用 fork 函数然后子进程主动调用 PTRACE_TRACEME, 这是由 tracee 发起的, 对应内核函数 ptrace_traceme
2 是进程调用 PTRACE_ATTACH 或者 PTRACE_SEIZE 去主动 trace 其他进程, 这是由 tracer 发起的, 对应内核函数 ptrace_attach
不管是哪种方式,最后都会调用 ptrace_link 函数去建立 tracer 和 tracee 之间的 trace 关系
ptrace_attach 关联的双方是 ‘task’ (tracee) 和 ‘current’ (tracer)
ptrace_traceme 关联的双方是 ‘current’ (tracee) 和 ‘current->real_parent’ (tracer)
这里我们要仔细记住上面 2 种模式下 tracer 和 tracee 分别是什么,因为这就是漏洞的关键
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();

[1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]  下一页