#ifdef UNSAFE_BUFFERS_BUILD
#pragma allow_unsafe_buffers
#endif
#include "base/memory/raw_ptr_asan_service.h"
#if PA_BUILDFLAG(USE_ASAN_BACKUP_REF_PTR)
#include <sanitizer/allocator_interface.h>
#include <sanitizer/asan_interface.h>
#include <stdarg.h>
#include <string.h>
#include "base/check_op.h"
#include "base/compiler_specific.h"
#include "base/debug/asan_service.h"
#include "base/immediate_crash.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ptr_asan_bound_arg_tracker.h"
#include "base/memory/raw_ptr_asan_hooks.h"
#include "base/process/process.h"
#include "base/strings/stringprintf.h"
#include "base/task/thread_pool/thread_group.h"
#include "third_party/abseil-cpp/absl/base/attributes.h"
namespace base {
RawPtrAsanService RawPtrAsanService::instance_;
namespace {
constexpr size_t kShadowScale = 3;
constexpr size_t kChunkHeaderSize = 16;
constexpr uint8_t kAsanHeapLeftRedzoneMagic = 0xfa;
constexpr uint8_t kAsanUserPoisonedMemoryMagic = 0xf7;
ABSL_CONST_INIT thread_local RawPtrAsanService::PendingReport pending_report;
}
NO_SANITIZE("address")
void RawPtrAsanService::MallocHook(const volatile void* ptr, size_t size) {
uint8_t* header =
static_cast<uint8_t*>(const_cast<void*>(ptr)) - kChunkHeaderSize;
*RawPtrAsanService::GetInstance().GetShadow(header) =
kAsanUserPoisonedMemoryMagic;
}
NO_SANITIZE("address")
bool RawPtrAsanService::IsSupportedAllocation(void* allocation_start) const {
uint8_t* header = static_cast<uint8_t*>(allocation_start) - kChunkHeaderSize;
return *GetShadow(header) == kAsanUserPoisonedMemoryMagic;
}
NO_SANITIZE("address")
void RawPtrAsanService::Configure(
EnableDereferenceCheck enable_dereference_check,
EnableExtractionCheck enable_extraction_check,
EnableInstantiationCheck enable_instantiation_check) {
CHECK_EQ(mode_, Mode::kUninitialized);
Mode new_mode = enable_dereference_check || enable_extraction_check ||
enable_instantiation_check
? Mode::kEnabled
: Mode::kDisabled;
if (new_mode == Mode::kEnabled) {
size_t shadow_scale;
__asan_get_shadow_mapping(&shadow_scale, &shadow_offset_);
CHECK_EQ(shadow_scale, kShadowScale);
uint8_t* dummy_alloc = new uint8_t;
CHECK_EQ(*GetShadow(dummy_alloc - kChunkHeaderSize),
kAsanHeapLeftRedzoneMagic);
__asan_poison_memory_region(dummy_alloc, 1);
CHECK_EQ(*GetShadow(dummy_alloc), kAsanUserPoisonedMemoryMagic);
delete dummy_alloc;
__sanitizer_install_malloc_and_free_hooks(MallocHook, FreeHook);
debug::AsanService::GetInstance()->AddErrorCallback(ErrorReportCallback);
internal::InstallRawPtrHooks(base::internal::GetRawPtrAsanHooks());
is_dereference_check_enabled_ = !!enable_dereference_check;
is_extraction_check_enabled_ = !!enable_extraction_check;
is_instantiation_check_enabled_ = !!enable_instantiation_check;
}
mode_ = new_mode;
}
uint8_t* RawPtrAsanService::GetShadow(void* ptr) const {
return reinterpret_cast<uint8_t*>(
(reinterpret_cast<uintptr_t>(ptr) >> kShadowScale) + shadow_offset_);
}
void RawPtrAsanService::SetPendingReport(ReportType type,
const volatile void* ptr) {
void* region_base;
size_t region_size;
__asan_locate_address(const_cast<void*>(ptr), nullptr, 0, ®ion_base,
®ion_size);
pending_report = {type, reinterpret_cast<uintptr_t>(region_base),
region_size};
}
namespace {
enum class ProtectionStatus {
kNotProtected,
kManualAnalysisRequired,
kProtected,
};
const char* ProtectionStatusToString(ProtectionStatus status) {
switch (status) {
case ProtectionStatus::kNotProtected:
return "NOT PROTECTED";
case ProtectionStatus::kManualAnalysisRequired:
return "MANUAL ANALYSIS REQUIRED";
case ProtectionStatus::kProtected:
return "PROTECTED";
}
}
int GetCurrentThreadId() {
int* dummy = new int;
int id = -1;
__asan_get_alloc_stack(dummy, nullptr, 0, &id);
delete dummy;
return id;
}
}
void RawPtrAsanService::ErrorReportCallback(const char* report, bool*) {
if (strcmp(__asan_get_report_description(), "heap-use-after-free") != 0) {
return;
}
struct {
ProtectionStatus protection_status;
const char* crash_details;
const char* protection_details;
} crash_info;
uintptr_t ptr = reinterpret_cast<uintptr_t>(__asan_get_report_address());
uintptr_t bound_arg_ptr = RawPtrAsanBoundArgTracker::GetProtectedArgPtr(ptr);
if (pending_report.allocation_base <= ptr &&
ptr < pending_report.allocation_base + pending_report.allocation_size) {
bool is_supported_allocation =
RawPtrAsanService::GetInstance().IsSupportedAllocation(
reinterpret_cast<void*>(pending_report.allocation_base));
switch (pending_report.type) {
case ReportType::kDereference: {
if (is_supported_allocation) {
crash_info = {ProtectionStatus::kProtected,
"This crash occurred while a raw_ptr<T> object "
"containing a dangling pointer was being dereferenced.",
"MiraclePtr is expected to make this crash "
"non-exploitable once fully enabled."};
} else {
crash_info = {ProtectionStatus::kNotProtected,
"This crash occurred while accessing a region that was "
"allocated before MiraclePtr was activated.",
"This crash is still exploitable with MiraclePtr."};
}
break;
}
case ReportType::kExtraction: {
if (is_supported_allocation && bound_arg_ptr) {
crash_info = {
ProtectionStatus::kProtected,
"This crash occurred inside a callback where a raw_ptr<T> "
"pointing to the same region was bound to one of the arguments.",
"MiraclePtr is expected to make this crash non-exploitable once "
"fully enabled."};
} else if (is_supported_allocation) {
crash_info = {
ProtectionStatus::kManualAnalysisRequired,
"A pointer to the same region was extracted from a raw_ptr<T> "
"object prior to this crash.",
"To determine the protection status, enable extraction warnings "
"and check whether the raw_ptr<T> object can be destroyed or "
"overwritten between the extraction and use."};
} else {
crash_info = {ProtectionStatus::kNotProtected,
"This crash occurred while accessing a region that was "
"allocated before MiraclePtr was activated.",
"This crash is still exploitable with MiraclePtr."};
}
break;
}
case ReportType::kInstantiation: {
crash_info = {ProtectionStatus::kNotProtected,
"A pointer to an already freed region was assigned to a "
"raw_ptr<T> object, which may lead to memory corruption.",
"This crash is still exploitable with MiraclePtr."};
}
}
} else if (bound_arg_ptr) {
bool is_supported_allocation =
RawPtrAsanService::GetInstance().IsSupportedAllocation(
reinterpret_cast<void*>(bound_arg_ptr));
if (is_supported_allocation) {
crash_info = {
ProtectionStatus::kProtected,
"This crash occurred inside a callback where a raw_ptr<T> pointing "
"to the same region was bound to one of the arguments.",
"MiraclePtr is expected to make this crash non-exploitable once "
"fully enabled."};
} else {
crash_info = {ProtectionStatus::kNotProtected,
"This crash occurred while accessing a region that was "
"allocated before MiraclePtr was activated.",
"This crash is still exploitable with MiraclePtr."};
}
} else {
crash_info = {
ProtectionStatus::kNotProtected,
"No raw_ptr<T> access to this region was detected prior to this crash.",
"This crash is still exploitable with MiraclePtr."};
}
if (crash_info.protection_status != ProtectionStatus::kNotProtected) {
int free_thread_id = -1;
__asan_get_free_stack(reinterpret_cast<void*>(ptr), nullptr, 0,
&free_thread_id);
if (free_thread_id != GetCurrentThreadId()) {
crash_info.protection_status = ProtectionStatus::kManualAnalysisRequired;
crash_info.protection_details =
"The \"use\" and \"free\" threads don't match. This crash is likely "
"to have been caused by a race condition that is mislabeled as a "
"use-after-free. Make sure that the \"free\" is sequenced after the "
"\"use\" (e.g. both are on the same sequence, or the \"free\" is in "
"a task posted after the \"use\"). Otherwise, the crash is still "
"exploitable with MiraclePtr.";
} else if (internal::ThreadGroup::CurrentThreadHasGroup()) {
crash_info.protection_status = ProtectionStatus::kManualAnalysisRequired;
crash_info.protection_details =
"This crash occurred in the thread pool. The sequence which invoked "
"the \"free\" is unknown, so the crash may have been caused by a "
"race condition that is mislabeled as a use-after-free. Make sure "
"that the \"free\" is sequenced after the \"use\" (e.g. both are on "
"the same sequence, or the \"free\" is in a task posted after the "
"\"use\"). Otherwise, the crash is still exploitable with "
"MiraclePtr.";
}
}
debug::AsanService::GetInstance()->Log(
"\nMiraclePtr Status: %s\n%s\n%s\n"
"Refer to "
"https://chromium.googlesource.com/chromium/src/+/main/base/memory/"
"raw_ptr.md for details.",
ProtectionStatusToString(crash_info.protection_status),
crash_info.crash_details, crash_info.protection_details);
}
namespace {
enum class MessageLevel {
kWarning,
kError,
};
const char* LevelToString(MessageLevel level) {
switch (level) {
case MessageLevel::kWarning:
return "WARNING";
case MessageLevel::kError:
return "ERROR";
}
}
void Log(MessageLevel level,
uintptr_t address,
const char* type,
const char* description) {
#if __has_builtin(__builtin_extract_return_addr) && \
__has_builtin(__builtin_return_address)
void* pc = __builtin_extract_return_addr(__builtin_return_address(0));
#else
void* pc = nullptr;
#endif
#if __has_builtin(__builtin_frame_address)
void* bp = __builtin_frame_address(0);
#else
void* bp = nullptr;
#endif
void* local_stack;
void* sp = &local_stack;
debug::AsanService::GetInstance()->Log(
"=================================================================\n"
"==%d==%s: MiraclePtr: %s on address %p at pc %p bp %p sp %p",
Process::Current().Pid(), LevelToString(level), type, address, pc, bp,
sp);
__sanitizer_print_stack_trace();
__asan_describe_address(reinterpret_cast<void*>(address));
debug::AsanService::GetInstance()->Log(
"%s\n"
"=================================================================",
description);
}
}
void RawPtrAsanService::WarnOnDanglingExtraction(
const volatile void* ptr) const {
Log(MessageLevel::kWarning, reinterpret_cast<uintptr_t>(ptr),
"dangling-pointer-extraction",
"A regular ASan report will follow if the extracted pointer is "
"dereferenced later.\n"
"Otherwise, it is still likely a bug to rely on the address of an "
"already freed allocation.\n"
"Refer to "
"https://chromium.googlesource.com/chromium/src/+/main/base/memory/"
"raw_ptr.md for details.");
}
void RawPtrAsanService::CrashOnDanglingInstantiation(
const volatile void* ptr) const {
Log(MessageLevel::kError, reinterpret_cast<uintptr_t>(ptr),
"dangling-pointer-instantiation",
"This crash occurred due to an attempt to assign a dangling pointer to a "
"raw_ptr<T> variable, which might lead to use-after-free.\n"
"Note that this report might be a false positive if at the moment of the "
"crash another raw_ptr<T> is guaranteed to keep the allocation alive.\n"
"Refer to "
"https://chromium.googlesource.com/chromium/src/+/main/base/memory/"
"raw_ptr.md for details.");
base::ImmediateCrash();
}
}
#endif