
Глубокий анализ и эксплойт для CVE-2019-13272, уязвимости повышения привилегий в ядре Linux через ptrace. Включает разбор кода и сценарий эксплуатации.
PTRACE_TRACEME — уязвимость повышения привилегий в ядре Linux, обнаруженная Янном Хорном (Jann Horn) в июле 2019 года.
Ptrace — это системный вызов, который предоставляет механизм, позволяющий одному процессу (tracer) наблюдать и управлять выполнением другого процесса (tracee), а также проверять и изменять его образ ядра (core image) и регистры. В основном используется для установки точек останова (break point) при отладке и отслеживания вызовов системных вызовов.``` 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);
Существует два способа установить trace-отношение:
- Процесс вызывает функцию fork, и его дочерний процесс вызывает `PTRACE_TRACEME` (соответствует функции `ptrace_traceme` в ядре) для инициализации tracee.
- Процесс вызывает `PTRACE_ATTACH` или `PTRACE_SEIZE` (соответствует функции `ptrace_attach` в ядре) для инициализации tracer’а, который будет трассировать другой процесс.
Независимо от того, какой способ используется, в конечном итоге вызывается функция `ptrace_link` для установки trace-отношения между tracer’ом и tracee.
- Два параметра, передаваемые в `ptrace_link` для `ptrace_attach`: это 'task' (tracee) и 'current' (tracer)
- Два параметра, передаваемые в `ptrace_link` для `ptrace_traceme`: это 'current' (tracee) и 'current->real_parent' (tracer)
Здесь необходимо обратить внимание на то, какие именно два параметра tracer’а и tracee передаются в двух вышеуказанных способах при вызове функции `ptrace_link`, поскольку уязвимость будет находиться в функции `ptrace_link`.``` 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
}
Мấu chốt để thiết lập trace relationship là tracee sẽ ghi lại cred của tracer và lưu nó trong biến 'ptracer_cred' của tracee.
Khái niệm về 'ptracer_cred' đã được giới thiệu bởi một bản vá vào năm 2016, ptrace: Capture the ptracer's creds not PT_PTRACE_CAP. Mục đích của việc giới thiệu 'ptracer_cred' là để thực hiện kiểm tra bảo mật khi tracee thực thi exec để load setuid executable
Tại sao chúng ta cần kiểm tra sự an toàn này?
Family của exec có thể cập nhật image của process. Nếu setuid bit của file thực thi được set, khi file thực thi được chạy, euid của process sẽ được sửa đổi thành uid của chủ sở hữu file thực thi. Quyền của process cao hơn quyền của người dùng gọi exec và việc chạy loại setuid executable này sẽ có tác động leo thang (escalation).
Hãy tưởng tượng, nếu bản thân process thực thi exec là một tracee, sau khi nó thực hiện setuid executable để leo thang đặc quyền, tracer của nó có thể sửa đổi các thanh ghi và bộ nhớ của nó (tracee) bất kỳ lúc nào, và nếu tracer có đặc quyền thấp có thể kiểm soát tracee có đặc quyền cao, tracer có thể thực hiện các hoạt động trái phép thông qua tracee.
Tuy nhiên, trong kernel, dường như không cho phép tồn tại những hành vi vượt quá thẩm quyền như vậy, vì vậy khi thiết lập trace relationships, tracee cần lưu cred của tracer (tức là ptracer_cred), nếu tracee thực thi một exec process, nó sẽ kiểm tra xem setuid bit của file thực thi được chạy có được set hay không, nếu có, nó sẽ xem xét quyền của 'ptracer_cred'. Nếu quyền không thỏa, quyền thực thi của setuid bit (đặc quyền của chủ sở hữu file) sẽ không được dùng để thực thi exec mà sẽ được thực thi với quyền của người dùng ban đầu.
Phân tích code của process này như sau (phân tích code của bài viết này dựa trên 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)