Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e1aa0ca5 authored by Josh Gao's avatar Josh Gao
Browse files

debuggerd_handler: implement missing fallback functionality.

Allow the fallback implementation to dump traces and create tombstones
in seccomped processes.

Bug: http://b/35858739
Test: debuggerd -b `pidof media.codec`; killall -ABRT media.codec
Change-Id: I381b283de39a66d8900f1c320d32497d6f2b4ec4
parent 4e5e797d
Loading
Loading
Loading
Loading
+32 −8
Original line number Original line Diff line number Diff line
@@ -8,17 +8,35 @@ cc_defaults {
        "-Os",
        "-Os",
    ],
    ],


    // util.cpp gets async signal safe logging via libc_logging,
    // which defines its interface in bionic private headers.
    include_dirs: ["bionic/libc"],

    local_include_dirs: ["include"],
    local_include_dirs: ["include"],
}
}


// Utility library to tombstoned and get an output fd.
cc_library_static {
    name: "libtombstoned_client",
    defaults: ["debuggerd_defaults"],
    srcs: [
        "tombstoned_client.cpp",
        "util.cpp",
    ],

    whole_static_libs: [
        "libc_logging",
        "libcutils",
        "libbase",
    ],
}

// Core implementation, linked into libdebuggerd_handler and the dynamic linker.
cc_library_static {
cc_library_static {
    name: "libdebuggerd_handler_core",
    name: "libdebuggerd_handler_core",
    defaults: ["debuggerd_defaults"],
    defaults: ["debuggerd_defaults"],
    srcs: ["handler/debuggerd_handler.cpp"],
    srcs: ["handler/debuggerd_handler.cpp"],


    // libdebuggerd_handler gets async signal safe logging via libc_logging,
    // which defines its interface in bionic private headers.
    include_dirs: ["bionic/libc"],
    whole_static_libs: [
    whole_static_libs: [
        "libc_logging",
        "libc_logging",
        "libdebuggerd",
        "libdebuggerd",
@@ -27,6 +45,7 @@ cc_library_static {
    export_include_dirs: ["include"],
    export_include_dirs: ["include"],
}
}


// Implementation with a no-op fallback.
cc_library_static {
cc_library_static {
    name: "libdebuggerd_handler",
    name: "libdebuggerd_handler",
    defaults: ["debuggerd_defaults"],
    defaults: ["debuggerd_defaults"],
@@ -39,15 +58,18 @@ cc_library_static {
    export_include_dirs: ["include"],
    export_include_dirs: ["include"],
}
}


// Fallback implementation.
cc_library_static {
cc_library_static {
    name: "libdebuggerd_handler_fallback",
    name: "libdebuggerd_handler_fallback",
    defaults: ["debuggerd_defaults"],
    defaults: ["debuggerd_defaults"],
    srcs: ["handler/debuggerd_fallback.cpp"],
    srcs: [
        "handler/debuggerd_fallback.cpp",
    ],


    // libdebuggerd_handler gets async signal safe logging via libc_logging,
    whole_static_libs: [
    // which defines its interface in bionic private headers.
        "libdebuggerd_handler_core",
    include_dirs: ["bionic/libc"],
        "libtombstoned_client",
    static_libs: [
        "libbase",
        "libdebuggerd",
        "libdebuggerd",
        "libbacktrace",
        "libbacktrace",
        "libunwind",
        "libunwind",
@@ -70,6 +92,7 @@ cc_library {
        "libbase",
        "libbase",
        "libcutils",
        "libcutils",
    ],
    ],

    export_include_dirs: ["include"],
    export_include_dirs: ["include"],
}
}


@@ -187,6 +210,7 @@ cc_binary {
    },
    },


    static_libs: [
    static_libs: [
        "libtombstoned_client",
        "libdebuggerd",
        "libdebuggerd",
        "libcutils",
        "libcutils",
    ],
    ],
+1 −49
Original line number Original line Diff line number Diff line
@@ -48,6 +48,7 @@


#include "debuggerd/handler.h"
#include "debuggerd/handler.h"
#include "debuggerd/protocol.h"
#include "debuggerd/protocol.h"
#include "debuggerd/tombstoned.h"
#include "debuggerd/util.h"
#include "debuggerd/util.h"


using android::base::unique_fd;
using android::base::unique_fd;
@@ -128,55 +129,6 @@ static bool activity_manager_notify(int pid, int signal, const std::string& amfd
  return true;
  return true;
}
}


static bool tombstoned_connect(pid_t pid, unique_fd* tombstoned_socket, unique_fd* output_fd) {
  unique_fd sockfd(socket_local_client(kTombstonedCrashSocketName,
                                       ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_SEQPACKET));
  if (sockfd == -1) {
    PLOG(ERROR) << "failed to connect to tombstoned";
    return false;
  }

  TombstonedCrashPacket packet = {};
  packet.packet_type = CrashPacketType::kDumpRequest;
  packet.packet.dump_request.pid = pid;
  if (TEMP_FAILURE_RETRY(write(sockfd, &packet, sizeof(packet))) != sizeof(packet)) {
    PLOG(ERROR) << "failed to write DumpRequest packet";
    return false;
  }

  unique_fd tmp_output_fd;
  ssize_t rc = recv_fd(sockfd, &packet, sizeof(packet), &tmp_output_fd);
  if (rc == -1) {
    PLOG(ERROR) << "failed to read response to DumpRequest packet";
    return false;
  } else if (rc != sizeof(packet)) {
    LOG(ERROR) << "read DumpRequest response packet of incorrect length (expected "
               << sizeof(packet) << ", got " << rc << ")";
    return false;
  }

  // Make the fd O_APPEND so that our output is guaranteed to be at the end of a file.
  // (This also makes selinux rules consistent, because selinux distinguishes between writing to
  // a regular fd, and writing to an fd with O_APPEND).
  int flags = fcntl(tmp_output_fd.get(), F_GETFL);
  if (fcntl(tmp_output_fd.get(), F_SETFL, flags | O_APPEND) != 0) {
    PLOG(WARNING) << "failed to set output fd flags";
  }

  *tombstoned_socket = std::move(sockfd);
  *output_fd = std::move(tmp_output_fd);
  return true;
}

static bool tombstoned_notify_completion(int tombstoned_socket) {
  TombstonedCrashPacket packet = {};
  packet.packet_type = CrashPacketType::kCompletedDump;
  if (TEMP_FAILURE_RETRY(write(tombstoned_socket, &packet, sizeof(packet))) != sizeof(packet)) {
    return false;
  }
  return true;
}

static void signal_handler(int) {
static void signal_handler(int) {
  // We can't log easily, because the heap might be corrupt.
  // We can't log easily, because the heap might be corrupt.
  // Just die and let the surrounding log context explain things.
  // Just die and let the surrounding log context explain things.
+196 −13
Original line number Original line Diff line number Diff line
@@ -26,23 +26,206 @@
 * SUCH DAMAGE.
 * SUCH DAMAGE.
 */
 */


#include <dirent.h>
#include <fcntl.h>
#include <poll.h>
#include <pthread.h>
#include <stddef.h>
#include <stddef.h>
#include <sys/ucontext.h>
#include <sys/ucontext.h>
#include <syscall.h>
#include <unistd.h>
#include <unistd.h>


#include <atomic>

#include <android-base/file.h>
#include <android-base/unique_fd.h>

#include "debuggerd/handler.h"
#include "debuggerd/tombstoned.h"
#include "debuggerd/util.h"

#include "backtrace.h"
#include "tombstone.h"
#include "tombstone.h"


extern "C" void __linker_use_fallback_allocator();
#include "private/libc_logging.h"

using android::base::unique_fd;

extern "C" void __linker_enable_fallback_allocator();
extern "C" void __linker_disable_fallback_allocator();


extern "C" bool debuggerd_fallback(ucontext_t* ucontext, siginfo_t* siginfo, void* abort_message) {
// This is incredibly sketchy to do inside of a signal handler, especially when libbacktrace
// This is incredibly sketchy to do inside of a signal handler, especially when libbacktrace
// uses the C++ standard library throughout, but this code runs in the linker, so we'll be using
// uses the C++ standard library throughout, but this code runs in the linker, so we'll be using
// the linker's malloc instead of the libc one. Switch it out for a replacement, just in case.
// the linker's malloc instead of the libc one. Switch it out for a replacement, just in case.
//
//
  // This isn't the default method of dumping because it can fail in cases such as memory space
// This isn't the default method of dumping because it can fail in cases such as address space
// exhaustion.
// exhaustion.
  __linker_use_fallback_allocator();
static void debuggerd_fallback_trace(int output_fd, ucontext_t* ucontext) {
  engrave_tombstone_ucontext(-1, getpid(), gettid(), reinterpret_cast<uintptr_t>(abort_message),
  __linker_enable_fallback_allocator();
                             siginfo, ucontext);
  dump_backtrace_ucontext(output_fd, ucontext);
  __linker_disable_fallback_allocator();
}

static void debuggerd_fallback_tombstone(int output_fd, ucontext_t* ucontext, siginfo_t* siginfo,
                                         void* abort_message) {
  __linker_enable_fallback_allocator();
  engrave_tombstone_ucontext(output_fd, reinterpret_cast<uintptr_t>(abort_message), siginfo,
                             ucontext);
  __linker_disable_fallback_allocator();
}

static void iterate_siblings(bool (*callback)(pid_t, int), int output_fd) {
  pid_t current_tid = gettid();
  char buf[BUFSIZ];
  snprintf(buf, sizeof(buf), "/proc/%d/task", current_tid);
  DIR* dir = opendir(buf);

  if (!dir) {
    __libc_format_log(ANDROID_LOG_ERROR, "libc", "failed to open %s: %s", buf, strerror(errno));
    return;
  }

  struct dirent* ent;
  while ((ent = readdir(dir))) {
    char* end;
    long tid = strtol(ent->d_name, &end, 10);
    if (end == ent->d_name || *end != '\0') {
      continue;
    }

    if (tid != current_tid) {
      callback(tid, output_fd);
    }
  }
  closedir(dir);
}

static bool forward_output(int src_fd, int dst_fd) {
  // Make sure the thread actually got the signal.
  struct pollfd pfd = {
    .fd = src_fd, .events = POLLIN,
  };

  // Wait for up to a second for output to start flowing.
  if (poll(&pfd, 1, 1000) != 1) {
    return false;
  }

  while (true) {
    char buf[512];
    ssize_t rc = TEMP_FAILURE_RETRY(read(src_fd, buf, sizeof(buf)));
    if (rc == 0) {
      return true;
    } else if (rc < 0) {
      return false;
    }

    if (!android::base::WriteFully(dst_fd, buf, rc)) {
      // We failed to write to tombstoned, but there's not much we can do.
      // Keep reading from src_fd to keep things going.
      continue;
    }
  }
}

static void trace_handler(siginfo_t* info, ucontext_t* ucontext) {
  static std::atomic<int> trace_output_fd(-1);

  if (info->si_value.sival_int == ~0) {
    // Asked to dump by the original signal recipient.
    debuggerd_fallback_trace(trace_output_fd, ucontext);

    int tmp = trace_output_fd.load();
    trace_output_fd.store(-1);
    close(tmp);
    return;
  }

  // Only allow one thread to perform a trace at a time.
  static pthread_mutex_t trace_mutex = PTHREAD_MUTEX_INITIALIZER;
  int ret = pthread_mutex_trylock(&trace_mutex);
  if (ret != 0) {
    __libc_format_log(ANDROID_LOG_INFO, "libc", "pthread_mutex_try_lock failed: %s", strerror(ret));
    return;
  }

  // Fetch output fd from tombstoned.
  unique_fd tombstone_socket, output_fd;
  if (!tombstoned_connect(getpid(), &tombstone_socket, &output_fd)) {
    goto exit;
  }

  dump_backtrace_header(output_fd.get());

  // Dump our own stack.
  debuggerd_fallback_trace(output_fd.get(), ucontext);

  // Send a signal to all of our siblings, asking them to dump their stack.
  iterate_siblings(
    [](pid_t tid, int output_fd) {
      // Use a pipe, to be able to detect situations where the thread gracefully exits before
      // receiving our signal.
      unique_fd pipe_read, pipe_write;
      if (!Pipe(&pipe_read, &pipe_write)) {
        __libc_format_log(ANDROID_LOG_ERROR, "libc", "failed to create pipe: %s", strerror(errno));
        return false;
      }

      trace_output_fd.store(pipe_write.get());

      siginfo_t siginfo = {};
      siginfo.si_code = SI_QUEUE;
      siginfo.si_value.sival_int = ~0;
      siginfo.si_pid = getpid();
      siginfo.si_uid = getuid();

      if (syscall(__NR_rt_tgsigqueueinfo, getpid(), tid, DEBUGGER_SIGNAL, &siginfo) != 0) {
        __libc_format_log(ANDROID_LOG_ERROR, "libc", "failed to send trace signal to %d: %s", tid,
                          strerror(errno));
        return false;
      }

      bool success = forward_output(pipe_read.get(), output_fd);
      if (success) {
        // The signaled thread has closed trace_output_fd already.
        (void)pipe_write.release();
      } else {
        trace_output_fd.store(-1);
      }

      return true;
      return true;
    },
    output_fd.get());

  dump_backtrace_footer(output_fd.get());
  tombstoned_notify_completion(tombstone_socket.get());

exit:
  pthread_mutex_unlock(&trace_mutex);
}

static void crash_handler(siginfo_t* info, ucontext_t* ucontext, void* abort_message) {
  // Only allow one thread to handle a crash.
  static pthread_mutex_t crash_mutex = PTHREAD_MUTEX_INITIALIZER;
  int ret = pthread_mutex_lock(&crash_mutex);
  if (ret != 0) {
    __libc_format_log(ANDROID_LOG_INFO, "libc", "pthread_mutex_lock failed: %s", strerror(ret));
    return;
  }

  unique_fd tombstone_socket, output_fd;
  bool tombstoned_connected = tombstoned_connect(getpid(), &tombstone_socket, &output_fd);
  debuggerd_fallback_tombstone(output_fd.get(), ucontext, info, abort_message);
  if (tombstoned_connected) {
    tombstoned_notify_completion(tombstone_socket.get());
  }
}

extern "C" void debuggerd_fallback_handler(siginfo_t* info, ucontext_t* ucontext,
                                           void* abort_message) {
  if (info->si_signo == DEBUGGER_SIGNAL) {
    return trace_handler(info, ucontext);
  } else {
    return crash_handler(info, ucontext, abort_message);
  }
}
}
+1 −6
Original line number Original line Diff line number Diff line
@@ -26,10 +26,5 @@
 * SUCH DAMAGE.
 * SUCH DAMAGE.
 */
 */


#include <stddef.h>
extern "C" void debuggerd_fallback_handler(struct siginfo_t*, struct ucontext_t*, void*) {
#include <sys/ucontext.h>
#include <unistd.h>

extern "C" bool debuggerd_fallback(ucontext_t*, siginfo_t*, void*) {
  return false;
}
}
+19 −19
Original line number Original line Diff line number Diff line
@@ -62,7 +62,7 @@


#define CRASH_DUMP_PATH "/system/bin/" CRASH_DUMP_NAME
#define CRASH_DUMP_PATH "/system/bin/" CRASH_DUMP_NAME


extern "C" bool debuggerd_fallback(ucontext_t*, siginfo_t*, void*);
extern "C" void debuggerd_fallback_handler(siginfo_t*, ucontext_t*, void*);


static debuggerd_callbacks_t g_callbacks;
static debuggerd_callbacks_t g_callbacks;


@@ -323,21 +323,11 @@ static void resend_signal(siginfo_t* info, bool crash_dump_started) {
      fatal_errno("failed to resend signal during crash");
      fatal_errno("failed to resend signal during crash");
    }
    }
  }
  }

  if (info->si_signo == DEBUGGER_SIGNAL) {
    pthread_mutex_unlock(&crash_mutex);
  }
}
}


// Handler that does crash dumping by forking and doing the processing in the child.
// Handler that does crash dumping by forking and doing the processing in the child.
// Do this by ptracing the relevant thread, and then execing debuggerd to do the actual dump.
// Do this by ptracing the relevant thread, and then execing debuggerd to do the actual dump.
static void debuggerd_signal_handler(int signal_number, siginfo_t* info, void* context) {
static void debuggerd_signal_handler(int signal_number, siginfo_t* info, void* context) {
  int ret = pthread_mutex_lock(&crash_mutex);
  if (ret != 0) {
    __libc_format_log(ANDROID_LOG_INFO, "libc", "pthread_mutex_lock failed: %s", strerror(ret));
    return;
  }

  // It's possible somebody cleared the SA_SIGINFO flag, which would mean
  // It's possible somebody cleared the SA_SIGINFO flag, which would mean
  // our "info" arg holds an undefined value.
  // our "info" arg holds an undefined value.
  if (!have_siginfo(signal_number)) {
  if (!have_siginfo(signal_number)) {
@@ -359,24 +349,29 @@ static void debuggerd_signal_handler(int signal_number, siginfo_t* info, void* c
    // check to allow all si_code values in calls coming from inside the house.
    // check to allow all si_code values in calls coming from inside the house.
  }
  }


  log_signal_summary(signal_number, info);

  void* abort_message = nullptr;
  void* abort_message = nullptr;
  if (g_callbacks.get_abort_message) {
  if (g_callbacks.get_abort_message) {
    abort_message = g_callbacks.get_abort_message();
    abort_message = g_callbacks.get_abort_message();
  }
  }


  if (prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0) == 1) {
  if (prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0) == 1) {
    ucontext_t* ucontext = static_cast<ucontext_t*>(context);
    // This check might be racy if another thread sets NO_NEW_PRIVS, but this should be unlikely,
    if (signal_number == DEBUGGER_SIGNAL || !debuggerd_fallback(ucontext, info, abort_message)) {
    // you can only set NO_NEW_PRIVS to 1, and the effect should be at worst a single missing
      // The process has NO_NEW_PRIVS enabled, so we can't transition to the crash_dump context.
    // ANR trace.
      __libc_format_log(ANDROID_LOG_INFO, "libc",
    debuggerd_fallback_handler(info, static_cast<ucontext_t*>(context), abort_message);
                        "Suppressing debuggerd output because prctl(PR_GET_NO_NEW_PRIVS)==1");
    }
    resend_signal(info, false);
    resend_signal(info, false);
    return;
    return;
  }
  }


  // Only allow one thread to handle a signal at a time.
  int ret = pthread_mutex_lock(&crash_mutex);
  if (ret != 0) {
    __libc_format_log(ANDROID_LOG_INFO, "libc", "pthread_mutex_lock failed: %s", strerror(ret));
    return;
  }

  log_signal_summary(signal_number, info);

  // Populate si_value with the abort message address, if found.
  // Populate si_value with the abort message address, if found.
  if (abort_message) {
  if (abort_message) {
    info->si_value.sival_ptr = abort_message;
    info->si_value.sival_ptr = abort_message;
@@ -427,6 +422,11 @@ static void debuggerd_signal_handler(int signal_number, siginfo_t* info, void* c
  }
  }


  resend_signal(info, thread_info.crash_dump_started);
  resend_signal(info, thread_info.crash_dump_started);
  if (info->si_signo == DEBUGGER_SIGNAL) {
    // If the signal is fatal, don't unlock the mutex to prevent other crashing threads from
    // starting to dump right before our death.
    pthread_mutex_unlock(&crash_mutex);
  }
}
}


void debuggerd_init(debuggerd_callbacks_t* callbacks) {
void debuggerd_init(debuggerd_callbacks_t* callbacks) {
Loading