blob: 88f951915bffa8094326404b00f81d8dc0890e69 [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.
#ifndef BASE_MEMORY_RAW_PTR_ASAN_EVENT_H_
#define BASE_MEMORY_RAW_PTR_ASAN_EVENT_H_
#include "partition_alloc/buildflags.h"
#if PA_BUILDFLAG(USE_ASAN_BACKUP_REF_PTR_V2)
#include <array>
#include "base/memory/raw_ptr_asan_allocator.h"
#include "base/thread_annotations.h"
#include "base/threading/platform_thread.h"
#include "partition_alloc/partition_lock.h"
namespace base::internal {
template <typename T>
struct RawPtrAsanAllocator;
template <typename T>
using RawPtrAsanVector = std::vector<T, RawPtrAsanAllocator<T>>;
// RawPtrAsanThreadId is used inside MallocHook(). So if any memory allocation,
// e.g. malloc() or tls-alloc() or mmap(), ... is required to obtain the id,
// it will cause stack-overflow because the memory allocation is hooked by
// ASAN and ASAN invokes MallocHook() again.
// If using `PlatformThreadId`, i.e. PlatformThread::CurrentId(), it seems to
// have 2 problems. One is depending on syscall(__NR_gettid). syscall() is
// very slow. The other is depending on `thread_local` to avoid slow syscall()
// multiple times. i.e. `thread_local pid_t g_thread_id`. `thread_local` may
// causes memory allocation.
// If using `SequenceToken`, `SequenceToken::GetForCurrentThread()` and
// `internal::ThreadGroup::CurrentThreadHasGroup()` will cause the problem,
// because the both methods depends on `thread_local` variables, i.e.
// `current_sequence_token` and `current_thread_group`.
using RawPtrAsanThreadId = PlatformThreadRef;
inline RawPtrAsanThreadId GetCurrentRawPtrAsanThreadId() {
return PlatformThreadBase::CurrentRef();
}
// We collect a log of "relevant" events at runtime, and then either when a
// fatal crash occurs or at process exit we can process this log and determine
// whether any events occurred that should be reported, and whether those events
// should be considered to be protected by MiraclePtr.
//
// RawPtrAsanEvent is the type used to store these events, along with metadata
// such as the stack trace when the event occurred.
struct RawPtrAsanEvent {
enum class Type : uint8_t {
kQuarantineEntry,
kQuarantineAssignment,
kQuarantineRead,
kQuarantineWrite,
kQuarantineExit,
kFreeAssignment,
};
// TODO(crbug.com/447520906): base::debug::AsanService::Log() causes memory
// allocation/deallocation because it depends std::string. The method must
// not be used inside malloc_hook(), ignore_free_hook() and also free_hook().
// So the following methods: PrintEvent() and PrintEventStack() depend on
// AsanService::Log(), we must not use them inside the hooks. We will solve
// the Log()'s memory allocation issue later, e.g. use PartitionAlloc instead.
void PrintEvent(bool print_stack) const;
void PrintEventStack() const;
bool IsSameAllocation(const RawPtrAsanEvent& other) const {
return (address <= other.address && other.address <= address + size) ||
(other.address <= address && address <= other.address + other.size);
}
uintptr_t fault_address;
Type type;
RawPtrAsanThreadId thread_id;
uintptr_t address;
size_t size;
std::array<const void*, 12> stack;
};
// Since RawPtrAsanService is statically initialized, and we need to be able
// to access it in extremely hot paths, we move the logging into a separate
// class.
class RawPtrAsanEventLog {
public:
RawPtrAsanEventLog();
~RawPtrAsanEventLog();
void Add(RawPtrAsanEvent&& event) LOCKS_EXCLUDED(GetLock());
void Print(bool print_stack) LOCKS_EXCLUDED(GetLock());
void ClearForTesting() LOCKS_EXCLUDED(GetLock()) { // IN-TEST
internal::PartitionAutoLock lock(GetLock());
events_.clear();
}
internal::PartitionLock& GetLock() { return lock_; }
RawPtrAsanVector<RawPtrAsanEvent>& events()
EXCLUSIVE_LOCKS_REQUIRED(GetLock()) {
return events_;
}
private:
internal::PartitionLock lock_;
RawPtrAsanVector<RawPtrAsanEvent> events_ GUARDED_BY(GetLock());
};
} // namespace base::internal
#endif // PA_BUILDFLAG(USE_ASAN_BACKUP_REF_PTR_V2)
#endif // BASE_MEMORY_RAW_PTR_ASAN_EVENT_H_