blob: 54a6752b87db1bc23c1d430b5bc01779c972a876 [file] [log] [blame]
// Copyright 2019 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 "components/gwp_asan/client/sampling_partitionalloc_shims.h"
#include <stdlib.h>
#include <algorithm>
#include <iterator>
#include <set>
#include <string>
#include "base/allocator/partition_allocator/partition_alloc.h"
#include "base/bind_helpers.h"
#include "base/partition_alloc_buildflags.h"
#include "base/process/process_metrics.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/gtest_util.h"
#include "base/test/multiprocess_test.h"
#include "base/test/test_timeouts.h"
#include "build/build_config.h"
#include "components/crash/core/common/crash_key.h"
#include "components/gwp_asan/client/guarded_page_allocator.h"
#include "components/gwp_asan/common/crash_key_name.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/multiprocess_func_list.h"
// PartitionAlloc (and hence hooking) are disabled with sanitizers that replace
// allocation routines.
#if !defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
// These tests install global PartitionAlloc hooks so they are not safe to run
// in multi-threaded contexts. Instead they're implemented as multi-process
// tests.
namespace gwp_asan {
namespace internal {
extern GuardedPageAllocator& GetPartitionAllocGpaForTesting();
namespace {
constexpr const char* kFakeType = "fake type";
constexpr const char* kFakeType2 = "fake type #2";
constexpr size_t kSamplingFrequency = 10;
// Number of loop iterations required to definitely hit a sampled allocation.
constexpr size_t kLoopIterations = kSamplingFrequency * 4;
constexpr int kSuccess = 0;
constexpr int kFailure = 1;
class SamplingPartitionAllocShimsTest : public base::MultiProcessTest {
public:
static void multiprocessTestSetup() {
crash_reporter::InitializeCrashKeys();
InstallPartitionAllocHooks(
AllocatorState::kMaxMetadata, AllocatorState::kMaxMetadata,
AllocatorState::kMaxSlots, kSamplingFrequency, base::DoNothing());
}
protected:
void runTest(const char* name) {
base::Process process = SpawnChild(name);
int exit_code = -1;
ASSERT_TRUE(WaitForMultiprocessTestChildExit(
process, TestTimeouts::action_max_timeout(), &exit_code));
EXPECT_EQ(exit_code, kSuccess);
}
};
MULTIPROCESS_TEST_MAIN_WITH_SETUP(
BasicFunctionality,
SamplingPartitionAllocShimsTest::multiprocessTestSetup) {
base::PartitionAllocatorGeneric allocator;
allocator.init();
for (size_t i = 0; i < kLoopIterations; i++) {
void* ptr = allocator.root()->Alloc(1, kFakeType);
if (GetPartitionAllocGpaForTesting().PointerIsMine(ptr))
return kSuccess;
allocator.root()->Free(ptr);
}
return kFailure;
}
TEST_F(SamplingPartitionAllocShimsTest, BasicFunctionality) {
runTest("BasicFunctionality");
}
MULTIPROCESS_TEST_MAIN_WITH_SETUP(
Realloc,
SamplingPartitionAllocShimsTest::multiprocessTestSetup) {
base::PartitionAllocatorGeneric allocator;
allocator.init();
void* alloc = GetPartitionAllocGpaForTesting().Allocate(base::GetPageSize());
CHECK_NE(alloc, nullptr);
constexpr unsigned char kFillChar = 0xff;
memset(alloc, kFillChar, base::GetPageSize());
unsigned char* new_alloc = static_cast<unsigned char*>(
allocator.root()->Realloc(alloc, base::GetPageSize() + 1, kFakeType));
CHECK_NE(alloc, new_alloc);
CHECK_EQ(GetPartitionAllocGpaForTesting().PointerIsMine(new_alloc), false);
for (size_t i = 0; i < base::GetPageSize(); i++)
CHECK_EQ(new_alloc[i], kFillChar);
allocator.root()->Free(new_alloc);
return kSuccess;
}
TEST_F(SamplingPartitionAllocShimsTest, Realloc) {
runTest("Realloc");
}
// Ensure sampled GWP-ASan allocations with different types never overlap.
MULTIPROCESS_TEST_MAIN_WITH_SETUP(
DifferentTypesDontOverlap,
SamplingPartitionAllocShimsTest::multiprocessTestSetup) {
base::PartitionAllocatorGeneric allocator;
allocator.init();
std::set<void*> type1, type2;
for (size_t i = 0; i < kLoopIterations * AllocatorState::kMaxSlots; i++) {
void* ptr1 = allocator.root()->Alloc(1, kFakeType);
void* ptr2 = allocator.root()->Alloc(1, kFakeType2);
if (GetPartitionAllocGpaForTesting().PointerIsMine(ptr1))
type1.insert(ptr1);
if (GetPartitionAllocGpaForTesting().PointerIsMine(ptr2))
type2.insert(ptr2);
allocator.root()->Free(ptr1);
allocator.root()->Free(ptr2);
}
std::vector<void*> intersection;
std::set_intersection(type1.begin(), type1.end(), type2.begin(), type2.end(),
std::back_inserter(intersection));
if (intersection.size() != 0)
return kFailure;
return kSuccess;
}
TEST_F(SamplingPartitionAllocShimsTest, DifferentTypesDontOverlap) {
runTest("DifferentTypesDontOverlap");
}
// GetCrashKeyValue() operates on a per-component basis, can't read the crash
// key from the gwp_asan_client component in a component build.
#if !defined(COMPONENT_BUILD)
MULTIPROCESS_TEST_MAIN_WITH_SETUP(
CrashKey,
SamplingPartitionAllocShimsTest::multiprocessTestSetup) {
if (crash_reporter::GetCrashKeyValue(kPartitionAllocCrashKey) !=
GetPartitionAllocGpaForTesting().GetCrashKey()) {
return kFailure;
}
return kSuccess;
}
TEST_F(SamplingPartitionAllocShimsTest, CrashKey) {
runTest("CrashKey");
}
#endif // !defined(COMPONENT_BUILD)
} // namespace
} // namespace internal
} // namespace gwp_asan
#endif // !defined(MEMORY_TOOL_REPLACES_ALLOCATOR)