blob: f418230380e73330101398085c71b14dba7c8b2c [file]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "partition_alloc/slot_start.h"
#include "partition_alloc/in_slot_metadata.h"
#include <atomic>
#include "partition_alloc/build_config.h"
#include "partition_alloc/buildflags.h"
#include "partition_alloc/partition_alloc_base/logging.h"
#include "partition_alloc/partition_bucket.h"
#include "partition_alloc/partition_freelist_entry.h"
#include "partition_alloc/partition_page.h"
#include "partition_alloc/partition_root.h"
#include "partition_alloc/thread_cache.h"
#if PA_BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
namespace partition_alloc::internal {
namespace {
// If double-free, the freed `slot` will be a freelist entry.
bool IsInFreelist(UntaggedSlotStart slot_start,
SlotSpanMetadata* slot_span,
size_t& position) {
size_t slot_size = slot_span->bucket->slot_size;
// Next check if the `slot_start` is in the `slot_span`'s freelist.
FreelistEntry* node = slot_span->get_freelist_head();
size_t length = slot_span->GetFreelistLength();
size_t index = 0;
while (node != nullptr && index < length) {
if (UntagAddr(reinterpret_cast<uintptr_t>(node)) == slot_start.value()) {
// This means `double-free`.
position = index;
return true;
}
// GetNext() causes crash if the freelist is corrupted.
node = node->GetNext(slot_size);
++index;
}
position = 0;
return false;
}
#if PA_BUILDFLAG(IS_IOS)
std::atomic_bool suppress_double_free_detected_crash = false;
std::atomic_bool suppress_corruption_detected_crash = false;
#endif // PA_BUILDFLAG(IS_IOS)
std::atomic<void (*)(uintptr_t)> corruption_detected_fn = nullptr;
} // namespace
// static
void InSlotMetadata::SetCorruptionDetectedFn(void (*fn)(uintptr_t)) {
corruption_detected_fn.store(fn, std::memory_order_relaxed);
}
#if !PA_BUILDFLAG(IS_IOS)
[[noreturn]]
#endif // !PA_BUILDFLAG(IS_IOS)
PA_NOINLINE PA_NOT_TAIL_CALLED void DoubleFreeDetected(size_t position) {
#if PA_BUILDFLAG(IS_IOS)
if (suppress_double_free_detected_crash) {
return;
}
#endif // PA_BUILDFLAG(IS_IOS)
// If the double free happens very soon, `position` will be small.
// We can use the value to estimate how large buffer we need to remember
// freed slots. i.e. |slot_size * position| bytes.
PA_DEBUG_DATA_ON_STACK("entrypos", position);
// If we want to add more data related to the double-free, we will
// add PA_DEBUG_DATA_ON_STACK() here.
PA_NO_CODE_FOLDING();
PA_IMMEDIATE_CRASH();
}
#if !PA_BUILDFLAG(IS_IOS)
[[noreturn]]
#endif // !PA_BUILDFLAG(IS_IOS)
PA_NOINLINE PA_NOT_TAIL_CALLED void CorruptionDetected() {
#if PA_BUILDFLAG(IS_IOS)
if (suppress_corruption_detected_crash) {
return;
}
#endif // PA_BUILDFLAG(IS_IOS)
// If we want to add more data related to the corruption, we will
// add PA_DEBUG_DATA_ON_STACK() here.
PA_NO_CODE_FOLDING();
PA_IMMEDIATE_CRASH();
}
#if !PA_BUILDFLAG(IS_IOS)
[[noreturn]]
#endif // !PA_BUILDFLAG(IS_IOS)
PA_NOINLINE PA_NOT_TAIL_CALLED void
InSlotMetadata::DoubleFreeOrCorruptionDetected(InSlotMetadata::CountType count,
UntaggedSlotStart slot_start,
SlotSpanMetadata* slot_span) {
// Lock the PartitionRoot here, because to travserse SlotSpanMetadata's
// freelist, we need PartitionRootLock().
PartitionRoot* root = PartitionRoot::FromSlotSpanMetadata(slot_span);
size_t slot_size = slot_span->bucket->slot_size;
size_t position = 0;
ScopedGuard scope(PartitionRootLock(root));
PA_DEBUG_DATA_ON_STACK("refcount", count);
// Record `slot_size` here. If crashes inside IsInFreelist(), minidump
// will have `slot_size` in its stack data.
PA_DEBUG_DATA_ON_STACK("slotsize", slot_size);
void (*hook)(uintptr_t) =
corruption_detected_fn.load(std::memory_order_relaxed);
if (hook) {
(*hook)(slot_start.value());
}
auto* thread_cache = root->GetThreadCache();
if (ThreadCache::IsValid(thread_cache)) {
size_t bucket_index = slot_span->bucket - root->buckets_;
if (thread_cache->IsInFreelist(slot_start, bucket_index, position)) {
DoubleFreeDetected(position);
}
}
if (IsInFreelist(slot_start, slot_span, position)) {
DoubleFreeDetected(position);
}
CorruptionDetected();
}
#if PA_BUILDFLAG(IS_IOS)
void SuppressDoubleFreeDetectedCrash() {
suppress_double_free_detected_crash = true;
}
void SuppressCorruptionDetectedCrash() {
suppress_corruption_detected_crash = true;
}
#endif // PA_BUILDFLAG(IS_IOS)
} // namespace partition_alloc::internal
#endif // PA_BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)