blob: a30b45c7466172733e37e40e48079520492a709b [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/instrument/transforms/asan_transform.h"
#include <algorithm>
#include <list>
#include <vector>
#include "base/logging.h"
#include "base/rand_util.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_vector.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "syzygy/block_graph/basic_block.h"
#include "syzygy/block_graph/basic_block_assembler.h"
#include "syzygy/block_graph/block_builder.h"
#include "syzygy/block_graph/block_util.h"
#include "syzygy/block_graph/typed_block.h"
#include "syzygy/common/defs.h"
#include "syzygy/instrument/transforms/asan_intercepts.h"
#include "syzygy/instrument/transforms/entry_thunk_transform.h"
#include "syzygy/pe/pe_utils.h"
#include "syzygy/pe/transforms/add_hot_patching_metadata_transform.h"
#include "syzygy/pe/transforms/coff_add_imports_transform.h"
#include "syzygy/pe/transforms/coff_rename_symbols_transform.h"
#include "syzygy/pe/transforms/pe_hot_patching_basic_block_transform.h"
#include "third_party/distorm/files/include/mnemonics.h"
#include "third_party/distorm/files/src/x86defs.h"
namespace instrument {
namespace transforms {
namespace {
using block_graph::BasicBlock;
using block_graph::BasicCodeBlock;
using block_graph::BasicBlockAssembler;
using block_graph::BasicBlockSubGraph;
using block_graph::BasicBlockReference;
using block_graph::BlockBuilder;
using block_graph::BlockGraph;
using block_graph::Displacement;
using block_graph::Immediate;
using block_graph::Instruction;
using block_graph::Operand;
using block_graph::TransformPolicyInterface;
using block_graph::TypedBlock;
using block_graph::analysis::LivenessAnalysis;
using block_graph::analysis::MemoryAccessAnalysis;
using assm::Register32;
using pe::transforms::CoffAddImportsTransform;
using pe::transforms::ImportedModule;
using pe::transforms::PEAddImportsTransform;
using pe::transforms::PECoffAddImportsTransform;
// A simple struct that can be used to let us access strings using TypedBlock.
struct StringStruct {
const char string[1];
};
typedef AsanBasicBlockTransform::MemoryAccessMode AsanMemoryAccessMode;
typedef AsanBasicBlockTransform::AsanHookMap HookMap;
typedef std::vector<AsanBasicBlockTransform::AsanHookMapEntryKey>
AccessHookParamVector;
typedef TypedBlock<IMAGE_IMPORT_DESCRIPTOR> ImageImportDescriptor;
typedef TypedBlock<StringStruct> String;
// The timestamp 1 corresponds to Thursday, 01 Jan 1970 00:00:01 GMT. Setting
// the timestamp of the image import descriptor to this value allows us to
// temporarily bind the library until the loader finishes loading this module.
// As the value is far in the past this means that the entries in the IAT for
// this module will all be replaced by pointers into the actual library.
// We need to bind the IAT for our module to make sure the stub is used until
// the sandbox lets the loader finish patching the IAT entries.
static const size_t kDateInThePast = 1;
// Returns true iff opcode should be instrumented.
bool ShouldInstrumentOpcode(uint16_t opcode) {
switch (opcode) {
// LEA does not actually access memory.
case I_LEA:
return false;
// We can ignore the prefetch and clflush instructions. The instrumentation
// will detect memory errors if and when the memory is actually accessed.
case I_CLFLUSH:
case I_PREFETCH:
case I_PREFETCHNTA:
case I_PREFETCHT0:
case I_PREFETCHT1:
case I_PREFETCHT2:
case I_PREFETCHW:
return false;
}
return true;
}
// Computes the correct displacement, if any, for operand
// number @p operand of @p instr.
BasicBlockAssembler::Displacement ComputeDisplacementForOperand(
const Instruction& instr, size_t operand) {
const _DInst& repr = instr.representation();
DCHECK(repr.ops[operand].type == O_SMEM ||
repr.ops[operand].type == O_MEM);
size_t access_size_bytes = repr.ops[operand].size / 8;
if (repr.dispSize == 0)
return Displacement(access_size_bytes - 1);
BasicBlockReference reference;
if (instr.FindOperandReference(operand, &reference)) {
if (reference.referred_type() == BasicBlockReference::REFERRED_TYPE_BLOCK) {
return Displacement(reference.block(),
reference.offset() + access_size_bytes - 1);
} else {
return Displacement(reference.basic_block());
}
} else {
return Displacement(repr.disp + access_size_bytes - 1);
}
}
// Returns true if operand @p op is instrumentable, e.g.
// if it implies a memory access.
bool IsInstrumentable(const _Operand& op) {
switch (op.type) {
case O_SMEM:
case O_MEM:
return true;
default:
return false;
}
}
// Returns true if opcode @p opcode is a special instruction.
// Memory checks for special instructions (string instructions, instructions
// with prefix, etc) are handled by calling specialized functions rather than
// the standard memory checks.
bool IsSpecialInstruction(uint16_t opcode) {
switch (opcode) {
case I_CMPS:
case I_LODS:
case I_MOVS:
case I_STOS:
return true;
default:
return false;
}
}
// Decodes the first O_MEM or O_SMEM operand of @p instr, if any to the
// corresponding Operand.
bool DecodeMemoryAccess(const Instruction& instr,
BasicBlockAssembler::Operand* access,
AsanBasicBlockTransform::MemoryAccessInfo* info) {
DCHECK(access != NULL);
DCHECK(info != NULL);
const _DInst& repr = instr.representation();
// Don't instrument NOP instructions. These can often make reference to
// registers, but their contents aren't actually meaningful.
if (core::IsNop(repr))
return false;
// Figure out which operand we're instrumenting.
size_t mem_op_id = SIZE_MAX;
if (IsInstrumentable(repr.ops[0]) && IsInstrumentable(repr.ops[1])) {
// This happens with instructions like: MOVS [EDI], [ESI].
DCHECK(repr.ops[0].size == repr.ops[1].size);
mem_op_id = 0;
} else if (IsInstrumentable(repr.ops[0])) {
// The first operand is instrumentable.
mem_op_id = 0;
} else if (IsInstrumentable(repr.ops[1])) {
// The second operand is instrumentable.
mem_op_id = 1;
} else {
// Neither of the first two operands is instrumentable.
return false;
}
// Determine the size of the access.
info->size = repr.ops[mem_op_id].size / 8;
// Determine the kind of access (read/write/instr/repz).
if (FLAG_GET_PREFIX(repr.flags) & FLAG_REPNZ) {
info->mode = AsanBasicBlockTransform::kRepnzAccess;
} else if (FLAG_GET_PREFIX(repr.flags) & FLAG_REP) {
info->mode = AsanBasicBlockTransform::kRepzAccess;
} else if (IsSpecialInstruction(instr.opcode())) {
info->mode = AsanBasicBlockTransform::kInstrAccess;
} else if ((repr.flags & FLAG_DST_WR) && mem_op_id == 0) {
// The first operand is written to.
info->mode = AsanBasicBlockTransform::kWriteAccess;
} else {
info->mode = AsanBasicBlockTransform::kReadAccess;
}
// Determine the opcode of this instruction (when needed).
if (info->mode == AsanBasicBlockTransform::kRepnzAccess ||
info->mode == AsanBasicBlockTransform::kRepzAccess ||
info->mode == AsanBasicBlockTransform::kInstrAccess) {
info->opcode = instr.opcode();
}
// Determine operand of the access.
if (repr.ops[mem_op_id].type == O_SMEM) {
// Simple memory dereference with optional displacement.
const Register32& base_reg = assm::CastAsRegister32(
core::GetRegister(repr.ops[mem_op_id].index));
// Get the displacement for the operand.
auto displ = ComputeDisplacementForOperand(instr, mem_op_id);
*access = Operand(base_reg, displ);
} else if (repr.ops[0].type == O_MEM || repr.ops[1].type == O_MEM) {
// Complex memory dereference.
const Register32& index_reg = assm::CastAsRegister32(
core::GetRegister(repr.ops[mem_op_id].index));
assm::ScaleFactor scale = assm::kTimes1;
switch (repr.scale) {
case 2:
scale = assm::kTimes2;
break;
case 4:
scale = assm::kTimes4;
break;
case 8:
scale = assm::kTimes8;
break;
default:
break;
}
// Get the displacement for the operand (if any).
auto displ = ComputeDisplacementForOperand(instr, mem_op_id);
// Compute the full operand.
if (repr.base != R_NONE) {
const Register32& base_reg = assm::CastAsRegister32(
core::GetRegister(repr.base));
if (displ.size() == assm::kSizeNone) {
// No displacement, it's a [base + index * scale] access.
*access = Operand(base_reg, index_reg, scale);
} else {
// This is a [base + index * scale + displ] access.
*access = Operand(base_reg, index_reg, scale, displ);
}
} else {
// No base, this is an [index * scale + displ] access.
// TODO(siggi): AFAIK, there's no encoding for [index * scale] without
// a displacement. If this assert fires, I'm proven wrong.
DCHECK_NE(assm::kSizeNone, displ.size());
*access = Operand(index_reg, scale, displ);
}
} else {
NOTREACHED();
return false;
}
return true;
}
// Use @p bb_asm to inject a hook to @p hook to instrument the access to the
// address stored in the operand @p op.
void InjectAsanHook(BasicBlockAssembler* bb_asm,
const AsanBasicBlockTransform::MemoryAccessInfo& info,
const BasicBlockAssembler::Operand& op,
BlockGraph::Reference* hook,
const LivenessAnalysis::State& state,
BlockGraph::ImageFormat image_format) {
DCHECK(hook != NULL);
// Determine which kind of probe to inject.
// - The standard load/store probe assume the address is in EDX.
// It restore the original version of EDX and cleanup the stack.
// - The special instruction probe take addresses directly in registers.
// The probe doesn't have any effects on stack, registers and flags.
if (info.mode == AsanBasicBlockTransform::kReadAccess ||
info.mode == AsanBasicBlockTransform::kWriteAccess) {
// Load/store probe.
bb_asm->push(assm::edx);
bb_asm->lea(assm::edx, op);
}
// Call the hook.
if (image_format == BlockGraph::PE_IMAGE) {
// In PE images the hooks are brought in as imports, so they are indirect
// references.
bb_asm->call(Operand(Displacement(hook->referenced(), hook->offset())));
} else {
DCHECK_EQ(BlockGraph::COFF_IMAGE, image_format);
// In COFF images the hooks are brought in as symbols, so they are direct
// references.
bb_asm->call(Immediate(hook->referenced(), hook->offset()));
}
}
// Get the name of an asan check access function for an @p access_mode access.
// @param info The memory access information, e.g. the size on a load/store,
// the instruction opcode and the kind of access.
std::string GetAsanCheckAccessFunctionName(
AsanBasicBlockTransform::MemoryAccessInfo info,
BlockGraph::ImageFormat image_format) {
DCHECK(info.mode != AsanBasicBlockTransform::kNoAccess);
DCHECK_NE(0U, info.size);
DCHECK(info.mode == AsanBasicBlockTransform::kReadAccess ||
info.mode == AsanBasicBlockTransform::kWriteAccess ||
info.opcode != 0);
const char* rep_str = NULL;
if (info.mode == AsanBasicBlockTransform::kRepzAccess)
rep_str = "_repz";
else if (info.mode == AsanBasicBlockTransform::kRepnzAccess)
rep_str = "_repnz";
else
rep_str = "";
const char* access_mode_str = NULL;
if (info.mode == AsanBasicBlockTransform::kReadAccess)
access_mode_str = "read";
else if (info.mode == AsanBasicBlockTransform::kWriteAccess)
access_mode_str = "write";
else
access_mode_str = reinterpret_cast<char*>(GET_MNEMONIC_NAME(info.opcode));
// For COFF images we use the decorated function name, which contains a
// leading underscore.
std::string function_name =
base::StringPrintf("%sasan_check%s_%d_byte_%s_access%s",
image_format == BlockGraph::PE_IMAGE ? "" : "_",
rep_str,
info.size,
access_mode_str,
info.save_flags ? "" : "_no_flags");
function_name = base::ToLowerASCII(function_name);
return function_name;
}
// Add imports from the specified module to the block graph, altering the
// contents of its header/special blocks.
// @param policy the policy object restricting how the transform is applied.
// @param block_graph the block graph to modify.
// @param header_block the header block of @p block_graph.
// @param module the module to import, with its symbols.
// @returns true on success, or false on failure.
bool AddImportsFromModule(const TransformPolicyInterface* policy,
BlockGraph* block_graph,
BlockGraph::Block* header_block,
ImportedModule* module) {
if (block_graph->image_format() == BlockGraph::PE_IMAGE) {
PEAddImportsTransform transform;
transform.AddModule(module);
if (!ApplyBlockGraphTransform(&transform, policy,
block_graph, header_block)) {
return false;
}
} else {
DCHECK_EQ(BlockGraph::COFF_IMAGE, block_graph->image_format());
CoffAddImportsTransform transform;
transform.AddModule(module);
if (!ApplyBlockGraphTransform(&transform, policy,
block_graph, header_block)) {
return false;
}
}
return true;
}
// Add the imports for the asan check access hooks to the block-graph.
// @param hooks_param_vector A vector of hook parameter values.
// @param default_stub_map Stubs for the asan check access functions.
// @param import_module The module for which the import should be added.
// @param check_access_hook_map The map where the reference to the imports
// should be stored.
// @param policy The policy object restricting how the transform is applied.
// @param block_graph The block-graph to populate.
// @param header_block The block containing the module's DOS header of this
// block-graph.
// @returns True on success, false otherwise.
bool AddAsanCheckAccessHooks(
const AccessHookParamVector& hook_param_vector,
const AsanBasicBlockTransform::AsanDefaultHookMap& default_stub_map,
ImportedModule* import_module,
HookMap* check_access_hook_map,
const TransformPolicyInterface* policy,
BlockGraph* block_graph,
BlockGraph::Block* header_block) {
DCHECK(import_module != NULL);
DCHECK(check_access_hook_map != NULL);
DCHECK(policy != NULL);
DCHECK(block_graph != NULL);
DCHECK(header_block != NULL);
typedef std::map<AsanBasicBlockTransform::AsanHookMapEntryKey, size_t>
HooksParamsToIdxMap;
HooksParamsToIdxMap hooks_params_to_idx;
// Add the hooks to the import module.
AccessHookParamVector::const_iterator iter_params = hook_param_vector.begin();
for (; iter_params != hook_param_vector.end(); ++iter_params) {
size_t symbol_idx = import_module->AddSymbol(
GetAsanCheckAccessFunctionName(*iter_params,
block_graph->image_format()),
ImportedModule::kAlwaysImport);
hooks_params_to_idx[*iter_params] = symbol_idx;
}
DCHECK_EQ(hooks_params_to_idx.size(), hook_param_vector.size());
// Add the imports. This takes care of invoking the appropriate format
// specific transform.
if (!AddImportsFromModule(policy, block_graph, header_block, import_module)) {
LOG(ERROR) << "Unable to add imports for Asan instrumentation DLL.";
return false;
}
// Get a reference to each hook and put it in the hooks map.
HooksParamsToIdxMap::iterator iter_hooks = hooks_params_to_idx.begin();
for (; iter_hooks != hooks_params_to_idx.end(); ++iter_hooks) {
BlockGraph::Reference import_reference;
if (!import_module->GetSymbolReference(iter_hooks->second,
&import_reference)) {
LOG(ERROR) << "Unable to get import reference for Asan.";
return false;
}
HookMap& hook_map = *check_access_hook_map;
hook_map[iter_hooks->first] = import_reference;
// We only need dummy implementation stubs for PE images, as the hooks are
// imported. COFF instrumented images contain the hooks directly.
if (block_graph->image_format() == BlockGraph::PE_IMAGE) {
// In a Chrome sandboxed process the NtMapViewOfSection function is
// intercepted by the sandbox agent. This causes execution in the
// executable before imports have been resolved, as the ntdll patch
// invokes into the executable while resolving imports. As the Asan
// instrumentation directly refers to the IAT entries we need to
// temporarily stub these function until the Asan imports are resolved. To
// do this we need to make the IAT entries for those functions point to a
// temporarily block and we need to mark the image import descriptor for
// this DLL as bound.
AsanBasicBlockTransform::AsanDefaultHookMap::const_iterator
stub_reference = default_stub_map.find(iter_hooks->first.mode);
if (stub_reference == default_stub_map.end()) {
LOG(ERROR) << "Could not find the default hook for "
<< GetAsanCheckAccessFunctionName(iter_hooks->first,
BlockGraph::PE_IMAGE)
<< ".";
return false;
}
import_reference.referenced()->SetReference(import_reference.offset(),
stub_reference->second);
}
}
return true;
}
// Create a stub for the asan_check_access functions. For load/store, the stub
// consists of a small block of code that restores the value of EDX and returns
// to the caller. Otherwise, the stub do return.
// @param block_graph The block-graph to populate with the stub.
// @param stub_name The stub's name.
// @param mode The kind of memory access.
// @param reference Will receive the reference to the created hook.
// @returns true on success, false otherwise.
bool CreateHooksStub(BlockGraph* block_graph,
const base::StringPiece& stub_name,
AsanBasicBlockTransform::MemoryAccessMode mode,
BlockGraph::Reference* reference) {
DCHECK(reference != NULL);
// Find or create the section we put our thunks in.
BlockGraph::Section* thunk_section = block_graph->FindOrAddSection(
common::kThunkSectionName, pe::kCodeCharacteristics);
if (thunk_section == NULL) {
LOG(ERROR) << "Unable to find or create .thunks section.";
return false;
}
std::string stub_name_with_id = base::StringPrintf(
"%.*s%d", stub_name.length(), stub_name.data(), mode);
// Create the thunk for standard "load/store" (received address in EDX).
BasicBlockSubGraph bbsg;
BasicBlockSubGraph::BlockDescription* block_desc = bbsg.AddBlockDescription(
stub_name_with_id,
thunk_section->name(),
BlockGraph::CODE_BLOCK,
thunk_section->id(),
1,
0);
BasicCodeBlock* bb = bbsg.AddBasicCodeBlock(stub_name_with_id);
block_desc->basic_block_order.push_back(bb);
BasicBlockAssembler assm(bb->instructions().begin(), &bb->instructions());
if (mode == AsanBasicBlockTransform::kReadAccess ||
mode == AsanBasicBlockTransform::kWriteAccess) {
// The thunk body restores the original value of EDX and cleans the stack on
// return.
assm.mov(assm::edx, Operand(assm::esp, Displacement(4)));
assm.ret(4);
} else {
assm.ret();
}
// Condense into a block.
BlockBuilder block_builder(block_graph);
if (!block_builder.Merge(&bbsg)) {
LOG(ERROR) << "Failed to build thunk block.";
return NULL;
}
// Exactly one new block should have been created.
DCHECK_EQ(1u, block_builder.new_blocks().size());
BlockGraph::Block* thunk = block_builder.new_blocks().front();
*reference = BlockGraph::Reference(BlockGraph::ABSOLUTE_REF, 4, thunk, 0, 0);
return true;
}
// Creates stubs for Asan check access hooks (PE only), imports them from the
// runtime module and adds them to the block graph.
// @param asan_hook_stub_name Name prefix of the stubs for the asan check access
// functions.
// @param use_liveness_analysis true iff we use liveness analysis.
// @param import_module The module for which the import should be added.
// @param check_access_hooks_ref The map where the reference to the imports
// should be stored.
// @param policy The policy object restricting how the transform is applied.
// @param block_graph The block-graph to populate.
// @param header_block The block containing the module's DOS header of this
// block-graph.
// @returns True on success, false otherwise.
bool ImportAsanCheckAccessHooks(
const char* asan_hook_stub_name,
bool use_liveness_analysis,
ImportedModule* import_module,
AsanBasicBlockTransform::AsanHookMap* check_access_hooks_ref,
const TransformPolicyInterface* policy,
BlockGraph* block_graph,
BlockGraph::Block* header_block) {
typedef AsanBasicBlockTransform::MemoryAccessInfo MemoryAccessInfo;
AccessHookParamVector access_hook_param_vec;
AsanBasicBlockTransform::AsanDefaultHookMap default_stub_map;
// We only need to add stubs for PE images. COFF images use direct references,
// and the linker takes care of dragging in the appropriate code for us.
// Also, hot patching mode does not need the stubs as it will load them
// dynamically at runtime.
if (block_graph->image_format() == BlockGraph::PE_IMAGE) {
// Create the hook stub for read/write instructions.
BlockGraph::Reference read_write_hook;
if (!CreateHooksStub(block_graph, asan_hook_stub_name,
AsanBasicBlockTransform::kReadAccess,
&read_write_hook)) {
return false;
}
// Create the hook stub for strings instructions.
BlockGraph::Reference instr_hook;
if (!CreateHooksStub(block_graph, asan_hook_stub_name,
AsanBasicBlockTransform::kInstrAccess,
&instr_hook)) {
return false;
}
// Map each memory access kind to an appropriate stub.
default_stub_map[AsanBasicBlockTransform::kReadAccess] = read_write_hook;
default_stub_map[AsanBasicBlockTransform::kWriteAccess] = read_write_hook;
default_stub_map[AsanBasicBlockTransform::kInstrAccess] = instr_hook;
default_stub_map[AsanBasicBlockTransform::kRepzAccess] = instr_hook;
default_stub_map[AsanBasicBlockTransform::kRepnzAccess] = instr_hook;
}
// Import the hooks for the read/write accesses.
for (int access_size = 1; access_size <= 32; access_size *= 2) {
MemoryAccessInfo read_info =
{ AsanBasicBlockTransform::kReadAccess, access_size, 0, true };
access_hook_param_vec.push_back(read_info);
if (use_liveness_analysis) {
read_info.save_flags = false;
access_hook_param_vec.push_back(read_info);
}
MemoryAccessInfo write_info =
{ AsanBasicBlockTransform::kWriteAccess, access_size, 0, true };
access_hook_param_vec.push_back(write_info);
if (use_liveness_analysis) {
write_info.save_flags = false;
access_hook_param_vec.push_back(write_info);
}
}
// Import the hooks for the read/write 10-byte accesses.
MemoryAccessInfo read_info_10 =
{ AsanBasicBlockTransform::kReadAccess, 10, 0, true };
access_hook_param_vec.push_back(read_info_10);
if (use_liveness_analysis) {
read_info_10.save_flags = false;
access_hook_param_vec.push_back(read_info_10);
}
MemoryAccessInfo write_info_10 =
{ AsanBasicBlockTransform::kWriteAccess, 10, 0, true };
access_hook_param_vec.push_back(write_info_10);
if (use_liveness_analysis) {
write_info_10.save_flags = false;
access_hook_param_vec.push_back(write_info_10);
}
// Import the hooks for string/prefix memory accesses.
const _InstructionType strings[] = {I_CMPS, I_LODS, I_MOVS, I_STOS};
int strings_length = sizeof(strings)/sizeof(_InstructionType);
for (int access_size = 1; access_size <= 4; access_size *= 2) {
for (int inst = 0; inst < strings_length; ++inst) {
MemoryAccessInfo repz_inst_info = {
AsanBasicBlockTransform::kRepzAccess,
access_size,
strings[inst],
true
};
access_hook_param_vec.push_back(repz_inst_info);
MemoryAccessInfo inst_info = {
AsanBasicBlockTransform::kInstrAccess,
access_size,
strings[inst],
true
};
access_hook_param_vec.push_back(inst_info);
}
}
if (!AddAsanCheckAccessHooks(access_hook_param_vec,
default_stub_map,
import_module,
check_access_hooks_ref,
policy,
block_graph,
header_block)) {
return false;
}
return true;
}
// Create a thunk that does the following call:
// ::HeapCreate(0, 0x1000, 0);
//
// This block has the same signature as the ::GetProcessHeap function.
//
// As the ::GetProcessHeap function is usually called via an indirect reference
// (i.e. it's an entry in the IAT) this function returns also an indirect
// reference to the replacement block. To do this it first creates a code block,
// and then a data block containing a reference t it. It returns the data block.
//
// @param block_graph the block graph that should receive this thunk.
// @param thunk_name The name of the thunk to create. This name will be used to
// name the code block that gets created, the data block will append '_data'
// to it.
// @param heap_create_ref The reference to the heap create function.
// @returns a pointer to a data block that contains a reference to this block,
// nullptr otherwise.
BlockGraph::Block* CreateGetProcessHeapReplacement(
BlockGraph* block_graph,
base::StringPiece thunk_name,
const BlockGraph::Reference& heap_create_ref) {
// Find or create the section we put our thunks in.
BlockGraph::Section* thunk_section = block_graph->FindOrAddSection(
common::kThunkSectionName, pe::kCodeCharacteristics);
if (thunk_section == NULL) {
LOG(ERROR) << "Unable to find or create .thunks section.";
return nullptr;
}
BasicBlockSubGraph code_bbsg;
BasicBlockSubGraph::BlockDescription* code_block_desc =
code_bbsg.AddBlockDescription(thunk_name,
thunk_section->name(),
BlockGraph::CODE_BLOCK,
thunk_section->id(),
1,
0);
BasicCodeBlock* code_bb = code_bbsg.AddBasicCodeBlock(thunk_name);
code_block_desc->basic_block_order.push_back(code_bb);
BasicBlockAssembler assm(code_bb->instructions().begin(),
&code_bb->instructions());
assm.push(Immediate(0U, assm::kSize32Bit));
assm.push(Immediate(0x1000U, assm::kSize32Bit));
assm.push(Immediate(0U, assm::kSize32Bit));
assm.call(Operand(Displacement(heap_create_ref.referenced(),
heap_create_ref.offset())));
assm.ret();
// Condense into a block.
BlockBuilder block_builder(block_graph);
if (!block_builder.Merge(&code_bbsg)) {
LOG(ERROR) << "Failed to build thunk block.";
return nullptr;
}
// Exactly one new block should have been created.
DCHECK_EQ(1u, block_builder.new_blocks().size());
BlockGraph::Block* code_block = block_builder.new_blocks().front();
// Create a data block containing the address of the new code block, it'll be
// use to call it via an indirect reference.
std::string data_block_name =
base::StringPrintf("%s_data", thunk_name.data());
BlockGraph::Reference ref(BlockGraph::ABSOLUTE_REF, 4, code_block, 0, 0);
BlockGraph::Block* data_block = block_graph->AddBlock(BlockGraph::DATA_BLOCK,
ref.size(),
data_block_name);
data_block->set_section(thunk_section->id());
data_block->SetReference(0, ref);
return data_block;
}
// Since MSVS 2012 the implementation of the CRT _heap_init function has
// changed and as a result the CRT defers all its allocation to the process
// heap. Since MSVS 2015 the function has changed names to
// _acrt_heap_initialize.
//
// As we don't want to replace the process heap by an Asan heap we need to
// patch this function to make it use ::HeapCreate instead of
// ::GetProcessHeap.
//
// We do this by replacing the reference to ::GetProcessHeap by a reference
// to a thunk that calls ::HeapCreate.
//
// TODO(sebmarchand): Also patch the _heap_term/_acrt_uninitialize_heap
// functions. These functions arent't always present and is just used to
// reset the crt_heap pointer and free the underlying heap. This isn't so
// important in this case because it only happens when the process
// terminates and the heap will be automatically freed when we unload the
// SyzyAsan agent DLL.
//
// @param block_graph The block-graph to populate with the stub.
// @param header_block the header block of @p block_graph.
// @param policy the policy object restricting how the transform is applied.
// @param heap_create_dll_name the name of the DLL exporting the ::HeapCreate
// function, it is either kernel32.dll or the name of the agent DLL used
// by this transform.
// @param heap_create_function_name the name of the ::HeapCreate export in
// |heap_create_dll_name|.
// @param heap_init_blocks The heap initialization functions that we want to
// patch.
// @returns true on success, false otherwise.
bool PatchCRTHeapInitialization(
BlockGraph* block_graph,
BlockGraph::Block* header_block,
const TransformPolicyInterface* policy,
base::StringPiece heap_create_dll_name,
base::StringPiece heap_create_function_name,
const std::vector<BlockGraph::Block*>& heap_init_blocks) {
DCHECK_NE(static_cast<BlockGraph*>(nullptr), block_graph);
DCHECK_NE(static_cast<BlockGraph::Block*>(nullptr), header_block);
DCHECK_NE(static_cast<const TransformPolicyInterface*>(nullptr), policy);
// Add the |heap_create_dll_name| module.
ImportedModule heap_create_module(heap_create_dll_name);
size_t heap_create_idx = heap_create_module.AddSymbol(
heap_create_function_name, ImportedModule::kAlwaysImport);
// Add the module containing the GetProcessHeap function.
ImportedModule* kernel32_module = nullptr;
// This scoped pointer will only be used if we need to dynamically allocate
// the kernel32 module to make sure that it gets correctly freed.
const char* kKernel32 = "kernel32.dll";
std::unique_ptr<ImportedModule> scoped_get_process_heap_module;
if (base::CompareCaseInsensitiveASCII(heap_create_dll_name,
kKernel32) != 0) {
scoped_get_process_heap_module.reset(new ImportedModule(kKernel32));
kernel32_module = scoped_get_process_heap_module.get();
} else {
kernel32_module = &heap_create_module;
}
size_t get_process_heap_idx = kernel32_module->AddSymbol(
"GetProcessHeap", ImportedModule::kFindOnly);
// Apply the AddImport transform to add or find the required modules.
PEAddImportsTransform transform;
transform.AddModule(&heap_create_module);
if (kernel32_module != &heap_create_module)
transform.AddModule(kernel32_module);
if (!ApplyBlockGraphTransform(&transform, policy,
block_graph, header_block)) {
LOG(ERROR) << "Unable to add or find the imports required to patch the CRT "
<< "heap initialization.";
return false;
}
BlockGraph::Reference heap_create_ref;
CHECK(heap_create_module.GetSymbolReference(heap_create_idx,
&heap_create_ref));
// Create the GetProcessHeap replacement function.
BlockGraph::Block* get_process_heap_stub = CreateGetProcessHeapReplacement(
block_graph, "asan_get_process_heap_replacement", heap_create_ref);
BlockGraph::Reference get_process_heap_ref;
CHECK(kernel32_module->GetSymbolReference(get_process_heap_idx,
&get_process_heap_ref));
BlockGraph::Reference new_ref(BlockGraph::ABSOLUTE_REF,
get_process_heap_ref.size(),
get_process_heap_stub, 0U, 0U);
// Iterates over the list of blocks to patch.
for (auto iter : heap_init_blocks) {
VLOG(1) << "Patching " << iter->name() << ".";
for (const auto& ref : iter->references()) {
if (ref.second == get_process_heap_ref)
iter->SetReference(ref.first, new_ref);
}
}
return true;
}
typedef std::map<std::string, size_t> ImportNameIndexMap;
bool PeFindImportsToIntercept(bool use_interceptors,
const AsanIntercept* intercepts,
const TransformPolicyInterface* policy,
BlockGraph* block_graph,
BlockGraph::Block* header_block,
ScopedVector<ImportedModule>* imported_modules,
ImportNameIndexMap* import_name_index_map,
ImportedModule* asan_rtl,
const char* asan_intercept_prefix) {
DCHECK_NE(reinterpret_cast<AsanIntercept*>(NULL), intercepts);
DCHECK_NE(reinterpret_cast<TransformPolicyInterface*>(NULL), policy);
DCHECK_NE(reinterpret_cast<BlockGraph*>(NULL), block_graph);
DCHECK_NE(reinterpret_cast<BlockGraph::Block*>(NULL), header_block);
DCHECK_NE(reinterpret_cast<ScopedVector<ImportedModule>*>(NULL),
imported_modules);
DCHECK_NE(reinterpret_cast<ImportNameIndexMap*>(NULL), import_name_index_map);
DCHECK_NE(reinterpret_cast<ImportedModule*>(NULL), asan_rtl);
// Process all of the import intercepts.
PEAddImportsTransform find_imports;
ImportedModule* current_module = NULL;
const char* current_module_name = NULL;
const AsanIntercept* intercept = intercepts;
for (; intercept->undecorated_name != NULL; ++intercept) {
// Create a new module to house these imports.
if (intercept->module != current_module_name) {
current_module_name = intercept->module;
current_module = NULL;
if (current_module_name) {
current_module = new ImportedModule(current_module_name);
imported_modules->push_back(current_module);
find_imports.AddModule(current_module);
}
}
// If no module name is specified then this interception is not an import
// interception.
if (current_module_name == NULL)
continue;
// Don't process optional intercepts unless asked to.
if (!use_interceptors && intercept->optional)
continue;
current_module->AddSymbol(intercept->undecorated_name,
ImportedModule::kFindOnly);
}
// Query the imports to see which ones are present.
if (!find_imports.TransformBlockGraph(policy, block_graph, header_block)) {
LOG(ERROR) << "Unable to find imports for redirection.";
return false;
}
// Add Asan imports for those functions found in the import tables. These will
// later be redirected.
for (const auto& module : *imported_modules) {
for (size_t i = 0; i < module->size(); ++i) {
if (!module->SymbolIsImported(i))
continue;
// The function should not already be imported. If it is then the
// intercepts data contains duplicates.
const std::string& function_name = module->GetSymbolName(i);
DCHECK(import_name_index_map->find(function_name) ==
import_name_index_map->end());
std::string asan_function_name = asan_intercept_prefix;
asan_function_name += function_name;
size_t index = asan_rtl->AddSymbol(asan_function_name,
ImportedModule::kAlwaysImport);
import_name_index_map->insert(std::make_pair(function_name, index));
}
}
return true;
}
// Loads the intercepts for the statically linked functions that need to be
// intercepted into the imported module and the import index map.
// @param static_blocks The blocks containing the statically linked functions we
// want to intercept.
// @param import_name_index_map The import index map of the runtime library.
// @param asan_rtl The module of the runtime library.
void PeLoadInterceptsForStaticallyLinkedFunctions(
const AsanTransform::BlockSet& static_blocks,
ImportNameIndexMap* import_name_index_map,
ImportedModule* asan_rtl,
const char* block_name_prefix) {
DCHECK_NE(static_cast<ImportNameIndexMap*>(nullptr), import_name_index_map);
DCHECK_NE(static_cast<ImportedModule*>(nullptr), asan_rtl);
for (BlockGraph::Block* block : static_blocks) {
// Don't add an import entry for names that have already been processed.
if (import_name_index_map->find(block->name()) !=
import_name_index_map->end()) {
continue;
}
std::string name = block_name_prefix;
name += block->name();
size_t index = asan_rtl->AddSymbol(name, ImportedModule::kAlwaysImport);
import_name_index_map->insert(std::make_pair(block->name(), index));
}
}
void PeGetRedirectsForInterceptedImports(
const ScopedVector<ImportedModule>& imported_modules,
const ImportNameIndexMap& import_name_index_map,
const ImportedModule& asan_rtl,
pe::ReferenceMap* reference_redirect_map) {
DCHECK_NE(reinterpret_cast<pe::ReferenceMap*>(NULL), reference_redirect_map);
// Register redirections related to the original.
for (const auto& module : imported_modules) {
for (size_t j = 0; j < module->size(); ++j) {
if (!module->SymbolIsImported(j))
continue;
// Get a reference to the original import.
BlockGraph::Reference src;
CHECK(module->GetSymbolReference(j, &src));
// Get a reference to the newly created import.
const std::string& name = module->GetSymbolName(j);
ImportNameIndexMap::const_iterator import_it =
import_name_index_map.find(name);
DCHECK(import_it != import_name_index_map.end());
BlockGraph::Reference dst;
CHECK(asan_rtl.GetSymbolReference(import_it->second, &dst));
// Record the reference mapping.
reference_redirect_map->insert(
std::make_pair(pe::ReferenceDest(src.referenced(), src.offset()),
pe::ReferenceDest(dst.referenced(), dst.offset())));
}
}
}
bool PeGetRedirectsForStaticallyLinkedFunctions(
const AsanTransform::BlockSet& static_blocks,
const ImportNameIndexMap& import_name_index_map,
const ImportedModule& asan_rtl,
BlockGraph* block_graph,
pe::ReferenceMap* reference_redirect_map,
const char* thunk_prefix) {
DCHECK_NE(reinterpret_cast<BlockGraph*>(NULL), block_graph);
DCHECK_NE(reinterpret_cast<pe::ReferenceMap*>(NULL), reference_redirect_map);
BlockGraph::Section* thunk_section = block_graph->FindOrAddSection(
common::kThunkSectionName, pe::kCodeCharacteristics);
DCHECK_NE(reinterpret_cast<BlockGraph::Section*>(NULL), thunk_section);
typedef std::map<std::string, BlockGraph::Block*> ThunkMap;
ThunkMap thunk_map;
for (BlockGraph::Block* block : static_blocks) {
ThunkMap::iterator thunk_it = thunk_map.find(block->name());
if (thunk_it == thunk_map.end()) {
// Generate the name of the thunk for this function.
std::string thunk_name = thunk_prefix;
thunk_name += block->name();
thunk_name += "_thunk";
// Get a reference to the newly created import.
ImportNameIndexMap::const_iterator import_it =
import_name_index_map.find(block->name());
DCHECK(import_it != import_name_index_map.end());
BlockGraph::Reference import_ref;
CHECK(asan_rtl.GetSymbolReference(import_it->second, &import_ref));
// Generate a basic code block for this thunk.
BasicBlockSubGraph bbsg;
BasicBlockSubGraph::BlockDescription* block_desc =
bbsg.AddBlockDescription(thunk_name,
thunk_section->name(),
BlockGraph::CODE_BLOCK,
thunk_section->id(),
1,
0);
BasicCodeBlock* bb = bbsg.AddBasicCodeBlock(thunk_name);
block_desc->basic_block_order.push_back(bb);
BasicBlockAssembler assm(bb->instructions().begin(),
&bb->instructions());
assm.jmp(Operand(Displacement(import_ref.referenced(),
import_ref.offset())));
// Condense into a block.
BlockBuilder block_builder(block_graph);
if (!block_builder.Merge(&bbsg)) {
LOG(ERROR) << "Failed to build thunk block \"" << thunk_name << "\".";
return false;
}
// Exactly one new block should have been created.
DCHECK_EQ(1u, block_builder.new_blocks().size());
BlockGraph::Block* thunk = block_builder.new_blocks().front();
thunk_it = thunk_map.insert(std::make_pair(block->name(), thunk)).first;
}
DCHECK(thunk_it != thunk_map.end());
// Register a redirection of references, from the original block to the
// newly created thunk.
reference_redirect_map->insert(std::make_pair(
pe::ReferenceDest(block, 0),
pe::ReferenceDest(thunk_it->second, 0)));
}
return true;
}
} // namespace
const char AsanBasicBlockTransform::kTransformName[] =
"SyzyAsanBasicBlockTransform";
bool AsanBasicBlockTransform::InstrumentBasicBlock(
BasicCodeBlock* basic_block,
StackAccessMode stack_mode,
BlockGraph::ImageFormat image_format) {
DCHECK_NE(reinterpret_cast<BasicCodeBlock*>(NULL), basic_block);
if (instrumentation_rate_ == 0.0)
return true;
// Pre-compute liveness information for each instruction.
std::list<LivenessAnalysis::State> states;
LivenessAnalysis::State state;
if (use_liveness_analysis_) {
liveness_.GetStateAtExitOf(basic_block, &state);
BasicBlock::Instructions::reverse_iterator rev_iter_inst =
basic_block->instructions().rbegin();
BasicBlock::Instructions::const_reverse_iterator rev_iter_inst_end =
basic_block->instructions().rend();
for (; rev_iter_inst != rev_iter_inst_end; ++rev_iter_inst) {
const Instruction& instr = *rev_iter_inst;
liveness_.PropagateBackward(instr, &state);
states.push_front(state);
}
DCHECK_EQ(states.size(), basic_block->instructions().size());
}
// Get the memory accesses information for this basic block.
MemoryAccessAnalysis::State memory_state;
if (remove_redundant_checks_)
memory_accesses_.GetStateAtEntryOf(basic_block, &memory_state);
// Process each instruction and inject a call to Asan when we find an
// instrumentable memory access.
BasicBlock::Instructions::iterator iter_inst =
basic_block->instructions().begin();
std::list<LivenessAnalysis::State>::iterator iter_state = states.begin();
for (; iter_inst != basic_block->instructions().end(); ++iter_inst) {
auto operand(Operand(assm::eax));
const Instruction& instr = *iter_inst;
const _DInst& repr = instr.representation();
MemoryAccessInfo info;
info.mode = kNoAccess;
info.size = 0;
info.opcode = 0;
info.save_flags = true;
// Get current instruction liveness information.
if (use_liveness_analysis_) {
state = *iter_state;
++iter_state;
}
// When activated, skip redundant memory access check.
if (remove_redundant_checks_) {
bool need_memory_access_check = false;
if (memory_state.HasNonRedundantAccess(instr))
need_memory_access_check = true;
// Update the memory accesses information for the current instruction.
memory_accesses_.PropagateForward(instr, &memory_state);
if (!need_memory_access_check)
continue;
}
// Insert hook for a standard instruction.
if (!DecodeMemoryAccess(instr, &operand, &info))
continue;
// Bail if this is not a memory access.
if (info.mode == kNoAccess)
continue;
// A basic block reference means that can be either a computed jump,
// or a load from a case table. In either case it doesn't make sense
// to instrument the access.
if (operand.displacement().reference().referred_type() ==
BasicBlockReference::REFERRED_TYPE_BASIC_BLOCK) {
continue;
}
// A block reference means this instruction is reading or writing to
// a global variable or some such. It's viable to pad and align global
// variables and to red-zone the padding, but without that, there's nothing
// to gain by instrumenting these accesses.
if (operand.displacement().reference().referred_type() ==
BasicBlockReference::REFERRED_TYPE_BLOCK) {
continue;
}
// Is this an instruction we should be instrumenting.
if (!ShouldInstrumentOpcode(repr.opcode))
continue;
// If there are no unconventional manipulations of the stack frame, we can
// skip instrumenting stack-based memory access (based on ESP or EBP).
// Conventionally, accesses through ESP/EBP are always on stack.
if (stack_mode == kSafeStackAccess &&
(operand.base() == assm::kRegisterEsp ||
operand.base() == assm::kRegisterEbp)) {
continue;
}
// We do not instrument memory accesses through special segments.
// FS is used for thread local specifics and GS for CPU info.
uint8_t segment = SEGMENT_GET(repr.segment);
if (segment == R_FS || segment == R_GS)
continue;
// Don't instrument any filtered instructions.
if (IsFiltered(*iter_inst))
continue;
// Randomly sample to effect partial instrumentation.
if (instrumentation_rate_ < 1.0 &&
base::RandDouble() >= instrumentation_rate_) {
continue;
}
// Create a BasicBlockAssembler to insert new instruction.
BasicBlockAssembler bb_asm(iter_inst, &basic_block->instructions());
// Configure the assembler to copy the SourceRange information of the
// current instrumented instruction into newly created instructions. This is
// a hack to allow valid stack walking and better error reporting, but
// breaks the 1:1 OMAP mapping and may confuse some debuggers.
if (debug_friendly_)
bb_asm.set_source_range(instr.source_range());
if (use_liveness_analysis_ &&
(info.mode == kReadAccess || info.mode == kWriteAccess)) {
// Use the liveness information to skip saving the flags if possible.
info.save_flags = state.AreArithmeticFlagsLive();
}
// Mark that an instrumentation will happen. Do this before selecting a
// hook so we can call a dry run without hooks present.
instrumentation_happened_ = true;
if (!dry_run_) {
// Insert hook for standard instructions.
AsanHookMap::iterator hook = check_access_hooks_->find(info);
if (hook == check_access_hooks_->end()) {
LOG(ERROR) << "Invalid access : "
<< GetAsanCheckAccessFunctionName(info, image_format);
return false;
}
// Instrument this instruction.
InjectAsanHook(
&bb_asm, info, operand, &hook->second, state, image_format);
}
}
DCHECK(iter_state == states.end());
return true;
}
void AsanBasicBlockTransform::set_instrumentation_rate(
double instrumentation_rate) {
// Set the instrumentation rate, capping it between 0 and 1.
instrumentation_rate_ = std::max(0.0, std::min(1.0, instrumentation_rate));
}
bool AsanBasicBlockTransform::TransformBasicBlockSubGraph(
const TransformPolicyInterface* policy,
BlockGraph* block_graph,
BasicBlockSubGraph* subgraph) {
DCHECK(policy != NULL);
DCHECK(block_graph != NULL);
DCHECK(subgraph != NULL);
// Perform a global liveness analysis.
if (use_liveness_analysis_)
liveness_.Analyze(subgraph);
// Perform a redundant memory access analysis.
if (remove_redundant_checks_)
memory_accesses_.Analyze(subgraph);
// Determines if this subgraph uses unconventional stack pointer
// manipulations.
StackAccessMode stack_mode = kUnsafeStackAccess;
if (!block_graph::HasUnexpectedStackFrameManipulation(subgraph))
stack_mode = kSafeStackAccess;
// Iterates through each basic block and instruments it.
BasicBlockSubGraph::BBCollection::iterator it =
subgraph->basic_blocks().begin();
for (; it != subgraph->basic_blocks().end(); ++it) {
BasicCodeBlock* bb = BasicCodeBlock::Cast(*it);
if (bb != NULL &&
!InstrumentBasicBlock(bb, stack_mode, block_graph->image_format())) {
return false;
}
}
return true;
}
HotPatchingAsanBasicBlockTransform::HotPatchingAsanBasicBlockTransform(
AsanBasicBlockTransform* asan_bb_transform)
: asan_bb_transform_(asan_bb_transform),
prepared_for_hot_patching_(false) {
DCHECK_NE(static_cast<AsanBasicBlockTransform*>(nullptr), asan_bb_transform);
DCHECK(asan_bb_transform_->dry_run());
}
bool HotPatchingAsanBasicBlockTransform::TransformBasicBlockSubGraph(
const TransformPolicyInterface* policy,
BlockGraph* block_graph,
BasicBlockSubGraph* basic_block_subgraph) {
DCHECK_NE(static_cast<TransformPolicyInterface*>(nullptr), policy);
DCHECK_NE(static_cast<BlockGraph*>(nullptr), block_graph);
DCHECK_NE(static_cast<BasicBlockSubGraph*>(nullptr), basic_block_subgraph);
prepared_for_hot_patching_ = false;
// Run Asan basic block transform in dry run mode.
DCHECK(asan_bb_transform_->dry_run());
asan_bb_transform_->TransformBasicBlockSubGraph(policy,
block_graph,
basic_block_subgraph);
// Prepare the block for hot patching if needed.
if (asan_bb_transform_->instrumentation_happened()) {
pe::transforms::PEHotPatchingBasicBlockTransform hp_bb_transform;
hp_bb_transform.TransformBasicBlockSubGraph(policy,
block_graph,
basic_block_subgraph);
prepared_for_hot_patching_ = true;
}
return true;
}
const char AsanTransform::kTransformName[] = "SyzyAsanTransform";
const char AsanTransform::kAsanHookStubName[] = "asan_hook_stub";
const char AsanTransform::kSyzyAsanDll[] = "syzyasan_rtl.dll";
const char AsanTransform::kSyzyAsanHpDll[] = "syzyasan_hp.dll";
AsanTransform::AsanTransform()
: debug_friendly_(false),
use_liveness_analysis_(false),
remove_redundant_checks_(false),
use_interceptors_(false),
instrumentation_rate_(1.0),
asan_parameters_(nullptr),
check_access_hooks_ref_(),
asan_parameters_block_(nullptr),
hot_patching_(false) {
}
AsanTransform::~AsanTransform() { }
void AsanTransform::set_instrumentation_rate(double instrumentation_rate) {
// Set the instrumentation rate, capping it between 0 and 1.
instrumentation_rate_ = std::max(0.0, std::min(1.0, instrumentation_rate));
}
bool AsanTransform::PreBlockGraphIteration(
const TransformPolicyInterface* policy,
BlockGraph* block_graph,
BlockGraph::Block* header_block) {
DCHECK_NE(reinterpret_cast<TransformPolicyInterface*>(NULL), policy);
DCHECK_NE(reinterpret_cast<BlockGraph*>(NULL), block_graph);
DCHECK_NE(reinterpret_cast<BlockGraph::Block*>(NULL), header_block);
DCHECK(block_graph->image_format() == BlockGraph::PE_IMAGE ||
block_graph->image_format() == BlockGraph::COFF_IMAGE);
// Ensure that this image has not already been instrumented.
if (block_graph->FindSection(common::kThunkSectionName)) {
LOG(ERROR) << "The image is already instrumented.";
return false;
}
// Initialize heap initialization blocks.
FindHeapInitAndCrtHeapBlocks(block_graph);
// Add an import entry for the Asan runtime.
ImportedModule import_module(instrument_dll_name(), kDateInThePast);
// Find static intercepts in PE images before the transform so that OnBlock
// can skip them.
if (block_graph->image_format() == BlockGraph::PE_IMAGE) {
if (!PeFindStaticallyLinkedFunctionsToIntercept(kAsanIntercepts,
block_graph))
return false;
}
// We don't need to import any hooks in hot patching mode.
if (!hot_patching_) {
if (!ImportAsanCheckAccessHooks(kAsanHookStubName,
use_liveness_analysis(),
&import_module,
&check_access_hooks_ref_,
policy,
block_graph,
header_block)) {
return false;
}
}
// Redirect DllMain entry thunk in hot patching mode.
if (hot_patching_) {
EntryThunkTransform entry_thunk_tx;
entry_thunk_tx.set_instrument_unsafe_references(false);
entry_thunk_tx.set_only_instrument_module_entry(true);
entry_thunk_tx.set_instrument_dll_name(instrument_dll_name());
if (!block_graph::ApplyBlockGraphTransform(&entry_thunk_tx,
policy,
block_graph,
header_block)) {
LOG(ERROR) << "Failed to rewrite DLL entry thunk.";
return false;
}
}
return true;
}
bool AsanTransform::OnBlock(const TransformPolicyInterface* policy,
BlockGraph* block_graph,
BlockGraph::Block* block) {
DCHECK(policy != NULL);
DCHECK(block_graph != NULL);
DCHECK(block != NULL);
if (ShouldSkipBlock(policy, block))
return true;
// Use the filter that was passed to us for our child transform.
AsanBasicBlockTransform transform(&check_access_hooks_ref_);
transform.set_debug_friendly(debug_friendly());
transform.set_use_liveness_analysis(use_liveness_analysis());
transform.set_remove_redundant_checks(remove_redundant_checks());
transform.set_filter(filter());
transform.set_instrumentation_rate(instrumentation_rate_);
if (!hot_patching_) {
if (!ApplyBasicBlockSubGraphTransform(
&transform, policy, block_graph, block, NULL)) {
return false;
}
} else {
// If we run in hot patching mode we just want to check if the block would
// be instrumented.
transform.set_dry_run(true);
HotPatchingAsanBasicBlockTransform hp_asan_bb_transform(&transform);
block_graph::BlockVector new_blocks;
if (!ApplyBasicBlockSubGraphTransform(
&hp_asan_bb_transform, policy, block_graph, block, &new_blocks)) {
return false;
}
// Save the block to be inserted into the hot patching section.
if (hp_asan_bb_transform.prepared_for_hot_patching()) {
CHECK_EQ(1U, new_blocks.size());
hot_patched_blocks_.push_back(new_blocks.front());
}
}
return true;
}
bool AsanTransform::PostBlockGraphIteration(
const TransformPolicyInterface* policy,
BlockGraph* block_graph,
BlockGraph::Block* header_block) {
DCHECK(policy != NULL);
DCHECK(block_graph != NULL);
DCHECK(header_block != NULL);
if (block_graph->image_format() == BlockGraph::PE_IMAGE) {
if (!PeInterceptFunctions(kAsanIntercepts, policy, block_graph,
header_block)) {
return false;
}
if (!PeInjectAsanParameters(policy, block_graph, header_block))
return false;
} else {
DCHECK_EQ(BlockGraph::COFF_IMAGE, block_graph->image_format());
if (!CoffInterceptFunctions(kAsanIntercepts, policy, block_graph,
header_block)) {
return false;
}
}
// If the heap initialization blocks were encountered in the
// PreBlockGraphIteration, patch them now.
if (!heap_init_blocks_.empty()) {
// We don't instrument HeapCreate in hot patching mode.
base::StringPiece heap_create_dll_name =
!hot_patching_ ? instrument_dll_name() : "kernel32.dll";
base::StringPiece heap_create_function_name =
!hot_patching_ ? "asan_HeapCreate" : "HeapCreate";
if (!PatchCRTHeapInitialization(block_graph,
header_block,
policy,
heap_create_dll_name,
heap_create_function_name,
heap_init_blocks_)) {
return false;
}
}
if (hot_patching_) {
pe::transforms::AddHotPatchingMetadataTransform hp_metadata_transform;
hp_metadata_transform.set_blocks_prepared(&hot_patched_blocks_);
if (!block_graph::ApplyBlockGraphTransform(&hp_metadata_transform,
policy,
block_graph,
header_block)) {
LOG(ERROR) << "Failed to insert hot patching metadata.";
return false;
}
}
return true;
}
base::StringPiece AsanTransform::instrument_dll_name() const {
if (asan_dll_name_.empty()) {
if (!hot_patching_) {
return kSyzyAsanDll;
} else {
return kSyzyAsanHpDll;
}
} else {
return asan_dll_name_.c_str();
}
}
void AsanTransform::FindHeapInitAndCrtHeapBlocks(BlockGraph* block_graph) {
for (auto& iter : block_graph->blocks_mutable()) {
bool add_block = false;
if (iter.second.name().find("_heap_init") != std::string::npos) {
// VS2012 CRT heap initialization.
add_block = true;
} else if (iter.second.name().find("_acrt_initialize_heap") !=
std::string::npos) {
// VS2015 CRT heap initialization.
add_block = true;
}
if (add_block) {
DCHECK(std::find(heap_init_blocks_.begin(),
heap_init_blocks_.end(), &(iter.second)) == heap_init_blocks_.end());
heap_init_blocks_.push_back(&(iter.second));
}
}
}
bool AsanTransform::ShouldSkipBlock(const TransformPolicyInterface* policy,
BlockGraph::Block* block) {
// Heap initialization blocks and intercepted blocks must be skipped.
if (std::find(heap_init_blocks_.begin(),
heap_init_blocks_.end(), block) != heap_init_blocks_.end()) {
return true;
}
if (static_intercepted_blocks_.count(block))
return true;
// Blocks that are not safe to basic block decompose should also be skipped.
if (!policy->BlockIsSafeToBasicBlockDecompose(block))
return true;
return false;
}
bool AsanTransform::PeFindStaticallyLinkedFunctionsToIntercept(
const AsanIntercept* intercepts,
BlockGraph* block_graph) {
DCHECK_NE(static_cast<AsanIntercept*>(nullptr), intercepts);
DCHECK_NE(static_cast<BlockGraph*>(nullptr), block_graph);
DCHECK(static_intercepted_blocks_.empty());
// Populate the filter with known hashes.
AsanInterceptorFilter filter;
filter.InitializeContentHashes(intercepts, use_interceptors_);
if (filter.empty())
return true;
// Discover statically linked functions that need to be intercepted.
BlockGraph::BlockMap::iterator block_it =
block_graph->blocks_mutable().begin();
for (; block_it != block_graph->blocks_mutable().end(); ++block_it) {
BlockGraph::Block* block = &block_it->second;
AsanInterceptorFilter::ShouldInterceptResult should_intercept_result =
filter.ShouldIntercept(block);
if (should_intercept_result == AsanInterceptorFilter::NOT_INTERCEPTED) {
continue;
} else if (should_intercept_result == AsanInterceptorFilter::INVALID_HASH) {
return false;
}
static_intercepted_blocks_.insert(block);
}
return true;
}
bool AsanTransform::PeInterceptFunctions(
const AsanIntercept* intercepts,
const TransformPolicyInterface* policy,
BlockGraph* block_graph,
BlockGraph::Block* header_block) {
DCHECK_NE(reinterpret_cast<AsanIntercept*>(NULL), intercepts);
DCHECK_NE(reinterpret_cast<TransformPolicyInterface*>(NULL), policy);
DCHECK_NE(reinterpret_cast<BlockGraph*>(NULL), block_graph);
DCHECK_NE(reinterpret_cast<BlockGraph::Block*>(NULL), header_block);
DCHECK_EQ(BlockGraph::PE_IMAGE, block_graph->image_format());
// This is used to keep track of the index of imports to the Asan RTL.
ImportNameIndexMap import_name_index_map;
// Keeps track of all imported modules with imports that we intercept.
ScopedVector<ImportedModule> imported_modules;
ImportedModule asan_rtl(instrument_dll_name(), kDateInThePast);
const char* asan_intercept_prefix = nullptr;
if (!hot_patching_) {
asan_intercept_prefix = kUndecoratedAsanInterceptPrefix;
} else {
asan_intercept_prefix = kUndecoratedHotPatchingAsanInterceptPrefix;
}
// Dynamic imports are only intercepted when hot patching is inactive.
if (!hot_patching()) {
// Determines what PE imports need to be intercepted, adding them to
// |asan_rtl| and |import_name_index_map|.
if (!PeFindImportsToIntercept(use_interceptors_,
intercepts,
policy,
block_graph,
header_block,
&imported_modules,
&import_name_index_map,
&asan_rtl,
asan_intercept_prefix)) {
return false;
}
}
// Add the intercepts of statically linked functions to |asan_rtl| and
// |import_name_index_map|.
PeLoadInterceptsForStaticallyLinkedFunctions(static_intercepted_blocks_,
&import_name_index_map,
&asan_rtl,
asan_intercept_prefix);
// Keep track of how many import redirections are to be performed. This allows
// a minor optimization later on when there are none to be performed.
size_t import_redirection_count = asan_rtl.size();
// If no imports were found at all, then there are no redirections to perform.
if (asan_rtl.size() == 0)
return true;
// Add the Asan RTL imports to the image.
PEAddImportsTransform add_imports_transform;
add_imports_transform.AddModule(&asan_rtl);
if (!add_imports_transform.TransformBlockGraph(
policy, block_graph, header_block)) {
LOG(ERROR) << "Unable to add imports for redirection.";
return false;
}
// This keeps track of reference redirections that need to be performed.
pe::ReferenceMap reference_redirect_map;
if (import_redirection_count > 0) {
PeGetRedirectsForInterceptedImports(imported_modules,
import_name_index_map,
asan_rtl,
&reference_redirect_map);
}
// Adds redirect information for any intercepted statically linked functions.
if (!static_intercepted_blocks_.empty()) {
if (!PeGetRedirectsForStaticallyLinkedFunctions(static_intercepted_blocks_,
import_name_index_map,
asan_rtl,
block_graph,
&reference_redirect_map,
asan_intercept_prefix)) {
return false;
}
}
// Finally, redirect all references to intercepted functions.
pe::RedirectReferences(reference_redirect_map);
return true;
}
bool AsanTransform::PeInjectAsanParameters(
const TransformPolicyInterface* policy,
BlockGraph* block_graph,
BlockGraph::Block* header_block) {
DCHECK_NE(reinterpret_cast<TransformPolicyInterface*>(NULL), policy);
DCHECK_NE(reinterpret_cast<BlockGraph*>(NULL), block_graph);
DCHECK_NE(reinterpret_cast<BlockGraph::Block*>(NULL), header_block);
DCHECK_EQ(BlockGraph::PE_IMAGE, block_graph->image_format());
// If there are no parameters then do nothing.
if (asan_parameters_ == NULL)
return true;
// Serialize the parameters into a new block.
common::FlatAsanParameters fparams(*asan_parameters_);
BlockGraph::Block* params_block = block_graph->AddBlock(
BlockGraph::DATA_BLOCK, fparams.data().size(), "AsanParameters");
DCHECK_NE(reinterpret_cast<BlockGraph::Block*>(NULL), params_block);
params_block->CopyData(fparams.data().size(), fparams.data().data());
// Wire up any references that are required.
static_assert(15 == common::kAsanParametersVersion,
"Pointers in the params must be linked up here.");
block_graph::TypedBlock<common::AsanParameters> params;
CHECK(params.Init(0, params_block));
if (fparams->ignored_stack_ids != NULL) {
size_t offset =
reinterpret_cast<const uint8_t*>(fparams->ignored_stack_ids) -
reinterpret_cast<const uint8_t*>(&fparams.params());
CHECK(params.SetReference(BlockGraph::ABSOLUTE_REF,
params->ignored_stack_ids,
params_block,
offset,
offset));
}
// Create an appropriately named section and put the parameters there. The
// RTL looks for this named section to find the parameters.
BlockGraph::Section* section = block_graph->FindOrAddSection(
common::kAsanParametersSectionName,
common::kAsanParametersSectionCharacteristics);
DCHECK_NE(reinterpret_cast<BlockGraph::Section*>(NULL), section);
params_block->set_section(section->id());
// Remember the block containing the parameters. This is a unittesting seam.
asan_parameters_block_ = params_block;
return true;
}
bool AsanTransform::CoffInterceptFunctions(
const AsanIntercept* intercepts,
const TransformPolicyInterface* policy,
BlockGraph* block_graph,
BlockGraph::Block* header_block) {
DCHECK_NE(reinterpret_cast<AsanIntercept*>(NULL), intercepts);
DCHECK_NE(reinterpret_cast<TransformPolicyInterface*>(NULL), policy);
DCHECK_NE(reinterpret_cast<BlockGraph*>(NULL), block_graph);
DCHECK_NE(reinterpret_cast<BlockGraph::Block*>(NULL), header_block);
// Extract the existing symbols.
pe::CoffSymbolNameOffsetMap symbol_map;
BlockGraph::Block* symbols_block = NULL;
BlockGraph::Block* strings_block = NULL;
if (!pe::FindCoffSpecialBlocks(block_graph, NULL, &symbols_block,
&strings_block)) {
LOG(ERROR) << "Unable to find COFF header blocks.";
return false;
}
if (!pe::BuildCoffSymbolNameOffsetMap(block_graph, &symbol_map)) {
LOG(ERROR) << "Unable to build symbol map.";
return false;
}
// Populate a COFF symbol rename transform for each function to be
// intercepted. We simply try to rename all possible symbols that may exist
// and allow the transform to ignore any that aren't present.
pe::transforms::CoffRenameSymbolsTransform rename_tx;
rename_tx.set_symbols_must_exist(false);
const AsanIntercept* intercept = intercepts;
bool defines_asan_functions = false;
for (; intercept->undecorated_name != NULL; ++intercept) {
// Skip disabled optional functions.
if (!use_interceptors_ && intercept->optional)
continue;
// Skip functions for which we have no decorated name.
if (intercept->decorated_name == NULL)
continue;
// Build the name of the imported version of this symbol.
std::string imp_name(kDecoratedImportPrefix);
imp_name += intercept->decorated_name;
// Build the name of the Asan instrumented version of this symbol.
std::string asan_name(kDecoratedAsanInterceptPrefix);
asan_name += intercept->decorated_name;
// Build the name of the Asan instrumented imported version of this symbol.
std::string imp_asan_name(kDecoratedImportPrefix);
imp_asan_name += asan_name;
// Build symbol rename mappings for the direct and indirect versions of the
// function.
rename_tx.AddSymbolMapping(intercept->decorated_name, asan_name);
rename_tx.AddSymbolMapping(imp_name, imp_asan_name);
// We use the add imports transform to try to find names for the Asan
// implementation. If these already exist in the object file then our
// instrumentation will fail.
const std::string* names[] = { &asan_name, &imp_asan_name };
for (size_t i = 0; i < arraysize(names); ++i) {
if (symbol_map.count(*names[i])) {
LOG(ERROR) << "Object file being instrumented defines Asan function \""
<< asan_name << "\".";
defines_asan_functions = true;
}
}
}
if (defines_asan_functions)
return false;
// Apply the rename transform.
if (!block_graph::ApplyBlockGraphTransform(&rename_tx,
policy,
block_graph,
header_block)) {
LOG(ERROR) << "Failed to apply COFF symbol rename transform.";
return false;
}
return true;
}
bool operator<(const AsanBasicBlockTransform::MemoryAccessInfo& left,
const AsanBasicBlockTransform::MemoryAccessInfo& right) {
if (left.mode != right.mode)
return left.mode < right.mode;
if (left.size != right.size)
return left.size < right.size;
if (left.save_flags != right.save_flags)
return left.save_flags < right.save_flags;
return left.opcode < right.opcode;
}
} // namespace transforms
} // namespace instrument