diff options
Diffstat (limited to 'security/sandbox/chromium/sandbox/win/src/crosscall_server.cc')
-rw-r--r-- | security/sandbox/chromium/sandbox/win/src/crosscall_server.cc | 345 |
1 files changed, 345 insertions, 0 deletions
diff --git a/security/sandbox/chromium/sandbox/win/src/crosscall_server.cc b/security/sandbox/chromium/sandbox/win/src/crosscall_server.cc new file mode 100644 index 0000000000..27bfdfbd65 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/crosscall_server.cc @@ -0,0 +1,345 @@ +// Copyright (c) 2012 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/crosscall_server.h" + +#include <stddef.h> +#include <stdint.h> + +#include <atomic> +#include <string> +#include <vector> + +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/crosscall_params.h" + +// See comment in atomicops.h. This is needed any time windows.h is included +// after atomicops.h. +#undef MemoryBarrier + +// This code performs the ipc message validation. Potential security flaws +// on the ipc are likelier to be found in this code than in the rest of +// the ipc code. + +namespace { + +// The buffer for a message must match the max channel size. +const size_t kMaxBufferSize = sandbox::kIPCChannelSize; + +} // namespace + +namespace sandbox { + +// The template types are used to calculate the maximum expected size. +typedef ActualCallParams<0, kMaxBufferSize> ActualCP0; +typedef ActualCallParams<1, kMaxBufferSize> ActualCP1; +typedef ActualCallParams<2, kMaxBufferSize> ActualCP2; +typedef ActualCallParams<3, kMaxBufferSize> ActualCP3; +typedef ActualCallParams<4, kMaxBufferSize> ActualCP4; +typedef ActualCallParams<5, kMaxBufferSize> ActualCP5; +typedef ActualCallParams<6, kMaxBufferSize> ActualCP6; +typedef ActualCallParams<7, kMaxBufferSize> ActualCP7; +typedef ActualCallParams<8, kMaxBufferSize> ActualCP8; +typedef ActualCallParams<9, kMaxBufferSize> ActualCP9; + +// Returns the actual size for the parameters in an IPC buffer. Returns +// zero if the |param_count| is zero or too big. +uint32_t GetActualBufferSize(uint32_t param_count, void* buffer_base) { + // Retrieve the actual size and the maximum size of the params buffer. + switch (param_count) { + case 0: + return 0; + case 1: + return reinterpret_cast<ActualCP1*>(buffer_base)->GetSize(); + case 2: + return reinterpret_cast<ActualCP2*>(buffer_base)->GetSize(); + case 3: + return reinterpret_cast<ActualCP3*>(buffer_base)->GetSize(); + case 4: + return reinterpret_cast<ActualCP4*>(buffer_base)->GetSize(); + case 5: + return reinterpret_cast<ActualCP5*>(buffer_base)->GetSize(); + case 6: + return reinterpret_cast<ActualCP6*>(buffer_base)->GetSize(); + case 7: + return reinterpret_cast<ActualCP7*>(buffer_base)->GetSize(); + case 8: + return reinterpret_cast<ActualCP8*>(buffer_base)->GetSize(); + case 9: + return reinterpret_cast<ActualCP9*>(buffer_base)->GetSize(); + default: + return 0; + } +} + +// Returns the minimum size for the parameters in an IPC buffer. Returns +// zero if the |param_count| is less than zero or too big. +uint32_t GetMinDeclaredActualCallParamsSize(uint32_t param_count) { + switch (param_count) { + case 0: + return offsetof(ActualCP0, parameters_); + case 1: + return offsetof(ActualCP1, parameters_); + case 2: + return offsetof(ActualCP2, parameters_); + case 3: + return offsetof(ActualCP3, parameters_); + case 4: + return offsetof(ActualCP4, parameters_); + case 5: + return offsetof(ActualCP5, parameters_); + case 6: + return offsetof(ActualCP6, parameters_); + case 7: + return offsetof(ActualCP7, parameters_); + case 8: + return offsetof(ActualCP8, parameters_); + case 9: + return offsetof(ActualCP9, parameters_); + default: + return 0; + } +} + +// Verifies that the declared sizes of an IPC buffer are within range. +bool IsSizeWithinRange(uint32_t buffer_size, + uint32_t min_declared_size, + uint32_t declared_size) { + if ((buffer_size < min_declared_size) || + (sizeof(CrossCallParamsEx) > min_declared_size)) { + // Minimal computed size bigger than existing buffer or param_count + // integer overflow. + return false; + } + + if ((declared_size > buffer_size) || (declared_size < min_declared_size)) { + // Declared size is bigger than buffer or smaller than computed size + // or param_count is equal to 0 or bigger than 9. + return false; + } + + return true; +} + +CrossCallParamsEx::CrossCallParamsEx() : CrossCallParams(IpcTag::UNUSED, 0) {} + +// We override the delete operator because the object's backing memory +// is hand allocated in CreateFromBuffer. We don't override the new operator +// because the constructors are private so there is no way to mismatch +// new & delete. +void CrossCallParamsEx::operator delete(void* raw_memory) throw() { + if (!raw_memory) { + // C++ standard allows 'delete 0' behavior. + return; + } + delete[] reinterpret_cast<char*>(raw_memory); +} + +// This function uses a SEH try block so cannot use C++ objects that +// have destructors or else you get Compiler Error C2712. So no DCHECKs +// inside this function. +CrossCallParamsEx* CrossCallParamsEx::CreateFromBuffer(void* buffer_base, + uint32_t buffer_size, + uint32_t* output_size) { + // IMPORTANT: Everything inside buffer_base and derived from it such + // as param_count and declared_size is untrusted. + if (!buffer_base) + return nullptr; + if (buffer_size < sizeof(CrossCallParams)) + return nullptr; + if (buffer_size > kMaxBufferSize) + return nullptr; + + char* backing_mem = nullptr; + uint32_t param_count = 0; + uint32_t declared_size; + uint32_t min_declared_size; + CrossCallParamsEx* copied_params = nullptr; + + // Touching the untrusted buffer is done under a SEH try block. This + // will catch memory access violations so we don't crash. + __try { + CrossCallParams* call_params = + reinterpret_cast<CrossCallParams*>(buffer_base); + + // Check against the minimum size given the number of stated params + // if too small we bail out. + param_count = call_params->GetParamsCount(); + min_declared_size = GetMinDeclaredActualCallParamsSize(param_count); + + // Initial check for the buffer being big enough to determine the actual + // buffer size. + if (buffer_size < min_declared_size) + return nullptr; + + // Retrieve the declared size which if it fails returns 0. + declared_size = GetActualBufferSize(param_count, buffer_base); + + if (!IsSizeWithinRange(buffer_size, min_declared_size, declared_size)) + return nullptr; + + // Now we copy the actual amount of the message. + *output_size = declared_size; + backing_mem = new char[declared_size]; + copied_params = reinterpret_cast<CrossCallParamsEx*>(backing_mem); + memcpy(backing_mem, call_params, declared_size); + + // Avoid compiler optimizations across this point. Any value stored in + // memory should be stored for real, and values previously read from memory + // should be actually read. + std::atomic_thread_fence(std::memory_order_seq_cst); + + min_declared_size = GetMinDeclaredActualCallParamsSize(param_count); + + // Check that the copied buffer is still valid. + if (copied_params->GetParamsCount() != param_count || + GetActualBufferSize(param_count, backing_mem) != declared_size || + !IsSizeWithinRange(buffer_size, min_declared_size, declared_size)) { + delete[] backing_mem; + return nullptr; + } + + } __except (EXCEPTION_EXECUTE_HANDLER) { + // In case of a windows exception we know it occurred while touching the + // untrusted buffer so we bail out as is. + delete[] backing_mem; + return nullptr; + } + + // Here and below we're making use of uintptr_t to have well-defined integer + // overflow when doing pointer arithmetic. + auto backing_mem_ptr = reinterpret_cast<uintptr_t>(backing_mem); + auto last_byte = reinterpret_cast<uintptr_t>(&backing_mem[declared_size]); + auto first_byte = + reinterpret_cast<uintptr_t>(&backing_mem[min_declared_size]); + + // Verify here that all and each parameters make sense. This is done in the + // local copy. + for (uint32_t ix = 0; ix != param_count; ++ix) { + uint32_t size = 0; + ArgType type; + auto address = reinterpret_cast<uintptr_t>( + copied_params->GetRawParameter(ix, &size, &type)); + if ((!address) || // No null params. + (INVALID_TYPE >= type) || (LAST_TYPE <= type) || // Unknown type. + (address < backing_mem_ptr) || // Start cannot point before buffer. + (address < first_byte) || // Start cannot point too low. + (address > last_byte) || // Start cannot point past buffer. + ((address + size) < address) || // Invalid size. + ((address + size) > last_byte)) { // End cannot point past buffer. + // Malformed. + delete[] backing_mem; + return nullptr; + } + } + // The parameter buffer looks good. + return copied_params; +} + +// Accessors to the parameters in the raw buffer. +void* CrossCallParamsEx::GetRawParameter(uint32_t index, + uint32_t* size, + ArgType* type) { + if (index >= GetParamsCount()) + return nullptr; + // The size is always computed from the parameter minus the next + // parameter, this works because the message has an extra parameter slot + *size = param_info_[index].size_; + *type = param_info_[index].type_; + + return param_info_[index].offset_ + reinterpret_cast<char*>(this); +} + +// Covers common case for 32 bit integers. +bool CrossCallParamsEx::GetParameter32(uint32_t index, uint32_t* param) { + uint32_t size = 0; + ArgType type; + void* start = GetRawParameter(index, &size, &type); + if (!start || (4 != size) || (UINT32_TYPE != type)) + return false; + // Copy the 4 bytes. + *(reinterpret_cast<uint32_t*>(param)) = *(reinterpret_cast<uint32_t*>(start)); + return true; +} + +bool CrossCallParamsEx::GetParameterVoidPtr(uint32_t index, void** param) { + uint32_t size = 0; + ArgType type; + void* start = GetRawParameter(index, &size, &type); + if (!start || (sizeof(void*) != size) || (VOIDPTR_TYPE != type)) + return false; + *param = *(reinterpret_cast<void**>(start)); + return true; +} + +// Covers the common case of reading a string. Note that the string is not +// scanned for invalid characters. +bool CrossCallParamsEx::GetParameterStr(uint32_t index, std::wstring* string) { + DCHECK(string->empty()); + uint32_t size = 0; + ArgType type; + void* start = GetRawParameter(index, &size, &type); + if (WCHAR_TYPE != type) + return false; + + // Check if this is an empty string. + if (size == 0) { + *string = std::wstring(); + return true; + } + + if (!start || ((size % sizeof(wchar_t)) != 0)) + return false; + + string->assign(reinterpret_cast<const wchar_t*>(start), + size / sizeof(wchar_t)); + return true; +} + +bool CrossCallParamsEx::GetParameterPtr(uint32_t index, + uint32_t expected_size, + void** pointer) { + uint32_t size = 0; + ArgType type; + void* start = GetRawParameter(index, &size, &type); + + if ((size != expected_size) || (INOUTPTR_TYPE != type && INPTR_TYPE != type)) + return false; + + if (!start) + return false; + + *pointer = start; + return true; +} + +void SetCallError(ResultCode error, CrossCallReturn* call_return) { + call_return->call_outcome = error; + call_return->extended_count = 0; +} + +void SetCallSuccess(CrossCallReturn* call_return) { + call_return->call_outcome = SBOX_ALL_OK; +} + +Dispatcher* Dispatcher::OnMessageReady(IPCParams* ipc, + CallbackGeneric* callback) { + DCHECK(callback); + std::vector<IPCCall>::iterator it = ipc_calls_.begin(); + for (; it != ipc_calls_.end(); ++it) { + if (it->params.Matches(ipc)) { + *callback = it->callback; + return this; + } + } + return nullptr; +} + +Dispatcher::Dispatcher() {} + +Dispatcher::~Dispatcher() {} + +} // namespace sandbox |