Commit 20e6edd5 authored by Vlad Tsyrklevich's avatar Vlad Tsyrklevich Committed by Commit Bot

GWP-ASan: Add PartitionAlloc client hooks

Add sampling shims for PartitionAlloc allocations to the GWP-ASan
client. One of PartitionAlloc's core security guaranteees is that
allocations with different types are always segregated in memory so that
use-after-frees can only be exploited within a given partition.

For now I have not yet taught the GuardedPageAllocator about types,
instead I only allow allocating every page a single time so that no two
allocations overlap to ensure this property. This is sufficient for a
prototype to test in-the-wild but will need to be fixed before enabling
widely.

Crash handler support and wiring into chrome/ will follow.

Bug: 956824
Change-Id: If98f6ca2b6aa9d8ffc1d5300e319d0c38c71458b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1597192
Auto-Submit: Vlad Tsyrklevich <vtsyrklevich@chromium.org>
Reviewed-by: default avatarVitaly Buka <vitalybuka@chromium.org>
Commit-Queue: Vitaly Buka <vitalybuka@chromium.org>
Cr-Commit-Position: refs/heads/master@{#657097}
parent f5c9f2a5
......@@ -27,6 +27,13 @@ component("client") {
]
}
if (use_partition_alloc) {
sources += [
"sampling_partitionalloc_shims.cc",
"sampling_partitionalloc_shims.h",
]
}
defines = [ "GWP_ASAN_IMPLEMENTATION" ]
deps = [
......@@ -47,6 +54,10 @@ source_set("unit_tests") {
sources += [ "sampling_malloc_shims_unittest.cc" ]
}
if (use_partition_alloc) {
sources += [ "sampling_partitionalloc_shims_unittest.cc" ]
}
deps = [
":client",
"//base/allocator:buildflags",
......
// 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 <algorithm>
#include "base/allocator/partition_allocator/partition_alloc.h"
#include "base/logging.h"
#include "base/partition_alloc_buildflags.h"
#include "components/crash/core/common/crash_key.h"
#include "components/gwp_asan/client/export.h"
#include "components/gwp_asan/client/guarded_page_allocator.h"
#include "components/gwp_asan/client/sampling_state.h"
#include "components/gwp_asan/common/crash_key_name.h"
namespace gwp_asan {
namespace internal {
namespace {
SamplingState<PARTITIONALLOC> sampling_state;
// The global allocator singleton used by the shims. Implemented as a global
// pointer instead of a function-local static to avoid initialization checks
// for every access.
GuardedPageAllocator* gpa = nullptr;
// TODO(vtsyrklevich): PartitionAlloc ensures that different typed allocations
// never overlap. For now, we ensure this property by only allowing one
// allocation for every page. In the future we need to teach the allocator about
// types so that it keeps track and pages can be reused.
size_t allocation_counter = 0;
size_t total_allocations = 0;
bool AllocationHook(void** out, int flags, size_t size, const char* type_name) {
if (UNLIKELY(sampling_state.Sample())) {
// Ignore allocation requests with unknown flags.
constexpr int kKnownFlags =
base::PartitionAllocReturnNull | base::PartitionAllocZeroFill;
if (flags & ~kKnownFlags)
return false;
// Ensure PartitionAlloc types are separated for now.
if (allocation_counter >= total_allocations)
return false;
allocation_counter++;
if (void* allocation = gpa->Allocate(size)) {
*out = allocation;
return true;
}
}
return false;
}
bool FreeHook(void* address) {
if (UNLIKELY(gpa->PointerIsMine(address))) {
gpa->Deallocate(address);
return true;
}
return false;
}
bool ReallocHook(size_t* out, void* address) {
if (UNLIKELY(gpa->PointerIsMine(address))) {
*out = gpa->GetRequestedSize(address);
return true;
}
return false;
}
} // namespace
// We expose the allocator singleton for unit tests.
GWP_ASAN_EXPORT GuardedPageAllocator& GetPartitionAllocGpaForTesting() {
return *gpa;
}
void InstallPartitionAllocHooks(size_t max_allocated_pages,
size_t num_metadata,
size_t total_pages,
size_t sampling_frequency) {
static crash_reporter::CrashKeyString<24> pa_crash_key(
kPartitionAllocCrashKey);
gpa = new GuardedPageAllocator();
gpa->Init(max_allocated_pages, num_metadata, total_pages);
pa_crash_key.Set(gpa->GetCrashKey());
sampling_state.Init(sampling_frequency);
total_allocations = total_pages;
// TODO(vtsyrklevich): Allow SetOverrideHooks to be passed in so we can hook
// PDFium's PartitionAlloc fork.
base::PartitionAllocHooks::SetOverrideHooks(&AllocationHook, &FreeHook,
&ReallocHook);
}
} // namespace internal
} // namespace gwp_asan
// Copyright (c) 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.
#ifndef COMPONENTS_GWP_ASAN_CLIENT_SAMPLING_PARTITIONALLOC_SHIMS_H_
#define COMPONENTS_GWP_ASAN_CLIENT_SAMPLING_PARTITIONALLOC_SHIMS_H_
#include <stddef.h> // for size_t
#include "components/gwp_asan/client/export.h"
namespace gwp_asan {
namespace internal {
GWP_ASAN_EXPORT void InstallPartitionAllocHooks(size_t max_allocated_pages,
size_t num_metadata,
size_t total_pages,
size_t sampling_frequency);
} // namespace internal
} // namespace gwp_asan
#endif // COMPONENTS_GWP_ASAN_CLIENT_SAMPLING_PARTITIONALLOC_SHIMS_H_
// 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/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"
// 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);
}
protected:
void runTest(const char* name) {
base::Process process = SpawnChild(name);
int exit_code = -1;
ASSERT_TRUE(process.WaitForExitWithTimeout(
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
......@@ -20,6 +20,7 @@ namespace internal {
enum ParentAllocator {
MALLOC = 0,
PARTITIONALLOC = 1,
};
// Class that encapsulates the current sampling state. Sampling is performed
......
......@@ -9,8 +9,9 @@ namespace gwp_asan {
namespace internal {
// The name of the crash key used to convey the address of the AllocatorState
// for the malloc hooks to the crash handler.
// for the malloc/PartitionAlloc hooks to the crash handler.
const char kMallocCrashKey[] = "gwp-asan-malloc";
const char kPartitionAllocCrashKey[] = "gwp-asan-partitionalloc";
} // namespace internal
} // namespace gwp_asan
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment