blob: 816daa5af87c54361d77fd3e9d1f5a59207f1dad [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/memory_interceptors.h"
#include "base/bind.h"
#include "base/environment.h"
#include "base/strings/stringprintf.h"
#include "gtest/gtest.h"
#include "syzygy/agent/asan/asan_rtl_impl.h"
#include "syzygy/agent/asan/asan_runtime.h"
#include "syzygy/agent/asan/error_info.h"
#include "syzygy/agent/asan/unittest_util.h"
#include "syzygy/trace/protocol/call_trace_defs.h"
namespace agent {
namespace asan {
namespace {
using testing::MemoryAccessorTester;
// Redefine some enums for local use.
enum AccessMode {
AsanReadAccess = agent::asan::ASAN_READ_ACCESS,
AsanWriteAccess = agent::asan::ASAN_WRITE_ACCESS,
AsanUnknownAccess = agent::asan::ASAN_UNKNOWN_ACCESS,
};
struct InterceptFunction {
void(*function)();
size_t size;
};
static const InterceptFunction intercept_functions[] = {
#define DEFINE_INTERCEPT_FUNCTION_TABLE(access_size, access_mode_str, \
access_mode) \
{ asan_check_ ## access_size ## _byte_ ## access_mode_str, access_size },
ASAN_MEM_INTERCEPT_FUNCTIONS(DEFINE_INTERCEPT_FUNCTION_TABLE)
#undef DEFINE_INTERCEPT_FUNCTION_TABLE
};
struct StringInterceptFunction {
void(*function)();
size_t size;
AccessMode dst_access_mode;
AccessMode src_access_mode;
bool uses_counter;
};
const bool kCounterInit_ecx = true;
const bool kCounterInit_1 = false;
static const StringInterceptFunction string_intercept_functions[] = {
#define DEFINE_STRING_INTERCEPT_FUNCTION_TABLE(func, prefix, counter, \
dst_mode, src_mode, access_size, compare) \
{ asan_check ## prefix ## access_size ## _byte_ ## func ## _access, \
access_size, dst_mode, src_mode, kCounterInit_##counter },
ASAN_STRING_INTERCEPT_FUNCTIONS(DEFINE_STRING_INTERCEPT_FUNCTION_TABLE)
#undef DEFINE_STRINGINTERCEPT_FUNCTION_TABLE
};
class MemoryInterceptorsTest : public testing::TestWithAsanLogger {
public:
MemoryInterceptorsTest() : heap_(NULL), src_(NULL), dst_(NULL) {
}
void SetUp() OVERRIDE {
testing::TestWithAsanLogger::SetUp();
// Make sure the logging routes to our instance.
AppendToLoggerEnv(base::StringPrintf("syzyasan_rtl_unittests.exe,%u",
::GetCurrentProcessId()));
asan_runtime_.SetUp(std::wstring());
// Heap checking on error is expensive, so turn it down here.
asan_runtime_.params().check_heap_on_failure = false;
agent::asan::SetUpRtl(&asan_runtime_);
asan_runtime_.SetErrorCallBack(
base::Bind(testing::MemoryAccessorTester::AsanErrorCallback));
heap_ = asan_HeapCreate(0, 0, 0);
ASSERT_TRUE(heap_ != NULL);
src_ = reinterpret_cast<byte*>(asan_HeapAlloc(heap_, 0, kAllocSize));
dst_ = reinterpret_cast<byte*>(asan_HeapAlloc(heap_, 0, kAllocSize));
ASSERT_TRUE(src_ && dst_);
// String instructions may compare memory contents and bail early on
// differences, so fill the buffers to make sure the checks go the full
// distance.
::memset(src_, 0xFF, kAllocSize);
::memset(dst_, 0xFF, kAllocSize);
}
void TearDown() OVERRIDE {
if (heap_ != NULL) {
asan_HeapFree(heap_, 0, src_);
asan_HeapFree(heap_, 0, dst_);
asan_HeapDestroy(heap_);
heap_ = NULL;
}
agent::asan::TearDownRtl();
asan_runtime_.TearDown();
testing::TestWithAsanLogger::TearDown();
}
protected:
const size_t kAllocSize = 64;
agent::asan::AsanRuntime asan_runtime_;
HANDLE heap_;
// Convenience allocs of kAllocSize. Valid from SetUp to TearDown.
byte* src_;
byte* dst_;
};
} // namespace
TEST_F(MemoryInterceptorsTest, TestValidAccess) {
for (size_t i = 0; i < arraysize(intercept_functions); ++i) {
const InterceptFunction& fn = intercept_functions[i];
MemoryAccessorTester tester;
tester.CheckAccessAndCompareContexts(
reinterpret_cast<FARPROC>(fn.function), src_);
ASSERT_FALSE(tester.memory_error_detected());
}
}
TEST_F(MemoryInterceptorsTest, TestOverrunAccess) {
for (size_t i = 0; i < arraysize(intercept_functions); ++i) {
const InterceptFunction& fn = intercept_functions[i];
MemoryAccessorTester tester;
tester.AssertMemoryErrorIsDetected(
reinterpret_cast<FARPROC>(fn.function),
src_ + kAllocSize,
MemoryAccessorTester::BadAccessKind::HEAP_BUFFER_OVERFLOW);
ASSERT_TRUE(tester.memory_error_detected());
}
}
TEST_F(MemoryInterceptorsTest, TestUnderrrunAccess) {
for (size_t i = 0; i < arraysize(intercept_functions); ++i) {
const InterceptFunction& fn = intercept_functions[i];
// TODO(someone): the 32 byte access checker does not fire on 32 byte
// underrun. I guess the checkers test a single shadow byte at most
// whereas it'd be more correct for access checkers to test as many
// shadow bytes as is appropriate for the range of memory they touch.
MemoryAccessorTester tester;
tester.AssertMemoryErrorIsDetected(
reinterpret_cast<FARPROC>(fn.function),
src_ - 8,
MemoryAccessorTester::BadAccessKind::HEAP_BUFFER_UNDERFLOW);
ASSERT_TRUE(tester.memory_error_detected());
}
}
TEST_F(MemoryInterceptorsTest, TestStringValidAccess) {
for (size_t i = 0; i < arraysize(string_intercept_functions); ++i) {
const StringInterceptFunction& fn = string_intercept_functions[i];
MemoryAccessorTester tester;
tester.CheckSpecialAccessAndCompareContexts(
reinterpret_cast<FARPROC>(fn.function),
MemoryAccessorTester::DIRECTION_FORWARD,
dst_, src_, kAllocSize / fn.size);
ASSERT_FALSE(tester.memory_error_detected());
tester.CheckSpecialAccessAndCompareContexts(
reinterpret_cast<FARPROC>(fn.function),
MemoryAccessorTester::DIRECTION_BACKWARD,
dst_ + kAllocSize - fn.size, src_ + kAllocSize - fn.size,
kAllocSize / fn.size);
ASSERT_FALSE(tester.memory_error_detected());
}
}
TEST_F(MemoryInterceptorsTest, TestStringOverrunAccess) {
for (size_t i = 0; i < arraysize(string_intercept_functions); ++i) {
const StringInterceptFunction& fn = string_intercept_functions[i];
MemoryAccessorTester tester;
size_t oob_len = 0;
byte* oob_dst = NULL;
byte* oob_src = NULL;
// Half the string function intercepts are for rep-prefixed instructions,
// which count on "ecx", and the other half is for non-prefixed
// instructions that always perform a single access.
// Compute appropriate pointers for both variants, forwards.
if (fn.uses_counter) {
oob_len = kAllocSize / fn.size;
oob_dst = dst_ + fn.size;
oob_src = src_ + fn.size;
} else {
oob_len = 1;
oob_dst = dst_ + kAllocSize;
oob_src = src_ + kAllocSize;
}
ASSERT_NE(ASAN_UNKNOWN_ACCESS, fn.dst_access_mode);
// Overflow on dst forwards.
tester.ExpectSpecialMemoryErrorIsDetected(
reinterpret_cast<FARPROC>(fn.function),
MemoryAccessorTester::DIRECTION_FORWARD, true,
oob_dst, src_, oob_len,
MemoryAccessorTester::BadAccessKind::HEAP_BUFFER_OVERFLOW);
if (fn.src_access_mode != ASAN_UNKNOWN_ACCESS) {
// Overflow on src forwards.
tester.ExpectSpecialMemoryErrorIsDetected(
reinterpret_cast<FARPROC>(fn.function),
MemoryAccessorTester::DIRECTION_FORWARD, true,
dst_, oob_src, oob_len,
MemoryAccessorTester::BadAccessKind::HEAP_BUFFER_OVERFLOW);
}
// Compute appropriate pointers for both variants, backwards.
if (fn.uses_counter) {
oob_len = kAllocSize / fn.size;
} else {
oob_len = 1;
}
oob_dst = dst_ + kAllocSize;
oob_src = src_ + kAllocSize;
ASSERT_NE(ASAN_UNKNOWN_ACCESS, fn.dst_access_mode);
// Overflow on dst backwards.
tester.ExpectSpecialMemoryErrorIsDetected(
reinterpret_cast<FARPROC>(fn.function),
MemoryAccessorTester::DIRECTION_BACKWARD, true,
oob_dst, src_ + kAllocSize - fn.size, oob_len,
MemoryAccessorTester::BadAccessKind::HEAP_BUFFER_OVERFLOW);
if (fn.src_access_mode != ASAN_UNKNOWN_ACCESS) {
// Overflow on src backwards.
tester.ExpectSpecialMemoryErrorIsDetected(
reinterpret_cast<FARPROC>(fn.function),
MemoryAccessorTester::DIRECTION_BACKWARD, true,
dst_ + kAllocSize - fn.size, oob_dst, oob_len,
MemoryAccessorTester::BadAccessKind::HEAP_BUFFER_OVERFLOW);
}
}
}
} // namespace asan
} // namespace agent