blob: 4a3fe45786fa840c8766cceddb4a104a7a6dd3d5 [file] [log] [blame]
// Copyright 2012 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/asan_heap.h"
#include "base/logging.h"
#include "base/debug/stack_trace.h"
#include "syzygy/agent/asan/asan_shadow.h"
namespace agent {
namespace asan {
namespace {
// Redzone size allocated at the start of every heap block.
const size_t kRedZoneSize = 32U;
// Utility class which implements an auto lock for a HeapProxy.
class HeapLocker {
public:
explicit HeapLocker(HeapProxy* const heap) : heap_(heap) {
DCHECK(heap != NULL);
if (!heap->Lock()) {
LOG(ERROR) << "Unable to lock the heap.";
}
}
~HeapLocker() {
DCHECK(heap_ != NULL);
if (!heap_->Unlock()) {
LOG(ERROR) << "Unable to lock the heap.";
}
}
private:
HeapProxy* const heap_;
DISALLOW_COPY_AND_ASSIGN(HeapLocker);
};
// Capture a stack trace and store it in an array of instruction pointer values.
// @param stack_trace A pointer to a non-allocated array of instruction pointer,
// *stack_trace is assumed to be equal NULL when we call this function, the
// array will be dynamically allocated.
// @param trace_size A pointer to the variable where the stack size should be
// saved.
void CaptureStackTrace(void** stack_trace, uint8* trace_size) {
DCHECK(stack_trace != NULL);
DCHECK(*stack_trace == NULL);
size_t trace_depth = 0;
base::debug::StackTrace trace;
const void* temp_trace = trace.Addresses(&trace_depth);
*stack_trace = new void*[trace_depth];
memcpy(*stack_trace, temp_trace, trace_depth * sizeof(void*));
// From http://msdn.microsoft.com/en-us/library/bb204633.aspx,
// the sum of FramesToSkip and FramesToCapture must be less than 63.
DCHECK_LE(trace_depth, 62U);
*trace_size = static_cast<uint8>(trace_depth);
}
} // namespace
HeapProxy::HeapProxy()
: heap_(NULL),
head_(NULL),
tail_(NULL),
quarantine_size_(0) {
}
HeapProxy::~HeapProxy() {
if (heap_ != NULL)
Destroy();
DCHECK(heap_ == NULL);
}
HANDLE HeapProxy::ToHandle(HeapProxy* proxy) {
return proxy;
}
HeapProxy* HeapProxy::FromHandle(HANDLE heap) {
return reinterpret_cast<HeapProxy*>(heap);
}
bool HeapProxy::Create(DWORD options,
size_t initial_size,
size_t maximum_size) {
DCHECK(heap_ == NULL);
COMPILE_ASSERT(sizeof(HeapProxy::BlockHeader) <= kRedZoneSize,
asan_block_header_too_big);
heap_ = ::HeapCreate(options, initial_size, maximum_size);
if (heap_ != NULL)
return true;
return false;
}
bool HeapProxy::Destroy() {
DCHECK(heap_ != NULL);
if (::HeapDestroy(heap_)) {
heap_ = NULL;
return true;
}
return false;
}
void* HeapProxy::Alloc(DWORD flags, size_t bytes) {
DCHECK(heap_ != NULL);
size_t alloc_size = GetAllocSize(bytes);
BlockHeader* block =
reinterpret_cast<BlockHeader*>(::HeapAlloc(heap_, flags, alloc_size));
if (block == NULL)
return NULL;
// Poison head and tail zones, and unpoison alloc.
size_t header_size = kRedZoneSize;
size_t trailer_size = alloc_size - kRedZoneSize - bytes;
memset(block, '0xCC', header_size);
Shadow::Poison(block, kRedZoneSize);
block->magic_number = kBlockHeaderSignature;
block->size = bytes;
block->state = ALLOCATED;
block->alloc_stack_trace = NULL;
block->free_stack_trace = NULL;
CaptureStackTrace(&block->alloc_stack_trace, &block->alloc_stack_trace_size);
uint8* block_alloc = ToAlloc(block);
Shadow::Unpoison(block_alloc, bytes);
memset(block_alloc + bytes, '0xCD', trailer_size);
Shadow::Poison(block_alloc + bytes, trailer_size);
return block_alloc;
}
void* HeapProxy::ReAlloc(DWORD flags, void* mem, size_t bytes) {
DCHECK(heap_ != NULL);
void *new_mem = Alloc(flags, bytes);
if (new_mem != NULL && mem != NULL)
memcpy(new_mem, mem, std::min(bytes, Size(0, mem)));
if (mem)
Free(flags, mem);
return new_mem;
}
bool HeapProxy::Free(DWORD flags, void* mem) {
DCHECK(heap_ != NULL);
BlockHeader* block = ToBlock(mem);
if (block == NULL)
return true;
if (block->state != ALLOCATED) {
// We're not supposed to see another kind of block here, the FREED state
// is only applied to block after invalidating their magic number and freed
// them.
DCHECK(block->state == QUARANTINED);
BadAccessKind bad_access_kind =
GetBadAccessKind(static_cast<const uint8*>(mem), block);
DCHECK_NE(UNKNOWN_BAD_ACCESS, bad_access_kind);
ReportAsanError("attempting double-free", static_cast<const uint8*>(mem),
bad_access_kind, block);
return false;
}
CaptureStackTrace(&block->free_stack_trace, &block->free_stack_trace_size);
DCHECK(ToAlloc(block) == mem);
if (!Shadow::IsAccessible(ToAlloc(block)))
return false;
QuarantineBlock(block);
return true;
}
size_t HeapProxy::Size(DWORD flags, const void* mem) {
DCHECK(heap_ != NULL);
BlockHeader* block = ToBlock(mem);
if (block == NULL)
return -1;
return block->size;
}
bool HeapProxy::Validate(DWORD flags, const void* mem) {
DCHECK(heap_ != NULL);
return ::HeapValidate(heap_, flags, ToBlock(mem)) == TRUE;
}
size_t HeapProxy::Compact(DWORD flags) {
DCHECK(heap_ != NULL);
return ::HeapCompact(heap_, flags);
}
bool HeapProxy::Lock() {
DCHECK(heap_ != NULL);
return ::HeapLock(heap_) == TRUE;
}
bool HeapProxy::Unlock() {
DCHECK(heap_ != NULL);
return ::HeapUnlock(heap_) == TRUE;
}
bool HeapProxy::Walk(PROCESS_HEAP_ENTRY* entry) {
DCHECK(heap_ != NULL);
return ::HeapWalk(heap_, entry) == TRUE;
}
bool HeapProxy::SetInformation(HEAP_INFORMATION_CLASS info_class,
void* info,
size_t info_length) {
DCHECK(heap_ != NULL);
return ::HeapSetInformation(heap_, info_class, info, info_length) == TRUE;
}
bool HeapProxy::QueryInformation(HEAP_INFORMATION_CLASS info_class,
void* info,
size_t info_length,
unsigned long* return_length) {
DCHECK(heap_ != NULL);
return ::HeapQueryInformation(heap_,
info_class,
info,
info_length,
return_length) == TRUE;
}
void HeapProxy::QuarantineBlock(BlockHeader* block) {
base::AutoLock lock(lock_);
FreeBlockHeader* free_block = static_cast<FreeBlockHeader*>(block);
free_block->next = NULL;
if (tail_ != NULL) {
tail_->next = free_block;
} else {
DCHECK(head_ == NULL);
head_ = free_block;
}
tail_ = free_block;
// Poison the released alloc.
size_t alloc_size = GetAllocSize(free_block->size);
// Trash the data in the block and poison it.
memset(ToAlloc(free_block), 0xCC, free_block->size);
Shadow::Poison(free_block, alloc_size);
quarantine_size_ += alloc_size;
// Mark the block as quarantined.
free_block->state = QUARANTINED;
// Arbitrarily keep ten megabytes of quarantine per heap.
const size_t kMaxQuarantineSizeBytes = 10 * 1024 * 1024;
// Flush quarantine overage.
while (quarantine_size_ > kMaxQuarantineSizeBytes) {
DCHECK(head_ != NULL && tail_ != NULL);
free_block = head_;
head_ = free_block->next;
if (head_ == NULL)
tail_ = NULL;
alloc_size = GetAllocSize(free_block->size);
Shadow::Unpoison(free_block, alloc_size);
free_block->state = FREED;
free_block->magic_number = ~kBlockHeaderSignature;
if (free_block->alloc_stack_trace != NULL) {
delete free_block->alloc_stack_trace;
free_block->alloc_stack_trace = NULL;
free_block->alloc_stack_trace_size = 0;
}
if (free_block->free_stack_trace != NULL) {
delete free_block->free_stack_trace;
free_block->free_stack_trace = NULL;
free_block->free_stack_trace_size = 0;
}
DCHECK_NE(kBlockHeaderSignature, free_block->magic_number);
::HeapFree(heap_, 0, free_block);
DCHECK_GE(quarantine_size_, alloc_size);
quarantine_size_ -= alloc_size;
}
}
size_t HeapProxy::GetAllocSize(size_t bytes) {
bytes += kRedZoneSize;
return (bytes + kRedZoneSize + kRedZoneSize - 1) & ~(kRedZoneSize - 1);
}
HeapProxy::BlockHeader* HeapProxy::ToBlock(const void* alloc) {
if (alloc == NULL)
return NULL;
const uint8* mem = static_cast<const uint8*>(alloc);
const BlockHeader* header =
reinterpret_cast<const BlockHeader*>(mem -kRedZoneSize);
if (header->magic_number != kBlockHeaderSignature) {
if (!OnBadAccess(mem)) {
ReportUnknownError(mem);
Shadow::PrintShadowMemoryForAddress(alloc);
}
return NULL;
}
return const_cast<BlockHeader*>(header);
}
uint8* HeapProxy::ToAlloc(BlockHeader* block) {
DCHECK_EQ(kBlockHeaderSignature, block->magic_number);
DCHECK(block->state == ALLOCATED || block->state == QUARANTINED);
uint8* mem = reinterpret_cast<uint8*>(block);
return mem + kRedZoneSize;
}
void HeapProxy::PrintAddressInformation(const void* addr,
BlockHeader* header,
BadAccessKind bad_access_kind) {
DCHECK(addr != NULL);
DCHECK(header != NULL);
uint8* block_alloc = ToAlloc(header);
int offset = 0;
char* offset_relativity = "";
switch (bad_access_kind) {
case HEAP_BUFFER_OVERFLOW:
offset = static_cast<const uint8*>(addr) - block_alloc - header->size;
offset_relativity = "to the right";
break;
case HEAP_BUFFER_UNDERFLOW:
offset = block_alloc - static_cast<const uint8*>(addr);
offset_relativity = "to the left";
break;
case USE_AFTER_FREE:
offset = static_cast<const uint8*>(addr) - block_alloc;
offset_relativity = "inside";
break;
default:
NOTREACHED() << "Error trying to dump address information.";
}
fprintf(stderr, "0x%08X is located %d bytes %s of %d-bytes region "
"[0x%08X,0x%08X)\n",
addr,
offset,
offset_relativity,
header->size,
block_alloc,
block_alloc + header->size);
if (header->free_stack_trace != NULL) {
fprintf(stderr, "freed here:\n");
base::debug::StackTrace alloc_trace(
static_cast<const void* const*>(header->free_stack_trace),
header->free_stack_trace_size);
alloc_trace.PrintBacktrace();
}
if (header->alloc_stack_trace != NULL) {
fprintf(stderr, "previously allocated here:\n");
base::debug::StackTrace alloc_trace(
static_cast<const void* const*>(header->alloc_stack_trace),
header->alloc_stack_trace_size);
alloc_trace.PrintBacktrace();
}
Shadow::PrintShadowMemoryForAddress(addr);
}
HeapProxy::BadAccessKind HeapProxy::GetBadAccessKind(const void* addr,
BlockHeader* header) {
BadAccessKind bad_access_kind = UNKNOWN_BAD_ACCESS;
if (header->state == QUARANTINED) {
// At this point we can't know if this address belongs to this
// quarantined block... If the block containing this address has been
// moved from the quarantine list its memory space could have been re-used
// and freed again (so having this block in the quarantine list don't
// guarantee that this is the original block).
// TODO(sebmarchand): Find a way to fix this bug.
bad_access_kind = USE_AFTER_FREE;
} else {
if (addr < (ToAlloc(header)))
bad_access_kind = HEAP_BUFFER_UNDERFLOW;
else if (addr >= (ToAlloc(header) + header->size))
bad_access_kind = HEAP_BUFFER_OVERFLOW;
}
return bad_access_kind;
}
HeapProxy::BlockHeader* HeapProxy::FindAddressBlock(const void* addr) {
PROCESS_HEAP_ENTRY heap_entry = {};
memset(&heap_entry, 0, sizeof(heap_entry));
BlockHeader* header = NULL;
// Walk through the heap to find the block containing @p addr.
HeapLocker heap_locker(this);
while (Walk(&heap_entry)) {
uint8* entry_upper_bound =
static_cast<uint8*>(heap_entry.lpData) + heap_entry.cbData;
if (heap_entry.lpData <= addr && entry_upper_bound > addr) {
header = reinterpret_cast<BlockHeader*>(heap_entry.lpData);
// Ensures that the block have been allocated by this proxy.
if (header->magic_number == kBlockHeaderSignature) {
DCHECK(header->state != FREED);
break;
} else {
header = NULL;
}
}
}
return header;
}
bool HeapProxy::OnBadAccess(const void* addr) {
base::AutoLock lock(lock_);
BadAccessKind bad_access_kind = UNKNOWN_BAD_ACCESS;
BlockHeader* header = FindAddressBlock(addr);
if (header == NULL)
return false;
bad_access_kind = GetBadAccessKind(addr, header);
// Get the bad access description if we've been able to determine its kind.
if (bad_access_kind != UNKNOWN_BAD_ACCESS) {
const char* bug_descr = AccessTypeToStr(bad_access_kind);
ReportAsanError(bug_descr, addr, bad_access_kind, header);
return true;
}
return false;
}
void HeapProxy::ReportUnknownError(const void* addr) {
ReportAsanErrorBase("unknown-crash", addr, UNKNOWN_BAD_ACCESS);
}
void HeapProxy::ReportAsanError(const char* bug_descr,
const void* addr,
BadAccessKind bad_access_kind,
BlockHeader* header) {
DCHECK(header != NULL);
ReportAsanErrorBase(bug_descr, addr, bad_access_kind);
PrintAddressInformation(static_cast<const uint8*>(addr),
header, bad_access_kind);
}
void HeapProxy::ReportAsanErrorBase(const char* bug_descr,
const void* addr,
BadAccessKind bad_access_kind) {
DCHECK(bug_descr != NULL);
DCHECK(addr != NULL);
// TODO(sebmarchand): Print PC, BP and SP.
fprintf(stderr, "SyzyASAN error: %s on address 0x%08X\n", bug_descr, addr);
base::debug::StackTrace stack_trace;
stack_trace.PrintBacktrace();
}
char* HeapProxy::AccessTypeToStr(BadAccessKind bad_access_kind) {
switch (bad_access_kind) {
case USE_AFTER_FREE:
return "heap-use-after-free";
case HEAP_BUFFER_UNDERFLOW:
return "heap-buffer-underflow";
case HEAP_BUFFER_OVERFLOW:
return "heap-buffer-overflow";
default:
NOTREACHED() << "Unexpected bad access kind.";
return NULL;
}
}
LIST_ENTRY* HeapProxy::ToListEntry(HeapProxy* proxy) {
DCHECK(proxy != NULL);
return &proxy->list_entry_;
}
HeapProxy* HeapProxy::FromListEntry(LIST_ENTRY* list_entry) {
DCHECK(list_entry != NULL);
return CONTAINING_RECORD(list_entry, HeapProxy, list_entry_);
}
} // namespace asan
} // namespace agent