blob: 59274893b8e06a7f23a9eb3da89d201b39b8b6a4 [file] [log] [blame]
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sandbox/win/src/sidestep_resolver.h"
#include <stddef.h>
#include "base/win/pe_image.h"
#include "sandbox/win/src/sandbox_nt_util.h"
#include "sandbox/win/src/sidestep/preamble_patcher.h"
namespace {
const size_t kSizeOfSidestepStub = sidestep::kMaxPreambleStubSize;
struct SidestepThunk {
char sidestep[kSizeOfSidestepStub]; // Storage for the sidestep stub.
int internal_thunk; // Dummy member to the beginning of the internal thunk.
};
struct SmartThunk {
const void* module_base; // Target module's base.
const void* interceptor; // Real interceptor.
SidestepThunk sidestep; // Standard sidestep thunk.
};
} // namespace
namespace sandbox {
NTSTATUS SidestepResolverThunk::Setup(const void* target_module,
const void* interceptor_module,
const char* target_name,
const char* interceptor_name,
const void* interceptor_entry_point,
void* thunk_storage,
size_t storage_bytes,
size_t* storage_used) {
NTSTATUS ret =
Init(target_module, interceptor_module, target_name, interceptor_name,
interceptor_entry_point, thunk_storage, storage_bytes);
if (!NT_SUCCESS(ret))
return ret;
SidestepThunk* thunk = reinterpret_cast<SidestepThunk*>(thunk_storage);
size_t internal_bytes = storage_bytes - kSizeOfSidestepStub;
if (!SetInternalThunk(&thunk->internal_thunk, internal_bytes, thunk_storage,
interceptor_))
return STATUS_BUFFER_TOO_SMALL;
AutoProtectMemory memory;
ret = memory.ChangeProtection(target_, kSizeOfSidestepStub, PAGE_READWRITE);
if (!NT_SUCCESS(ret))
return ret;
sidestep::SideStepError rv = sidestep::PreamblePatcher::Patch(
target_, reinterpret_cast<void*>(&thunk->internal_thunk), thunk_storage,
kSizeOfSidestepStub);
if (sidestep::SIDESTEP_INSUFFICIENT_BUFFER == rv)
return STATUS_BUFFER_TOO_SMALL;
if (sidestep::SIDESTEP_SUCCESS != rv)
return STATUS_UNSUCCESSFUL;
if (storage_used)
*storage_used = GetThunkSize();
return ret;
}
size_t SidestepResolverThunk::GetThunkSize() const {
return GetInternalThunkSize() + kSizeOfSidestepStub;
}
// This is basically a wrapper around the normal sidestep patch that extends
// the thunk to use a chained interceptor. It uses the fact that
// SetInternalThunk generates the code to pass as the first parameter whatever
// it receives as original_function; we let SidestepResolverThunk set this value
// to its saved code, and then we change it to our thunk data.
NTSTATUS SmartSidestepResolverThunk::Setup(const void* target_module,
const void* interceptor_module,
const char* target_name,
const char* interceptor_name,
const void* interceptor_entry_point,
void* thunk_storage,
size_t storage_bytes,
size_t* storage_used) {
if (storage_bytes < GetThunkSize())
return STATUS_BUFFER_TOO_SMALL;
SmartThunk* thunk = reinterpret_cast<SmartThunk*>(thunk_storage);
thunk->module_base = target_module;
NTSTATUS ret;
if (interceptor_entry_point) {
thunk->interceptor = interceptor_entry_point;
} else {
ret = ResolveInterceptor(interceptor_module, interceptor_name,
&thunk->interceptor);
if (!NT_SUCCESS(ret))
return ret;
}
// Perform a standard sidestep patch on the last part of the thunk, but point
// to our internal smart interceptor.
size_t standard_bytes = storage_bytes - offsetof(SmartThunk, sidestep);
ret = SidestepResolverThunk::Setup(target_module, interceptor_module,
target_name, nullptr,
reinterpret_cast<void*>(&SmartStub),
&thunk->sidestep, standard_bytes, nullptr);
if (!NT_SUCCESS(ret))
return ret;
// Fix the internal thunk to pass the whole buffer to the interceptor.
SetInternalThunk(&thunk->sidestep.internal_thunk, GetInternalThunkSize(),
thunk_storage, reinterpret_cast<void*>(&SmartStub));
if (storage_used)
*storage_used = GetThunkSize();
return ret;
}
size_t SmartSidestepResolverThunk::GetThunkSize() const {
return GetInternalThunkSize() + kSizeOfSidestepStub +
offsetof(SmartThunk, sidestep);
}
// This code must basically either call the intended interceptor or skip the
// call and invoke instead the original function. In any case, we are saving
// the registers that may be trashed by our c++ code.
//
// This function is called with a first parameter inserted by us, that points
// to our SmartThunk. When we call the interceptor we have to replace this
// parameter with the one expected by that function (stored inside our
// structure); on the other hand, when we skip the interceptor we have to remove
// that extra argument before calling the original function.
//
// When we skip the interceptor, the transformation of the stack looks like:
// On Entry: On Use: On Exit:
// [param 2] = first real argument [param 2] (esp+1c) [param 2]
// [param 1] = our SmartThunk [param 1] (esp+18) [ret address]
// [ret address] = real caller [ret address] (esp+14) [xxx]
// [xxx] [addr to jump to] (esp+10) [xxx]
// [xxx] [saved eax] [xxx]
// [xxx] [saved ebx] [xxx]
// [xxx] [saved ecx] [xxx]
// [xxx] [saved edx] [xxx]
__declspec(naked)
void SmartSidestepResolverThunk::SmartStub() {
__asm {
push eax // Space for the jump.
push eax // Save registers.
push ebx
push ecx
push edx
mov ebx, [esp + 0x18] // First parameter = SmartThunk.
mov edx, [esp + 0x14] // Get the return address.
mov eax, [ebx]SmartThunk.module_base
push edx
push eax
call SmartSidestepResolverThunk::IsInternalCall
add esp, 8
test eax, eax
lea edx, [ebx]SmartThunk.sidestep // The original function.
jz call_interceptor
// Skip this call
mov ecx, [esp + 0x14] // Return address.
mov [esp + 0x18], ecx // Remove first parameter.
mov [esp + 0x10], edx
pop edx // Restore registers.
pop ecx
pop ebx
pop eax
ret 4 // Jump to original function.
call_interceptor:
mov ecx, [ebx]SmartThunk.interceptor
mov [esp + 0x18], edx // Replace first parameter.
mov [esp + 0x10], ecx
pop edx // Restore registers.
pop ecx
pop ebx
pop eax
ret // Jump to original function.
}
}
bool SmartSidestepResolverThunk::IsInternalCall(const void* base,
void* return_address) {
DCHECK_NT(base);
DCHECK_NT(return_address);
base::win::PEImage pe(base);
if (pe.GetImageSectionFromAddr(return_address))
return true;
return false;
}
} // namespace sandbox