Merge "debuggerd: fork and drop privileges when dumping."
This commit is contained in:
commit
b8e9ebf8b4
4 changed files with 118 additions and 89 deletions
|
@ -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 @@ static void handle_request(int fd) {
|
|||
}
|
||||
#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 @@ static void handle_request(int fd) {
|
|||
// 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 @@ static void handle_request(int fd) {
|
|||
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 @@ static void handle_request(int fd) {
|
|||
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 @@ static void handle_request(int fd) {
|
|||
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 @@ static void handle_request(int fd) {
|
|||
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 @@ static void handle_request(int fd) {
|
|||
}
|
||||
}
|
||||
|
||||
// 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() {
|
||||
|
|
|
@ -632,7 +632,7 @@ static void dump_abort_message(Backtrace* backtrace, log_t* log, uintptr_t addre
|
|||
}
|
||||
|
||||
// Dumps all information about the specified pid to the tombstone.
|
||||
static bool dump_crash(log_t* log, pid_t pid, pid_t tid, int signal, int si_code,
|
||||
static bool dump_crash(log_t* log, BacktraceMap* map, pid_t pid, pid_t tid, int signal, int si_code,
|
||||
uintptr_t abort_msg_address, bool dump_sibling_threads,
|
||||
int* total_sleep_time_usec) {
|
||||
// don't copy log messages to tombstone unless this is a dev device
|
||||
|
@ -659,8 +659,7 @@ static bool dump_crash(log_t* log, pid_t pid, pid_t tid, int signal, int si_code
|
|||
dump_signal_info(log, tid, signal, si_code);
|
||||
}
|
||||
|
||||
std::unique_ptr<BacktraceMap> map(BacktraceMap::Create(pid));
|
||||
std::unique_ptr<Backtrace> backtrace(Backtrace::Create(pid, tid, map.get()));
|
||||
std::unique_ptr<Backtrace> backtrace(Backtrace::Create(pid, tid, map));
|
||||
dump_abort_message(backtrace.get(), log, abort_msg_address);
|
||||
dump_registers(log, tid);
|
||||
if (backtrace->Unwind(0)) {
|
||||
|
@ -669,8 +668,8 @@ static bool dump_crash(log_t* log, pid_t pid, pid_t tid, int signal, int si_code
|
|||
ALOGE("Unwind failed: pid = %d, tid = %d", pid, tid);
|
||||
}
|
||||
dump_memory_and_code(log, backtrace.get());
|
||||
if (map.get() != nullptr) {
|
||||
dump_all_maps(backtrace.get(), map.get(), log, tid);
|
||||
if (map) {
|
||||
dump_all_maps(backtrace.get(), map, log, tid);
|
||||
}
|
||||
|
||||
if (want_logs) {
|
||||
|
@ -679,7 +678,7 @@ static bool dump_crash(log_t* log, pid_t pid, pid_t tid, int signal, int si_code
|
|||
|
||||
bool detach_failed = false;
|
||||
if (dump_sibling_threads) {
|
||||
detach_failed = dump_sibling_thread_report(log, pid, tid, total_sleep_time_usec, map.get());
|
||||
detach_failed = dump_sibling_thread_report(log, pid, tid, total_sleep_time_usec, map);
|
||||
}
|
||||
|
||||
if (want_logs) {
|
||||
|
@ -698,53 +697,57 @@ static bool dump_crash(log_t* log, pid_t pid, pid_t tid, int signal, int si_code
|
|||
return detach_failed;
|
||||
}
|
||||
|
||||
// find_and_open_tombstone - find an available tombstone slot, if any, of the
|
||||
// open_tombstone - find an available tombstone slot, if any, of the
|
||||
// form tombstone_XX where XX is 00 to MAX_TOMBSTONES-1, inclusive. If no
|
||||
// file is available, we reuse the least-recently-modified file.
|
||||
//
|
||||
// Returns the path of the tombstone file, allocated using malloc(). Caller must free() it.
|
||||
static char* find_and_open_tombstone(int* fd) {
|
||||
int open_tombstone(std::string* out_path) {
|
||||
// In a single pass, find an available slot and, in case none
|
||||
// exist, find and record the least-recently-modified file.
|
||||
char path[128];
|
||||
int fd = -1;
|
||||
int oldest = -1;
|
||||
struct stat oldest_sb;
|
||||
for (int i = 0; i < MAX_TOMBSTONES; i++) {
|
||||
snprintf(path, sizeof(path), TOMBSTONE_TEMPLATE, i);
|
||||
|
||||
struct stat sb;
|
||||
if (!stat(path, &sb)) {
|
||||
if (stat(path, &sb) == 0) {
|
||||
if (oldest < 0 || sb.st_mtime < oldest_sb.st_mtime) {
|
||||
oldest = i;
|
||||
oldest_sb.st_mtime = sb.st_mtime;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (errno != ENOENT)
|
||||
continue;
|
||||
if (errno != ENOENT) continue;
|
||||
|
||||
*fd = open(path, O_CREAT | O_EXCL | O_WRONLY | O_NOFOLLOW | O_CLOEXEC, 0600);
|
||||
if (*fd < 0)
|
||||
continue; // raced ?
|
||||
fd = open(path, O_CREAT | O_EXCL | O_WRONLY | O_NOFOLLOW | O_CLOEXEC, 0600);
|
||||
if (fd < 0) continue; // raced ?
|
||||
|
||||
fchown(*fd, AID_SYSTEM, AID_SYSTEM);
|
||||
return strdup(path);
|
||||
if (out_path) {
|
||||
*out_path = path;
|
||||
}
|
||||
fchown(fd, AID_SYSTEM, AID_SYSTEM);
|
||||
return fd;
|
||||
}
|
||||
|
||||
if (oldest < 0) {
|
||||
ALOGE("Failed to find a valid tombstone, default to using tombstone 0.\n");
|
||||
ALOGE("debuggerd: failed to find a valid tombstone, default to using tombstone 0.\n");
|
||||
oldest = 0;
|
||||
}
|
||||
|
||||
// we didn't find an available file, so we clobber the oldest one
|
||||
snprintf(path, sizeof(path), TOMBSTONE_TEMPLATE, oldest);
|
||||
*fd = open(path, O_CREAT | O_TRUNC | O_WRONLY | O_NOFOLLOW | O_CLOEXEC, 0600);
|
||||
if (*fd < 0) {
|
||||
ALOGE("failed to open tombstone file '%s': %s\n", path, strerror(errno));
|
||||
return NULL;
|
||||
fd = open(path, O_CREAT | O_TRUNC | O_WRONLY | O_NOFOLLOW | O_CLOEXEC, 0600);
|
||||
if (fd < 0) {
|
||||
ALOGE("debuggerd: failed to open tombstone file '%s': %s\n", path, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
fchown(*fd, AID_SYSTEM, AID_SYSTEM);
|
||||
return strdup(path);
|
||||
|
||||
if (out_path) {
|
||||
*out_path = path;
|
||||
}
|
||||
fchown(fd, AID_SYSTEM, AID_SYSTEM);
|
||||
return fd;
|
||||
}
|
||||
|
||||
static int activity_manager_connect() {
|
||||
|
@ -777,49 +780,27 @@ static int activity_manager_connect() {
|
|||
return amfd;
|
||||
}
|
||||
|
||||
char* engrave_tombstone(pid_t pid, pid_t tid, int signal, int original_si_code,
|
||||
uintptr_t abort_msg_address, bool dump_sibling_threads,
|
||||
bool* detach_failed, int* total_sleep_time_usec) {
|
||||
|
||||
void engrave_tombstone(int tombstone_fd, BacktraceMap* map, pid_t pid, pid_t tid, int signal,
|
||||
int original_si_code, uintptr_t abort_msg_address, bool dump_sibling_threads,
|
||||
bool* detach_failed, int* total_sleep_time_usec) {
|
||||
log_t log;
|
||||
log.current_tid = tid;
|
||||
log.crashed_tid = tid;
|
||||
|
||||
if ((mkdir(TOMBSTONE_DIR, 0755) == -1) && (errno != EEXIST)) {
|
||||
ALOGE("failed to create %s: %s\n", TOMBSTONE_DIR, strerror(errno));
|
||||
}
|
||||
|
||||
if (chown(TOMBSTONE_DIR, AID_SYSTEM, AID_SYSTEM) == -1) {
|
||||
ALOGE("failed to change ownership of %s: %s\n", TOMBSTONE_DIR, strerror(errno));
|
||||
}
|
||||
|
||||
int fd = -1;
|
||||
char* path = NULL;
|
||||
if (selinux_android_restorecon(TOMBSTONE_DIR, 0) == 0) {
|
||||
path = find_and_open_tombstone(&fd);
|
||||
} else {
|
||||
ALOGE("Failed to restore security context, not writing tombstone.\n");
|
||||
}
|
||||
|
||||
if (fd < 0) {
|
||||
ALOGE("Skipping tombstone write, nothing to do.\n");
|
||||
if (tombstone_fd < 0) {
|
||||
ALOGE("debuggerd: skipping tombstone write, nothing to do.\n");
|
||||
*detach_failed = false;
|
||||
return NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
log.tfd = fd;
|
||||
log.tfd = tombstone_fd;
|
||||
// Preserve amfd since it can be modified through the calls below without
|
||||
// being closed.
|
||||
int amfd = activity_manager_connect();
|
||||
log.amfd = amfd;
|
||||
*detach_failed = dump_crash(&log, pid, tid, signal, original_si_code, abort_msg_address,
|
||||
*detach_failed = dump_crash(&log, map, pid, tid, signal, original_si_code, abort_msg_address,
|
||||
dump_sibling_threads, total_sleep_time_usec);
|
||||
|
||||
_LOG(&log, logtype::BACKTRACE, "\nTombstone written to: %s\n", path);
|
||||
|
||||
// Either of these file descriptors can be -1, any error is ignored.
|
||||
// This file descriptor can be -1, any error is ignored.
|
||||
close(amfd);
|
||||
close(fd);
|
||||
|
||||
return path;
|
||||
}
|
||||
|
|
|
@ -17,15 +17,23 @@
|
|||
#ifndef _DEBUGGERD_TOMBSTONE_H
|
||||
#define _DEBUGGERD_TOMBSTONE_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <sys/types.h>
|
||||
#include <string>
|
||||
|
||||
class BacktraceMap;
|
||||
|
||||
/* Create and open a tombstone file for writing.
|
||||
* Returns a writable file descriptor, or -1 with errno set appropriately.
|
||||
* If out_path is non-null, *out_path is set to the path of the tombstone file.
|
||||
*/
|
||||
int open_tombstone(std::string* path);
|
||||
|
||||
/* Creates a tombstone file and writes the crash dump to it.
|
||||
* Returns the path of the tombstone, which must be freed using free(). */
|
||||
char* engrave_tombstone(pid_t pid, pid_t tid, int signal, int original_si_code,
|
||||
uintptr_t abort_msg_address,
|
||||
bool dump_sibling_threads, bool* detach_failed,
|
||||
int* total_sleep_time_usec);
|
||||
void engrave_tombstone(int tombstone_fd, BacktraceMap* map, pid_t pid, pid_t tid, int signal,
|
||||
int original_si_code, uintptr_t abort_msg_address, bool dump_sibling_threads,
|
||||
bool* detach_failed, int* total_sleep_time_usec);
|
||||
|
||||
#endif // _DEBUGGERD_TOMBSTONE_H
|
||||
|
|
|
@ -86,6 +86,7 @@
|
|||
#define AID_METRICS_COLL 1042 /* metrics_collector process */
|
||||
#define AID_METRICSD 1043 /* metricsd process */
|
||||
#define AID_WEBSERV 1044 /* webservd process */
|
||||
#define AID_DEBUGGERD 1045 /* debuggerd unprivileged user */
|
||||
|
||||
#define AID_SHELL 2000 /* adb and debug shell user */
|
||||
#define AID_CACHE 2001 /* cache access */
|
||||
|
@ -190,6 +191,7 @@ static const struct android_id_info android_ids[] = {
|
|||
{ "metrics_coll", AID_METRICS_COLL },
|
||||
{ "metricsd", AID_METRICSD },
|
||||
{ "webserv", AID_WEBSERV },
|
||||
{ "debuggerd", AID_DEBUGGERD, },
|
||||
|
||||
{ "shell", AID_SHELL, },
|
||||
{ "cache", AID_CACHE, },
|
||||
|
|
Loading…
Reference in a new issue