blob: 333701966b483b3bd8af471f9c56edca93328257 [file]
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/memory/raw_ptr_asan_event.h"
#if PA_BUILDFLAG(USE_ASAN_BACKUP_REF_PTR_V2)
#include <sanitizer/allocator_interface.h>
#include <sanitizer/asan_interface.h>
#include <sstream>
#include "base/compiler_specific.h"
#include "base/debug/asan_service.h"
#include "base/memory/raw_ptr_asan_allocator.h"
#include "base/memory/raw_ptr_asan_service.h"
namespace base::internal {
namespace {
template <typename T>
struct InternalDeleter {
constexpr InternalDeleter() = default;
void operator()(T* ptr) const { RawPtrAsanAllocator<T>().deallocate(ptr, 1); }
};
} // namespace
void RawPtrAsanEvent::PrintEventStack() const {
using BufferType = std::array<char, 4096>;
std::unique_ptr<BufferType, InternalDeleter<BufferType>> buffer(
RawPtrAsanAllocator<BufferType>().allocate(1));
size_t frame_index = 0;
for (size_t i = 0; i < stack.size(); ++i) {
if (!stack[i]) {
break;
}
void* frame_ptr = const_cast<void*>(stack[i]);
__sanitizer_symbolize_pc(frame_ptr, "%p %F %L", buffer->data(),
buffer->size());
for (const char* ptr = buffer->data(); *ptr != '\0';
UNSAFE_BUFFERS(ptr += strlen(ptr))) {
debug::AsanService::GetInstance()->Log(" #%i %s", frame_index++, ptr);
}
}
debug::AsanService::GetInstance()->Log("");
}
void RawPtrAsanEvent::PrintEvent(bool print_stack = false) const {
const char* type_string = "quarantine-entry";
if (type == RawPtrAsanEvent::Type::kQuarantineAssignment) {
type_string = "quarantine-assignment";
} else if (type == RawPtrAsanEvent::Type::kQuarantineRead) {
type_string = "quarantine-read";
} else if (type == RawPtrAsanEvent::Type::kQuarantineWrite) {
type_string = "quarantine-write";
} else if (type == RawPtrAsanEvent::Type::kQuarantineExit) {
type_string = "quarantine-exit";
} else if (type == RawPtrAsanEvent::Type::kFreeAssignment) {
type_string = "free-assignment";
}
std::stringstream ss_for_thread_id;
ss_for_thread_id << thread_id;
debug::AsanService::GetInstance()->Log(
"[0x%zx:%zu] (%s) %s", address, size,
std::move(ss_for_thread_id).str().c_str(), type_string);
if (print_stack) {
PrintEventStack();
}
}
RawPtrAsanEventLog::RawPtrAsanEventLog() = default;
RawPtrAsanEventLog::~RawPtrAsanEventLog() = default;
NO_SANITIZE("address")
void RawPtrAsanEventLog::Add(RawPtrAsanEvent&& event) {
using Type = RawPtrAsanEvent::Type;
internal::PartitionAutoLock event_lock(GetLock());
// Check if we have previous logged accesses to this allocation; this will
// tell us if we need to store it, and whether we need to synthesize the
// corresponding kQuarantineEntry event.
bool has_entry = false;
for (auto iter = events_.rbegin(); iter != events_.rend(); ++iter) {
if (event.IsSameAllocation(*iter)) {
// If the last matching event that we find is a quarantine-exit event,
// then this event refers to a reused allocation that doesn't have any
// logged events.
if (iter->type != Type::kQuarantineExit) {
has_entry = true;
}
break;
}
}
if (event.type == Type::kQuarantineEntry) {
// We don't store kQuarantineEntry events directly, since we can synthesize
// them later when they are needed.
return;
}
if (event.type == Type::kQuarantineExit) {
// kQuarantineExit without any other accesses is uninteresting, we can just
// discard this event as long as the threads match.
RawPtrAsanThreadId free_thread_id =
RawPtrAsanService::GetInstance().GetFreeThreadIdOfAllocation(
event.address);
if (!has_entry && event.thread_id == free_thread_id) {
return;
}
}
if (!has_entry && (event.type == Type::kQuarantineAssignment ||
event.type == Type::kQuarantineRead ||
event.type == Type::kQuarantineWrite ||
event.type == Type::kQuarantineExit)) {
// If we reach here, then this allocation in quarantine just became
// interesting, so we should synthesize a kQuarantineEntry event for it.
RawPtrAsanThreadId free_thread_id =
RawPtrAsanService::GetInstance().GetFreeThreadIdOfAllocation(
event.address);
uintptr_t allocation_start_address =
RawPtrAsanService::GetInstance().GetAllocationStart(event.address);
RawPtrAsanEvent entry_event;
entry_event.type = Type::kQuarantineEntry;
entry_event.thread_id = free_thread_id;
entry_event.address = allocation_start_address;
if (allocation_start_address) {
void* allocation_start_ptr =
reinterpret_cast<void*>(allocation_start_address);
entry_event.size = __sanitizer_get_allocated_size(allocation_start_ptr);
__asan_get_alloc_stack(allocation_start_ptr,
const_cast<void**>(entry_event.stack.data()),
entry_event.stack.size(), nullptr);
} else {
// `ptr` does not point to ASAN allocated memory.
entry_event.size = 0;
}
events_.emplace_back(std::move(entry_event));
}
events_.emplace_back(std::move(event));
}
void RawPtrAsanEventLog::Print(bool print_stack = false) {
internal::PartitionAutoLock lock(GetLock());
for (const auto& event : events_) {
event.PrintEvent(print_stack);
}
}
} // namespace base::internal
#endif // PA_BUILDFLAG(USE_ASAN_BACKUP_REF_PTR_V2)