blob: defc8ba6229884f1d63d6c407719f8e92b82b03a [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/error_info.h"
#include <windows.h>
#include <string>
#include "base/strings/stringprintf.h"
#include "gtest/gtest.h"
#include "syzygy/agent/asan/unittest_util.h"
#include "syzygy/crashdata/json.h"
namespace agent {
namespace asan {
namespace {
class AsanErrorInfoTest : public testing::TestWithAsanRuntime {
public:
typedef testing::TestWithAsanRuntime Super;
void SetUp() override {
Super::SetUp();
}
void TearDown() override {
// Clean up the fake asan block if there is one.
// TODO(chrisha): Migrate this to using a dynamic shadow.
if (!dummy_block_data_.empty()) {
runtime_->shadow()->Unpoison(dummy_block_data_.data(),
dummy_block_data_.size());
}
Super::TearDown();
}
void InitAsanBlockInfo(AsanBlockInfo* block_info) {
if (dummy_block_data_.empty()) {
// Create a dummy block to physically back the report.
BlockLayout layout = {};
BlockPlanLayout(kShadowRatio, kShadowRatio, 8, 0, 0, &layout);
dummy_block_data_.resize(layout.block_size);
BlockInfo info = {};
BlockInitialize(layout, &dummy_block_data_.at(0), &info);
runtime_->shadow()->PoisonAllocatedBlock(info);
// Normalize a handful of fields to make the comparison simpler.
info.trailer->alloc_ticks = 0;
info.trailer->alloc_tid = 0;
}
block_info->header = &dummy_block_data_.at(0);
block_info->user_size = 8;
block_info->state = ALLOCATED_BLOCK;
block_info->alloc_tid = 47;
block_info->analysis.block_state = kDataIsCorrupt;
block_info->analysis.header_state = kDataIsCorrupt;
block_info->analysis.body_state = kDataStateUnknown;
block_info->analysis.trailer_state = kDataIsClean;
block_info->alloc_stack[0] = reinterpret_cast<void*>(1);
block_info->alloc_stack[1] = reinterpret_cast<void*>(2);
block_info->alloc_stack_size = 2;
block_info->heap_type = kWinHeap;
}
const void* BlockShadowAddress() {
return runtime_->shadow()->shadow() +
reinterpret_cast<uintptr_t>(dummy_block_data_.data()) / kShadowRatio;
}
private:
std::vector<unsigned char> dummy_block_data_;
};
} // namespace
TEST_F(AsanErrorInfoTest, ErrorInfoAccessTypeToStr) {
EXPECT_EQ(kHeapUseAfterFree, ErrorInfoAccessTypeToStr(USE_AFTER_FREE));
EXPECT_EQ(kHeapBufferUnderFlow,
ErrorInfoAccessTypeToStr(HEAP_BUFFER_UNDERFLOW));
EXPECT_EQ(kHeapBufferOverFlow,
ErrorInfoAccessTypeToStr(HEAP_BUFFER_OVERFLOW));
EXPECT_EQ(kAttemptingDoubleFree, ErrorInfoAccessTypeToStr(DOUBLE_FREE));
EXPECT_EQ(kInvalidAddress, ErrorInfoAccessTypeToStr(INVALID_ADDRESS));
EXPECT_EQ(kWildAccess, ErrorInfoAccessTypeToStr(WILD_ACCESS));
EXPECT_EQ(kHeapUnknownError, ErrorInfoAccessTypeToStr(UNKNOWN_BAD_ACCESS));
EXPECT_EQ(kHeapCorruptBlock, ErrorInfoAccessTypeToStr(CORRUPT_BLOCK));
EXPECT_EQ(kCorruptHeap, ErrorInfoAccessTypeToStr(CORRUPT_HEAP));
}
TEST_F(AsanErrorInfoTest, ErrorInfoGetBadAccessInformation) {
testing::FakeAsanBlock fake_block(
runtime_->shadow(), kShadowRatioLog, runtime_->stack_cache());
const size_t kAllocSize = 100;
EXPECT_TRUE(fake_block.InitializeBlock(kAllocSize));
AsanErrorInfo error_info = {};
error_info.location = fake_block.block_info.RawBody() + kAllocSize + 1;
EXPECT_TRUE(ErrorInfoGetBadAccessInformation(runtime_->shadow(),
runtime_->stack_cache(),
&error_info));
EXPECT_EQ(HEAP_BUFFER_OVERFLOW, error_info.error_type);
EXPECT_EQ(kUnknownHeapType, error_info.block_info.heap_type);
EXPECT_TRUE(fake_block.MarkBlockAsQuarantined());
error_info.location = fake_block.block_info.body;
EXPECT_TRUE(ErrorInfoGetBadAccessInformation(runtime_->shadow(),
runtime_->stack_cache(),
&error_info));
EXPECT_EQ(USE_AFTER_FREE, error_info.error_type);
EXPECT_EQ(kUnknownHeapType, error_info.block_info.heap_type);
error_info.location = fake_block.buffer_align_begin - 1;
EXPECT_FALSE(ErrorInfoGetBadAccessInformation(runtime_->shadow(),
runtime_->stack_cache(),
&error_info));
}
TEST_F(AsanErrorInfoTest, GetBadAccessInformationNestedBlock) {
// Test a nested use after free. We allocate an outer block and an inner block
// inside it, then we mark the outer block as quarantined and we test a bad
// access inside the inner block.
testing::FakeAsanBlock fake_block(
runtime_->shadow(), kShadowRatioLog, runtime_->stack_cache());
const size_t kInnerBlockAllocSize = 100;
// Allocates the outer block.
BlockLayout outer_block_layout = {};
EXPECT_TRUE(BlockPlanLayout(kShadowRatio, kShadowRatio, kInnerBlockAllocSize,
0, 0, &outer_block_layout));
EXPECT_TRUE(fake_block.InitializeBlock(outer_block_layout.block_size));
common::StackCapture stack;
stack.InitFromStack();
// Initializes the inner block.
BlockLayout inner_block_layout = {};
EXPECT_TRUE(BlockPlanLayout(kShadowRatio,
kShadowRatio,
kInnerBlockAllocSize,
0,
0,
&inner_block_layout));
BlockInfo inner_block_info = {};
BlockInitialize(inner_block_layout, fake_block.block_info.body,
&inner_block_info);
ASSERT_NE(reinterpret_cast<void*>(NULL), inner_block_info.body);
runtime_->shadow()->PoisonAllocatedBlock(inner_block_info);
inner_block_info.header->alloc_stack =
runtime_->stack_cache()->SaveStackTrace(stack);
BlockHeader* inner_header = inner_block_info.header;
BlockHeader* outer_header = reinterpret_cast<BlockHeader*>(
fake_block.buffer_align_begin);
AsanErrorInfo error_info = {};
// Mark the inner block as quarantined and check that we detect a use after
// free when trying to access its data.
inner_block_info.header->free_stack =
runtime_->stack_cache()->SaveStackTrace(stack);
EXPECT_NE(reinterpret_cast<void*>(NULL), inner_header->free_stack);
inner_header->state = QUARANTINED_BLOCK;
error_info.location = fake_block.block_info.body;
EXPECT_TRUE(ErrorInfoGetBadAccessInformation(runtime_->shadow(),
runtime_->stack_cache(),
&error_info));
EXPECT_EQ(USE_AFTER_FREE, error_info.error_type);
EXPECT_NE(reinterpret_cast<void*>(NULL), error_info.block_info.free_stack);
EXPECT_EQ(kUnknownHeapType, error_info.block_info.heap_type);
EXPECT_EQ(inner_header->free_stack->num_frames(),
error_info.block_info.free_stack_size);
for (size_t i = 0; i < inner_header->free_stack->num_frames(); ++i) {
EXPECT_EQ(inner_header->free_stack->frames()[i],
error_info.block_info.free_stack[i]);
}
// Mark the outer block as quarantined, we should detect a use after free
// when trying to access the data of the inner block, and the free stack
// should be the one of the inner block.
EXPECT_TRUE(fake_block.MarkBlockAsQuarantined());
EXPECT_NE(ALLOCATED_BLOCK, static_cast<BlockState>(outer_header->state));
EXPECT_NE(reinterpret_cast<void*>(NULL), outer_header->free_stack);
// Tests an access in the inner block.
error_info.location = inner_block_info.body;
EXPECT_TRUE(ErrorInfoGetBadAccessInformation(runtime_->shadow(),
runtime_->stack_cache(),
&error_info));
EXPECT_EQ(USE_AFTER_FREE, error_info.error_type);
EXPECT_NE(reinterpret_cast<void*>(NULL), error_info.block_info.free_stack);
EXPECT_EQ(kUnknownHeapType, error_info.block_info.heap_type);
EXPECT_EQ(inner_header->free_stack->num_frames(),
error_info.block_info.free_stack_size);
for (size_t i = 0; i < inner_header->free_stack->num_frames(); ++i) {
EXPECT_EQ(inner_header->free_stack->frames()[i],
error_info.block_info.free_stack[i]);
}
}
TEST_F(AsanErrorInfoTest, ErrorInfoGetBadAccessKind) {
const size_t kAllocSize = 100;
testing::FakeAsanBlock fake_block(
runtime_->shadow(), kShadowRatioLog, runtime_->stack_cache());
EXPECT_TRUE(fake_block.InitializeBlock(kAllocSize));
uint8_t* heap_underflow_address = fake_block.block_info.RawBody() - 1;
uint8_t* heap_overflow_address =
fake_block.block_info.RawBody() + kAllocSize * sizeof(uint8_t);
EXPECT_EQ(HEAP_BUFFER_UNDERFLOW,
ErrorInfoGetBadAccessKind(runtime_->shadow(),
heap_underflow_address,
fake_block.block_info.header));
EXPECT_EQ(HEAP_BUFFER_OVERFLOW,
ErrorInfoGetBadAccessKind(runtime_->shadow(),
heap_overflow_address,
fake_block.block_info.header));
EXPECT_TRUE(fake_block.MarkBlockAsQuarantined());
EXPECT_EQ(USE_AFTER_FREE, ErrorInfoGetBadAccessKind(runtime_->shadow(),
fake_block.block_info.body, fake_block.block_info.header));
}
TEST_F(AsanErrorInfoTest, ErrorInfoGetAsanBlockInfo) {
const size_t kAllocSize = 100;
testing::FakeAsanBlock fake_block(
runtime_->shadow(), kShadowRatioLog, runtime_->stack_cache());
EXPECT_TRUE(fake_block.InitializeBlock(kAllocSize));
AsanBlockInfo asan_block_info = {};
ErrorInfoGetAsanBlockInfo(runtime_->shadow(), fake_block.block_info,
runtime_->stack_cache(), &asan_block_info);
// Test ErrorInfoGetAsanBlockInfo with an allocated block.
EXPECT_EQ(fake_block.block_info.body_size, asan_block_info.user_size);
EXPECT_EQ(ALLOCATED_BLOCK, static_cast<BlockState>(asan_block_info.state));
EXPECT_EQ(fake_block.block_info.header->state,
static_cast<BlockState>(asan_block_info.state));
EXPECT_EQ(::GetCurrentThreadId(), asan_block_info.alloc_tid);
EXPECT_EQ(0, asan_block_info.free_tid);
EXPECT_EQ(kDataIsClean, asan_block_info.analysis.block_state);
EXPECT_EQ(fake_block.block_info.header->alloc_stack->num_frames(),
asan_block_info.alloc_stack_size);
EXPECT_EQ(0, asan_block_info.free_stack_size);
EXPECT_EQ(kUnknownHeapType, asan_block_info.heap_type);
// Now test it with a quarantined block.
EXPECT_TRUE(fake_block.MarkBlockAsQuarantined());
ErrorInfoGetAsanBlockInfo(runtime_->shadow(), fake_block.block_info,
runtime_->stack_cache(), &asan_block_info);
EXPECT_EQ(QUARANTINED_BLOCK, static_cast<BlockState>(asan_block_info.state));
EXPECT_EQ(fake_block.block_info.header->state,
static_cast<BlockState>(asan_block_info.state));
EXPECT_EQ(::GetCurrentThreadId(), asan_block_info.free_tid);
EXPECT_EQ(fake_block.block_info.header->free_stack->num_frames(),
asan_block_info.free_stack_size);
EXPECT_EQ(kUnknownHeapType, asan_block_info.heap_type);
// Ensure that the block is correctly tagged as corrupt if the header is
// invalid.
fake_block.block_info.header->magic =
static_cast<uint16_t>(~kBlockHeaderMagic);
ErrorInfoGetAsanBlockInfo(runtime_->shadow(), fake_block.block_info,
runtime_->stack_cache(), &asan_block_info);
EXPECT_EQ(kDataIsCorrupt, asan_block_info.analysis.block_state);
fake_block.block_info.header->magic =
static_cast<uint16_t>(~kBlockHeaderMagic);
}
TEST_F(AsanErrorInfoTest, GetTimeSinceFree) {
const size_t kAllocSize = 100;
const size_t kSleepTime = 25;
testing::FakeAsanBlock fake_block(
runtime_->shadow(), kShadowRatioLog, runtime_->stack_cache());
EXPECT_TRUE(fake_block.InitializeBlock(kAllocSize));
uint32_t ticks_before_free = ::GetTickCount();
EXPECT_TRUE(fake_block.MarkBlockAsQuarantined());
::Sleep(kSleepTime);
AsanErrorInfo error_info = {};
error_info.error_type = USE_AFTER_FREE;
error_info.location = fake_block.block_info.body;
EXPECT_TRUE(ErrorInfoGetBadAccessInformation(runtime_->shadow(),
runtime_->stack_cache(),
&error_info));
EXPECT_NE(0U, error_info.block_info.milliseconds_since_free);
uint32_t ticks_delta = ::GetTickCount() - ticks_before_free;
EXPECT_GT(ticks_delta, 0U);
EXPECT_GE(ticks_delta, error_info.block_info.milliseconds_since_free);
}
TEST_F(AsanErrorInfoTest, PopulateBlockInfo) {
AsanBlockInfo block_info = {};
InitAsanBlockInfo(&block_info);
{
crashdata::Value info;
PopulateBlockInfo(runtime_->shadow(), block_info, false, &info, nullptr);
std::string json;
EXPECT_TRUE(crashdata::ToJson(true, &info, &json));
const char kExpected[] =
"{\n"
" \"header\": \"0x%08IX\",\n"
" \"user-size\": 8,\n"
" \"state\": \"allocated\",\n"
" \"heap-type\": \"WinHeap\",\n"
" \"analysis\": {\n"
" \"block\": \"corrupt\",\n"
" \"header\": \"corrupt\",\n"
" \"body\": \"(unknown)\",\n"
" \"trailer\": \"clean\"\n"
" },\n"
" \"alloc-thread-id\": 47,\n"
" \"alloc-stack\": [\n"
" \"0x00000001\", \"0x00000002\"\n"
" ]\n"
"}";
std::string expected = base::StringPrintf(
kExpected, block_info.header);
EXPECT_EQ(expected, json);
}
{
block_info.state = QUARANTINED_FLOODED_BLOCK;
block_info.free_tid = 32;
block_info.free_stack[0] = reinterpret_cast<void*>(3);
block_info.free_stack[1] = reinterpret_cast<void*>(4);
block_info.free_stack[2] = reinterpret_cast<void*>(5);
block_info.free_stack_size = 3;
block_info.heap_type = kWinHeap;
block_info.milliseconds_since_free = 100;
crashdata::Value value;
PopulateBlockInfo(runtime_->shadow(), block_info, true, &value, nullptr);
std::string json;
EXPECT_TRUE(crashdata::ToJson(true, &value, &json));
const char kExpected[] =
"{\n"
" \"header\": \"0x%08IX\",\n"
" \"user-size\": 8,\n"
" \"state\": \"quarantined (flooded)\",\n"
" \"heap-type\": \"WinHeap\",\n"
" \"analysis\": {\n"
" \"block\": \"corrupt\",\n"
" \"header\": \"corrupt\",\n"
" \"body\": \"(unknown)\",\n"
" \"trailer\": \"clean\"\n"
" },\n"
" \"alloc-thread-id\": 47,\n"
" \"alloc-stack\": [\n"
" \"0x00000001\", \"0x00000002\"\n"
" ],\n"
" \"free-thread-id\": 32,\n"
" \"free-stack\": [\n"
" \"0x00000003\", \"0x00000004\", \"0x00000005\"\n"
" ],\n"
" \"milliseconds-since-free\": 100,\n"
" \"contents\": {\n"
" \"type\": \"blob\",\n"
" \"address\": \"0x%08IX\",\n"
" \"size\": null,\n"
" \"data\": [\n"
" \"0x80\", \"0xCA\", \"0x00\", \"0x00\", \"0x10\", \"0x00\","
" \"0x00\", \"0x00\",\n"
" \"0x00\", \"0x00\", \"0x00\", \"0x00\", \"0x00\", \"0x00\","
" \"0x00\", \"0x00\",\n"
" \"0x00\", \"0x00\", \"0x00\", \"0x00\", \"0x00\", \"0x00\","
" \"0x00\", \"0x00\",\n"
#ifdef _WIN64
" \"0x00\", \"0x00\", \"0x00\", \"0x00\", \"0x00\", \"0x00\","
" \"0x00\", \"0x00\",\n"
#endif
" \"0xC3\", \"0xC3\", \"0xC3\", \"0xC3\", \"0x00\", \"0x00\","
" \"0x00\", \"0x00\",\n"
#ifdef _WIN64
" \"0x00\", \"0x00\", \"0x00\", \"0x00\", \"0x00\", \"0x00\","
" \"0x00\", \"0x00\",\n"
#endif
" \"0x00\", \"0x00\", \"0x00\", \"0x00\", \"0x00\", \"0x00\","
" \"0x00\", \"0x00\",\n"
" \"0x00\", \"0x00\", \"0x00\", \"0x00\", \"0x00\", \"0x00\","
" \"0x00\", \"0x00\"\n"
" ]\n"
" },\n"
" \"shadow\": {\n"
" \"type\": \"blob\",\n"
" \"address\": \"0x%08IX\",\n"
" \"size\": null,\n"
" \"data\": [\n"
#ifndef _WIN64
" \"0xE0\", \"0xFA\", \"0x00\", \"0xFB\", \"0xFB\", \"0xF4\"\n"
#else
" \"0xE0\", \"0xFA\", \"0xFA\", \"0x00\", \"0xFB\", \"0xFB\","
" \"0xFB\", \"0xF4\"\n"
#endif
" ]\n"
" }\n"
"}";
std::string expected = base::StringPrintf(
kExpected,
block_info.header,
block_info.header,
BlockShadowAddress());
EXPECT_EQ(expected, json);
}
}
TEST_F(AsanErrorInfoTest, PopulateBlockInfoWithMemoryRanges) {
AsanBlockInfo block_info = {};
InitAsanBlockInfo(&block_info);
{
crashdata::Value info;
PopulateBlockInfo(runtime_->shadow(), block_info, false, &info, nullptr);
std::string json;
EXPECT_TRUE(crashdata::ToJson(true, &info, &json));
const char kExpected[] =
"{\n"
" \"header\": \"0x%08IX\",\n"
" \"user-size\": 8,\n"
" \"state\": \"allocated\",\n"
" \"heap-type\": \"WinHeap\",\n"
" \"analysis\": {\n"
" \"block\": \"corrupt\",\n"
" \"header\": \"corrupt\",\n"
" \"body\": \"(unknown)\",\n"
" \"trailer\": \"clean\"\n"
" },\n"
" \"alloc-thread-id\": 47,\n"
" \"alloc-stack\": [\n"
" \"0x00000001\", \"0x00000002\"\n"
" ]\n"
"}";
std::string expected = base::StringPrintf(kExpected, block_info.header);
EXPECT_EQ(expected, json);
}
{
block_info.state = QUARANTINED_FLOODED_BLOCK;
block_info.free_tid = 32;
block_info.free_stack[0] = reinterpret_cast<void*>(3);
block_info.free_stack[1] = reinterpret_cast<void*>(4);
block_info.free_stack[2] = reinterpret_cast<void*>(5);
block_info.free_stack_size = 3;
block_info.heap_type = kWinHeap;
block_info.milliseconds_since_free = 100;
crashdata::Value value;
MemoryRanges memory_ranges;
PopulateBlockInfo(runtime_->shadow(), block_info, true, &value,
&memory_ranges);
std::string json;
EXPECT_TRUE(crashdata::ToJson(true, &value, &json));
const char kExpected[] =
"{\n"
" \"header\": \"0x%08IX\",\n"
" \"user-size\": 8,\n"
" \"state\": \"quarantined (flooded)\",\n"
" \"heap-type\": \"WinHeap\",\n"
" \"analysis\": {\n"
" \"block\": \"corrupt\",\n"
" \"header\": \"corrupt\",\n"
" \"body\": \"(unknown)\",\n"
" \"trailer\": \"clean\"\n"
" },\n"
" \"alloc-thread-id\": 47,\n"
" \"alloc-stack\": [\n"
" \"0x00000001\", \"0x00000002\"\n"
" ],\n"
" \"free-thread-id\": 32,\n"
" \"free-stack\": [\n"
" \"0x00000003\", \"0x00000004\", \"0x00000005\"\n"
" ],\n"
" \"milliseconds-since-free\": 100,\n"
" \"contents\": {\n"
" \"type\": \"blob\",\n"
" \"address\": \"0x%08zX\",\n"
#ifndef _WIN64
" \"size\": 48,\n"
#else
" \"size\": 64,\n"
#endif
" \"data\": null\n"
" },\n"
" \"shadow\": {\n"
" \"type\": \"blob\",\n"
" \"address\": \"0x%08zX\",\n"
#ifndef _WIN64
" \"size\": 6,\n"
#else
" \"size\": 8,\n"
#endif
" \"data\": null\n"
" }\n"
"}";
std::string expected = base::StringPrintf(
kExpected, block_info.header, block_info.header, BlockShadowAddress());
EXPECT_EQ(expected, json);
ASSERT_EQ(2, memory_ranges.size());
const char* kExpectedMemoryRangesAddresses[] = {
reinterpret_cast<const char*>(block_info.header),
reinterpret_cast<const char*>(BlockShadowAddress())};
#ifndef _WIN64
size_t kExpectedMemoryRangesSize[] = {48, 6};
#else
size_t kExpectedMemoryRangesSize[] = {64, 8};
#endif
for (int i = 0; i < 2; i++) {
EXPECT_EQ(kExpectedMemoryRangesAddresses[i], memory_ranges[i].first);
EXPECT_EQ(kExpectedMemoryRangesSize[i], memory_ranges[i].second);
}
}
}
TEST_F(AsanErrorInfoTest, PopulateCorruptBlockRange) {
AsanBlockInfo block_info = {};
InitAsanBlockInfo(&block_info);
AsanCorruptBlockRange range = {};
range.address = reinterpret_cast<void*>(0xBAADF00D);
range.length = 1024 * 1024;
range.block_count = 100;
range.block_info_count = 1;
range.block_info = &block_info;
crashdata::Value info;
PopulateCorruptBlockRange(runtime_->shadow(), range, &info, nullptr);
std::string json;
EXPECT_TRUE(crashdata::ToJson(true, &info, &json));
const char kExpected[] =
"{\n"
" \"address\": \"0xBAADF00D\",\n"
" \"length\": 1048576,\n"
" \"block-count\": 100,\n"
" \"blocks\": [\n"
" {\n"
" \"header\": \"0x%08IX\",\n"
" \"user-size\": 8,\n"
" \"state\": \"allocated\",\n"
" \"heap-type\": \"WinHeap\",\n"
" \"analysis\": {\n"
" \"block\": \"corrupt\",\n"
" \"header\": \"corrupt\",\n"
" \"body\": \"(unknown)\",\n"
" \"trailer\": \"clean\"\n"
" },\n"
" \"alloc-thread-id\": 47,\n"
" \"alloc-stack\": [\n"
" \"0x00000001\", \"0x00000002\"\n"
" ]\n"
" }\n"
" ]\n"
"}";
std::string expected = base::StringPrintf(kExpected, block_info.header);
EXPECT_EQ(expected, json);
}
TEST_F(AsanErrorInfoTest, PopulateErrorInfo) {
AsanBlockInfo block_info = {};
InitAsanBlockInfo(&block_info);
AsanCorruptBlockRange range = {};
range.address = reinterpret_cast<void*>(0xBAADF00D);
range.length = 1024 * 1024;
range.block_count = 100;
range.block_info_count = 1;
range.block_info = &block_info;
// The 'location' address needs to be at a consistent place in system memory
// so that shadow memory contents and page bits don't vary, otherwise the
// test won't be deterministic.
AsanErrorInfo error_info = {};
error_info.location = reinterpret_cast<void*>(0x00001000);
error_info.crash_stack_id = 1234;
InitAsanBlockInfo(&error_info.block_info);
error_info.error_type = WILD_ACCESS;
error_info.access_mode = ASAN_READ_ACCESS;
error_info.access_size = 4;
::strncpy(error_info.shadow_info,
"shadow info!",
sizeof(error_info.shadow_info));
::strncpy(error_info.shadow_memory,
"shadow memory!",
sizeof(error_info.shadow_memory));
error_info.heap_is_corrupt = true;
error_info.corrupt_range_count = 10;
error_info.corrupt_block_count = 200;
error_info.corrupt_ranges_reported = 1;
error_info.corrupt_ranges = &range;
::common::SetDefaultAsanParameters(&error_info.asan_parameters);
crashdata::Value info;
PopulateErrorInfo(runtime_->shadow(), error_info, &info, nullptr);
std::string json;
EXPECT_TRUE(crashdata::ToJson(true, &info, &json));
const char kExpected[] =
"{\n"
" \"location\": \"0x00001000\",\n"
" \"crash-stack-id\": 1234,\n"
" \"block-info\": {\n"
" \"header\": \"0x%08IX\",\n"
" \"user-size\": 8,\n"
" \"state\": \"allocated\",\n"
" \"heap-type\": \"WinHeap\",\n"
" \"analysis\": {\n"
" \"block\": \"corrupt\",\n"
" \"header\": \"corrupt\",\n"
" \"body\": \"(unknown)\",\n"
" \"trailer\": \"clean\"\n"
" },\n"
" \"alloc-thread-id\": 47,\n"
" \"alloc-stack\": [\n"
" \"0x00000001\", \"0x00000002\"\n"
" ],\n"
" \"contents\": {\n"
" \"type\": \"blob\",\n"
" \"address\": \"0x%08zX\",\n"
" \"size\": null,\n"
" \"data\": [\n"
" \"0x80\", \"0xCA\", \"0x00\", \"0x00\", \"0x10\", \"0x00\","
" \"0x00\", \"0x00\",\n"
" \"0x00\", \"0x00\", \"0x00\", \"0x00\", \"0x00\", \"0x00\","
" \"0x00\", \"0x00\",\n"
" \"0x00\", \"0x00\", \"0x00\", \"0x00\", \"0x00\", \"0x00\","
" \"0x00\", \"0x00\",\n"
#ifdef _WIN64
" \"0x00\", \"0x00\", \"0x00\", \"0x00\", \"0x00\", \"0x00\","
" \"0x00\", \"0x00\",\n"
#endif
" \"0xC3\", \"0xC3\", \"0xC3\", \"0xC3\", \"0x00\", \"0x00\","
" \"0x00\", \"0x00\",\n"
#ifdef _WIN64
" \"0x00\", \"0x00\", \"0x00\", \"0x00\", \"0x00\", \"0x00\","
" \"0x00\", \"0x00\",\n"
#endif
" \"0x00\", \"0x00\", \"0x00\", \"0x00\", \"0x00\", \"0x00\","
" \"0x00\", \"0x00\",\n"
" \"0x00\", \"0x00\", \"0x00\", \"0x00\", \"0x00\", \"0x00\","
" \"0x00\", \"0x00\"\n"
" ]\n"
" },\n"
" \"shadow\": {\n"
" \"type\": \"blob\",\n"
" \"address\": \"0x%08zX\",\n"
" \"size\": null,\n"
" \"data\": [\n"
#ifndef _WIN64
" \"0xE0\", \"0xFA\", \"0x00\", \"0xFB\", \"0xFB\", \"0xF4\"\n"
#else
" \"0xE0\", \"0xFA\", \"0xFA\", \"0x00\", \"0xFB\", \"0xFB\","
" \"0xFB\", \"0xF4\"\n"
#endif
" ]\n"
" }\n"
" },\n"
" \"error-type\": \"wild-access\",\n"
" \"access-mode\": \"read\",\n"
" \"access-size\": 4,\n"
" \"shadow-memory-index\": 512,\n"
" \"shadow-memory\": {\n"
" \"type\": \"blob\",\n"
" \"address\": \"0x%08zX\",\n"
" \"size\": null,\n"
" \"data\": [\n"
" \"0xF2\", \"0xF2\", \"0xF2\", \"0xF2\", \"0xF2\", \"0xF2\","
" \"0xF2\", \"0xF2\",\n"
" \"0xF2\", \"0xF2\", \"0xF2\", \"0xF2\", \"0xF2\", \"0xF2\","
" \"0xF2\", \"0xF2\",\n"
" \"0xF2\", \"0xF2\", \"0xF2\", \"0xF2\", \"0xF2\", \"0xF2\","
" \"0xF2\", \"0xF2\",\n"
" \"0xF2\", \"0xF2\", \"0xF2\", \"0xF2\", \"0xF2\", \"0xF2\","
" \"0xF2\", \"0xF2\",\n"
" \"0xF2\", \"0xF2\", \"0xF2\", \"0xF2\", \"0xF2\", \"0xF2\","
" \"0xF2\", \"0xF2\",\n"
" \"0xF2\", \"0xF2\", \"0xF2\", \"0xF2\", \"0xF2\", \"0xF2\","
" \"0xF2\", \"0xF2\",\n"
" \"0xF2\", \"0xF2\", \"0xF2\", \"0xF2\", \"0xF2\", \"0xF2\","
" \"0xF2\", \"0xF2\",\n"
" \"0xF2\", \"0xF2\", \"0xF2\", \"0xF2\", \"0xF2\", \"0xF2\","
" \"0xF2\", \"0xF2\"\n"
" ]\n"
" },\n"
" \"page-bits-index\": 0,\n"
" \"page-bits\": {\n"
" \"type\": \"blob\",\n"
" \"address\": \"0x%08zX\",\n"
" \"size\": null,\n"
" \"data\": [\n"
" \"0x00\", \"0x00\", \"0x00\"\n"
" ]\n"
" },\n"
" \"heap-is-corrupt\": 1,\n"
" \"corrupt-range-count\": 10,\n"
" \"corrupt-block-count\": 200,\n"
" \"corrupt-ranges\": [\n"
" {\n"
" \"address\": \"0xBAADF00D\",\n"
" \"length\": 1048576,\n"
" \"block-count\": 100,\n"
" \"blocks\": [\n"
" {\n"
" \"header\": \"0x%08IX\",\n"
" \"user-size\": 8,\n"
" \"state\": \"allocated\",\n"
" \"heap-type\": \"WinHeap\",\n"
" \"analysis\": {\n"
" \"block\": \"corrupt\",\n"
" \"header\": \"corrupt\",\n"
" \"body\": \"(unknown)\",\n"
" \"trailer\": \"clean\"\n"
" },\n"
" \"alloc-thread-id\": 47,\n"
" \"alloc-stack\": [\n"
" \"0x00000001\", \"0x00000002\"\n"
" ]\n"
" }\n"
" ]\n"
" }\n"
" ],\n"
" \"asan-parameters\": {\n"
" \"quarantine-size\": 16777216,\n"
" \"trailer-padding-size\": 0,\n"
" \"quarantine-block-size\": 4194304,\n"
" \"check-heap-on-failure\": 1,\n"
" \"enable-zebra-block-heap\": 0,\n"
" \"enable-large-block-heap\": 1,\n"
" \"enable-allocation-filter\": 0,\n"
" \"allocation-guard-rate\": 1.0000000000000000E+00,\n"
" \"zebra-block-heap-size\": 16777216,\n"
" \"zebra-block-heap-quarantine-ratio\": 2.5000000000000000E-01,\n"
" \"large-allocation-threshold\": 20480,\n"
" \"quarantine-flood-fill-rate\": 5.0000000000000000E-01\n"
" }\n"
"}";
AsanErrorShadowMemory shadow_memory = {};
GetAsanErrorShadowMemory(runtime_->shadow(), error_info.location,
&shadow_memory);
std::string expected =
base::StringPrintf(kExpected, block_info.header, block_info.header,
BlockShadowAddress(), shadow_memory.address,
runtime_->shadow()->page_bits(), block_info.header);
EXPECT_EQ(expected, json);
}
TEST_F(AsanErrorInfoTest, PopulateErrorInfoWithMemoryRanges) {
AsanBlockInfo block_info = {};
InitAsanBlockInfo(&block_info);
AsanCorruptBlockRange range = {};
range.address = reinterpret_cast<void*>(0xBAADF00D);
range.length = 1024 * 1024;
range.block_count = 100;
range.block_info_count = 1;
range.block_info = &block_info;
// The 'location' address needs to be at a consistent place in system memory
// so that shadow memory contents and page bits don't vary, otherwise the
// test won't be deterministic.
AsanErrorInfo error_info = {};
error_info.location = reinterpret_cast<void*>(0x00001000);
error_info.crash_stack_id = 1234;
InitAsanBlockInfo(&error_info.block_info);
error_info.error_type = WILD_ACCESS;
error_info.access_mode = ASAN_READ_ACCESS;
error_info.access_size = 4;
::strncpy(error_info.shadow_info, "shadow info!",
sizeof(error_info.shadow_info));
::strncpy(error_info.shadow_memory, "shadow memory!",
sizeof(error_info.shadow_memory));
error_info.heap_is_corrupt = true;
error_info.corrupt_range_count = 10;
error_info.corrupt_block_count = 200;
error_info.corrupt_ranges_reported = 1;
error_info.corrupt_ranges = &range;
::common::SetDefaultAsanParameters(&error_info.asan_parameters);
crashdata::Value info;
MemoryRanges memory_ranges;
PopulateErrorInfo(runtime_->shadow(), error_info, &info, &memory_ranges);
std::string json;
EXPECT_TRUE(crashdata::ToJson(true, &info, &json));
const char kExpected[] =
"{\n"
" \"location\": \"0x00001000\",\n"
" \"crash-stack-id\": 1234,\n"
" \"block-info\": {\n"
" \"header\": \"0x%08IX\",\n"
" \"user-size\": 8,\n"
" \"state\": \"allocated\",\n"
" \"heap-type\": \"WinHeap\",\n"
" \"analysis\": {\n"
" \"block\": \"corrupt\",\n"
" \"header\": \"corrupt\",\n"
" \"body\": \"(unknown)\",\n"
" \"trailer\": \"clean\"\n"
" },\n"
" \"alloc-thread-id\": 47,\n"
" \"alloc-stack\": [\n"
" \"0x00000001\", \"0x00000002\"\n"
" ],\n"
" \"contents\": {\n"
" \"type\": \"blob\",\n"
" \"address\": \"0x%08IX\",\n"
#ifndef _WIN64
" \"size\": 48,\n"
#else
" \"size\": 64,\n"
#endif
" \"data\": null\n"
" },\n"
" \"shadow\": {\n"
" \"type\": \"blob\",\n"
" \"address\": \"0x%08IX\",\n"
#ifndef _WIN64
" \"size\": 6,\n"
#else
" \"size\": 8,\n"
#endif
" \"data\": null\n"
" }\n"
" },\n"
" \"error-type\": \"wild-access\",\n"
" \"access-mode\": \"read\",\n"
" \"access-size\": 4,\n"
" \"shadow-memory-index\": 512,\n"
" \"shadow-memory\": {\n"
" \"type\": \"blob\",\n"
" \"address\": \"0x%08IX\",\n"
" \"size\": 64,\n"
" \"data\": null\n"
" },\n"
" \"page-bits-index\": 0,\n"
" \"page-bits\": {\n"
" \"type\": \"blob\",\n"
" \"address\": \"0x%08IX\",\n"
" \"size\": 3,\n"
" \"data\": null\n"
" },\n"
" \"heap-is-corrupt\": 1,\n"
" \"corrupt-range-count\": 10,\n"
" \"corrupt-block-count\": 200,\n"
" \"corrupt-ranges\": [\n"
" {\n"
" \"address\": \"0xBAADF00D\",\n"
" \"length\": 1048576,\n"
" \"block-count\": 100,\n"
" \"blocks\": [\n"
" {\n"
" \"header\": \"0x%08IX\",\n"
" \"user-size\": 8,\n"
" \"state\": \"allocated\",\n"
" \"heap-type\": \"WinHeap\",\n"
" \"analysis\": {\n"
" \"block\": \"corrupt\",\n"
" \"header\": \"corrupt\",\n"
" \"body\": \"(unknown)\",\n"
" \"trailer\": \"clean\"\n"
" },\n"
" \"alloc-thread-id\": 47,\n"
" \"alloc-stack\": [\n"
" \"0x00000001\", \"0x00000002\"\n"
" ]\n"
" }\n"
" ]\n"
" }\n"
" ],\n"
" \"asan-parameters\": {\n"
" \"quarantine-size\": 16777216,\n"
" \"trailer-padding-size\": 0,\n"
" \"quarantine-block-size\": 4194304,\n"
" \"check-heap-on-failure\": 1,\n"
" \"enable-zebra-block-heap\": 0,\n"
" \"enable-large-block-heap\": 1,\n"
" \"enable-allocation-filter\": 0,\n"
" \"allocation-guard-rate\": 1.0000000000000000E+00,\n"
" \"zebra-block-heap-size\": 16777216,\n"
" \"zebra-block-heap-quarantine-ratio\": 2.5000000000000000E-01,\n"
" \"large-allocation-threshold\": 20480,\n"
" \"quarantine-flood-fill-rate\": 5.0000000000000000E-01\n"
" }\n"
"}";
AsanErrorShadowMemory shadow_memory = {};
GetAsanErrorShadowMemory(runtime_->shadow(), error_info.location,
&shadow_memory);
std::string expected =
base::StringPrintf(kExpected, block_info.header, block_info.header,
BlockShadowAddress(), shadow_memory.address,
runtime_->shadow()->page_bits(), block_info.header);
EXPECT_EQ(expected, json);
// Check memory ranges.
ASSERT_EQ(4, memory_ranges.size());
const char* kExpectedMemoryRangesAddresses[] = {
reinterpret_cast<const char*>(block_info.header),
reinterpret_cast<const char*>(BlockShadowAddress()),
reinterpret_cast<const char*>(shadow_memory.address),
reinterpret_cast<const char*>(runtime_->shadow()->page_bits())};
#ifndef _WIN64
size_t kExpectedMemoryRangesSize[] = {48, 6, 64, 3};
#else
size_t kExpectedMemoryRangesSize[] = {64, 8, 64, 3};
#endif
for (int i = 0; i < memory_ranges.size(); i++) {
EXPECT_EQ(kExpectedMemoryRangesAddresses[i], memory_ranges[i].first)
<< " Where i = " << i;
EXPECT_EQ(kExpectedMemoryRangesSize[i], memory_ranges[i].second)
<< " Where i = " << i;
}
}
TEST_F(AsanErrorInfoTest, CrashdataProtobufToErrorInfo) {
AsanBlockInfo block_info = {};
InitAsanBlockInfo(&block_info);
block_info.state = QUARANTINED_FLOODED_BLOCK;
block_info.free_tid = 32;
block_info.free_stack[0] = reinterpret_cast<void*>(3);
block_info.free_stack[1] = reinterpret_cast<void*>(4);
block_info.free_stack[2] = reinterpret_cast<void*>(5);
block_info.free_stack_size = 3;
block_info.heap_type = kWinHeap;
block_info.milliseconds_since_free = 100;
crashdata::Value value;
PopulateBlockInfo(runtime_->shadow(), block_info, true, &value, nullptr);
AsanErrorInfo error_info_from_proto = {};
CrashdataProtobufToErrorInfo(value, &error_info_from_proto);
EXPECT_EQ(block_info.header, error_info_from_proto.block_info.header);
EXPECT_EQ(block_info.user_size, error_info_from_proto.block_info.user_size);
EXPECT_EQ(block_info.state, error_info_from_proto.block_info.state);
EXPECT_EQ(block_info.heap_type, error_info_from_proto.block_info.heap_type);
EXPECT_EQ(block_info.alloc_tid, error_info_from_proto.block_info.alloc_tid);
EXPECT_EQ(block_info.free_tid, error_info_from_proto.block_info.free_tid);
EXPECT_EQ(block_info.milliseconds_since_free,
error_info_from_proto.block_info.milliseconds_since_free);
}
} // namespace asan
} // namespace agent