123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157 |
- // Copyright 2021 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include <cstdint>
- #include <string>
- #include <vector>
- #include "base/allocator/partition_allocator/partition_alloc_config.h"
- #include "base/allocator/partition_allocator/partition_freelist_entry.h"
- #include "base/allocator/partition_allocator/partition_page.h"
- #include "base/allocator/partition_allocator/partition_root.h"
- #include "build/build_config.h"
- #include "testing/gtest/include/gtest/gtest.h"
- // With *SAN, PartitionAlloc is rerouted to malloc().
- #if !defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
- namespace partition_alloc::internal {
- namespace {
- // Death tests misbehave on Android, crbug.com/1240184
- #if !BUILDFLAG(IS_ANDROID) && defined(GTEST_HAS_DEATH_TEST) && \
- defined(PA_HAS_FREELIST_SHADOW_ENTRY)
- TEST(HardeningTest, PartialCorruption) {
- std::string important_data("very important");
- char* to_corrupt = const_cast<char*>(important_data.c_str());
- PartitionRoot<ThreadSafe> root({
- PartitionOptions::AlignedAlloc::kAllowed,
- PartitionOptions::ThreadCache::kDisabled,
- PartitionOptions::Quarantine::kDisallowed,
- PartitionOptions::Cookie::kDisallowed,
- PartitionOptions::BackupRefPtr::kDisabled,
- PartitionOptions::BackupRefPtrZapping::kDisabled,
- PartitionOptions::UseConfigurablePool::kNo,
- });
- root.UncapEmptySlotSpanMemoryForTesting();
- const size_t kAllocSize = 100;
- void* data = root.Alloc(kAllocSize, "");
- void* data2 = root.Alloc(kAllocSize, "");
- root.Free(data2);
- root.Free(data);
- // root->bucket->active_slot_span_head->freelist_head points to data, next_
- // points to data2. We can corrupt *data to get overwrite the next_ pointer.
- // Even if it looks reasonable (valid encoded pointer), freelist corruption
- // detection will make the code crash, because shadow_ doesn't match
- // encoded_next_.
- PartitionFreelistEntry::EmplaceAndInitForTest(root.ObjectToSlotStart(data),
- to_corrupt, false);
- EXPECT_DEATH(root.Alloc(kAllocSize, ""), "");
- }
- TEST(HardeningTest, OffHeapPointerCrashing) {
- std::string important_data("very important");
- char* to_corrupt = const_cast<char*>(important_data.c_str());
- PartitionRoot<ThreadSafe> root({
- PartitionOptions::AlignedAlloc::kAllowed,
- PartitionOptions::ThreadCache::kDisabled,
- PartitionOptions::Quarantine::kDisallowed,
- PartitionOptions::Cookie::kDisallowed,
- PartitionOptions::BackupRefPtr::kDisabled,
- PartitionOptions::BackupRefPtrZapping::kDisabled,
- PartitionOptions::UseConfigurablePool::kNo,
- });
- root.UncapEmptySlotSpanMemoryForTesting();
- const size_t kAllocSize = 100;
- void* data = root.Alloc(kAllocSize, "");
- void* data2 = root.Alloc(kAllocSize, "");
- root.Free(data2);
- root.Free(data);
- // See "PartialCorruption" above for details. This time, make shadow_
- // consistent.
- PartitionFreelistEntry::EmplaceAndInitForTest(root.ObjectToSlotStart(data),
- to_corrupt, true);
- // Crashes, because |to_corrupt| is not on the same superpage as data.
- EXPECT_DEATH(root.Alloc(kAllocSize, ""), "");
- }
- TEST(HardeningTest, MetadataPointerCrashing) {
- PartitionRoot<ThreadSafe> root({
- PartitionOptions::AlignedAlloc::kAllowed,
- PartitionOptions::ThreadCache::kDisabled,
- PartitionOptions::Quarantine::kDisallowed,
- PartitionOptions::Cookie::kDisallowed,
- PartitionOptions::BackupRefPtr::kDisabled,
- PartitionOptions::BackupRefPtrZapping::kDisabled,
- PartitionOptions::UseConfigurablePool::kNo,
- });
- root.UncapEmptySlotSpanMemoryForTesting();
- const size_t kAllocSize = 100;
- void* data = root.Alloc(kAllocSize, "");
- void* data2 = root.Alloc(kAllocSize, "");
- root.Free(data2);
- root.Free(data);
- uintptr_t slot_start = root.ObjectToSlotStart(data);
- auto* metadata = SlotSpanMetadata<ThreadSafe>::FromSlotStart(slot_start);
- PartitionFreelistEntry::EmplaceAndInitForTest(slot_start, metadata, true);
- // Crashes, because |metadata| points inside the metadata area.
- EXPECT_DEATH(root.Alloc(kAllocSize, ""), "");
- }
- #endif // !BUILDFLAG(IS_ANDROID) && defined(GTEST_HAS_DEATH_TEST) &&
- // defined(PA_HAS_FREELIST_SHADOW_ENTRY)
- TEST(HardeningTest, SuccessfulCorruption) {
- PartitionRoot<ThreadSafe> root({
- PartitionOptions::AlignedAlloc::kAllowed,
- PartitionOptions::ThreadCache::kDisabled,
- PartitionOptions::Quarantine::kDisallowed,
- PartitionOptions::Cookie::kDisallowed,
- PartitionOptions::BackupRefPtr::kDisabled,
- PartitionOptions::BackupRefPtrZapping::kDisabled,
- PartitionOptions::UseConfigurablePool::kNo,
- });
- root.UncapEmptySlotSpanMemoryForTesting();
- uintptr_t* zero_vector = reinterpret_cast<uintptr_t*>(
- root.AllocWithFlags(AllocFlags::kZeroFill, 100 * sizeof(uintptr_t), ""));
- ASSERT_TRUE(zero_vector);
- // Pointer to the middle of an existing allocation.
- uintptr_t* to_corrupt = zero_vector + 20;
- const size_t kAllocSize = 100;
- void* data = root.Alloc(kAllocSize, "");
- void* data2 = root.Alloc(kAllocSize, "");
- root.Free(data2);
- root.Free(data);
- PartitionFreelistEntry::EmplaceAndInitForTest(root.ObjectToSlotStart(data),
- to_corrupt, true);
- // Next allocation is what was in
- // root->bucket->active_slot_span_head->freelist_head, so not the corrupted
- // pointer.
- void* new_data = root.Alloc(kAllocSize, "");
- ASSERT_EQ(new_data, data);
- // Not crashing, because a zeroed area is a "valid" freelist entry.
- void* new_data2 = root.Alloc(kAllocSize, "");
- // Now we have a pointer to the middle of an existing allocation.
- EXPECT_EQ(new_data2, to_corrupt);
- }
- } // namespace
- } // namespace partition_alloc::internal
- #endif // !defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
|