blob: 9ebb82d392615c09bb0563f26e6d876d0c8813d7 [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/block.h"
#include <algorithm>
#include "base/hash.h"
#include "base/logging.h"
#include "syzygy/agent/asan/stack_capture_cache.h"
#include "syzygy/common/align.h"
namespace agent {
namespace asan {
namespace {
using common::IsAligned;
// Declares a function that returns the maximum value representable by
// the given bitfield.
#define DECLARE_GET_MAX_BITFIELD_VALUE_FUNCTION(Type, FieldName) \
size_t GetMaxValue ## Type ## _ ## FieldName() { \
Type t = {}; \
t.FieldName = 0; \
--t.FieldName; \
size_t value = t.FieldName; \
return value; \
}
DECLARE_GET_MAX_BITFIELD_VALUE_FUNCTION(BlockHeader, body_size);
#undef DECLARE_GET_MAX_BITFIELD_VALUE_FUNCTION
const size_t kMaxBlockHeaderBodySize = GetMaxValueBlockHeader_body_size();
void InitializeBlockHeader(BlockInfo* block_info) {
DCHECK_NE(static_cast<BlockInfo*>(NULL), block_info);
DCHECK_NE(static_cast<BlockHeader*>(NULL), block_info->header);
::memset(block_info->header, 0, sizeof(BlockHeader));
block_info->header->magic = kBlockHeaderMagic;
block_info->header->is_nested = block_info->is_nested;
block_info->header->has_header_padding = block_info->header_padding_size > 0;
block_info->header->has_excess_trailer_padding =
block_info->trailer_padding_size > sizeof(uint32);
block_info->header->state = ALLOCATED_BLOCK;
block_info->header->body_size = block_info->body_size;
}
void InitializeBlockHeaderPadding(BlockInfo* block_info) {
DCHECK_NE(static_cast<BlockInfo*>(NULL), block_info);
if (block_info->header_padding_size == 0)
return;
DCHECK(IsAligned(block_info->header_padding_size, kShadowRatio));
DCHECK(IsAligned(block_info->header_padding_size,
2 * sizeof(uint32)));
::memset(block_info->header_padding + sizeof(uint32),
kBlockHeaderPaddingByte,
block_info->header_padding_size - 2 * sizeof(uint32));
uint32* head = reinterpret_cast<uint32*>(block_info->header_padding);
uint32* tail = reinterpret_cast<uint32*>(
block_info->header_padding + block_info->header_padding_size -
sizeof(uint32));
*head = block_info->header_padding_size;
*tail = block_info->header_padding_size;
}
void InitializeBlockTrailerPadding(BlockInfo* block_info) {
DCHECK_NE(static_cast<BlockInfo*>(NULL), block_info);
if (block_info->trailer_padding_size == 0)
return;
::memset(block_info->trailer_padding, kBlockTrailerPaddingByte,
block_info->trailer_padding_size);
if (block_info->trailer_padding_size > (kShadowRatio / 2)) {
// This is guaranteed by kShadowRatio being >= 8, but we double check
// for sanity's sake.
DCHECK_LE(sizeof(uint32), block_info->trailer_padding_size);
uint32* head = reinterpret_cast<uint32*>(block_info->trailer_padding);
*head = block_info->trailer_padding_size;
}
}
void InitializeBlockTrailer(BlockInfo* block_info) {
DCHECK_NE(static_cast<BlockInfo*>(NULL), block_info);
::memset(block_info->trailer, 0, sizeof(BlockTrailer));
block_info->trailer->alloc_ticks = ::GetTickCount();
block_info->trailer->alloc_tid = ::GetCurrentThreadId();
}
// Combines the bits of a uint32 into the number of bits used to store the
// block checksum.
uint32 CombineUInt32IntoBlockChecksum(uint32 val) {
uint32 checksum = 0;
while (val != 0) {
checksum ^= val;
val >>= kBlockHeaderChecksumBits;
}
checksum &= ((1 << kBlockHeaderChecksumBits) - 1);
return checksum;
}
// An exception filter that only catches access violations.
DWORD AccessViolationFilter(EXCEPTION_POINTERS* e) {
if (e->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
return EXCEPTION_EXECUTE_HANDLER;
return EXCEPTION_CONTINUE_SEARCH;
}
bool BlockInfoFromMemoryImpl(const void* const_raw_block,
BlockInfo* block_info) {
DCHECK_NE(static_cast<void*>(NULL), const_raw_block);
DCHECK_NE(static_cast<BlockInfo*>(NULL), block_info);
void* raw_block = const_cast<void*>(const_raw_block);
// The raw_block header must be minimally aligned and begin with the expected
// magic.
if (!IsAligned(reinterpret_cast<uint32>(raw_block), kShadowRatio))
return false;
BlockHeader* header = reinterpret_cast<BlockHeader*>(raw_block);
if (header->magic != kBlockHeaderMagic)
return false;
// Parse the header padding if present.
uint32 header_padding_size = 0;
if (header->has_header_padding) {
uint8* padding = reinterpret_cast<uint8*>(header + 1);
uint32* head = reinterpret_cast<uint32*>(padding);
header_padding_size = *head;
if (!IsAligned(header_padding_size, kShadowRatio))
return false;
uint32* tail = reinterpret_cast<uint32*>(
padding + header_padding_size - sizeof(uint32));
if (*head != *tail)
return false;
}
// Parse the body.
uint8* body = reinterpret_cast<uint8*>(header + 1) + header_padding_size;
// Parse the trailer padding.
uint32 trailer_padding_size = 0;
if ((header->body_size % kShadowRatio) != (kShadowRatio / 2)) {
trailer_padding_size = (kShadowRatio / 2) -
(header->body_size % (kShadowRatio / 2));
}
if (header->has_excess_trailer_padding) {
uint32* head = reinterpret_cast<uint32*>(body + header->body_size);
if ((*head % (kShadowRatio / 2)) != trailer_padding_size)
return false;
trailer_padding_size = *head;
}
// Parse the trailer. The end of it must be 8 aligned.
BlockTrailer* trailer = reinterpret_cast<BlockTrailer*>(
body + header->body_size + trailer_padding_size);
if (!IsAligned(reinterpret_cast<uint32>(trailer + 1), kShadowRatio))
return false;
block_info->block = reinterpret_cast<uint8*>(raw_block);
block_info->block_size = reinterpret_cast<uint8*>(trailer + 1)
- reinterpret_cast<uint8*>(header);
block_info->header = header;
block_info->header_padding_size = header_padding_size;
block_info->header_padding = block_info->block + sizeof(BlockHeader);
block_info->body = body;
block_info->body_size = header->body_size;
block_info->trailer_padding_size = trailer_padding_size;
block_info->trailer_padding = body + header->body_size;
block_info->trailer = trailer;
block_info->is_nested = header->is_nested;
BlockIdentifyWholePages(block_info);
return true;
}
BlockHeader* BlockGetHeaderFromBodyImpl(const void* const_body) {
DCHECK_NE(static_cast<void*>(NULL), const_body);
void* body = const_cast<void*>(const_body);
// The header must be appropriately aligned.
if (!IsAligned(reinterpret_cast<uint32>(body), kShadowRatio))
return NULL;
// First assume that there is no padding, and check if a valid block header
// is found there.
BlockHeader* header = reinterpret_cast<BlockHeader*>(body) - 1;
if (header->magic == kBlockHeaderMagic && header->has_header_padding == 0)
return header;
// Otherwise assume there is padding. The padding must be formatted
// correctly and have a valid length.
uint32* tail = reinterpret_cast<uint32*>(body) - 1;
if (*tail == 0 || !IsAligned(*tail, kShadowRatio))
return NULL;
uint32* head = (tail + 1) - ((*tail) / sizeof(uint32));
if (*head != *tail)
return NULL;
// Expect there to be a valid block header.
header = reinterpret_cast<BlockHeader*>(head) - 1;
if (header->magic == kBlockHeaderMagic && header->has_header_padding == 1)
return header;
// No valid block header was found before the provided body address.
return NULL;
}
} // namespace
void BlockPlanLayout(size_t chunk_size,
size_t alignment,
size_t size,
size_t min_left_redzone_size,
size_t min_right_redzone_size,
BlockLayout* layout) {
DCHECK_LE(kShadowRatio, chunk_size);
DCHECK(common::IsPowerOfTwo(chunk_size));
DCHECK_LE(kShadowRatio, alignment);
DCHECK_GE(chunk_size, alignment);
DCHECK(common::IsPowerOfTwo(alignment));
// Calculate minimum redzone sizes that respect the parameters.
size_t left_redzone_size = common::AlignUp(
std::max(min_left_redzone_size, sizeof(BlockHeader)),
alignment);
size_t right_redzone_size = std::max(min_right_redzone_size,
sizeof(BlockTrailer));
// Calculate the total size of the allocation.
size_t total_size = common::AlignUp(
left_redzone_size + size + right_redzone_size, chunk_size);
// Now figure out the sizes of things such that the body of the allocation is
// aligned as close as possible to the beginning of the right redzone while
// respecting the body alignment requirements. This favors catching overflows
// vs underflows when page protection mechanisms are active.
size_t body_trailer_size = size + right_redzone_size;
size_t body_trailer_size_aligned = common::AlignUp(body_trailer_size,
alignment);
size_t body_padding_size = body_trailer_size_aligned - body_trailer_size;
right_redzone_size += body_padding_size;
// The left redzone takes up the rest of the space.
left_redzone_size = total_size - right_redzone_size - size;
// Make sure the basic layout invariants are satisfied.
DCHECK_LE(min_left_redzone_size, left_redzone_size);
DCHECK_LE(min_right_redzone_size, right_redzone_size);
DCHECK_EQ(total_size, (left_redzone_size + size + right_redzone_size));
DCHECK(IsAligned(total_size, chunk_size));
DCHECK(IsAligned(left_redzone_size, alignment));
// Fill out the layout structure.
layout->block_alignment = chunk_size;
layout->block_size = total_size;
layout->header_size = sizeof(BlockHeader);
layout->header_padding_size = left_redzone_size - sizeof(BlockHeader);
layout->body_size = size;
layout->trailer_padding_size = right_redzone_size - sizeof(BlockTrailer);
layout->trailer_size = sizeof(BlockTrailer);
}
void BlockInitialize(const BlockLayout& layout,
void* allocation,
bool is_nested,
BlockInfo* block_info) {
DCHECK_NE(static_cast<void*>(NULL), allocation);
DCHECK(IsAligned(reinterpret_cast<uint32>(allocation),
layout.block_alignment));
// If no output structure is provided then use a local one. We need the data
// locally, but the caller might not be interested in it.
BlockInfo local_block_info = {};
if (block_info == NULL) {
block_info = &local_block_info;
} else {
::memset(block_info, 0, sizeof(BlockInfo));
}
// Get pointers to the various components of the block.
uint8* cursor = reinterpret_cast<uint8*>(allocation);
block_info->block = reinterpret_cast<uint8*>(cursor);
block_info->block_size = layout.block_size;
block_info->is_nested = is_nested;
block_info->header = reinterpret_cast<BlockHeader*>(cursor);
cursor += sizeof(BlockHeader);
block_info->header_padding = cursor;
cursor += layout.header_padding_size;
block_info->header_padding_size = layout.header_padding_size;
block_info->body = reinterpret_cast<uint8*>(cursor);
cursor += layout.body_size;
block_info->body_size = layout.body_size;
block_info->trailer_padding = cursor;
cursor += layout.trailer_padding_size;
block_info->trailer_padding_size = layout.trailer_padding_size;
block_info->trailer = reinterpret_cast<BlockTrailer*>(cursor);
// Indicates if the block is nested.
block_info->is_nested = is_nested;
// If the block information is being returned to the user then determine
// the extents of whole pages within it.
if (block_info != &local_block_info)
BlockIdentifyWholePages(block_info);
// Initialize the various portions of the memory. The body is not initialized
// as this is an unnecessary performance hit.
InitializeBlockHeader(block_info);
InitializeBlockHeaderPadding(block_info);
InitializeBlockTrailerPadding(block_info);
InitializeBlockTrailer(block_info);
}
bool BlockInfoFromMemory(const void* raw_block, BlockInfo* block_info) {
DCHECK_NE(static_cast<void*>(NULL), raw_block);
DCHECK_NE(static_cast<BlockInfo*>(NULL), block_info);
__try {
// As little code as possible is inside the body of the __try so that
// our code coverage can instrument it.
bool result = BlockInfoFromMemoryImpl(raw_block, block_info);
return result;
} __except (AccessViolationFilter(GetExceptionInformation())) { // NOLINT
// The block is either corrupt, or the pages are protected.
return false;
}
}
BlockHeader* BlockGetHeaderFromBody(const void* body) {
DCHECK_NE(static_cast<void*>(NULL), body);
__try {
// As little code as possible is inside the body of the __try so that
// our code coverage can instrument it.
BlockHeader* header = BlockGetHeaderFromBodyImpl(body);
return header;
} __except (AccessViolationFilter(GetExceptionInformation())) { // NOLINT
// The block is either corrupt, or the pages are protected.
return NULL;
}
}
uint32 BlockCalculateChecksum(const BlockInfo& block_info) {
// It is much easier to calculate the checksum in place so this actually
// causes the block to be modified, but restores the original value.
uint32 old_checksum = block_info.header->checksum;
block_info.header->checksum = 0;
BlockSetChecksum(block_info);
uint32 new_checksum = block_info.header->checksum;
block_info.header->checksum = old_checksum;
return new_checksum;
}
bool BlockChecksumIsValid(const BlockInfo& block_info) {
uint32 checksum = BlockCalculateChecksum(block_info);
if (checksum == block_info.header->checksum)
return true;
return false;
}
void BlockSetChecksum(const BlockInfo& block_info) {
block_info.header->checksum = 0;
uint32 checksum = 0;
switch (block_info.header->state) {
case ALLOCATED_BLOCK: {
// Only checksum the header and trailer regions.
checksum = base::SuperFastHash(
reinterpret_cast<const char*>(block_info.block),
block_info.body - block_info.block);
checksum ^= base::SuperFastHash(
reinterpret_cast<const char*>(block_info.trailer_padding),
block_info.block + block_info.block_size -
block_info.trailer_padding);
break;
}
// The checksum is the calculated in the same way in these two cases.
// Similary, the catch all default case is calculated in this way so as to
// allow the hash to successfully be calculated even for a block with a
// corrupt state.
case QUARANTINED_BLOCK:
case FREED_BLOCK:
default: {
checksum = base::SuperFastHash(
reinterpret_cast<const char*>(block_info.block),
block_info.block_size);
break;
}
}
checksum = CombineUInt32IntoBlockChecksum(checksum);
DCHECK_EQ(0u, checksum >> kBlockHeaderChecksumBits);
block_info.header->checksum = checksum;
}
void BlockProtectNone(const BlockInfo& block_info) {
if (block_info.block_pages_size == 0)
return;
DWORD old_protection = 0;
DWORD ret = ::VirtualProtect(block_info.block_pages,
block_info.block_pages_size,
PAGE_READWRITE, &old_protection);
DCHECK_NE(0u, ret);
}
void BlockProtectRedzones(const BlockInfo& block_info) {
BlockProtectNone(block_info);
// Protect the left redzone pages if any.
DWORD old_protection = 0;
DWORD ret = 0;
if (block_info.left_redzone_pages_size > 0) {
ret = ::VirtualProtect(block_info.left_redzone_pages,
block_info.left_redzone_pages_size,
PAGE_NOACCESS, &old_protection);
DCHECK_NE(0u, ret);
}
// Protect the right redzone pages if any.
if (block_info.right_redzone_pages_size > 0) {
ret = ::VirtualProtect(block_info.right_redzone_pages,
block_info.right_redzone_pages_size,
PAGE_NOACCESS, &old_protection);
DCHECK_NE(0u, ret);
}
}
void BlockProtectAll(const BlockInfo& block_info) {
if (block_info.block_pages_size == 0)
return;
DWORD old_protection = 0;
DWORD ret = ::VirtualProtect(block_info.block_pages,
block_info.block_pages_size,
PAGE_NOACCESS, &old_protection);
DCHECK_NE(0u, ret);
}
// Identifies whole pages in the given block_info.
void BlockIdentifyWholePages(BlockInfo* block_info) {
DCHECK_NE(static_cast<BlockInfo*>(NULL), block_info);
if (block_info->block_size < kPageSize)
return;
uint32 alloc_start = reinterpret_cast<uint32>(block_info->block);
uint32 alloc_end = alloc_start + block_info->block_size;
alloc_start = common::AlignUp(alloc_start, kPageSize);
alloc_end = common::AlignDown(alloc_end, kPageSize);
if (alloc_start >= alloc_end)
return;
block_info->block_pages = reinterpret_cast<uint8*>(alloc_start);
block_info->block_pages_size = alloc_end - alloc_start;
uint32 left_redzone_end = reinterpret_cast<uint32>(block_info->body);
uint32 right_redzone_start = left_redzone_end + block_info->body_size;
left_redzone_end = common::AlignDown(left_redzone_end, kPageSize);
right_redzone_start = common::AlignUp(right_redzone_start, kPageSize);
if (alloc_start < left_redzone_end) {
block_info->left_redzone_pages = reinterpret_cast<uint8*>(alloc_start);
block_info->left_redzone_pages_size = left_redzone_end - alloc_start;
}
if (right_redzone_start < alloc_end) {
block_info->right_redzone_pages =
reinterpret_cast<uint8*>(right_redzone_start);
block_info->right_redzone_pages_size = alloc_end - right_redzone_start;
}
}
} // namespace asan
} // namespace agent