
In-depth analysis and exploit for CVE-2019-13272, a Linux kernel ptrace privilege escalation vulnerability. Includes code walkthrough and exploitation scenario.
PTRACE_TRACEME is a privilege escalation vulnerability in the Linux Kernel discovered by Jann Horn in July 2019.
Ptrace is a system call that provides a method allowing a process (tracer) to observe and control the execution of another process (tracee), examine and change its core image and registers, mainly used to set breakpoints in debugging and to trace system call invocations.``` c 1 396 kernel/ptrace.c <<ptrace_attach>> ptrace_link(task, current); 2 469 kernel/ptrace.c <<ptrace_traceme>> ptrace_link(current, current->real_parent);
There are two ways to establish a trace relationship:
- A process calls the fork function and its child process calls `PTRACE_TRACEME` (corresponding to the `ptrace_traceme` function in the kernel) to initialize the tracee.
- A process calls `PTRACE_ATTACH` or `PTRACE_SEIZE` (corresponding to the `ptrace_attach` function in the kernel) to initialize a tracer to trace another process.
Regardless of which method is used, the `ptrace_link` function will ultimately be called to establish the trace relationship between the tracer and the tracee.
- The two parameters passed to `ptrace_link` for `ptrace_attach` are 'task' (tracee) and 'current' (tracer)
- The two parameters passed to `ptrace_link` for `ptrace_traceme` are 'current' (tracee) and 'current->real_parent' (tracer)
Here, we need to note what the two parameters passed for the tracer and tracee are in the two methods above when calling the `ptrace_link` function, because the vulnerability lies in the `ptrace_link` function.``` c
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();
}
void __ptrace_link(struct task_struct *child, struct task_struct *new_parent,
const struct cred *ptracer_cred)
{
BUG_ON(!list_empty(&child->ptrace_entry));
list_add(&child->ptrace_entry, &new_parent->ptraced); // 1. thêm chính nó vào hàng đợi
// ptraced của process cha
child->parent = new_parent; // 2. Lưu địa chỉ của process cha trong con trỏ parent
child->ptracer_cred = get_cred(ptracer_cred); // 3. Lưu ptracer_cred lại, ta cần tập trung
// vào biến này vì lỗi nằm ở đây
}
The key to establishing a trace relationship is that the tracee will record the tracer's cred and store it in the tracee's 'ptracer_cred' variable.
The concept of 'ptracer_cred' was introduced by a patch in 2016, ptrace: Capture the ptracer's creds not PT_PTRACE_CAP. The purpose of introducing 'ptracer_cred' is to perform a security check when the tracee executes exec to load a setuid executable
Why do we need this security check?
The exec family can update the process image. If the setuid bit of an executable file is set, when the executable file is run, the process's euid will be changed to the uid of the file's owner. The process's privilege becomes higher than that of the user who called exec, and running such a setuid executable will have an escalation effect (escalation).
Imagine, if the process that executes exec is itself a tracee, after it runs a setuid executable to escalate privileges, its tracer can modify its (the tracee's) registers and memory at any time. If a low-privileged tracer can control a high-privileged tracee, the tracer could perform unauthorized operations through the tracee.
However, in the kernel, such unauthorized behavior is generally not allowed. Therefore, when establishing a trace relationship, the tracee needs to store the tracer's cred (i.e., ptracer_cred). If the tracee executes an exec process, it will check whether the setuid bit of the executable being run is set. If it is, it will examine the privileges of 'ptracer_cred'. If the privileges are insufficient, the setuid bit's execution privilege (the file owner's privilege) will not be used for the exec execution; instead, it will be executed with the original user's privilege.
The code analysis of this process is as follows (the code analysis in this article is based on v4.19-rc8).``` python do_execve -> __do_execve_file -> prepare_binprm -> bprm_fill_uid -> security_bprm_set_creds ->cap_bprm_set_creds -> ptracer_capable ->selinux_bprm_set_creds ->(apparmor_bprm_set_creds) ->(smack_bprm_set_creds) ->(tomoyo_bprm_set_creds)