| // 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 <string> |
| #include <vector> |
| |
| #include "base/atomicops.h" |
| #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 { |
| |
| // 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) { |
| // The template types are used to calculate the maximum expected size. |
| 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; |
| |
| // 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; |
| } |
| } |
| |
| // 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(0, 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 = |
| sizeof(CrossCallParams) + ((param_count + 1) * sizeof(ParamInfo)); |
| |
| // 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. |
| base::subtle::MemoryBarrier(); |
| |
| min_declared_size = |
| sizeof(CrossCallParams) + ((param_count + 1) * sizeof(ParamInfo)); |
| |
| // 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, |
| base::string16* 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 = base::WideToUTF16(L""); |
| return true; |
| } |
| |
| if (!start || ((size % sizeof(wchar_t)) != 0)) |
| return false; |
| return base::WideToUTF16(reinterpret_cast<wchar_t*>(start), |
| size / sizeof(wchar_t), string); |
| } |
| |
| 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)) |
| 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 |