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

Commit f8622520 authored by Peter Collingbourne's avatar Peter Collingbourne
Browse files

Add support for MTE error reports in tombstones.

Teach debuggerd to use the new scudo APIs proposed in
https://reviews.llvm.org/D77283 for extracing MTE error reports from crashed
processes, and include those reports in tombstones if possible.

Bug: 135772972
Change-Id: I082dfd0ac9d781cfed2b8c34cc73562614bb0dbb
parent e0edc7ec
Loading
Loading
Loading
Loading
+25 −3
Original line number Diff line number Diff line
@@ -169,6 +169,7 @@ cc_library_static {
        "libdebuggerd/backtrace.cpp",
        "libdebuggerd/gwp_asan.cpp",
        "libdebuggerd/open_files_list.cpp",
        "libdebuggerd/scudo.cpp",
        "libdebuggerd/tombstone.cpp",
        "libdebuggerd/utility.cpp",
    ],
@@ -176,8 +177,13 @@ cc_library_static {
    local_include_dirs: ["libdebuggerd/include"],
    export_include_dirs: ["libdebuggerd/include"],

    include_dirs: [
        // Needed for private/bionic_fdsan.h
    include_dirs: ["bionic/libc"],
        "bionic/libc",

        // Needed for scudo/interface.h
        "external/scudo/standalone/include",
    ],
    header_libs: [
        "bionic_libc_platform_headers",
        "gwp_asan_headers",
@@ -192,7 +198,10 @@ cc_library_static {
        "liblog",
    ],

    whole_static_libs: ["gwp_asan_crash_handler"],
    whole_static_libs: [
        "gwp_asan_crash_handler",
        "libscudo",
    ],

    target: {
        recovery: {
@@ -206,6 +215,9 @@ cc_library_static {
        debuggable: {
            cflags: ["-DROOT_POSSIBLE"],
        },
        experimental_mte: {
            cflags: ["-DANDROID_EXPERIMENTAL_MTE"],
        },
    },
}

@@ -256,6 +268,10 @@ cc_test {
        "gwp_asan_headers",
    ],

    include_dirs: [
        "external/scudo/standalone/include",
    ],

    local_include_dirs: [
        "libdebuggerd",
    ],
@@ -271,6 +287,12 @@ cc_test {
    },

    test_suites: ["device-tests"],

    product_variables: {
        experimental_mte: {
            cflags: ["-DANDROID_EXPERIMENTAL_MTE"],
        },
    },
}

cc_benchmark {
+2 −0
Original line number Diff line number Diff line
@@ -289,6 +289,8 @@ static void ReadCrashInfo(unique_fd& fd, siginfo_t* siginfo,
      process_info->fdsan_table_address = crash_info->data.d.fdsan_table_address;
      process_info->gwp_asan_state = crash_info->data.d.gwp_asan_state;
      process_info->gwp_asan_metadata = crash_info->data.d.gwp_asan_metadata;
      process_info->scudo_stack_depot = crash_info->data.d.scudo_stack_depot;
      process_info->scudo_region_info = crash_info->data.d.scudo_region_info;
      FALLTHROUGH_INTENDED;
    case 1:
    case 2:
+170 −0
Original line number Diff line number Diff line
@@ -31,6 +31,9 @@

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

#include <android-base/cmsg.h>
@@ -331,6 +334,173 @@ TEST_F(CrasherTest, tagged_fault_addr) {
      R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr (0x100000000000dead|0xdead))");
}

#if defined(__aarch64__) && defined(ANDROID_EXPERIMENTAL_MTE)
static void SetTagCheckingLevelSync() {
  int tagged_addr_ctrl = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0);
  if (tagged_addr_ctrl < 0) {
    abort();
  }

  tagged_addr_ctrl = (tagged_addr_ctrl & ~PR_MTE_TCF_MASK) | PR_MTE_TCF_SYNC;
  if (prctl(PR_SET_TAGGED_ADDR_CTRL, tagged_addr_ctrl, 0, 0, 0) != 0) {
    abort();
  }

  HeapTaggingLevel heap_tagging_level = M_HEAP_TAGGING_LEVEL_SYNC;
  if (!android_mallopt(M_SET_HEAP_TAGGING_LEVEL, &heap_tagging_level, sizeof(heap_tagging_level))) {
    abort();
  }
}
#endif

TEST_F(CrasherTest, mte_uaf) {
#if defined(__aarch64__) && defined(ANDROID_EXPERIMENTAL_MTE)
  if (!mte_supported()) {
    GTEST_SKIP() << "Requires MTE";
  }

  int intercept_result;
  unique_fd output_fd;
  StartProcess([]() {
    SetTagCheckingLevelSync();
    volatile int* p = (volatile int*)malloc(16);
    free((void *)p);
    p[0] = 42;
  });

  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 9 \(SEGV_MTESERR\))");
  ASSERT_MATCH(result, R"(Cause: \[MTE\]: Use After Free, 0 bytes into a 16-byte allocation)");
#else
  GTEST_SKIP() << "Requires aarch64 + ANDROID_EXPERIMENTAL_MTE";
#endif
}

TEST_F(CrasherTest, mte_overflow) {
#if defined(__aarch64__) && defined(ANDROID_EXPERIMENTAL_MTE)
  if (!mte_supported()) {
    GTEST_SKIP() << "Requires MTE";
  }

  int intercept_result;
  unique_fd output_fd;
  StartProcess([]() {
    SetTagCheckingLevelSync();
    volatile int* p = (volatile int*)malloc(16);
    p[4] = 42;
  });

  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\))");
  ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Overflow, 0 bytes right of a 16-byte allocation)");
