blob: d7b6660d13c0d571c34ede636a0a8b84bfe7152e [file] [log] [blame]
// Copyright 2018 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_malloc_shims.h"
#include <stdlib.h>
#include <cstdlib>
#include <memory>
#include <string>
#include "base/allocator/allocator_shim.h"
#include "base/bind_helpers.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"
// These tests install global allocator shims so they are not safe to run in
// multi-threaded contexts. Instead they're implemented as multi-process tests.
#if defined(OS_WIN)
#include <malloc.h>
static size_t GetAllocatedSize(void* mem) {
return _msize(mem);
}
#elif defined(OS_MACOSX)
#include <malloc/malloc.h>
static size_t GetAllocatedSize(void* mem) {
return malloc_size(mem);
}
#elif defined(OS_LINUX)
#include <malloc.h>
static size_t GetAllocatedSize(void* mem) {
return malloc_usable_size(mem);
}
#endif
namespace gwp_asan {
namespace internal {
extern GuardedPageAllocator& GetMallocGpaForTesting();
namespace {
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 SamplingMallocShimsTest : public base::MultiProcessTest {
public:
static void multiprocessTestSetup() {
#if defined(OS_MACOSX)
base::allocator::InitializeAllocatorShim();
#endif // defined(OS_MACOSX)
crash_reporter::InitializeCrashKeys();
InstallMallocHooks(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);
}
};
// Return whether some of the allocations returned by the calling the allocate
// parameter are sampled to the guarded allocator. Keep count of failures
// encountered.
bool allocationCheck(std::function<void*(void)> allocate,
std::function<void(void*)> free,
int* failures) {
size_t guarded = 0;
size_t unguarded = 0;
for (size_t i = 0; i < kLoopIterations; i++) {
std::unique_ptr<void, decltype(free)> alloc(allocate(), free);
EXPECT_NE(alloc.get(), nullptr);
if (!alloc.get()) {
*failures += 1;
return false;
}
if (GetMallocGpaForTesting().PointerIsMine(alloc.get()))
guarded++;
else
unguarded++;
}
if (guarded > 0 && unguarded > 0)
return true;
*failures += 1;
return false;
}
MULTIPROCESS_TEST_MAIN_WITH_SETUP(
BasicFunctionality,
SamplingMallocShimsTest::multiprocessTestSetup) {
const size_t page_size = base::GetPageSize();
int failures = 0;
EXPECT_TRUE(
allocationCheck([&] { return malloc(page_size); }, &free, &failures));
EXPECT_TRUE(
allocationCheck([&] { return calloc(1, page_size); }, &free, &failures));
EXPECT_TRUE(allocationCheck([&] { return realloc(nullptr, page_size); },
&free, &failures));
#if defined(OS_WIN)
EXPECT_TRUE(allocationCheck([&] { return _aligned_malloc(123, 16); },
&_aligned_free, &failures));
EXPECT_TRUE(
allocationCheck([&] { return _aligned_realloc(nullptr, 123, 16); },
&_aligned_free, &failures));
#endif // defined(OS_WIN)
#if defined(OS_POSIX)
EXPECT_TRUE(allocationCheck(
[&]() -> void* {
void* ptr;
if (posix_memalign(&ptr, page_size, page_size))
return nullptr;
return ptr;
},
&free, &failures));
#endif // defined(OS_POSIX)
EXPECT_TRUE(allocationCheck([&] { return std::malloc(page_size); },
&std::free, &failures));
EXPECT_TRUE(allocationCheck([&] { return std::calloc(1, page_size); },
&std::free, &failures));
EXPECT_TRUE(allocationCheck([&] { return std::realloc(nullptr, page_size); },
&std::free, &failures));
EXPECT_TRUE(allocationCheck([] { return new int; },
[](void* ptr) { delete (int*)ptr; }, &failures));
EXPECT_TRUE(allocationCheck([] { return new int[4]; },
[](void* ptr) { delete[](int*) ptr; },
&failures));
if (failures)
return kFailure;
EXPECT_FALSE(
allocationCheck([&] { return malloc(page_size + 1); }, &free, &failures));
// Make sure exactly 1 negative test case was hit.
if (failures == 1)
return kSuccess;
return kFailure;
}
TEST_F(SamplingMallocShimsTest, BasicFunctionality) {
runTest("BasicFunctionality");
}
MULTIPROCESS_TEST_MAIN_WITH_SETUP(
Realloc,
SamplingMallocShimsTest::multiprocessTestSetup) {
void* alloc = GetMallocGpaForTesting().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*>(realloc(alloc, base::GetPageSize() + 1));
CHECK_NE(alloc, new_alloc);
CHECK_EQ(GetMallocGpaForTesting().PointerIsMine(new_alloc), false);
for (size_t i = 0; i < base::GetPageSize(); i++)
CHECK_EQ(new_alloc[i], kFillChar);
free(new_alloc);
return kSuccess;
}
TEST_F(SamplingMallocShimsTest, Realloc) {
runTest("Realloc");
}
MULTIPROCESS_TEST_MAIN_WITH_SETUP(
Calloc,
SamplingMallocShimsTest::multiprocessTestSetup) {
for (size_t i = 0; i < kLoopIterations; i++) {
unsigned char* alloc =
static_cast<unsigned char*>(calloc(base::GetPageSize(), 1));
CHECK_NE(alloc, nullptr);
if (GetMallocGpaForTesting().PointerIsMine(alloc)) {
for (size_t i = 0; i < base::GetPageSize(); i++)
CHECK_EQ(alloc[i], 0U);
free(alloc);
return kSuccess;
}
free(alloc);
}
return kFailure;
}
TEST_F(SamplingMallocShimsTest, Calloc) {
runTest("Calloc");
}
// 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,
SamplingMallocShimsTest::multiprocessTestSetup) {
if (crash_reporter::GetCrashKeyValue(kMallocCrashKey) !=
GetMallocGpaForTesting().GetCrashKey()) {
return kFailure;
}
return kSuccess;
}
TEST_F(SamplingMallocShimsTest, CrashKey) {
runTest("CrashKey");
}
#endif // !defined(COMPONENT_BUILD)
// malloc_usable_size() is not currently used/shimmed on Android.
#if !defined(OS_ANDROID)
MULTIPROCESS_TEST_MAIN_WITH_SETUP(
GetSizeEstimate,
SamplingMallocShimsTest::multiprocessTestSetup) {
constexpr size_t kAllocationSize = 123;
for (size_t i = 0; i < kLoopIterations; i++) {
std::unique_ptr<void, decltype(&free)> alloc(malloc(kAllocationSize), free);
CHECK_NE(alloc.get(), nullptr);
size_t alloc_sz = GetAllocatedSize(alloc.get());
if (GetMallocGpaForTesting().PointerIsMine(alloc.get()))
CHECK_EQ(alloc_sz, kAllocationSize);
else
CHECK_GE(alloc_sz, kAllocationSize);
}
return kSuccess;
}
TEST_F(SamplingMallocShimsTest, GetSizeEstimate) {
runTest("GetSizeEstimate");
}
#endif
#if defined(OS_WIN)
MULTIPROCESS_TEST_MAIN_WITH_SETUP(
AlignedRealloc,
SamplingMallocShimsTest::multiprocessTestSetup) {
// Exercise the _aligned_* shims and ensure that we handle them stably.
constexpr size_t kAllocationSize = 123;
constexpr size_t kAllocationAlignment = 64;
for (size_t i = 0; i < kLoopIterations; i++) {
void* ptr = _aligned_malloc(kAllocationSize, kAllocationAlignment);
CHECK(ptr);
ptr = _aligned_realloc(ptr, kAllocationSize * 2, kAllocationAlignment);
CHECK(ptr);
_aligned_free(ptr);
}
return kSuccess;
}
TEST_F(SamplingMallocShimsTest, AlignedRealloc) {
runTest("AlignedRealloc");
}
#endif // defined(OS_WIN)
#if defined(OS_MACOSX)
MULTIPROCESS_TEST_MAIN_WITH_SETUP(
BatchFree,
SamplingMallocShimsTest::multiprocessTestSetup) {
void* ptrs[AllocatorState::kMaxMetadata + 1];
for (size_t i = 0; i < AllocatorState::kMaxMetadata; i++) {
ptrs[i] = GetMallocGpaForTesting().Allocate(16);
CHECK(ptrs[i]);
}
// Check that all GPA allocations were consumed.
CHECK_EQ(GetMallocGpaForTesting().Allocate(16), nullptr);
ptrs[AllocatorState::kMaxMetadata] =
malloc_zone_malloc(malloc_default_zone(), 16);
CHECK(ptrs[AllocatorState::kMaxMetadata]);
malloc_zone_batch_free(malloc_default_zone(), ptrs,
AllocatorState::kMaxMetadata + 1);
// Check that GPA allocations were freed.
CHECK(GetMallocGpaForTesting().Allocate(16));
return kSuccess;
}
TEST_F(SamplingMallocShimsTest, BatchFree) {
runTest("BatchFree");
}
#endif // defined(OS_MACOSX)
} // namespace
} // namespace internal
} // namespace gwp_asan