blob: e83560978ec883bbd12713b553fa217133fd72d0 [file] [log] [blame]
// Copyright 2014 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "syzygy/agent/asan/direct_allocation.h"
#include "windows.h"
#include "base/basictypes.h"
#include "base/debug/alias.h"
#include "gtest/gtest.h"
namespace agent {
namespace asan {
namespace {
// Frequently used constants.
const DirectAllocation::Justification kAutoJustification =
DirectAllocation::kAutoJustification;
const DirectAllocation::Justification kLeftJustification =
DirectAllocation::kLeftJustification;
const DirectAllocation::Justification kRightJustification =
DirectAllocation::kRightJustification;
class TestDirectAllocation : public DirectAllocation {
public:
using DirectAllocation::FinalizeParameters;
using DirectAllocation::ToNoPages;
using DirectAllocation::ToReservedPages;
using DirectAllocation::ToAllocatedPages;
using DirectAllocation::ProtectNoPages;
using DirectAllocation::ProtectGuardPages;
using DirectAllocation::ProtectAllPages;
};
struct Configuration {
size_t size;
size_t alignment;
bool left_guard;
bool right_guard;
size_t left_redzone;
size_t right_redzone;
TestDirectAllocation::Justification justification;
};
// Helper function for setting all DirectAllocation parameters at once.
void Configure(const Configuration& configuration, TestDirectAllocation* da) {
ASSERT_TRUE(da != NULL);
da->set_size(configuration.size);
da->set_alignment(configuration.alignment);
da->set_left_guard_page(configuration.left_guard);
da->set_right_guard_page(configuration.right_guard);
da->set_left_redzone_size(configuration.left_redzone);
da->set_right_redzone_size(configuration.right_redzone);
da->set_justification(configuration.justification);
}
// Validates that the given configuration matches the direct allocation.
// Intended to be wrapped in EXPECT_/ASSERT_NO_FATAL_FAILURE.
void CheckConfiguration(const Configuration& configuration,
const TestDirectAllocation& da) {
EXPECT_EQ(configuration.size, da.size());
EXPECT_EQ(configuration.alignment, da.alignment());
EXPECT_EQ(configuration.left_guard, da.left_guard_page());
EXPECT_EQ(configuration.right_guard, da.right_guard_page());
EXPECT_EQ(configuration.left_redzone, da.left_redzone_size());
EXPECT_EQ(configuration.right_redzone, da.right_redzone_size());
EXPECT_EQ(configuration.justification, da.justification());
}
} // namespace
TEST(DirectAllocationTest, ConstructionSettersAndGetters) {
TestDirectAllocation da;
// Check default values after construction.
EXPECT_EQ(0u, da.size());
EXPECT_EQ(DirectAllocation::kDefaultAlignment, da.alignment());
EXPECT_FALSE(da.left_guard_page());
EXPECT_FALSE(da.right_guard_page());
EXPECT_EQ(0u, da.left_redzone_size());
EXPECT_EQ(0u, da.right_redzone_size());
EXPECT_EQ(kAutoJustification, da.justification());
EXPECT_EQ(DirectAllocation::kNoPages, da.memory_state());
EXPECT_EQ(DirectAllocation::kNoPagesProtected, da.protection_state());;
EXPECT_EQ(NULL, da.pages());
// Modify the allocation parameters.
da.set_size(100);
da.set_alignment(16u);
da.set_left_guard_page(true);
da.set_right_guard_page(true);
da.set_left_redzone_size(100);
da.set_right_redzone_size(100);
da.set_justification(DirectAllocation::kRightJustification);
// Check values after they've been modified.
EXPECT_EQ(100u, da.size());
EXPECT_EQ(16u, da.alignment());
EXPECT_TRUE(da.left_guard_page());
EXPECT_TRUE(da.right_guard_page());
EXPECT_EQ(100u, da.left_redzone_size());
EXPECT_EQ(100u, da.right_redzone_size());
EXPECT_EQ(DirectAllocation::kRightJustification, da.justification());
EXPECT_EQ(DirectAllocation::kNoPages, da.memory_state());
EXPECT_EQ(DirectAllocation::kNoPagesProtected, da.protection_state());;
EXPECT_EQ(NULL, da.pages());
}
TEST(DirectAllocationTest, FinalizeParameters) {
// Pairs of inputs and expected outputs.
static const std::pair<Configuration, Configuration> kConfigurations[] = {
// The left and right redzone sizes should grow to reflect the page size,
// and the allocation should be right-justified.
{ { 100, 8, true, true, 100, 100, kAutoJustification },
{ 100, 8, true, true, 8088, 4100, kRightJustification } },
// The left and right redzone sizes should grow to reflect the alignment,
// the guard pages should remain deactivated, and the allocation should be
// right-justified.
{ { 100, 8, false, false, 100, 100, kAutoJustification },
{ 100, 8, false, false, 3888, 108, kRightJustification } },
// The left and right redzone sizes should grow to reflect the alignment,
// the right guard page should be automatically activated, and the
// allocation should be right-justified.
{ { 100, 8, false, false, 100, 5000, kAutoJustification },
{ 100, 8, false, true, 3088, 5004, kRightJustification } },
// The left and right redzone sizes should grow to reflect the page size.
{ { 100, 8, true, true, 100, 100, kLeftJustification },
{ 100, 8, true, true, 4096, 8092, kLeftJustification } },
// The left and right redzone sizes should grow to reflect the alignment,
// the guard pages should remain deactivated.
{ { 100, 8, false, false, 100, 100, kLeftJustification },
{ 100, 8, false, false, 104, 3892, kLeftJustification } },
// The left and right redzone sizes should grow to reflect the alignment,
// the right guard page should be automatically activated.
{ { 100, 8, false, false, 100, 5000, kLeftJustification },
{ 100, 8, false, true, 104, 7988, kLeftJustification } },
// Everything should stay the same, but the guard pages should be auto
// activated.
{ { 4096, 16, false, false, 4096, 4096, kLeftJustification },
{ 4096, 16, true, true, 4096, 4096, kLeftJustification } },
// The justification should default to right justification.
{ { 4096, 16, true, true, 4096, 4096, kAutoJustification },
{ 4096, 16, true, true, 4096, 4096, kRightJustification } },
// Everything should stay exactly the same.
{ { 4096, 16, true, true, 4096, 4096, kLeftJustification },
{ 4096, 16, true, true, 4096, 4096, kLeftJustification } },
{ { 4096, 16, true, true, 4096, 4096, kRightJustification },
{ 4096, 16, true, true, 4096, 4096, kRightJustification } },
};
// Loop through the set of configurations, finalize parameters, and ensure
// that the calculated layout matches what is expected.
for (size_t i = 0; i < arraysize(kConfigurations); ++i) {
const Configuration& input = kConfigurations[i].first;
const Configuration& output = kConfigurations[i].second;
TestDirectAllocation da;
Configure(input, &da);
da.FinalizeParameters();
EXPECT_NO_FATAL_FAILURE(CheckConfiguration(output, da));
}
}
namespace {
// An exception filter that grabs and sets an exception pointer, and
// triggers only for access violations.
DWORD AccessViolationFilter(EXCEPTION_POINTERS* e,
EXCEPTION_POINTERS** pe) {
*pe = e;
if (e->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
return EXCEPTION_EXECUTE_HANDLER;
return EXCEPTION_CONTINUE_SEARCH;
}
// Tries to access the given address, validating whether or not an
// access violation occurs.
bool TestAccess(void* address, bool expect_access_violation) {
uint8* m = reinterpret_cast<uint8*>(address);
ULONG_PTR p = reinterpret_cast<ULONG_PTR>(address);
// Try a read.
uint8 value = 0;
EXCEPTION_POINTERS* e = NULL;
__try {
value = m[0];
if (expect_access_violation)
return false;
} __except (AccessViolationFilter(GetExceptionInformation(), &e)) {
if (!expect_access_violation)
return false;
if (e->ExceptionRecord == NULL ||
e->ExceptionRecord->NumberParameters < 2 ||
e->ExceptionRecord->ExceptionInformation[1] != p) {
return false;
}
return true;
}
// Try a write.
__try {
m[0] = 0;
if (expect_access_violation)
return false;
} __except (AccessViolationFilter(GetExceptionInformation(), &e)) {
if (!expect_access_violation)
return false;
if (e->ExceptionRecord == NULL ||
e->ExceptionRecord->NumberParameters < 2 ||
e->ExceptionRecord->ExceptionInformation[1] != p) {
return false;
}
}
// Ensure that |value| doesn't get optimized away. If so, the attempted
// read never occurs.
base::debug::Alias(&value);
return true;
}
// Readable wrappers to TestAccess.
bool IsAccessible(void* address) {
return TestAccess(address, false);
}
bool IsNotAccessible(void* address) {
return TestAccess(address, true);
}
// Transitions to the reserved state, and tests all protection state changes.
void TestToReserved(TestDirectAllocation* da) {
ASSERT_TRUE(da != NULL);
// Reserve the pages, and try all protection settings.
EXPECT_TRUE(da->ToReservedPages());
EXPECT_EQ(DirectAllocation::kReservedPages, da->memory_state());
EXPECT_EQ(DirectAllocation::kAllPagesProtected, da->protection_state());
EXPECT_TRUE(IsNotAccessible(da->GetAllocation()));
if (da->left_guard_page())
EXPECT_TRUE(IsNotAccessible(da->GetLeftGuardPage()));
if (da->right_guard_page())
EXPECT_TRUE(IsNotAccessible(da->GetRightGuardPage()));
EXPECT_FALSE(da->ProtectNoPages());
EXPECT_EQ(DirectAllocation::kAllPagesProtected, da->protection_state());
EXPECT_FALSE(da->ProtectGuardPages());
EXPECT_EQ(DirectAllocation::kAllPagesProtected, da->protection_state());
}
// Transitions to the allocated state, and tests all protection state changes.
void TestToAllocated(TestDirectAllocation* da) {
ASSERT_TRUE(da != NULL);
// Reserve the pages, and try all protection settings.
EXPECT_TRUE(da->ToAllocatedPages());
EXPECT_EQ(DirectAllocation::kAllocatedPages, da->memory_state());
EXPECT_EQ(DirectAllocation::kNoPagesProtected, da->protection_state());
EXPECT_TRUE(IsAccessible(da->GetAllocation()));
if (da->left_guard_page())
EXPECT_TRUE(IsAccessible(da->GetLeftGuardPage()));
if (da->right_guard_page())
EXPECT_TRUE(IsAccessible(da->GetRightGuardPage()));
EXPECT_TRUE(da->ProtectGuardPages());
if (da->left_guard_page() || da->right_guard_page()) {
EXPECT_EQ(DirectAllocation::kGuardPagesProtected, da->protection_state());
EXPECT_TRUE(IsAccessible(da->GetAllocation()));
if (da->left_guard_page())
EXPECT_TRUE(IsNotAccessible(da->GetLeftGuardPage()));
if (da->right_guard_page())
EXPECT_TRUE(IsNotAccessible(da->GetRightGuardPage()));
} else {
EXPECT_EQ(DirectAllocation::kNoPagesProtected, da->protection_state());
}
EXPECT_TRUE(da->ProtectAllPages());
EXPECT_EQ(DirectAllocation::kAllPagesProtected, da->protection_state());
EXPECT_TRUE(IsNotAccessible(da->GetAllocation()));
if (da->left_guard_page())
EXPECT_TRUE(IsNotAccessible(da->GetLeftGuardPage()));
if (da->right_guard_page())
EXPECT_TRUE(IsNotAccessible(da->GetRightGuardPage()));
// Leave ourselves at no page protections, same as when we entered. This
// facilitates running TestToAllocated back-to-back.
EXPECT_TRUE(da->ProtectNoPages());
EXPECT_EQ(DirectAllocation::kNoPagesProtected, da->protection_state());
EXPECT_TRUE(IsAccessible(da->GetAllocation()));
if (da->left_guard_page())
EXPECT_TRUE(IsAccessible(da->GetLeftGuardPage()));
if (da->right_guard_page())
EXPECT_TRUE(IsAccessible(da->GetRightGuardPage()));
}
// Transitions to the free state, and tests all protection state changes.
void TestToFree(TestDirectAllocation* da) {
ASSERT_TRUE(da != NULL);
// Free the pages, and try all protection settings.
EXPECT_TRUE(da->ToNoPages());
EXPECT_EQ(DirectAllocation::kNoPages, da->memory_state());
EXPECT_EQ(DirectAllocation::kNoPagesProtected, da->protection_state());
EXPECT_FALSE(da->ProtectNoPages());
EXPECT_EQ(DirectAllocation::kNoPagesProtected, da->protection_state());
EXPECT_FALSE(da->ProtectGuardPages());
EXPECT_EQ(DirectAllocation::kNoPagesProtected, da->protection_state());
EXPECT_FALSE(da->ProtectAllPages());
EXPECT_EQ(DirectAllocation::kNoPagesProtected, da->protection_state());
}
// Tests all possible state changes of a DirectAllocation object. Incidentally
// tests all of the accessors at the same time.
void TestAllStateChanges(TestDirectAllocation* da) {
ASSERT_TRUE(da != NULL);
// We test every possible state transition (self-transitions too).
// Within each of these tests we test every possible protection state
// transition, and actually test that the protections work as expected.
EXPECT_NO_FATAL_FAILURE(TestToReserved(da));
EXPECT_NO_FATAL_FAILURE(TestToAllocated(da));
EXPECT_NO_FATAL_FAILURE(TestToAllocated(da));
EXPECT_NO_FATAL_FAILURE(TestToReserved(da));
EXPECT_NO_FATAL_FAILURE(TestToReserved(da));
EXPECT_NO_FATAL_FAILURE(TestToFree(da));
EXPECT_NO_FATAL_FAILURE(TestToFree(da));
EXPECT_NO_FATAL_FAILURE(TestToAllocated(da));
EXPECT_NO_FATAL_FAILURE(TestToFree(da));
}
} // namespace
TEST(DirectAllocationTest, AllStateChangesNoGuards) {
TestDirectAllocation da;
EXPECT_EQ(DirectAllocation::kNoPages, da.memory_state());
da.set_size(da.GetPageSize());
EXPECT_NO_FATAL_FAILURE(TestAllStateChanges(&da));
}
TEST(DirectAllocationTest, AllStateChangesLeftGuard) {
TestDirectAllocation da;
EXPECT_EQ(DirectAllocation::kNoPages, da.memory_state());
da.set_size(da.GetPageSize());
da.set_left_guard_page(true);
EXPECT_NO_FATAL_FAILURE(TestAllStateChanges(&da));
}
TEST(DirectAllocationTest, AllStateChangesRightGuard) {
TestDirectAllocation da;
EXPECT_EQ(DirectAllocation::kNoPages, da.memory_state());
da.set_size(da.GetPageSize());
da.set_right_guard_page(true);
EXPECT_NO_FATAL_FAILURE(TestAllStateChanges(&da));
}
TEST(DirectAllocationTest, AllStateChangesBothGuards) {
TestDirectAllocation da;
EXPECT_EQ(DirectAllocation::kNoPages, da.memory_state());
da.set_size(1024 * 1024);
da.set_left_guard_page(true);
da.set_right_guard_page(true);
EXPECT_NO_FATAL_FAILURE(TestAllStateChanges(&da));
}
namespace {
// Tests the typical ASAN use of the allocation, using only external
// state transition functions.
void TestAsanUse(TestDirectAllocation* da) {
ASSERT_TRUE(da != NULL);
EXPECT_TRUE(da->Allocate());
EXPECT_EQ(DirectAllocation::kAllocatedPages, da->memory_state());
if (da->HasGuardPages()) {
EXPECT_EQ(DirectAllocation::kGuardPagesProtected, da->protection_state());
if (da->left_guard_page())
EXPECT_TRUE(IsNotAccessible(da->GetLeftGuardPage()));
if (da->right_guard_page())
EXPECT_TRUE(IsNotAccessible(da->GetRightGuardPage()));
EXPECT_TRUE(IsAccessible(da->GetAllocation()));
} else {
EXPECT_EQ(DirectAllocation::kNoPagesProtected, da->protection_state());
EXPECT_TRUE(IsAccessible(da->GetAllocation()));
}
EXPECT_TRUE(da->QuarantineKeepContents());
EXPECT_EQ(DirectAllocation::kAllocatedPages, da->memory_state());
EXPECT_EQ(DirectAllocation::kAllPagesProtected, da->protection_state());
if (da->left_guard_page())
EXPECT_TRUE(IsNotAccessible(da->GetLeftGuardPage()));
if (da->right_guard_page())
EXPECT_TRUE(IsNotAccessible(da->GetRightGuardPage()));
EXPECT_TRUE(IsNotAccessible(da->GetAllocation()));
EXPECT_TRUE(da->QuarantineDiscardContents());
EXPECT_EQ(DirectAllocation::kReservedPages, da->memory_state());
EXPECT_EQ(DirectAllocation::kAllPagesProtected, da->protection_state());
if (da->left_guard_page())
EXPECT_TRUE(IsNotAccessible(da->GetLeftGuardPage()));
if (da->right_guard_page())
EXPECT_TRUE(IsNotAccessible(da->GetRightGuardPage()));
EXPECT_TRUE(IsNotAccessible(da->GetAllocation()));
EXPECT_TRUE(da->Free());
EXPECT_EQ(DirectAllocation::kNoPages, da->memory_state());
EXPECT_EQ(DirectAllocation::kNoPagesProtected, da->protection_state());
}
} // namespace
TEST(DirectAllocationTest, AsanUseNoGuards) {
TestDirectAllocation da;
EXPECT_EQ(DirectAllocation::kNoPages, da.memory_state());
da.set_size(da.GetPageSize());
EXPECT_NO_FATAL_FAILURE(TestAsanUse(&da));
}
TEST(DirectAllocationTest, AsanUseLeftGuard) {
TestDirectAllocation da;
EXPECT_EQ(DirectAllocation::kNoPages, da.memory_state());
da.set_size(da.GetPageSize());
da.set_left_guard_page(true);
EXPECT_NO_FATAL_FAILURE(TestAsanUse(&da));
}
TEST(DirectAllocationTest, AsanUseRightGuard) {
TestDirectAllocation da;
EXPECT_EQ(DirectAllocation::kNoPages, da.memory_state());
da.set_size(da.GetPageSize());
da.set_right_guard_page(true);
EXPECT_NO_FATAL_FAILURE(TestAsanUse(&da));
}
TEST(DirectAllocationTest, AsanUseBothGuards) {
TestDirectAllocation da;
EXPECT_EQ(DirectAllocation::kNoPages, da.memory_state());
da.set_size(1024 * 1024);
da.set_left_guard_page(true);
da.set_right_guard_page(true);
EXPECT_NO_FATAL_FAILURE(TestAsanUse(&da));
}
namespace {
class TestDirectAllocationHeap : public DirectAllocationHeap {
public:
using DirectAllocationHeap::allocation_set_;
using DirectAllocationHeap::allocation_map_;
};
} // namespace
TEST(DirectAllocationHeapTest, EndToEnd) {
TestDirectAllocationHeap dah;
EXPECT_EQ(0u, dah.allocation_set_.size());
EXPECT_EQ(0u, dah.allocation_map_.size());
DirectAllocation* da1 = dah.Allocate(8, 4096);
EXPECT_TRUE(da1 != NULL);
EXPECT_EQ(DirectAllocation::kAllocatedPages, da1->memory_state());
EXPECT_EQ(DirectAllocation::kGuardPagesProtected, da1->protection_state());
EXPECT_TRUE(da1->left_guard_page());
EXPECT_TRUE(da1->right_guard_page());
EXPECT_TRUE(da1->GetAllocation() != NULL);
EXPECT_EQ(4096, da1->size());
EXPECT_EQ(1u, dah.allocation_set_.size());
EXPECT_EQ(1u, dah.allocation_map_.size());
EXPECT_EQ(da1, dah.Lookup(da1->GetLeftRedZone()));
EXPECT_EQ(da1, dah.Lookup(da1->GetAllocation()));
EXPECT_EQ(da1, dah.Lookup(da1->GetRightRedZone()));
uint8* before = reinterpret_cast<uint8*>(da1->GetLeftRedZone()) - 1;
uint8* after = reinterpret_cast<uint8*>(da1->GetRightRedZone()) +
da1->right_redzone_size();
EXPECT_TRUE(dah.Lookup(before) == NULL);
EXPECT_TRUE(dah.Lookup(after) == NULL);
DirectAllocation* da2 = dah.Allocate(8, 1024 * 1024);
EXPECT_TRUE(da2 != NULL);
EXPECT_EQ(DirectAllocation::kAllocatedPages, da2->memory_state());
EXPECT_EQ(DirectAllocation::kGuardPagesProtected, da2->protection_state());
EXPECT_TRUE(da2->left_guard_page());
EXPECT_TRUE(da2->right_guard_page());
EXPECT_TRUE(da2->GetAllocation() != NULL);
EXPECT_EQ(1024 * 1024, da2->size());
EXPECT_EQ(2u, dah.allocation_set_.size());
EXPECT_EQ(2u, dah.allocation_map_.size());
EXPECT_EQ(da2, dah.Lookup(da2->GetLeftRedZone()));
EXPECT_EQ(da2, dah.Lookup(da2->GetAllocation()));
EXPECT_EQ(da2, dah.Lookup(da2->GetRightRedZone()));
// Quarantine the block. It should be freed just fine.
ASSERT_TRUE(da1->QuarantineDiscardContents());
EXPECT_TRUE(dah.Free(da1));
EXPECT_EQ(1u, dah.allocation_set_.size());
EXPECT_EQ(1u, dah.allocation_map_.size());
// Free this block while it's still live.
EXPECT_TRUE(dah.Free(da2));
EXPECT_EQ(0u, dah.allocation_set_.size());
EXPECT_EQ(0u, dah.allocation_map_.size());
}
} // namespace asan
} // namespace agent