| // Copyright 2012 The Chromium Authors | |
| // 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 <string.h> | |
| #include <algorithm> | |
| #include <string_view> | |
| #include "base/compiler_specific.h" | |
| #include "base/containers/span_reader.h" | |
| #include "base/numerics/safe_math.h" | |
| #include "base/pickle.h" | |
| #include "base/strings/string_view_util.h" | |
| #include "gpu/command_buffer/service/command_buffer_service.h" | |
| #include "gpu/command_buffer/service/decoder_client.h" | |
| namespace gpu { | |
| namespace { | |
| static const size_t kDefaultMaxBucketSize = 1u << 30; // 1 GB | |
| uint32_t LoadU32Unaligned(const void* ptr) { | |
| uint32_t ret; | |
| UNSAFE_TODO(memcpy(&ret, ptr, sizeof(uint32_t))); | |
| return ret; | |
| } | |
| void StoreU32Unaligned(uint32_t v, void* ptr) { | |
| UNSAFE_TODO(memcpy(ptr, &v, sizeof(uint32_t))); | |
| } | |
| } // namespace | |
| const std::array<CommonDecoder::CommandInfo, cmd::kNumCommands> | |
| 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; | |
| base::span<uint8_t> CommonDecoder::Bucket::GetDataAsByteSpan(size_t offset, | |
| size_t size) { | |
| if (OffsetSizeValid(offset, size)) { | |
| return data_.subspan(offset, size); | |
| } | |
| return {}; | |
| } | |
| void CommonDecoder::Bucket::SetSize(size_t size) { | |
| if (size != size_) { | |
| // WithSize() value-initializes the elements, i.e. the buffer will be | |
| // zero-initialized. | |
| data_ = base::HeapArray<uint8_t>::WithSize(size); | |
| size_ = size; | |
| } | |
| } | |
| bool CommonDecoder::Bucket::SetData(base::span<const volatile uint8_t> src, | |
| size_t offset) { | |
| if (OffsetSizeValid(offset, src.size())) { | |
| data_.subspan(offset, src.size()).copy_from(src); | |
| 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 { | |
| std::string_view str_view(str); | |
| size_t size = str_view.size() + 1; | |
| SetSize(size); | |
| SetData(base::as_byte_span(str_view), 0); | |
| data_[str_view.size()] = 0; | |
| } | |
| } | |
| bool CommonDecoder::Bucket::GetAsString(std::string* str) { | |
| DCHECK(str); | |
| if (size_ == 0) { | |
| return false; | |
| } | |
| base::span<const uint8_t> bytes = GetDataAsByteSpan(0, size_ - 1); | |
| str->assign(base::as_string_view(bytes)); | |
| return true; | |
| } | |
| std::optional<std::vector<std::string_view>> | |
| CommonDecoder::Bucket::GetAsStrings() { | |
| const size_t bucket_size = this->size(); | |
| if (bucket_size < sizeof(int32_t)) { | |
| return std::nullopt; | |
| } | |
| base::SpanReader reader{GetDataAsByteSpan(0, bucket_size)}; | |
| std::optional<int32_t> count32 = reader.ReadI32NativeEndian(); | |
| if (!count32.has_value() || *count32 < 0) { | |
| return std::nullopt; | |
| } | |
| const int32_t count = *count32; | |
| // Don't pre-size the vectors from the untrusted `count`: a bogus value would | |
| // try to allocate a huge amount of memory. Reserve is bounded by the bucket | |
| // size instead, and SpanReader fails gracefully once the bucket runs out of | |
| // data. | |
| const size_t reserve = bucket_size / (sizeof(int32_t) + 1u); | |
| std::vector<int32_t> lengths; | |
| lengths.reserve(reserve); | |
| for (int32_t ii = 0; ii < count; ++ii) { | |
| std::optional<int32_t> length32 = reader.ReadI32NativeEndian(); | |
| if (!length32.has_value() || *length32 < 0) { | |
| return std::nullopt; | |
| } | |
| lengths.push_back(*length32); | |
| } | |
| std::vector<std::string_view> strings; | |
| strings.reserve(lengths.size()); | |
| for (const int32_t length : lengths) { | |
| std::optional<base::span<uint8_t>> str = | |
| reader.Read(static_cast<size_t>(length) + 1u); | |
| if (!str.has_value()) { | |
| return std::nullopt; | |
| } | |
| if ((*str)[length] != 0) { | |
| return std::nullopt; | |
| } | |
| strings.push_back(base::as_string_view( | |
| base::span<const uint8_t>(*str).first(static_cast<size_t>(length)))); | |
| } | |
| if (reader.remaining() != 0u) { | |
| return std::nullopt; | |
| } | |
| return strings; | |
| } | |
| bool CommonDecoder::Bucket::OffsetSizeValid(size_t offset, size_t size) const { | |
| size_t end = 0; | |
| if (!base::CheckAdd<size_t>(offset, size).AssignIfValid(&end)) | |
| return false; | |
| return end <= size_; | |
| } | |
| 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; | |
| std::optional<base::span<uint8_t>> CommonDecoder::GetSharedMemoryAsByteSpan( | |
| uint32_t shm_id, | |
| uint32_t offset, | |
| uint32_t size_in_bytes) { | |
| scoped_refptr<gpu::Buffer> buffer = | |
| command_buffer_service_->GetTransferBuffer(shm_id); | |
| if (!buffer) { | |
| return std::nullopt; | |
| } | |
| base::CheckedNumeric<uint32_t> end = offset; | |
| end += size_in_bytes; | |
| if (!end.IsValid() || end.ValueOrDie() > buffer->size()) { | |
| return std::nullopt; | |
| } | |
| return buffer->GetSpanData(offset, size_in_bytes); | |
| } | |
| 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 UNSAFE_TODO(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 < command_info.size()) { | |
| 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; | |
| std::optional<base::span<uint8_t>> data = GetSharedMemoryAsByteSpan( | |
| args.shared_memory_id, args.shared_memory_offset, size); | |
| if (!data.has_value()) { | |
| return error::kInvalidArguments; | |
| } | |
| Bucket* bucket = GetBucket(bucket_id); | |
| if (!bucket) { | |
| return error::kInvalidArguments; | |
| } | |
| if (!bucket->SetData(*data, offset)) { | |
| 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); | |
| 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; | |
| } | |
| const volatile uint8_t* data = | |
| GetImmediateDataAs<const volatile uint8_t*>(args); | |
| auto data_span = UNSAFE_TODO(base::span<const volatile uint8_t>(data, size)); | |
| Bucket* bucket = GetBucket(bucket_id); | |
| if (!bucket) { | |
| return error::kInvalidArguments; | |
| } | |
| if (!bucket->SetData(data_span, offset)) { | |
| 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; | |
| // `result` may not be aligned, so cast to `void*` and use `memcpy` to load | |
| // and store. | |
| void* result = GetSharedMemoryAs<void*>( | |
| args.result_memory_id, args.result_memory_offset, sizeof(uint32_t)); | |
| 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; | |
| std::optional<base::span<uint8_t>> data; | |
| if (data_memory_size != 0 || data_memory_id != 0 || data_memory_offset != 0) { | |
| data = GetSharedMemoryAsByteSpan(data_memory_id, data_memory_offset, | |
| data_memory_size); | |
| if (!data.has_value()) { | |
| return error::kInvalidArguments; | |
| } | |
| } | |
| if (!result) { | |
| return error::kInvalidArguments; | |
| } | |
| // Check that the client initialized the result. | |
| if (LoadU32Unaligned(result) != 0) { | |
| return error::kInvalidArguments; | |
| } | |
| Bucket* bucket = GetBucket(bucket_id); | |
| if (!bucket) { | |
| return error::kInvalidArguments; | |
| } | |
| uint32_t bucket_size = bucket->size(); | |
| StoreU32Unaligned(bucket_size, result); | |
| if (data.has_value()) { | |
| uint32_t size = std::min(data_memory_size, bucket_size); | |
| data->copy_prefix_from(bucket->GetDataAsByteSpan(0, 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; | |
| std::optional<base::span<uint8_t>> data = GetSharedMemoryAsByteSpan( | |
| args.shared_memory_id, args.shared_memory_offset, size); | |
| if (!data.has_value()) { | |
| return error::kInvalidArguments; | |
| } | |
| Bucket* bucket = GetBucket(bucket_id); | |
| if (!bucket) { | |
| return error::kInvalidArguments; | |
| } | |
| // Note that an empty span is returned both for a valid zero-sized request and | |
| // for an out-of-range one, so the range has to be validated explicitly. | |
| base::CheckedNumeric<size_t> end = offset; | |
| end += size; | |
| if (!end.IsValid() || end.ValueOrDie() > bucket->size()) { | |
| return error::kInvalidArguments; | |
| } | |
| data->copy_from(bucket->GetDataAsByteSpan(offset, 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; | |
| } | |
| } // namespace gpu |