debuggerd: fork and drop privileges when dumping.
Bug: http://b/25195825
Change-Id: I913d8425232e79df3f7a051a8cc63de9c60f4780
diff --git a/debuggerd/debuggerd.cpp b/debuggerd/debuggerd.cpp
index 0afa895..58b629b 100644
--- a/debuggerd/debuggerd.cpp
+++ b/debuggerd/debuggerd.cpp
@@ -24,11 +24,12 @@
#include <dirent.h>
#include <time.h>
-#include <sys/ptrace.h>
-#include <sys/wait.h>
#include <elf.h>
-#include <sys/stat.h>
#include <sys/poll.h>
+#include <sys/prctl.h>
+#include <sys/ptrace.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
#include <selinux/android.h>
@@ -363,6 +364,37 @@
}
#endif
+ // Fork a child to handle the rest of the request.
+ pid_t fork_pid = fork();
+ if (fork_pid == -1) {
+ ALOGE("debuggerd: failed to fork: %s\n", strerror(errno));
+ return;
+ } else if (fork_pid != 0) {
+ waitpid(fork_pid, nullptr, 0);
+ return;
+ }
+
+ // Open the tombstone file if we need it.
+ std::string tombstone_path;
+ int tombstone_fd = -1;
+ switch (request.action) {
+ case DEBUGGER_ACTION_DUMP_TOMBSTONE:
+ case DEBUGGER_ACTION_CRASH:
+ tombstone_fd = open_tombstone(&tombstone_path);
+ if (tombstone_fd == -1) {
+ ALOGE("debuggerd: failed to open tombstone file: %s\n", strerror(errno));
+ exit(1);
+ }
+ break;
+
+ case DEBUGGER_ACTION_DUMP_BACKTRACE:
+ break;
+
+ default:
+ ALOGE("debuggerd: unexpected request action: %d", request.action);
+ exit(1);
+ }
+
// At this point, the thread that made the request is blocked in
// a read() call. If the thread has crashed, then this gives us
// time to PTRACE_ATTACH to it before it has a chance to really fault.
@@ -374,19 +406,32 @@
// See details in bionic/libc/linker/debugger.c, in function
// debugger_signal_handler().
if (ptrace(PTRACE_ATTACH, request.tid, 0, 0)) {
- ALOGE("ptrace attach failed: %s\n", strerror(errno));
- return;
+ ALOGE("debuggerd: ptrace attach failed: %s\n", strerror(errno));
+ exit(1);
+ }
+
+ // Generate the backtrace map before dropping privileges.
+ std::unique_ptr<BacktraceMap> backtrace_map(BacktraceMap::Create(request.pid));
+
+ // Now that we've done everything that requires privileges, we can drop them.
+ if (setresgid(AID_DEBUGGERD, AID_DEBUGGERD, AID_DEBUGGERD) != 0) {
+ ALOGE("debuggerd: failed to setresgid");
+ exit(1);
+ }
+
+ if (setresuid(AID_DEBUGGERD, AID_DEBUGGERD, AID_DEBUGGERD) != 0) {
+ ALOGE("debuggerd: failed to setresuid");
+ exit(1);
}
bool detach_failed = false;
bool tid_unresponsive = false;
bool attach_gdb = should_attach_gdb(&request);
if (TEMP_FAILURE_RETRY(write(fd, "\0", 1)) != 1) {
- ALOGE("failed responding to client: %s\n", strerror(errno));
- return;
+ ALOGE("debuggerd: failed to respond to client: %s\n", strerror(errno));
+ exit(1);
}
- std::unique_ptr<char> tombstone_path;
int total_sleep_time_usec = 0;
while (true) {
int signal = wait_for_sigstop(request.tid, &total_sleep_time_usec, &detach_failed);
@@ -399,9 +444,9 @@
case SIGSTOP:
if (request.action == DEBUGGER_ACTION_DUMP_TOMBSTONE) {
ALOGV("stopped -- dumping to tombstone\n");
- tombstone_path.reset(engrave_tombstone(
- request.pid, request.tid, signal, request.original_si_code, request.abort_msg_address,
- true, &detach_failed, &total_sleep_time_usec));
+ engrave_tombstone(tombstone_fd, backtrace_map.get(), request.pid, request.tid, signal,
+ request.original_si_code, request.abort_msg_address, true,
+ &detach_failed, &total_sleep_time_usec);
} else if (request.action == DEBUGGER_ACTION_DUMP_BACKTRACE) {
ALOGV("stopped -- dumping to fd\n");
dump_backtrace(fd, -1, request.pid, request.tid, &detach_failed, &total_sleep_time_usec);
@@ -409,7 +454,7 @@
ALOGV("stopped -- continuing\n");
status = ptrace(PTRACE_CONT, request.tid, 0, 0);
if (status) {
- ALOGE("ptrace continue failed: %s\n", strerror(errno));
+ ALOGE("debuggerd: ptrace continue failed: %s\n", strerror(errno));
}
continue; // loop again
}
@@ -432,21 +477,21 @@
kill(request.pid, SIGSTOP);
// don't dump sibling threads when attaching to GDB because it
// makes the process less reliable, apparently...
- tombstone_path.reset(engrave_tombstone(
- request.pid, request.tid, signal, request.original_si_code, request.abort_msg_address,
- !attach_gdb, &detach_failed, &total_sleep_time_usec));
+ engrave_tombstone(tombstone_fd, backtrace_map.get(), request.pid, request.tid, signal,
+ request.original_si_code, request.abort_msg_address, !attach_gdb,
+ &detach_failed, &total_sleep_time_usec);
break;
default:
- ALOGE("process stopped due to unexpected signal %d\n", signal);
+ ALOGE("debuggerd: process stopped due to unexpected signal %d\n", signal);
break;
}
break;
}
if (request.action == DEBUGGER_ACTION_DUMP_TOMBSTONE) {
- if (tombstone_path) {
- write(fd, tombstone_path.get(), strlen(tombstone_path.get()));
+ if (!tombstone_path.empty()) {
+ write(fd, tombstone_path.c_str(), tombstone_path.length());
}
}
@@ -457,7 +502,7 @@
kill(request.pid, SIGSTOP);
}
if (ptrace(PTRACE_DETACH, request.tid, 0, 0)) {
- ALOGE("ptrace detach from %d failed: %s", request.tid, strerror(errno));
+ ALOGE("debuggerd: ptrace detach from %d failed: %s", request.tid, strerror(errno));
detach_failed = true;
} else if (attach_gdb) {
// if debug.db.uid is set, its value indicates if we should wait
@@ -468,16 +513,9 @@
}
}
- // resume stopped process (so it can crash in peace).
+ // Resume the stopped process so it can crash in peace, and exit.
kill(request.pid, SIGCONT);
-
- // If we didn't successfully detach, we're still the parent, and the
- // actual parent won't receive a death notification via wait(2). At this point
- // there's not much we can do about that.
- if (detach_failed) {
- ALOGE("debuggerd committing suicide to free the zombie!\n");
- kill(getpid(), SIGKILL);
- }
+ exit(0);
}
static int do_server() {