blob: b63392b8d39cd0ad8f06c3e8ccccc7dbfb2bc95b [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/heap_managers/block_heap_manager.h"
#include <utility>
#include "base/bind.h"
#include "base/rand_util.h"
#include "syzygy/agent/asan/asan_runtime.h"
#include "syzygy/agent/asan/shadow.h"
#include "syzygy/agent/asan/heaps/simple_block_heap.h"
#include "syzygy/agent/asan/heaps/win_heap.h"
#include "syzygy/common/asan_parameters.h"
namespace agent {
namespace asan {
namespace heap_managers {
namespace {
typedef HeapManagerInterface::HeapId HeapId;
using heaps::ZebraBlockHeap;
} // namespace
BlockHeapManager::BlockHeapManager(AsanRuntime* runtime)
: runtime_(runtime),
zebra_block_heap_(NULL) {
DCHECK_NE(static_cast<AsanRuntime*>(NULL), runtime);
SetDefaultAsanParameters(&parameters_);
// TODO(sebmarchand): Set this callback directly from AsanRuntime::Setup once
// everything has been plugged together.
SetHeapErrorCallback(base::Bind(&AsanRuntime::OnError,
base::Unretained(runtime_)));
PropagateParameters();
unguarded_allocation_heap_.reset(new heaps::WinHeap());
}
BlockHeapManager::~BlockHeapManager() {
base::AutoLock lock(lock_);
// Delete all the heaps.
HeapQuarantineMap::iterator iter_heaps = heaps_.begin();
for (; iter_heaps != heaps_.end(); ++iter_heaps)
DestroyHeapUnlocked(iter_heaps->first, iter_heaps->second);
heaps_.clear();
// Clear the zebra heap reference since it was deleted.
zebra_block_heap_ = NULL;
}
HeapId BlockHeapManager::CreateHeap() {
// Creates the underlying heap used by this heap.
HeapInterface* win_heap = new heaps::WinHeap();
// Creates the heap.
BlockHeapInterface* heap = new heaps::SimpleBlockHeap(win_heap);
base::AutoLock lock(lock_);
underlying_heaps_map_.insert(std::make_pair(heap, win_heap));
heaps_.insert(std::make_pair(heap, &shared_quarantine_));
return reinterpret_cast<HeapId>(heap);
}
bool BlockHeapManager::DestroyHeap(HeapId heap_id) {
base::AutoLock lock(lock_);
HeapQuarantineMap::iterator iter = heaps_.find(
reinterpret_cast<BlockHeapInterface*>(heap_id));
// We should always be able to retrieve a heap that we previously passed to
// the user.
CHECK(iter != heaps_.end());
// Destroy the heap and flush its quarantine.
DestroyHeapUnlocked(iter->first, iter->second);
heaps_.erase(iter);
return true;
}
void* BlockHeapManager::Allocate(HeapId heap_id, size_t bytes) {
DCHECK_NE(static_cast<HeapId>(NULL), heap_id);
// Some allocations can pass through without instrumentation.
if (parameters_.allocation_guard_rate < 1.0 &&
base::RandDouble() >= parameters_.allocation_guard_rate) {
void* alloc = unguarded_allocation_heap_->Allocate(bytes);
return alloc;
}
void* alloc = NULL;
BlockLayout block_layout = {};
// Always try to allocate in the zebra heap.
if (parameters_.enable_zebra_block_heap) {
CHECK_NE(reinterpret_cast<heaps::ZebraBlockHeap*>(NULL),
zebra_block_heap_);
alloc = zebra_block_heap_->AllocateBlock(
bytes,
0,
parameters_.trailer_padding_size + sizeof(BlockTrailer),
&block_layout);
if (alloc != NULL)
heap_id = reinterpret_cast<HeapId>(zebra_block_heap_);
}
// Fallback to the provided heap.
if (alloc == NULL) {
alloc = reinterpret_cast<BlockHeapInterface*>(heap_id)->AllocateBlock(
bytes,
0,
parameters_.trailer_padding_size + sizeof(BlockTrailer),
&block_layout);
}
DCHECK_NE(reinterpret_cast<void*>(NULL), alloc);
DCHECK_EQ(0u, reinterpret_cast<size_t>(alloc) % kShadowRatio);
BlockInfo block = {};
BlockInitialize(block_layout, alloc, false, &block);
// Capture the current stack. InitFromStack is inlined to preserve the
// greatest number of stack frames.
StackCapture stack;
stack.InitFromStack();
block.header->alloc_stack = runtime_->stack_cache()->SaveStackTrace(stack);
block.header->free_stack = NULL;
block.trailer->heap_id = heap_id;
BlockSetChecksum(block);
Shadow::PoisonAllocatedBlock(block);
return block.body;
}
bool BlockHeapManager::Free(HeapId heap_id, void* alloc) {
DCHECK_NE(static_cast<HeapId>(NULL), heap_id);
BlockInfo block_info = {};
if (!Shadow::IsBeginningOfBlockBody(alloc) ||
!Shadow::BlockInfoFromShadow(alloc, &block_info)) {
// TODO(chrisha|sebmarchand): Handle invalid allocation addresses.
// Assume that this block was allocated without guards.
return unguarded_allocation_heap_->Free(alloc);
}
if (!BlockChecksumIsValid(block_info)) {
// The free stack hasn't yet been set, but may have been filled with junk.
// Reset it.
block_info.header->free_stack = NULL;
ReportHeapError(alloc, CORRUPT_BLOCK);
return FreeCorruptBlock(&block_info);
}
if (block_info.header->state == QUARANTINED_BLOCK) {
ReportHeapError(alloc, DOUBLE_FREE);
return false;
}
// heap_id is just a hint, the block trailer contains the heap used for the
// allocation.
heap_id = block_info.trailer->heap_id;
DCHECK_NE(static_cast<HeapId>(NULL), heap_id);
BlockQuarantineInterface* quarantine = NULL;
{
base::AutoLock lock(lock_);
HeapQuarantineMap::iterator iter_heap = heaps_.find(
reinterpret_cast<BlockHeapInterface*>(heap_id));
// We should always be able to retrieve a heap that we previously passed to
// the user.
CHECK(iter_heap != heaps_.end());
DCHECK_NE(reinterpret_cast<ShardedBlockQuarantine*>(NULL),
iter_heap->second);
quarantine = iter_heap->second;
}
if (quarantine->Push(block_info.header)) {
StackCapture stack;
stack.InitFromStack();
block_info.header->free_stack =
runtime_->stack_cache()->SaveStackTrace(stack);
block_info.trailer->free_ticks = ::GetTickCount();
block_info.trailer->free_tid = ::GetCurrentThreadId();
block_info.header->state = QUARANTINED_BLOCK;
// Poison the released alloc (marked as freed) and quarantine the block.
// Note that the original data is left intact. This may make it easier
// to debug a crash report/dump on access to a quarantined block.
Shadow::MarkAsFreed(block_info.body, block_info.body_size);
BlockSetChecksum(block_info);
TrimQuarantine(quarantine);
} else {
return FreePristineBlock(&block_info);
}
return true;
}
size_t BlockHeapManager::Size(HeapId heap_id, const void* alloc) {
DCHECK_NE(static_cast<HeapId>(NULL), heap_id);
BlockHeader* header = BlockGetHeaderFromBody(alloc);
if (header == NULL)
return 0;
return header->body_size;
}
void BlockHeapManager::Lock(HeapId heap_id) {
DCHECK_NE(static_cast<HeapId>(NULL), heap_id);
reinterpret_cast<HeapInterface*>(heap_id)->Lock();
}
void BlockHeapManager::Unlock(HeapId heap_id) {
DCHECK_NE(static_cast<HeapId>(NULL), heap_id);
reinterpret_cast<HeapInterface*>(heap_id)->Unlock();
}
void BlockHeapManager::set_parameters(
const common::AsanParameters& parameters) {
{
base::AutoLock lock(lock_);
parameters_ = parameters;
}
// Releases the lock before propagating the parameters.
PropagateParameters();
}
void BlockHeapManager::PropagateParameters() {
size_t quarantine_size = shared_quarantine_.max_quarantine_size();
shared_quarantine_.set_max_quarantine_size(parameters_.quarantine_size);
shared_quarantine_.set_max_object_size(parameters_.quarantine_block_size);
// Trim the quarantine if its maximum size has decreased.
if (quarantine_size > parameters_.quarantine_size)
TrimQuarantine(&shared_quarantine_);
if (parameters_.enable_zebra_block_heap && zebra_block_heap_ == NULL) {
// Initialize the zebra heap only if it isnt't already initialized.
// The zebra heap cannot be resized once created.
base::AutoLock lock(lock_);
zebra_block_heap_ = new ZebraBlockHeap(parameters_.zebra_block_heap_size,
&null_memory_notifier);
heaps_.insert(std::make_pair(zebra_block_heap_, zebra_block_heap_));
}
if (zebra_block_heap_ != NULL) {
zebra_block_heap_->set_quarantine_ratio(
parameters_.zebra_block_heap_quarantine_ratio);
TrimQuarantine(zebra_block_heap_);
}
// TODO(chrisha|sebmarchand): Clean up existing blocks that exceed the
// maximum block size? This will require an entirely new TrimQuarantine
// function. Since this is never changed at runtime except in our
// unittests, this is not clearly useful.
}
bool BlockHeapManager::DestroyHeapUnlocked(
BlockHeapInterface* heap,
BlockQuarantineInterface* quarantine) {
DCHECK_NE(reinterpret_cast<BlockHeapInterface*>(NULL), heap);
DCHECK_NE(reinterpret_cast<BlockQuarantineInterface*>(NULL), quarantine);
// Starts by removing all the block from this heap from the quarantine.
BlockQuarantineInterface::ObjectVector blocks_vec;
// We'll keep the blocks that don't belong to this heap in a temporary list.
// While this isn't optimal in terms of performance, destroying a heap isn't a
// common operation.
// TODO(sebmarchand): Add a version of the ShardedBlockQuarantine::Empty
// method that accepts a functor to filter the blocks to remove.
BlockQuarantineInterface::ObjectVector blocks_to_reinsert;
quarantine->Empty(&blocks_vec);
BlockQuarantineInterface::ObjectVector::iterator iter_block =
blocks_vec.begin();
for (; iter_block != blocks_vec.end(); ++iter_block) {
BlockInfo block_info = {};
// If we can't retrieve the block information from the shadow then it means
// that something went terribly wrong and that the shadow has been
// corrupted, there's nothing we can do in this case.
CHECK(Shadow::BlockInfoFromShadow(*iter_block, &block_info));
if (reinterpret_cast<BlockHeapInterface*>(block_info.trailer->heap_id) ==
heap) {
if (!FreePotentiallyCorruptBlock(&block_info))
return false;
} else {
blocks_to_reinsert.push_back(*iter_block);
}
}
// Restore the blocks that don't belong to this quarantine.
iter_block = blocks_to_reinsert.begin();
for (; iter_block != blocks_to_reinsert.end(); ++iter_block)
quarantine->Push(*iter_block);
UnderlyingHeapMap::iterator iter = underlying_heaps_map_.find(heap);
// Not all the heaps have an underlying heap.
if (iter != underlying_heaps_map_.end()) {
DCHECK_NE(reinterpret_cast<HeapInterface*>(NULL), iter->second);
delete iter->second;
underlying_heaps_map_.erase(iter);
}
delete heap;
return true;
}
void BlockHeapManager::TrimQuarantine(BlockQuarantineInterface* quarantine) {
DCHECK_NE(reinterpret_cast<BlockQuarantineInterface*>(NULL), quarantine);
BlockQuarantineInterface::ObjectVector blocks_to_free;
// Trim the quarantine to the new maximum size.
if (parameters_.quarantine_size == 0) {
quarantine->Empty(&blocks_to_free);
} else {
BlockHeader* block_to_free = NULL;
while (quarantine->Pop(&block_to_free))
blocks_to_free.push_back(block_to_free);
}
BlockQuarantineInterface::ObjectVector::iterator iter_block =
blocks_to_free.begin();
for (; iter_block != blocks_to_free.end(); ++iter_block) {
DCHECK_NE(reinterpret_cast<BlockHeader*>(NULL), *iter_block);
BlockInfo block_info = {};
CHECK(Shadow::BlockInfoFromShadow(*iter_block, &block_info));
CHECK(FreePotentiallyCorruptBlock(&block_info));
}
}
bool BlockHeapManager::FreePotentiallyCorruptBlock(BlockInfo* block_info) {
DCHECK_NE(static_cast<BlockInfo*>(NULL), block_info);
if (block_info->header->magic != kBlockHeaderMagic ||
!BlockChecksumIsValid(*block_info)) {
ReportHeapError(block_info->block, CORRUPT_BLOCK);
return FreeCorruptBlock(block_info);
} else {
return FreePristineBlock(block_info);
}
}
bool BlockHeapManager::FreeCorruptBlock(BlockInfo* block_info) {
DCHECK_NE(static_cast<BlockInfo*>(NULL), block_info);
ClearCorruptBlockMetadata(block_info);
return FreePristineBlock(block_info);
}
bool BlockHeapManager::FreePristineBlock(BlockInfo* block_info) {
DCHECK_NE(static_cast<BlockInfo*>(NULL), block_info);
BlockHeapInterface* heap = reinterpret_cast<BlockHeapInterface*>(
block_info->trailer->heap_id);
if (heap == NULL) {
// TODO(sebmarchand): Iterates over the heaps to find the one owning this
// block. This is currently useless as we're using the WinHeap which
// doesn't have the kHeapSupportsIsAllocated feature.
return false;
}
// Return pointers to the stacks for reference counting purposes.
if (block_info->header->alloc_stack != NULL) {
runtime_->stack_cache()->ReleaseStackTrace(block_info->header->alloc_stack);
block_info->header->alloc_stack = NULL;
}
if (block_info->header->free_stack != NULL) {
runtime_->stack_cache()->ReleaseStackTrace(block_info->header->free_stack);
block_info->header->free_stack = NULL;
}
block_info->header->state = FREED_BLOCK;
Shadow::Unpoison(block_info->header, block_info->block_size);
return heap->FreeBlock(*block_info);
}
void BlockHeapManager::ClearCorruptBlockMetadata(BlockInfo* block_info) {
DCHECK_NE(static_cast<BlockInfo*>(NULL), block_info);
DCHECK_NE(static_cast<BlockHeader*>(NULL), block_info->header);
// Set the invalid stack captures to NULL.
if (!runtime_->stack_cache()->StackCapturePointerIsValid(
block_info->header->alloc_stack)) {
block_info->header->alloc_stack = NULL;
}
if (!runtime_->stack_cache()->StackCapturePointerIsValid(
block_info->header->free_stack)) {
block_info->header->free_stack = NULL;
}
}
void BlockHeapManager::ReportHeapError(void* address, BadAccessKind kind) {
DCHECK_NE(reinterpret_cast<void*>(NULL), address);
// Collect information about the error.
AsanErrorInfo error_info = {};
::RtlCaptureContext(&error_info.context);
error_info.access_mode = agent::asan::ASAN_UNKNOWN_ACCESS;
error_info.location = address;
error_info.error_type = kind;
ErrorInfoGetBadAccessInformation(runtime_->stack_cache(), &error_info);
agent::asan::StackCapture stack;
stack.InitFromStack();
error_info.crash_stack_id = stack.ComputeRelativeStackId();
// We expect a callback to be set.
DCHECK(!heap_error_callback_.is_null());
heap_error_callback_.Run(&error_info);
}
} // namespace heap_managers
} // namespace asan
} // namespace agent