| // 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 "gpu/command_buffer/service/common_decoder.h" |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <algorithm> |
| |
| #include "base/numerics/safe_math.h" |
| #include "base/stl_util.h" |
| #include "gpu/command_buffer/service/command_buffer_service.h" |
| #include "gpu/command_buffer/service/decoder_client.h" |
| #include "ui/gfx/ipc/color/gfx_param_traits.h" |
| |
| namespace gpu { |
| namespace { |
| static const size_t kDefaultMaxBucketSize = 1u << 30; // 1 GB |
| } |
| |
| const CommonDecoder::CommandInfo CommonDecoder::command_info[] = { |
| #define COMMON_COMMAND_BUFFER_CMD_OP(name) \ |
| { \ |
| &CommonDecoder::Handle##name, cmd::name::kArgFlags, \ |
| cmd::name::cmd_flags, \ |
| sizeof(cmd::name) / sizeof(CommandBufferEntry) - 1, \ |
| } \ |
| , /* NOLINT */ |
| COMMON_COMMAND_BUFFER_CMDS(COMMON_COMMAND_BUFFER_CMD_OP) |
| #undef COMMON_COMMAND_BUFFER_CMD_OP |
| }; |
| |
| |
| CommonDecoder::Bucket::Bucket() : size_(0) {} |
| |
| CommonDecoder::Bucket::~Bucket() = default; |
| |
| void* CommonDecoder::Bucket::GetData(size_t offset, size_t size) const { |
| if (OffsetSizeValid(offset, size)) { |
| return data_.get() + offset; |
| } |
| return nullptr; |
| } |
| |
| void CommonDecoder::Bucket::SetSize(size_t size) { |
| if (size != size_) { |
| data_.reset(size ? new int8_t[size] : nullptr); |
| size_ = size; |
| memset(data_.get(), 0, size); |
| } |
| } |
| |
| bool CommonDecoder::Bucket::SetData( |
| const volatile void* src, size_t offset, size_t size) { |
| if (OffsetSizeValid(offset, size)) { |
| memcpy(data_.get() + offset, const_cast<const void*>(src), size); |
| return true; |
| } |
| return false; |
| } |
| |
| void CommonDecoder::Bucket::SetFromString(const char* str) { |
| // Strings are passed nullptr terminated to distinguish between empty string |
| // and no string. |
| if (!str) { |
| SetSize(0); |
| } else { |
| size_t size = strlen(str) + 1; |
| SetSize(size); |
| SetData(str, 0, size); |
| } |
| } |
| |
| bool CommonDecoder::Bucket::GetAsString(std::string* str) { |
| DCHECK(str); |
| if (size_ == 0) { |
| return false; |
| } |
| str->assign(GetDataAs<const char*>(0, size_ - 1), size_ - 1); |
| return true; |
| } |
| |
| bool CommonDecoder::Bucket::GetAsStrings( |
| GLsizei* _count, std::vector<char*>* _string, std::vector<GLint>* _length) { |
| const size_t kMinBucketSize = sizeof(GLint); |
| // Each string has at least |length| in the header and a NUL character. |
| const size_t kMinStringSize = sizeof(GLint) + 1; |
| const size_t bucket_size = this->size(); |
| if (bucket_size < kMinBucketSize) { |
| return false; |
| } |
| char* bucket_data = this->GetDataAs<char*>(0, bucket_size); |
| GLint* header = reinterpret_cast<GLint*>(bucket_data); |
| GLsizei count = static_cast<GLsizei>(header[0]); |
| if (count < 0) { |
| return false; |
| } |
| const size_t max_count = (bucket_size - kMinBucketSize) / kMinStringSize; |
| if (max_count < static_cast<size_t>(count)) { |
| return false; |
| } |
| GLint* length = header + 1; |
| std::vector<char*> strs(count); |
| base::CheckedNumeric<size_t> total_size = sizeof(GLint); |
| total_size *= count + 1; // Header size. |
| if (!total_size.IsValid()) |
| return false; |
| for (GLsizei ii = 0; ii < count; ++ii) { |
| strs[ii] = bucket_data + total_size.ValueOrDefault(0); |
| total_size += length[ii]; |
| total_size += 1; // NUL char at the end of each char array. |
| if (!total_size.IsValid() || total_size.ValueOrDefault(0) > bucket_size || |
| strs[ii][length[ii]] != 0) { |
| return false; |
| } |
| } |
| if (total_size.ValueOrDefault(0) != bucket_size) { |
| return false; |
| } |
| DCHECK(_count && _string && _length); |
| *_count = count; |
| *_string = strs; |
| _length->resize(count); |
| for (GLsizei ii = 0; ii < count; ++ii) { |
| (*_length)[ii] = length[ii]; |
| } |
| return true; |
| } |
| |
| CommonDecoder::CommonDecoder(DecoderClient* client, |
| CommandBufferServiceBase* command_buffer_service) |
| : command_buffer_service_(command_buffer_service), |
| client_(client), |
| max_bucket_size_(kDefaultMaxBucketSize) { |
| DCHECK(command_buffer_service_); |
| } |
| |
| CommonDecoder::~CommonDecoder() = default; |
| |
| void* CommonDecoder::GetAddressAndCheckSize(unsigned int shm_id, |
| unsigned int data_offset, |
| unsigned int data_size) { |
| scoped_refptr<gpu::Buffer> buffer = |
| command_buffer_service_->GetTransferBuffer(shm_id); |
| if (!buffer.get()) |
| return nullptr; |
| return buffer->GetDataAddress(data_offset, data_size); |
| } |
| |
| void* CommonDecoder::GetAddressAndSize(unsigned int shm_id, |
| unsigned int data_offset, |
| unsigned int minimum_size, |
| unsigned int* data_size) { |
| scoped_refptr<gpu::Buffer> buffer = |
| command_buffer_service_->GetTransferBuffer(shm_id); |
| if (!buffer.get() || buffer->GetRemainingSize(data_offset) < minimum_size) |
| return nullptr; |
| return buffer->GetDataAddressAndSize(data_offset, data_size); |
| } |
| |
| unsigned int CommonDecoder::GetSharedMemorySize(unsigned int shm_id, |
| unsigned int offset) { |
| scoped_refptr<gpu::Buffer> buffer = |
| command_buffer_service_->GetTransferBuffer(shm_id); |
| if (!buffer.get()) |
| return 0; |
| return buffer->GetRemainingSize(offset); |
| } |
| |
| scoped_refptr<gpu::Buffer> CommonDecoder::GetSharedMemoryBuffer( |
| unsigned int shm_id) { |
| return command_buffer_service_->GetTransferBuffer(shm_id); |
| } |
| |
| const char* CommonDecoder::GetCommonCommandName( |
| cmd::CommandId command_id) const { |
| return cmd::GetCommandName(command_id); |
| } |
| |
| CommonDecoder::Bucket* CommonDecoder::GetBucket(uint32_t bucket_id) const { |
| BucketMap::const_iterator iter(buckets_.find(bucket_id)); |
| return iter != buckets_.end() ? &(*iter->second) : nullptr; |
| } |
| |
| CommonDecoder::Bucket* CommonDecoder::CreateBucket(uint32_t bucket_id) { |
| Bucket* bucket = GetBucket(bucket_id); |
| if (!bucket) { |
| bucket = new Bucket(); |
| buckets_[bucket_id] = std::unique_ptr<Bucket>(bucket); |
| } |
| return bucket; |
| } |
| |
| namespace { |
| |
| // Returns the address of the first byte after a struct. |
| template <typename T> |
| const volatile void* AddressAfterStruct(const volatile T& pod) { |
| return reinterpret_cast<const volatile uint8_t*>(&pod) + sizeof(pod); |
| } |
| |
| // Returns the address of the frst byte after the struct. |
| template <typename RETURN_TYPE, typename COMMAND_TYPE> |
| RETURN_TYPE GetImmediateDataAs(const volatile COMMAND_TYPE& pod) { |
| return static_cast<RETURN_TYPE>( |
| const_cast<volatile void*>(AddressAfterStruct(pod))); |
| } |
| |
| } // anonymous namespace. |
| |
| // Decode command with its arguments, and call the corresponding method. |
| // Note: args is a pointer to the command buffer. As such, it could be changed |
| // by a (malicious) client at any time, so if validation has to happen, it |
| // should operate on a copy of them. |
| error::Error CommonDecoder::DoCommonCommand(unsigned int command, |
| unsigned int arg_count, |
| const volatile void* cmd_data) { |
| if (command < base::size(command_info)) { |
| const CommandInfo& info = command_info[command]; |
| unsigned int info_arg_count = static_cast<unsigned int>(info.arg_count); |
| if ((info.arg_flags == cmd::kFixed && arg_count == info_arg_count) || |
| (info.arg_flags == cmd::kAtLeastN && arg_count >= info_arg_count)) { |
| uint32_t immediate_data_size = |
| (arg_count - info_arg_count) * sizeof(CommandBufferEntry); // NOLINT |
| return (this->*info.cmd_handler)(immediate_data_size, cmd_data); |
| } else { |
| return error::kInvalidArguments; |
| } |
| } |
| return error::kUnknownCommand; |
| } |
| |
| error::Error CommonDecoder::HandleNoop(uint32_t immediate_data_size, |
| const volatile void* cmd_data) { |
| return error::kNoError; |
| } |
| |
| error::Error CommonDecoder::HandleSetToken(uint32_t immediate_data_size, |
| const volatile void* cmd_data) { |
| const volatile cmd::SetToken& args = |
| *static_cast<const volatile cmd::SetToken*>(cmd_data); |
| command_buffer_service_->SetToken(args.token); |
| return error::kNoError; |
| } |
| |
| error::Error CommonDecoder::HandleSetBucketSize(uint32_t immediate_data_size, |
| const volatile void* cmd_data) { |
| const volatile cmd::SetBucketSize& args = |
| *static_cast<const volatile cmd::SetBucketSize*>(cmd_data); |
| uint32_t bucket_id = args.bucket_id; |
| uint32_t size = args.size; |
| if (size > max_bucket_size_) |
| return error::kOutOfBounds; |
| |
| Bucket* bucket = CreateBucket(bucket_id); |
| bucket->SetSize(size); |
| return error::kNoError; |
| } |
| |
| error::Error CommonDecoder::HandleSetBucketData(uint32_t immediate_data_size, |
| const volatile void* cmd_data) { |
| const volatile cmd::SetBucketData& args = |
| *static_cast<const volatile cmd::SetBucketData*>(cmd_data); |
| uint32_t bucket_id = args.bucket_id; |
| uint32_t offset = args.offset; |
| uint32_t size = args.size; |
| const void* data = GetSharedMemoryAs<const void*>( |
| args.shared_memory_id, args.shared_memory_offset, size); |
| if (!data) { |
| return error::kInvalidArguments; |
| } |
| Bucket* bucket = GetBucket(bucket_id); |
| if (!bucket) { |
| return error::kInvalidArguments; |
| } |
| if (!bucket->SetData(data, offset, size)) { |
| return error::kInvalidArguments; |
| } |
| |
| return error::kNoError; |
| } |
| |
| error::Error CommonDecoder::HandleSetBucketDataImmediate( |
| uint32_t immediate_data_size, |
| const volatile void* cmd_data) { |
| const volatile cmd::SetBucketDataImmediate& args = |
| *static_cast<const volatile cmd::SetBucketDataImmediate*>(cmd_data); |
| const volatile void* data = GetImmediateDataAs<const volatile void*>(args); |
| uint32_t bucket_id = args.bucket_id; |
| uint32_t offset = args.offset; |
| uint32_t size = args.size; |
| if (size > immediate_data_size) { |
| return error::kInvalidArguments; |
| } |
| Bucket* bucket = GetBucket(bucket_id); |
| if (!bucket) { |
| return error::kInvalidArguments; |
| } |
| if (!bucket->SetData(data, offset, size)) { |
| return error::kInvalidArguments; |
| } |
| return error::kNoError; |
| } |
| |
| error::Error CommonDecoder::HandleGetBucketStart( |
| uint32_t immediate_data_size, |
| const volatile void* cmd_data) { |
| const volatile cmd::GetBucketStart& args = |
| *static_cast<const volatile cmd::GetBucketStart*>(cmd_data); |
| uint32_t bucket_id = args.bucket_id; |
| uint32_t* result = GetSharedMemoryAs<uint32_t*>( |
| args.result_memory_id, args.result_memory_offset, sizeof(*result)); |
| int32_t data_memory_id = args.data_memory_id; |
| uint32_t data_memory_offset = args.data_memory_offset; |
| uint32_t data_memory_size = args.data_memory_size; |
| uint8_t* data = nullptr; |
| if (data_memory_size != 0 || data_memory_id != 0 || data_memory_offset != 0) { |
| data = GetSharedMemoryAs<uint8_t*>(data_memory_id, data_memory_offset, |
| data_memory_size); |
| if (!data) { |
| return error::kInvalidArguments; |
| } |
| } |
| if (!result) { |
| return error::kInvalidArguments; |
| } |
| // Check that the client initialized the result. |
| if (*result != 0) { |
| return error::kInvalidArguments; |
| } |
| Bucket* bucket = GetBucket(bucket_id); |
| if (!bucket) { |
| return error::kInvalidArguments; |
| } |
| uint32_t bucket_size = bucket->size(); |
| *result = bucket_size; |
| if (data) { |
| uint32_t size = std::min(data_memory_size, bucket_size); |
| memcpy(data, bucket->GetData(0, size), size); |
| } |
| return error::kNoError; |
| } |
| |
| error::Error CommonDecoder::HandleGetBucketData(uint32_t immediate_data_size, |
| const volatile void* cmd_data) { |
| const volatile cmd::GetBucketData& args = |
| *static_cast<const volatile cmd::GetBucketData*>(cmd_data); |
| uint32_t bucket_id = args.bucket_id; |
| uint32_t offset = args.offset; |
| uint32_t size = args.size; |
| void* data = GetSharedMemoryAs<void*>( |
| args.shared_memory_id, args.shared_memory_offset, size); |
| if (!data) { |
| return error::kInvalidArguments; |
| } |
| Bucket* bucket = GetBucket(bucket_id); |
| if (!bucket) { |
| return error::kInvalidArguments; |
| } |
| const void* src = bucket->GetData(offset, size); |
| if (!src) { |
| return error::kInvalidArguments; |
| } |
| memcpy(data, src, size); |
| return error::kNoError; |
| } |
| |
| error::Error CommonDecoder::HandleInsertFenceSync( |
| uint32_t immediate_data_size, |
| const volatile void* cmd_data) { |
| const volatile cmd::InsertFenceSync& c = |
| *static_cast<const volatile cmd::InsertFenceSync*>(cmd_data); |
| |
| const uint64_t release_count = c.release_count(); |
| client_->OnFenceSyncRelease(release_count); |
| // Exit inner command processing loop so that we check the scheduling state |
| // and yield if necessary as we may have unblocked a higher priority |
| // context. |
| ExitCommandProcessingEarly(); |
| return error::kNoError; |
| } |
| |
| bool CommonDecoder::ReadColorSpace(uint32_t shm_id, |
| uint32_t shm_offset, |
| uint32_t color_space_size, |
| gfx::ColorSpace* color_space) { |
| // Use the default (invalid) color space if no space was serialized. |
| if (!shm_id && !shm_offset && !color_space_size) { |
| *color_space = gfx::ColorSpace(); |
| return true; |
| } |
| |
| const char* data = static_cast<const char*>( |
| GetAddressAndCheckSize(shm_id, shm_offset, color_space_size)); |
| if (!data) { |
| return false; |
| } |
| |
| // Make a copy to reduce the risk of a time of check to time of use attack. |
| std::vector<char> color_space_data(data, data + color_space_size); |
| base::Pickle color_space_pickle(color_space_data.data(), color_space_size); |
| base::PickleIterator iterator(color_space_pickle); |
| if (!IPC::ParamTraits<gfx::ColorSpace>::Read(&color_space_pickle, &iterator, |
| color_space)) { |
| return false; |
| } |
| return true; |
| } |
| |
| } // namespace gpu |