#else
  GTEST_SKIP() << "Requires aarch64 + ANDROID_EXPERIMENTAL_MTE";
#endif
}

TEST_F(CrasherTest, mte_underflow) {
#if defined(__aarch64__) && defined(ANDROID_EXPERIMENTAL_MTE)
  if (!mte_supported()) {
    GTEST_SKIP() << "Requires MTE";
  }

  int intercept_result;
  unique_fd output_fd;
  StartProcess([]() {
    SetTagCheckingLevelSync();
    volatile int* p = (volatile int*)malloc(16);
    p[-1] = 42;
  });

  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 9 \(SEGV_MTESERR\))");
  ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Underflow, 4 bytes left of a 16-byte allocation)");
#else
  GTEST_SKIP() << "Requires aarch64 + ANDROID_EXPERIMENTAL_MTE";
#endif
}

TEST_F(CrasherTest, mte_multiple_causes) {
#if defined(__aarch64__) && defined(ANDROID_EXPERIMENTAL_MTE)
  if (!mte_supported()) {
    GTEST_SKIP() << "Requires MTE";
  }

  int intercept_result;
  unique_fd output_fd;
  StartProcess([]() {
    SetTagCheckingLevelSync();

    // Make two allocations with the same tag and close to one another. Check for both properties
    // with a bounds check -- this relies on the fact that only if the allocations have the same tag
    // would they be measured as closer than 128 bytes to each other. Otherwise they would be about
    // (some non-zero value << 56) apart.
    //
    // The out-of-bounds access will be considered either an overflow of one or an underflow of the
    // other.
    std::set<uintptr_t> allocs;
    for (int i = 0; i != 4096; ++i) {
      uintptr_t alloc = reinterpret_cast<uintptr_t>(malloc(16));
      auto it = allocs.insert(alloc).first;
      if (it != allocs.begin() && *std::prev(it) + 128 > alloc) {
        *reinterpret_cast<int*>(*std::prev(it) + 16) = 42;
      }
      if (std::next(it) != allocs.end() && alloc + 128 > *std::next(it)) {
        *reinterpret_cast<int*>(alloc + 16) = 42;
      }
    }
  });

  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\))");
  ASSERT_MATCH(
      result,
      R"(Note: multiple potential causes for this crash were detected, listing them in decreasing order of probability.)");

  // Adjacent untracked allocations may cause us to see the wrong underflow here (or only
  // overflows), so we can't match explicitly for an underflow message.
  ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Overflow, 0 bytes right of a 16-byte allocation)");
#else
  GTEST_SKIP() << "Requires aarch64 + ANDROID_EXPERIMENTAL_MTE";
#endif
}

TEST_F(CrasherTest, LD_PRELOAD) {
  int intercept_result;
  unique_fd output_fd;
+2 −0
Original line number Diff line number Diff line
@@ -40,6 +40,8 @@ struct debugger_process_info {
  void* fdsan_table;
  const gwp_asan::AllocatorState* gwp_asan_state;
  const gwp_asan::AllocationMetadata* gwp_asan_metadata;
  const char* scudo_stack_depot;
  const char* scudo_region_info;
};

// These callbacks are called in a signal handler, and thus must be async signal safe.
+42 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once

#include "types.h"
#include "utility.h"

#include <memory.h>

#include "scudo/interface.h"

class ScudoCrashData {
 public:
  ScudoCrashData() = delete;
  ~ScudoCrashData() = default;
  ScudoCrashData(unwindstack::Memory* process_memory, const ProcessInfo& process_info);

  bool CrashIsMine() const;

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

 private:
  scudo_error_info error_info_ = {};
  uintptr_t untagged_fault_addr_;

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