// Copyright 2022 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifdef UNSAFE_BUFFERS_BUILD // TODO(crbug.com/40284755): Remove this and spanify to fix the errors. #pragma allow_unsafe_buffers #endif #include "base/memory/raw_ptr_asan_service.h" #if PA_BUILDFLAG(USE_ASAN_BACKUP_REF_PTR) #include #include #include #include #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" namespace base { RawPtrAsanService RawPtrAsanService::instance_; namespace { // https://github.com/llvm/llvm-project/blob/b84673b3f424882c4c1961fb2c49b6302b68f344/compiler-rt/lib/asan/asan_mapping.h#L154 constexpr size_t kShadowScale = 3; // https://github.com/llvm/llvm-project/blob/b84673b3f424882c4c1961fb2c49b6302b68f344/compiler-rt/lib/asan/asan_allocator.cpp#L143 constexpr size_t kChunkHeaderSize = 16; // https://github.com/llvm/llvm-project/blob/b84673b3f424882c4c1961fb2c49b6302b68f344/compiler-rt/lib/asan/asan_internal.h#L138 constexpr uint8_t kAsanHeapLeftRedzoneMagic = 0xfa; // https://github.com/llvm/llvm-project/blob/b84673b3f424882c4c1961fb2c49b6302b68f344/compiler-rt/lib/asan/asan_internal.h#L145 constexpr uint8_t kAsanUserPoisonedMemoryMagic = 0xf7; // Intentionally use thread-local-storage here. Making this sequence-local // doesn't prevent sharing of PendingReport contents between unrelated tasks, so // we keep this at a lower-level and avoid introducing additional assumptions // about Chrome's sequence model. constinit thread_local RawPtrAsanService::PendingReport pending_report; } // namespace // Mark the first eight bytes of every allocation's header as "user poisoned". // This allows us to filter out allocations made before BRP-ASan is activated. // The change shouldn't reduce the regular ASan coverage. // static NO_SANITIZE("address") void RawPtrAsanService::MallocHook(const volatile void* ptr, size_t size) { uint8_t* header = static_cast(const_cast(ptr)) - kChunkHeaderSize; *RawPtrAsanService::GetInstance().GetShadow(header) = kAsanUserPoisonedMemoryMagic; } NO_SANITIZE("address") bool RawPtrAsanService::IsSupportedAllocation(void* allocation_start) const { uint8_t* header = static_cast(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) { // The constants we use aren't directly exposed by the API, so // validate them at runtime as carefully as possible. 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( (reinterpret_cast(ptr) >> kShadowScale) + shadow_offset_); } // static void RawPtrAsanService::SetPendingReport(ReportType type, const volatile void* ptr) { // The actual ASan crash may occur at an offset from the pointer passed // here, so track the whole region. void* region_base; size_t region_size; __asan_locate_address(const_cast(ptr), nullptr, 0, ®ion_base, ®ion_size); pending_report = {type, reinterpret_cast(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"; } } // ASan doesn't have an API to get the current thread's identifier. // We have to create a dummy allocation to determine it. int GetCurrentThreadId() { int* dummy = new int; int id = -1; __asan_get_alloc_stack(dummy, nullptr, 0, &id); delete dummy; return id; } } // namespace // static 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(__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(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 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 " "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 " "object prior to this crash.", "To determine the protection status, enable extraction warnings " "and check whether the raw_ptr 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 object, which may lead to memory corruption.", "This crash is still exploitable with MiraclePtr."}; } } } else if (bound_arg_ptr) { // Note - this branch comes second to avoid hiding invalid instantiations, // as we still consider it to be an error to instantiate a raw_ptr from // an invalid T* even if that T* is guaranteed to be quarantined. bool is_supported_allocation = RawPtrAsanService::GetInstance().IsSupportedAllocation( reinterpret_cast(bound_arg_ptr)); if (is_supported_allocation) { crash_info = { ProtectionStatus::kProtected, "This crash occurred inside a callback where a raw_ptr 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 access to this region was detected prior to this crash.", "This crash is still exploitable with MiraclePtr."}; } // The race condition check below may override the protection status. if (crash_info.protection_status != ProtectionStatus::kNotProtected) { int free_thread_id = -1; __asan_get_free_stack(reinterpret_cast(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()) { // We need to be especially careful with ThreadPool threads. Otherwise, // we might miss false-positives where the "use" and "free" happen on // different sequences but the same thread by chance. 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"; } } // Prints AddressSanitizer-like custom error messages. 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(address)); debug::AsanService::GetInstance()->Log( "%s\n" "=================================================================", description); } } // namespace void RawPtrAsanService::WarnOnDanglingExtraction( const volatile void* ptr) const { Log(MessageLevel::kWarning, reinterpret_cast(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(ptr), "dangling-pointer-instantiation", "This crash occurred due to an attempt to assign a dangling pointer to a " "raw_ptr 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 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(); } } // namespace base #endif // PA_BUILDFLAG(USE_ASAN_BACKUP_REF_PTR)