|
|
Android: debuggerd mitigation bypass and infoleak | ||
| Project Member Reported by jannh@google.com, Jun 21 2016 | Back to list | ||
Note: This issue was identified using manual code review and has not been verified using a PoC.
debuggerd was recently changed to drop privileges between attaching to a crashed process and dumping it to reduce its attack surface. The following issue allows that mitigation to be bypassed and also allows a privileged attacker (logcat access) to bypass userland ASLR.
When a process reports a crash to debuggerd, debuggerd attaches to that process and all other processes in the thread group in order to be able to report the register state of each thread. It attaches to the sibling threads in ptrace_siblings(), as follows:
static void ptrace_siblings(pid_t pid, pid_t main_tid, std::set<pid_t>& tids) {
char task_path[64];
snprintf(task_path, sizeof(task_path), "/proc/%d/task", pid);
std::unique_ptr<DIR, int (*)(DIR*)> d(opendir(task_path), closedir);
// Bail early if the task directory cannot be opened.
if (!d) {
ALOGE("debuggerd: failed to open /proc/%d/task: %s", pid, strerror(errno));
return;
}
struct dirent* de;
while ((de = readdir(d.get())) != NULL) {
// Ignore "." and "..".
if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) {
continue;
}
char* end;
pid_t tid = strtoul(de->d_name, &end, 10);
if (*end) {
continue;
}
if (tid == main_tid) {
continue;
}
if (ptrace(PTRACE_ATTACH, tid, 0, 0) < 0) {
ALOGE("debuggerd: ptrace attach to %d failed: %s", tid, strerror(errno));
continue;
}
tids.insert(tid);
}
}
When ptrace_siblings() is called, a SIGSTOP has been sent to the thread group to freeze the siblings, but only the crashing process is in a ptrace stop - the siblings are only signal-stopped and can be resumed by the owner of the process.
If an attacker triggers a crash in a multithreaded program, resumes that program's threads with SIGCONT while debuggerd is in ptrace_siblings() and quickly lets the sibling threads exit, a non-leader tid that ptrace_siblings() read with readdir() might be invalid at the time ptrace() is called with it. (It might actually be invalid even before the readdir() call - readdir() caches results from getdents() / getdents64() in userspace.) This means that at the time ptrace() is called, `pid` may have been reallocated for a new thread in another process, e.g. system_server.
If an attacker deliberately triggers this condition, he could reach two things:
- If he has an exploit for the post-privilege-drop code in debuggerd, he can e.g. cause debuggerd to be attached to system_server
via ptrace when the bug triggers. This would mean that, despite debuggerd having dropped privileges, the attacker could still pivot
over into system_server.
- If he has logcat access (yes, I know that that counts as a privileged context), he could cause the register state and backtrace of
threads in e.g. system_server to be dumped, allowing him to bypass ASLR in system_server.
Thanks to Zach Riggle for helping with figuring out the impact of this issue.
This bug is subject to a 90 day disclosure deadline. If 90 days elapse
without a broadly available patch, then the bug report will automatically
become visible to the public.
Project Member
Comment 1
by
jannh@google.com,
Jun 21 2016
,
Jun 22 2016
Android assigned AndroidID-29555636.
,
Sep 6 2016
Fixed: https://source.android.com/security/bulletin/2016-09-01.html
,
Jun 2
|
|||
| ► Sign in to add a comment | |||