michael@0: // Copyright (c) 2012 The Chromium Authors. All rights reserved. michael@0: // Use of this source code is governed by a BSD-style license that can be michael@0: // found in the LICENSE file. michael@0: michael@0: #include michael@0: #include michael@0: michael@0: #include "sandbox/win/src/crosscall_server.h" michael@0: #include "sandbox/win/src/crosscall_params.h" michael@0: #include "sandbox/win/src/crosscall_client.h" michael@0: #include "base/logging.h" michael@0: michael@0: // This code performs the ipc message validation. Potential security flaws michael@0: // on the ipc are likelier to be found in this code than in the rest of michael@0: // the ipc code. michael@0: michael@0: namespace { michael@0: michael@0: // The buffer for a message must match the max channel size. michael@0: const size_t kMaxBufferSize = sandbox::kIPCChannelSize; michael@0: michael@0: } michael@0: michael@0: namespace sandbox { michael@0: michael@0: // Returns the actual size for the parameters in an IPC buffer. Returns michael@0: // zero if the |param_count| is zero or too big. michael@0: uint32 GetActualBufferSize(uint32 param_count, void* buffer_base) { michael@0: // The template types are used to calculate the maximum expected size. michael@0: typedef ActualCallParams<1, kMaxBufferSize> ActualCP1; michael@0: typedef ActualCallParams<2, kMaxBufferSize> ActualCP2; michael@0: typedef ActualCallParams<3, kMaxBufferSize> ActualCP3; michael@0: typedef ActualCallParams<4, kMaxBufferSize> ActualCP4; michael@0: typedef ActualCallParams<5, kMaxBufferSize> ActualCP5; michael@0: typedef ActualCallParams<6, kMaxBufferSize> ActualCP6; michael@0: typedef ActualCallParams<7, kMaxBufferSize> ActualCP7; michael@0: typedef ActualCallParams<8, kMaxBufferSize> ActualCP8; michael@0: typedef ActualCallParams<9, kMaxBufferSize> ActualCP9; michael@0: michael@0: // Retrieve the actual size and the maximum size of the params buffer. michael@0: switch (param_count) { michael@0: case 0: michael@0: return 0; michael@0: case 1: michael@0: return reinterpret_cast(buffer_base)->GetSize(); michael@0: case 2: michael@0: return reinterpret_cast(buffer_base)->GetSize(); michael@0: case 3: michael@0: return reinterpret_cast(buffer_base)->GetSize(); michael@0: case 4: michael@0: return reinterpret_cast(buffer_base)->GetSize(); michael@0: case 5: michael@0: return reinterpret_cast(buffer_base)->GetSize(); michael@0: case 6: michael@0: return reinterpret_cast(buffer_base)->GetSize(); michael@0: case 7: michael@0: return reinterpret_cast(buffer_base)->GetSize(); michael@0: case 8: michael@0: return reinterpret_cast(buffer_base)->GetSize(); michael@0: case 9: michael@0: return reinterpret_cast(buffer_base)->GetSize(); michael@0: default: michael@0: NOTREACHED(); michael@0: return 0; michael@0: } michael@0: } michael@0: michael@0: // Verifies that the declared sizes of an IPC buffer are within range. michael@0: bool IsSizeWithinRange(uint32 buffer_size, uint32 min_declared_size, michael@0: uint32 declared_size) { michael@0: if ((buffer_size < min_declared_size) || michael@0: (sizeof(CrossCallParamsEx) > min_declared_size)) { michael@0: // Minimal computed size bigger than existing buffer or param_count michael@0: // integer overflow. michael@0: return false; michael@0: } michael@0: michael@0: if ((declared_size > buffer_size) || (declared_size < min_declared_size)) { michael@0: // Declared size is bigger than buffer or smaller than computed size michael@0: // or param_count is equal to 0 or bigger than 9. michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: CrossCallParamsEx::CrossCallParamsEx() michael@0: :CrossCallParams(0, 0) { michael@0: } michael@0: michael@0: // We override the delete operator because the object's backing memory michael@0: // is hand allocated in CreateFromBuffer. We don't override the new operator michael@0: // because the constructors are private so there is no way to mismatch michael@0: // new & delete. michael@0: void CrossCallParamsEx::operator delete(void* raw_memory) throw() { michael@0: if (NULL == raw_memory) { michael@0: // C++ standard allows 'delete 0' behavior. michael@0: return; michael@0: } michael@0: delete[] reinterpret_cast(raw_memory); michael@0: } michael@0: michael@0: // This function uses a SEH try block so cannot use C++ objects that michael@0: // have destructors or else you get Compiler Error C2712. So no DCHECKs michael@0: // inside this function. michael@0: CrossCallParamsEx* CrossCallParamsEx::CreateFromBuffer(void* buffer_base, michael@0: uint32 buffer_size, michael@0: uint32* output_size) { michael@0: // IMPORTANT: Everything inside buffer_base and derived from it such michael@0: // as param_count and declared_size is untrusted. michael@0: if (NULL == buffer_base) { michael@0: return NULL; michael@0: } michael@0: if (buffer_size < sizeof(CrossCallParams)) { michael@0: return NULL; michael@0: } michael@0: if (buffer_size > kMaxBufferSize) { michael@0: return NULL; michael@0: } michael@0: michael@0: char* backing_mem = NULL; michael@0: uint32 param_count = 0; michael@0: uint32 declared_size; michael@0: uint32 min_declared_size; michael@0: CrossCallParamsEx* copied_params = NULL; michael@0: michael@0: // Touching the untrusted buffer is done under a SEH try block. This michael@0: // will catch memory access violations so we don't crash. michael@0: __try { michael@0: CrossCallParams* call_params = michael@0: reinterpret_cast(buffer_base); michael@0: michael@0: // Check against the minimum size given the number of stated params michael@0: // if too small we bail out. michael@0: param_count = call_params->GetParamsCount(); michael@0: min_declared_size = sizeof(CrossCallParams) + michael@0: ((param_count + 1) * sizeof(ParamInfo)); michael@0: michael@0: // Retrieve the declared size which if it fails returns 0. michael@0: declared_size = GetActualBufferSize(param_count, buffer_base); michael@0: michael@0: if (!IsSizeWithinRange(buffer_size, min_declared_size, declared_size)) michael@0: return NULL; michael@0: michael@0: // Now we copy the actual amount of the message. michael@0: *output_size = declared_size; michael@0: backing_mem = new char[declared_size]; michael@0: copied_params = reinterpret_cast(backing_mem); michael@0: memcpy(backing_mem, call_params, declared_size); michael@0: michael@0: // Avoid compiler optimizations across this point. Any value stored in michael@0: // memory should be stored for real, and values previously read from memory michael@0: // should be actually read. michael@0: _ReadWriteBarrier(); michael@0: michael@0: min_declared_size = sizeof(CrossCallParams) + michael@0: ((param_count + 1) * sizeof(ParamInfo)); michael@0: michael@0: // Check that the copied buffer is still valid. michael@0: if (copied_params->GetParamsCount() != param_count || michael@0: GetActualBufferSize(param_count, backing_mem) != declared_size || michael@0: !IsSizeWithinRange(buffer_size, min_declared_size, declared_size)) { michael@0: delete [] backing_mem; michael@0: return NULL; michael@0: } michael@0: michael@0: } __except(EXCEPTION_EXECUTE_HANDLER) { michael@0: // In case of a windows exception we know it occurred while touching the michael@0: // untrusted buffer so we bail out as is. michael@0: delete [] backing_mem; michael@0: return NULL; michael@0: } michael@0: michael@0: const char* last_byte = &backing_mem[declared_size]; michael@0: const char* first_byte = &backing_mem[min_declared_size]; michael@0: michael@0: // Verify here that all and each parameters make sense. This is done in the michael@0: // local copy. michael@0: for (uint32 ix =0; ix != param_count; ++ix) { michael@0: uint32 size = 0; michael@0: ArgType type; michael@0: char* address = reinterpret_cast( michael@0: copied_params->GetRawParameter(ix, &size, &type)); michael@0: if ((NULL == address) || // No null params. michael@0: (INVALID_TYPE >= type) || (LAST_TYPE <= type) || // Unknown type. michael@0: (address < backing_mem) || // Start cannot point before buffer. michael@0: (address < first_byte) || // Start cannot point too low. michael@0: (address > last_byte) || // Start cannot point past buffer. michael@0: ((address + size) < address) || // Invalid size. michael@0: ((address + size) > last_byte)) { // End cannot point past buffer. michael@0: // Malformed. michael@0: delete[] backing_mem; michael@0: return NULL; michael@0: } michael@0: } michael@0: // The parameter buffer looks good. michael@0: return copied_params; michael@0: } michael@0: michael@0: // Accessors to the parameters in the raw buffer. michael@0: void* CrossCallParamsEx::GetRawParameter(uint32 index, uint32* size, michael@0: ArgType* type) { michael@0: if (index >= GetParamsCount()) { michael@0: return NULL; michael@0: } michael@0: // The size is always computed from the parameter minus the next michael@0: // parameter, this works because the message has an extra parameter slot michael@0: *size = param_info_[index].size_; michael@0: *type = param_info_[index].type_; michael@0: michael@0: return param_info_[index].offset_ + reinterpret_cast(this); michael@0: } michael@0: michael@0: // Covers common case for 32 bit integers. michael@0: bool CrossCallParamsEx::GetParameter32(uint32 index, uint32* param) { michael@0: uint32 size = 0; michael@0: ArgType type; michael@0: void* start = GetRawParameter(index, &size, &type); michael@0: if ((NULL == start) || (4 != size) || (ULONG_TYPE != type)) { michael@0: return false; michael@0: } michael@0: // Copy the 4 bytes. michael@0: *(reinterpret_cast(param)) = *(reinterpret_cast(start)); michael@0: return true; michael@0: } michael@0: michael@0: bool CrossCallParamsEx::GetParameterVoidPtr(uint32 index, void** param) { michael@0: uint32 size = 0; michael@0: ArgType type; michael@0: void* start = GetRawParameter(index, &size, &type); michael@0: if ((NULL == start) || (sizeof(void*) != size) || (VOIDPTR_TYPE != type)) { michael@0: return false; michael@0: } michael@0: *param = *(reinterpret_cast(start)); michael@0: return true; michael@0: } michael@0: michael@0: // Covers the common case of reading a string. Note that the string is not michael@0: // scanned for invalid characters. michael@0: bool CrossCallParamsEx::GetParameterStr(uint32 index, std::wstring* string) { michael@0: uint32 size = 0; michael@0: ArgType type; michael@0: void* start = GetRawParameter(index, &size, &type); michael@0: if (WCHAR_TYPE != type) { michael@0: return false; michael@0: } michael@0: michael@0: // Check if this is an empty string. michael@0: if (size == 0) { michael@0: *string = L""; michael@0: return true; michael@0: } michael@0: michael@0: if ((NULL == start) || ((size % sizeof(wchar_t)) != 0)) { michael@0: return false; michael@0: } michael@0: string->append(reinterpret_cast(start), size/(sizeof(wchar_t))); michael@0: return true; michael@0: } michael@0: michael@0: bool CrossCallParamsEx::GetParameterPtr(uint32 index, uint32 expected_size, michael@0: void** pointer) { michael@0: uint32 size = 0; michael@0: ArgType type; michael@0: void* start = GetRawParameter(index, &size, &type); michael@0: michael@0: if ((size != expected_size) || (INOUTPTR_TYPE != type)) { michael@0: return false; michael@0: } michael@0: michael@0: if (NULL == start) { michael@0: return false; michael@0: } michael@0: michael@0: *pointer = start; michael@0: return true; michael@0: } michael@0: michael@0: void SetCallError(ResultCode error, CrossCallReturn* call_return) { michael@0: call_return->call_outcome = error; michael@0: call_return->extended_count = 0; michael@0: } michael@0: michael@0: void SetCallSuccess(CrossCallReturn* call_return) { michael@0: call_return->call_outcome = SBOX_ALL_OK; michael@0: } michael@0: michael@0: Dispatcher* Dispatcher::OnMessageReady(IPCParams* ipc, michael@0: CallbackGeneric* callback) { michael@0: DCHECK(callback); michael@0: std::vector::iterator it = ipc_calls_.begin(); michael@0: for (; it != ipc_calls_.end(); ++it) { michael@0: if (it->params.Matches(ipc)) { michael@0: *callback = it->callback; michael@0: return this; michael@0: } michael@0: } michael@0: return NULL; michael@0: } michael@0: michael@0: } // namespace sandbox