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

Commit 18859b15 authored by android-build-team Robot's avatar android-build-team Robot
Browse files

Snap for 7227300 from bec6007d to sc-v2-release

Change-Id: I9d7490566dadcad898e6ada08e76cf53ec4e0eb3
parents bce40616 bec6007d
Loading
Loading
Loading
Loading
+81 −10
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@

#include <android/fdsan.h>
#include <android/set_abort_message.h>
#include <bionic/malloc.h>
#include <bionic/mte.h>
#include <bionic/reserved_signals.h>

@@ -98,6 +99,13 @@ constexpr char kWaitForDebuggerKey[] = "debug.debuggerd.wait_for_debugger";
  ASSERT_MATCH(result,                             \
               R"(#\d\d pc [0-9a-f]+\s+ \S+ (\(offset 0x[0-9a-f]+\) )?\()" frame_name R"(\+)");

// Enable GWP-ASan at the start of this process. GWP-ASan is enabled using
// process sampling, so we need to ensure we force GWP-ASan on.
__attribute__((constructor)) static void enable_gwp_asan() {
  bool force = true;
  android_mallopt(M_INITIALIZE_GWP_ASAN, &force, sizeof(force));
}

static void tombstoned_intercept(pid_t target_pid, unique_fd* intercept_fd, unique_fd* output_fd,
                                 InterceptStatus* status, DebuggerdDumpType intercept_type) {
  intercept_fd->reset(socket_local_client(kTombstonedInterceptSocketName,
@@ -397,6 +405,72 @@ static void SetTagCheckingLevelSync() {
}
#endif

// Number of iterations required to reliably guarantee a GWP-ASan crash.
// GWP-ASan's sample rate is not truly nondeterministic, it initialises a
// thread-local counter at 2*SampleRate, and decrements on each malloc(). Once
// the counter reaches zero, we provide a sampled allocation. Then, double that
// figure to allow for left/right allocation alignment, as this is done randomly
// without bias.
#define GWP_ASAN_ITERATIONS_TO_ENSURE_CRASH (0x20000)

struct GwpAsanTestParameters {
  size_t alloc_size;
  bool free_before_access;
  int access_offset;
  std::string cause_needle; // Needle to be found in the "Cause: [GWP-ASan]" line.
};

struct GwpAsanCrasherTest : CrasherTest, testing::WithParamInterface<GwpAsanTestParameters> {};

GwpAsanTestParameters gwp_asan_tests[] = {
  {/* alloc_size */ 7, /* free_before_access */ true, /* access_offset */ 0, "Use After Free, 0 bytes into a 7-byte allocation"},
  {/* alloc_size */ 7, /* free_before_access */ true, /* access_offset */ 1, "Use After Free, 1 byte into a 7-byte allocation"},
  {/* alloc_size */ 7, /* free_before_access */ false, /* access_offset */ 16, "Buffer Overflow, 9 bytes right of a 7-byte allocation"},
  {/* alloc_size */ 16, /* free_before_access */ false, /* access_offset */ -1, "Buffer Underflow, 1 byte left of a 16-byte allocation"},
};

INSTANTIATE_TEST_SUITE_P(GwpAsanTests, GwpAsanCrasherTest, testing::ValuesIn(gwp_asan_tests));

TEST_P(GwpAsanCrasherTest, gwp_asan_uaf) {
  if (mte_supported()) {
    // Skip this test on MTE hardware, as MTE will reliably catch these errors
    // instead of GWP-ASan.
    GTEST_SKIP() << "Skipped on MTE.";
  }

  GwpAsanTestParameters params = GetParam();

  int intercept_result;
  unique_fd output_fd;
  StartProcess([&params]() {
    for (unsigned i = 0; i < GWP_ASAN_ITERATIONS_TO_ENSURE_CRASH; ++i) {
      volatile char* p = reinterpret_cast<volatile char*>(malloc(params.alloc_size));
      if (params.free_before_access) free(static_cast<void*>(const_cast<char*>(p)));
      p[params.access_offset] = 42;
      if (!params.free_before_access) free(static_cast<void*>(const_cast<char*>(p)));
    }
  });

  StartIntercept(&output_fd);
  FinishCrasher();
  AssertDeath(SIGSEGV);
  FinishIntercept(&intercept_result);

  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";

  std::string result;
  ConsumeFd(std::move(output_fd), &result);

  ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 2 \(SEGV_ACCERR\))");
  ASSERT_MATCH(result, R"(Cause: \[GWP-ASan\]: )" + params.cause_needle);
  if (params.free_before_access) {
    ASSERT_MATCH(result, R"(deallocated by thread .*
      #00 pc)");
  }
  ASSERT_MATCH(result, R"(allocated by thread .*
      #00 pc)");
}

struct SizeParamCrasherTest : CrasherTest, testing::WithParamInterface<size_t> {};

INSTANTIATE_TEST_SUITE_P(Sizes, SizeParamCrasherTest, testing::Values(16, 131072));
@@ -428,12 +502,11 @@ TEST_P(SizeParamCrasherTest, mte_uaf) {

  ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
  ASSERT_MATCH(result, R"(Cause: \[MTE\]: Use After Free, 0 bytes into a )" +
                           std::to_string(GetParam()) + R"(-byte allocation.*

allocated by thread .*
      #00 pc)");
                           std::to_string(GetParam()) + R"(-byte allocation)");
  ASSERT_MATCH(result, R"(deallocated by thread .*
      #00 pc)");
  ASSERT_MATCH(result, R"(allocated by thread .*
      #00 pc)");
#else
  GTEST_SKIP() << "Requires aarch64";
#endif
@@ -465,9 +538,8 @@ TEST_P(SizeParamCrasherTest, mte_overflow) {

  ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
  ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Overflow, 0 bytes right of a )" +
                           std::to_string(GetParam()) + R"(-byte allocation.*

allocated by thread .*
                           std::to_string(GetParam()) + R"(-byte allocation)");
  ASSERT_MATCH(result, R"(allocated by thread .*
      #00 pc)");
#else
  GTEST_SKIP() << "Requires aarch64";
@@ -500,9 +572,8 @@ TEST_P(SizeParamCrasherTest, mte_underflow) {

  ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 9 \(SEGV_MTESERR\))");
  ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Underflow, 4 bytes left of a )" +
                           std::to_string(GetParam()) + R"(-byte allocation.*

allocated by thread .*
                           std::to_string(GetParam()) + R"(-byte allocation)");
  ASSERT_MATCH(result, R"(allocated by thread .*
      #00 pc)");
#else
  GTEST_SKIP() << "Requires aarch64";
+68 −13
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@
 */

#include "libdebuggerd/gwp_asan.h"
#include "libdebuggerd/tombstone.h"
#include "libdebuggerd/utility.h"

#include "gwp_asan/common.h"
@@ -25,6 +26,8 @@
#include <unwindstack/Regs.h>
#include <unwindstack/Unwinder.h>

#include "tombstone.pb.h"

// Retrieve GWP-ASan state from `state_addr` inside the process at
// `process_memory`. Place the state into `*state`.
static bool retrieve_gwp_asan_state(unwindstack::Memory* process_memory, uintptr_t state_addr,
@@ -98,6 +101,67 @@ bool GwpAsanCrashData::CrashIsMine() const {
  return is_gwp_asan_responsible_;
}

constexpr size_t kMaxTraceLength = gwp_asan::AllocationMetadata::kMaxTraceLengthToCollect;

void GwpAsanCrashData::AddCauseProtos(Tombstone* tombstone, unwindstack::Unwinder* unwinder) const {
  if (!CrashIsMine()) {
    ALOGE("Internal Error: AddCauseProtos() on a non-GWP-ASan crash.");
    return;
  }

  Cause* cause = tombstone->add_causes();
  MemoryError* memory_error = cause->mutable_memory_error();
  HeapObject* heap_object = memory_error->mutable_heap();

  memory_error->set_tool(MemoryError_Tool_GWP_ASAN);
  switch (error_) {
    case gwp_asan::Error::USE_AFTER_FREE:
      memory_error->set_type(MemoryError_Type_USE_AFTER_FREE);
      break;
    case gwp_asan::Error::DOUBLE_FREE:
      memory_error->set_type(MemoryError_Type_DOUBLE_FREE);
      break;
    case gwp_asan::Error::INVALID_FREE:
      memory_error->set_type(MemoryError_Type_INVALID_FREE);
      break;
    case gwp_asan::Error::BUFFER_OVERFLOW:
      memory_error->set_type(MemoryError_Type_BUFFER_OVERFLOW);
      break;
    case gwp_asan::Error::BUFFER_UNDERFLOW:
      memory_error->set_type(MemoryError_Type_BUFFER_UNDERFLOW);
      break;
    default:
      memory_error->set_type(MemoryError_Type_UNKNOWN);
      break;
  }

  heap_object->set_address(__gwp_asan_get_allocation_address(responsible_allocation_));
  heap_object->set_size(__gwp_asan_get_allocation_size(responsible_allocation_));
  unwinder->SetDisplayBuildID(true);

  std::unique_ptr<uintptr_t[]> frames(new uintptr_t[kMaxTraceLength]);

  heap_object->set_allocation_tid(__gwp_asan_get_allocation_thread_id(responsible_allocation_));
  size_t num_frames =
      __gwp_asan_get_allocation_trace(responsible_allocation_, frames.get(), kMaxTraceLength);
  for (size_t i = 0; i != num_frames; ++i) {
    unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames[i]);
    BacktraceFrame* f = heap_object->add_allocation_backtrace();
    fill_in_backtrace_frame(f, frame_data, unwinder->GetMaps());
  }

  heap_object->set_deallocation_tid(__gwp_asan_get_deallocation_thread_id(responsible_allocation_));
  num_frames =
      __gwp_asan_get_deallocation_trace(responsible_allocation_, frames.get(), kMaxTraceLength);
  for (size_t i = 0; i != num_frames; ++i) {
    unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames[i]);
    BacktraceFrame* f = heap_object->add_deallocation_backtrace();
    fill_in_backtrace_frame(f, frame_data, unwinder->GetMaps());
  }

  set_human_readable_cause(cause, crash_address_);
}

void GwpAsanCrashData::DumpCause(log_t* log) const {
  if (!CrashIsMine()) {
    ALOGE("Internal Error: DumpCause() on a non-GWP-ASan crash.");
@@ -119,13 +183,6 @@ void GwpAsanCrashData::DumpCause(log_t* log) const {
  uintptr_t alloc_address = __gwp_asan_get_allocation_address(responsible_allocation_);
  size_t alloc_size = __gwp_asan_get_allocation_size(responsible_allocation_);

  if (crash_address_ == alloc_address) {
    // Use After Free on a 41-byte allocation at 0xdeadbeef.
    _LOG(log, logtype::HEADER, "Cause: [GWP-ASan]: %s on a %zu-byte allocation at 0x%" PRIxPTR "\n",
         error_string_, alloc_size, alloc_address);
    return;
  }

  uintptr_t diff;
  const char* location_str;

@@ -157,8 +214,6 @@ void GwpAsanCrashData::DumpCause(log_t* log) const {
       error_string_, diff, byte_suffix, location_str, alloc_size, alloc_address);
}

constexpr size_t kMaxTraceLength = gwp_asan::AllocationMetadata::kMaxTraceLengthToCollect;

bool GwpAsanCrashData::HasDeallocationTrace() const {
  assert(CrashIsMine() && "HasDeallocationTrace(): Crash is not mine!");
  if (!responsible_allocation_ || !__gwp_asan_is_deallocated(responsible_allocation_)) {
@@ -171,7 +226,7 @@ void GwpAsanCrashData::DumpDeallocationTrace(log_t* log, unwindstack::Unwinder*
  assert(HasDeallocationTrace() && "DumpDeallocationTrace(): No dealloc trace!");
  uint64_t thread_id = __gwp_asan_get_deallocation_thread_id(responsible_allocation_);

  std::unique_ptr<uintptr_t> frames(new uintptr_t[kMaxTraceLength]);
  std::unique_ptr<uintptr_t[]> frames(new uintptr_t[kMaxTraceLength]);
  size_t num_frames =
      __gwp_asan_get_deallocation_trace(responsible_allocation_, frames.get(), kMaxTraceLength);

@@ -183,7 +238,7 @@ void GwpAsanCrashData::DumpDeallocationTrace(log_t* log, unwindstack::Unwinder*

  unwinder->SetDisplayBuildID(true);
  for (size_t i = 0; i < num_frames; ++i) {
    unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames.get()[i]);
    unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames[i]);
    frame_data.num = i;
    _LOG(log, logtype::BACKTRACE, "    %s\n", unwinder->FormatFrame(frame_data).c_str());
  }
@@ -198,7 +253,7 @@ void GwpAsanCrashData::DumpAllocationTrace(log_t* log, unwindstack::Unwinder* un
  assert(HasAllocationTrace() && "DumpAllocationTrace(): No dealloc trace!");
  uint64_t thread_id = __gwp_asan_get_allocation_thread_id(responsible_allocation_);

  std::unique_ptr<uintptr_t> frames(new uintptr_t[kMaxTraceLength]);
  std::unique_ptr<uintptr_t[]> frames(new uintptr_t[kMaxTraceLength]);
  size_t num_frames =
      __gwp_asan_get_allocation_trace(responsible_allocation_, frames.get(), kMaxTraceLength);

@@ -210,7 +265,7 @@ void GwpAsanCrashData::DumpAllocationTrace(log_t* log, unwindstack::Unwinder* un

  unwinder->SetDisplayBuildID(true);
  for (size_t i = 0; i < num_frames; ++i) {
    unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames.get()[i]);
    unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames[i]);
    frame_data.num = i;
    _LOG(log, logtype::BACKTRACE, "    %s\n", unwinder->FormatFrame(frame_data).c_str());
  }
+5 −0
Original line number Diff line number Diff line
@@ -26,6 +26,9 @@
#include "types.h"
#include "utility.h"

class Cause;
class Tombstone;

class GwpAsanCrashData {
 public:
  GwpAsanCrashData() = delete;
@@ -69,6 +72,8 @@ class GwpAsanCrashData {
  // HasAllocationTrace() returns true.
  void DumpAllocationTrace(log_t* log, unwindstack::Unwinder* unwinder) const;

  void AddCauseProtos(Tombstone* tombstone, unwindstack::Unwinder* unwinder) const;

 protected:
  // Is GWP-ASan responsible for this crash.
  bool is_gwp_asan_responsible_ = false;
+7 −0
Original line number Diff line number Diff line
@@ -23,6 +23,9 @@

#include "scudo/interface.h"

class Cause;
class Tombstone;

class ScudoCrashData {
 public:
  ScudoCrashData() = delete;
@@ -32,6 +35,7 @@ class ScudoCrashData {
  bool CrashIsMine() const;

  void DumpCause(log_t* log, unwindstack::Unwinder* unwinder) const;
  void AddCauseProtos(Tombstone* tombstone, unwindstack::Unwinder* unwinder) const;

 private:
  scudo_error_info error_info_ = {};
@@ -39,4 +43,7 @@ class ScudoCrashData {

  void DumpReport(const scudo_error_report* report, log_t* log,
                  unwindstack::Unwinder* unwinder) const;

  void FillInCause(Cause* cause, const scudo_error_report* report,
                   unwindstack::Unwinder* unwinder) const;
};
+8 −0
Original line number Diff line number Diff line
@@ -31,9 +31,13 @@
#include "types.h"

// Forward declarations
class BacktraceFrame;
class Cause;
class Tombstone;

namespace unwindstack {
struct FrameData;
class Maps;
class Unwinder;
}

@@ -64,4 +68,8 @@ bool tombstone_proto_to_text(
    const Tombstone& tombstone,
    std::function<void(const std::string& line, bool should_log)> callback);

void fill_in_backtrace_frame(BacktraceFrame* f, const unwindstack::FrameData& frame,
                             unwindstack::Maps* maps);
void set_human_readable_cause(Cause* cause, uint64_t fault_addr);

#endif  // _DEBUGGERD_TOMBSTONE_H
Loading