| // Copyright 2019 The Fuchsia 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 "secmem-session.h" |
| |
| #include <zircon/assert.h> |
| |
| #include <algorithm> |
| #include <iterator> |
| #include <limits> |
| |
| #include <fbl/algorithm.h> |
| #include <safemath/checked_math.h> |
| |
| #include "log.h" |
| #include "tee-client-api/tee-client-types.h" |
| |
| namespace { |
| |
| // Some secmem-specific marshaling definitions. |
| |
| enum TeeParamType { |
| kTeeParamTypeBuffer, |
| kTeeParamTypeUint32, |
| kTeeParamTypeUint64, |
| kTeeParamTypePvoid, |
| }; |
| |
| struct TeeCommandParam { |
| TeeParamType type; |
| union { |
| struct { |
| uint32_t buffer_length; |
| uint32_t pbuf[1]; |
| } buf; // kTeeParamTypeBuffer |
| uint32_t u32; // kTeeParamTypeUint32 |
| } param; |
| }; |
| |
| // Defined by secmem TA. |
| enum SecmemCommandIds { |
| kSecmemCommandIdAllocateSecureMemory = 101, |
| kSecmemCommandIdProtectMemory = 104, |
| kSecmemCommandIdUnprotectMemory = 105, |
| kSecmemCommandIdGetPadding = 107, |
| kSecmemCommandIdGetVp9HeaderSize = 108, |
| kSecmemCommandIdGetMemSize = 110, |
| }; |
| |
| constexpr uint32_t kProtectionRangeGranularity = 64 * 1024; |
| constexpr uint32_t kProtectionRangeGranularityMask = |
| 0xFFFFFFFF & ~(kProtectionRangeGranularity - 1); |
| |
| enum EnableFlags : uint32_t { |
| // Which sub-command. |
| kEnableFlag_SubCommand_Mask = 0xFu << 0, |
| kEnableFlag_SubCommand_Shift = 0, |
| |
| // Disable a currently-enabled range. |
| kEnableFlag_SubCommand_Disable = 0x0u, |
| |
| // Select a free range and enable it. |
| kEnableFlag_SubCommand_Enable = 0x1u, |
| |
| // For detecting whether a sub-command exists. If DetectSubCommand itself is the command being |
| // detected, the meaning of success and failure (for that one call only) are reversed for legacy |
| // reasons. |
| kEnableFlag_SubCommand_DetectSubCommand = 0x2u, |
| |
| // Adjust a currently-enabled range. If the range is adjusted to zero size, the range is |
| // disabled. |
| kEnableFlag_SubCommand_Adjust = 0x3u, |
| |
| // This command is equivalent to creating all ranges, then deleting all ranges with |
| // SkipDeviceSecureModeUpdate set, then explicitly disabling protected mode for each device. But |
| // with this command, we don't need to allocate 11 * 64KiB of 64KiB-aligned physically-contiguous |
| // memory just to get these effects to happen. |
| kEnableFlag_SubCommand_InitTvpForAllRanges = 0x4u, |
| |
| // This allows us to zero a page-aligned sub-range of a currently-active range, as long as the |
| // sub-range does not overlap with any other currently-active range. In other words the requested |
| // zeroing must be fully covered by exactly one active range and not overlap with any other active |
| // range. The extent of the zeroing is conveyed in the startaddr, size parameters, and must be |
| // page aligned (in contrast to other commands which must be 64KiB aligned). |
| kEnableFlag_SubCommand_ZeroSubRange = 0x5u, |
| |
| // This dumps ranges to debug output, if the firmware has debug output enabled. Else noop. |
| kEnableFlag_SubCommand_DumpRanges = 0x6u, |
| |
| // Field indicating which command is being checked for. If checking for DetectSubCommand itself, |
| // the meaning of success and failure are reversed for legacy reasons. |
| kEnableFlag_DetectSubCommand_CommandNumber_Mask = 0xFu << 28, |
| kEnableFlag_DetectSubCommand_CommandNumber_Shift = 28u, |
| |
| // Enable/Disable protected memory range without modifying device protected mode configuration, |
| // even if the number of enabled ranges is changing from 0 to 1 or 1 to 0. The Adjust command |
| // never modifies device protected mode configuration. |
| kEnableFlag_EnableDisable_SkipDeviceSecureModeUpdate = 1u << 31, |
| |
| // Adjust the start of the range instead of the end of the range. |
| kEnableFlag_Adjust_RangeAtStart = 1u << 31, |
| |
| // Adjust the range to be longer instead of shorter. |
| kEnableFlag_Adjust_RangeLonger = 1u << 30, |
| |
| // The adjustment size is 64 KiB << (value * 2) |
| kEnableFlag_Adjust_Size_Mask = 0x3u << 28, |
| kEnableFlag_Adjust_Size_Shift = 28u, |
| kEnableFlag_Adjust_Size_Coefficient = 64u * 1024, |
| kEnableFlag_Adjust_Size_ExponentMultiplier = 2u, |
| |
| kEnableFlag_ZeroSubRange_IsCoveringRangeExplicit = 1u << 31, |
| }; |
| |
| zx::vmo CreateVmo(uint64_t size) { |
| zx::vmo vmo; |
| zx_status_t status = zx::vmo::create(size, /*options=*/0, &vmo); |
| ZX_ASSERT(status == ZX_OK); |
| |
| return vmo; |
| } |
| |
| fpromise::result<fuchsia::tee::Buffer> CreateCommandBuffer(const std::vector<uint8_t>& contents) { |
| zx::vmo vmo = CreateVmo(static_cast<uint64_t>(contents.size())); |
| |
| zx_status_t status = vmo.write(contents.data(), /*offset=*/0, contents.size()); |
| if (status != ZX_OK) { |
| LOG(ERROR, "Failed to write to command buffer VMO - status: %d", status); |
| return fpromise::error(); |
| } |
| |
| fuchsia::tee::Buffer buffer; |
| buffer.set_vmo(std::move(vmo)) |
| .set_size(static_cast<uint64_t>(contents.size())) |
| .set_offset(0) |
| .set_direction(fuchsia::tee::Direction::INOUT); |
| return fpromise::ok(std::move(buffer)); |
| } |
| |
| fuchsia::tee::Value CreateReturnCodeParameter() { |
| fuchsia::tee::Value value; |
| value.set_direction(fuchsia::tee::Direction::OUTPUT); |
| return value; |
| } |
| |
| fpromise::result<fuchsia::tee::Buffer> GetCommandBuffer( |
| std::vector<fuchsia::tee::Parameter>* parameter_set) { |
| ZX_DEBUG_ASSERT(parameter_set); |
| constexpr size_t kParamBufferIndex = 0; |
| |
| if (!parameter_set->at(kParamBufferIndex).is_buffer()) { |
| return fpromise::error(); |
| } |
| |
| fuchsia::tee::Buffer& buffer = parameter_set->at(kParamBufferIndex).buffer(); |
| if (!buffer.has_vmo() || !buffer.has_size() || !buffer.has_offset() || !buffer.has_direction()) { |
| return fpromise::error(); |
| } |
| if (buffer.offset() >= buffer.size()) { |
| return fpromise::error(); |
| } |
| |
| return fpromise::ok(std::move(buffer)); |
| } |
| |
| bool IsExpectedSecmemCommandResult(const fuchsia::tee::OpResult& result) { |
| return result.has_parameter_set() && result.parameter_set().size() == 4 && |
| result.has_return_code() && result.has_return_origin(); |
| } |
| |
| } // namespace |
| |
| fpromise::result<SecmemSession, fuchsia::tee::ApplicationSyncPtr> SecmemSession::TryOpen( |
| fuchsia::tee::ApplicationSyncPtr tee_connection) { |
| if (!tee_connection.is_bound()) { |
| return fpromise::error(std::move(tee_connection)); |
| } |
| |
| fuchsia::tee::OpResult result; |
| uint32_t session_id = 0; |
| auto params = std::vector<fuchsia::tee::Parameter>(); |
| |
| if (zx_status_t status = tee_connection->OpenSession2(std::move(params), &session_id, &result); |
| status != ZX_OK) { |
| LOG(ERROR, "OpenSession channel call failed - status: %d", status); |
| return fpromise::error(std::move(tee_connection)); |
| } |
| |
| if (!result.has_return_code() || !result.has_return_origin()) { |
| LOG(ERROR, "OpenSession returned with result codes missing"); |
| return fpromise::error(std::move(tee_connection)); |
| } |
| |
| if (result.return_code() != TEEC_SUCCESS) { |
| LOG(WARNING, "OpenSession to secmem failed - TEEC_Result: %" PRIx64 ", origin: %" PRIu32 ".", |
| result.return_code(), static_cast<uint32_t>(result.return_origin())); |
| return fpromise::error(std::move(tee_connection)); |
| } |
| |
| return fpromise::ok(SecmemSession{session_id, std::move(tee_connection)}); |
| } |
| |
| SecmemSession::~SecmemSession() { |
| if (tee_connection_.is_bound()) { |
| tee_connection_->CloseSession(session_id_); |
| } |
| } |
| |
| void SecmemSession::PackUint32Parameter(uint32_t value, std::vector<uint8_t>* buffer) { |
| ZX_DEBUG_ASSERT(buffer); |
| |
| TeeCommandParam param; |
| param.type = kTeeParamTypeUint32; |
| param.param.u32 = value; |
| |
| auto param_begin = reinterpret_cast<const uint8_t*>(¶m); |
| auto param_end = reinterpret_cast<const uint8_t*>(¶m) + (sizeof(param) / sizeof(uint8_t)); |
| |
| const size_t new_buf_size = fbl::round_up(buffer->size() + sizeof(param), kParameterAlignment); |
| |
| buffer->reserve(new_buf_size); |
| buffer->insert(buffer->end(), param_begin, param_end); |
| |
| if (buffer->size() < new_buf_size) { |
| std::fill_n(std::back_inserter(*buffer), new_buf_size - buffer->size(), 0); |
| } |
| } |
| |
| TEEC_Result SecmemSession::InvokeSecmemCommand(uint32_t command, |
| std::vector<uint8_t>* cmd_buffer_vec) { |
| ZX_DEBUG_ASSERT(cmd_buffer_vec); |
| |
| if (!tee_connection_.is_bound()) { |
| return TEEC_ERROR_TARGET_DEAD; |
| } |
| |
| // The first parameter is where all of Amlogic's custom parameters are packed. |
| fuchsia::tee::Buffer in_cmd_buffer; |
| if (auto in_cmd_buffer_result = CreateCommandBuffer(*cmd_buffer_vec); |
| in_cmd_buffer_result.is_ok()) { |
| in_cmd_buffer = in_cmd_buffer_result.take_value(); |
| } else { |
| return TEEC_ERROR_COMMUNICATION; |
| } |
| |
| constexpr size_t kNumParams = 4; |
| auto params = std::vector<fuchsia::tee::Parameter>(); |
| params.reserve(kNumParams); |
| params.push_back(fuchsia::tee::Parameter::WithBuffer(std::move(in_cmd_buffer))); |
| params.push_back(fuchsia::tee::Parameter::WithNone(fuchsia::tee::None{})); |
| params.push_back(fuchsia::tee::Parameter::WithNone(fuchsia::tee::None{})); |
| params.push_back(fuchsia::tee::Parameter::WithValue(CreateReturnCodeParameter())); |
| |
| fuchsia::tee::OpResult result; |
| if (zx_status_t status = |
| tee_connection_->InvokeCommand(session_id_, command, std::move(params), &result); |
| status != ZX_OK) { |
| LOG(ERROR, "InvokeCommand channel call failed - status: %d", status); |
| return TEEC_ERROR_COMMUNICATION; |
| } |
| |
| if (!IsExpectedSecmemCommandResult(result)) { |
| LOG(ERROR, "InvokeCommand returned with unexpected OpResult"); |
| return TEEC_ERROR_COMMUNICATION; |
| } |
| |
| fuchsia::tee::Buffer out_cmd_buffer; |
| if (auto out_cmd_buffer_result = GetCommandBuffer(result.mutable_parameter_set()); |
| out_cmd_buffer_result.is_ok()) { |
| out_cmd_buffer = out_cmd_buffer_result.take_value(); |
| } else { |
| LOG(ERROR, "Secmem command returned with unexpected command buffer parameter"); |
| return TEEC_ERROR_COMMUNICATION; |
| } |
| |
| // Ensure that `cmd_buffer_vec` is of the appropriate size |
| cmd_buffer_vec->resize(out_cmd_buffer.size() - out_cmd_buffer.offset(), /*val=*/0); |
| |
| // Read output into provided `cmd_buffer_vec` |
| if (zx_status_t status = |
| out_cmd_buffer.vmo().read(cmd_buffer_vec->data(), out_cmd_buffer.offset(), |
| out_cmd_buffer.size() - out_cmd_buffer.offset()); |
| status != ZX_OK) { |
| LOG(ERROR, "Failed to read parameters from VMO - status: %d", status); |
| return TEEC_ERROR_COMMUNICATION; |
| } |
| |
| if (result.return_code() != TEEC_SUCCESS) { |
| // Inability to talk to the TA or similar. |
| return static_cast<TEEC_Result>(result.return_code()); |
| } |
| |
| // The "result.return_code()" is sortof a transport-level return code if something goes wrong |
| // communicating with the TA. The actual secmem TA return code is in params[3].a. |
| return static_cast<TEEC_Result>(result.parameter_set()[3].value().a()); |
| } |
| |
| fpromise::result<uint32_t> SecmemSession::UnpackUint32Parameter(const std::vector<uint8_t>& buffer, |
| size_t* offset_in_out) { |
| ZX_DEBUG_ASSERT(offset_in_out); |
| |
| size_t offset = *offset_in_out; |
| |
| if (offset + sizeof(TeeCommandParam) > buffer.size()) { |
| return fpromise::error(); |
| } |
| |
| const uint8_t* param_addr = buffer.data() + offset; |
| auto param = reinterpret_cast<const TeeCommandParam*>(param_addr); |
| if (param->type != kTeeParamTypeUint32) { |
| LOG(ERROR, "Received unexpected param type"); |
| return fpromise::error(); |
| } |
| |
| offset += sizeof(TeeCommandParam); |
| *offset_in_out = fbl::round_up(offset, kParameterAlignment); |
| |
| return fpromise::ok(param->param.u32); |
| } |
| |
| TEEC_Result SecmemSession::InvokeProtectMemory(uint32_t start, uint32_t length, |
| uint32_t enable_flags) { |
| std::vector<uint8_t> cmd_buffer; |
| // Reserve room for 4 parameters. |
| cmd_buffer.reserve(kParameterAlignment * 4); |
| |
| PackUint32Parameter(kSecmemCommandIdProtectMemory, &cmd_buffer); |
| |
| PackUint32Parameter(enable_flags, &cmd_buffer); |
| |
| // count of regions must be 1-4 inclusive |
| constexpr uint32_t kRegionNum = 1; |
| PackUint32Parameter(kRegionNum, &cmd_buffer); |
| |
| PackUint32Parameter(start, &cmd_buffer); |
| |
| PackUint32Parameter(length, &cmd_buffer); |
| |
| TEEC_Result result = InvokeSecmemCommand(kSecmemCommandIdProtectMemory, &cmd_buffer); |
| return result; |
| } |
| |
| bool SecmemSession::DetectIsAdjustAndSkipDeviceSecureModeUpdateAvailable() { |
| // If Adjust is available, then so is SkipDeviceSecureModeUpdate, so we only need to detect if |
| // Adjust is available. |
| // |
| // We don't expect to be running with back-version firmware in any normal situation, but we need |
| // to be sure that in abnormal situations we don't cause problems getting back to a normal |
| // situation asap, so we accommodate running on back-version firmware by detecting if we're |
| // missing new-version firmware, and if so, disabling dynamic protected contiguous memory |
| // management. |
| if (is_detect_called_) { |
| return is_adjust_known_available_; |
| } |
| is_detect_called_ = true; |
| // In the TEE, if the firmware doesn't have DetectSubCommand, this will result in an enabled HW |
| // protection range that has a last block address < first block address, which covers zero 64 KiB |
| // blocks. In addition, due to legacy firmware side-effects of creating a memory protection range |
| // this will modify per-device protected mode config, and then change those back as we unwind from |
| // discovering that we're running on legacy firmware somehow, temporarily. |
| uint32_t start = std::numeric_limits<uint32_t>::max() & kProtectionRangeGranularityMask; |
| // This can't be zero or the TEE will reject the request. If we find we're on older-version |
| // firmware (and only if we're on older-version firmware), we clean up the phantom block to regain |
| // use of all the HW protection ranges. |
| // |
| // Current-version firmware only requires this value to be non-zero, but otherwise ignores the |
| // value (when using DetectSubCommand). |
| uint32_t length = 0u - 1u; |
| ZX_DEBUG_ASSERT(length == 0xFFFFFFFFu); |
| |
| uint32_t enable_flags = 0; |
| static_assert(kEnableFlag_SubCommand_Shift == 0); |
| enable_flags |= kEnableFlag_SubCommand_DetectSubCommand << kEnableFlag_SubCommand_Shift; |
| enable_flags |= kEnableFlag_SubCommand_DetectSubCommand |
| << kEnableFlag_DetectSubCommand_CommandNumber_Shift; |
| |
| TEEC_Result detect_is_detect_available_result = InvokeProtectMemory(start, length, enable_flags); |
| // The sense of success/failure is flipped here, for legacy reasons. |
| bool is_detect_available = (detect_is_detect_available_result == TEEC_ERROR_GENERIC); |
| LOG(INFO, "is_detect_available: %d detect_is_detect_available_result: 0x%x", is_detect_available, |
| detect_is_detect_available_result); |
| if (!is_detect_available) { |
| LOG(INFO, "!is_detect_available"); |
| enable_flags = 0; |
| enable_flags |= kEnableFlag_SubCommand_Disable << kEnableFlag_SubCommand_Shift; |
| ZX_DEBUG_ASSERT(enable_flags == 0); |
| TEEC_Result cleanup_result = InvokeProtectMemory(start, length, enable_flags); |
| // This isn't verifying much since older firmware doesn't plumb status from very far down, but |
| // we should see TEEC_SUCCESS here. |
| ZX_ASSERT(cleanup_result == TEEC_SUCCESS); |
| return false; |
| } |
| |
| // Now we know that the DetectSubCommand sub-command exists. At this point we could just return |
| // true, since we know that DetectSubCommand existing implies Adjust existing, but in the interest |
| // of establishing a pattern, we go ahead and detect whether Adjust exists explicitly here. |
| enable_flags = 0; |
| enable_flags |= kEnableFlag_SubCommand_DetectSubCommand << kEnableFlag_SubCommand_Shift; |
| enable_flags |= kEnableFlag_SubCommand_Adjust << kEnableFlag_DetectSubCommand_CommandNumber_Shift; |
| // We just need start, length that both aren't zero; the specific non-zero values don't matter. |
| TEEC_Result detect_is_adjust_available_result = InvokeProtectMemory(start, length, enable_flags); |
| is_adjust_known_available_ = (detect_is_adjust_available_result == TEEC_SUCCESS); |
| // For this particular sub-command, we know this will be true given that detect is available. For |
| // potential future-added sub-commands, we won't be able to have a similar assert. |
| ZX_ASSERT(is_adjust_known_available_); |
| |
| enable_flags = 0; |
| enable_flags |= kEnableFlag_SubCommand_DetectSubCommand << kEnableFlag_SubCommand_Shift; |
| enable_flags |= kEnableFlag_SubCommand_InitTvpForAllRanges |
| << kEnableFlag_DetectSubCommand_CommandNumber_Shift; |
| // We just need start, length that both aren't zero; the specific non-zero values don't matter. |
| TEEC_Result detect_is_init_tvp_available_result = |
| InvokeProtectMemory(start, length, enable_flags); |
| bool is_init_tvp_available = (detect_is_init_tvp_available_result == TEEC_SUCCESS); |
| // For this particular sub-command, we know this will be true given that detect is available. For |
| // potential future-added sub-commands, we won't be able to have a similar assert. |
| ZX_ASSERT(is_init_tvp_available); |
| |
| enable_flags = 0; |
| enable_flags |= kEnableFlag_SubCommand_InitTvpForAllRanges << kEnableFlag_SubCommand_Shift; |
| // The start and length both need to be non-zero, but otherwise are ignored for this sub-command. |
| TEEC_Result enable_result = InvokeProtectMemory(0xFFFFFFFF, 0xFFFFFFFF, enable_flags); |
| ZX_ASSERT(enable_result == TEEC_SUCCESS); |
| |
| return is_adjust_known_available_; |
| } |
| |
| // Defer figuring out max_range_count_ until first request to protect a range, at which point we'll |
| // have a big contiguous chunk to use for detection. Then use AdjustRange() to detect whether a |
| // range was really created. |
| uint32_t SecmemSession::GetMaxClientUsableProtectedRangeCount(uint64_t phys_base, |
| uint64_t size_bytes) { |
| ZX_DEBUG_ASSERT(is_detect_called_); |
| // Only called once during early init. |
| ZX_DEBUG_ASSERT(!is_get_max_client_usable_protected_range_count_called_); |
| // As needed, we can create a separate earlier properties query to get this alignment before the |
| // phys range is allocated. At the moment, sysmem just knows. |
| ZX_DEBUG_ASSERT(phys_base % kProtectionRangeGranularity == 0); |
| ZX_DEBUG_ASSERT(size_bytes % kProtectionRangeGranularity == 0); |
| |
| is_get_max_client_usable_protected_range_count_called_ = true; |
| if (!is_adjust_known_available_) { |
| LOG(INFO, "!is_adjust_known_available_"); |
| return 1; |
| } |
| |
| uint32_t create_enable_flags = 0; |
| create_enable_flags |= kEnableFlag_SubCommand_Enable << kEnableFlag_SubCommand_Shift; |
| create_enable_flags |= kEnableFlag_EnableDisable_SkipDeviceSecureModeUpdate; |
| ZX_DEBUG_ASSERT(phys_base <= std::numeric_limits<uint32_t>::max()); |
| uint32_t start_0 = static_cast<uint32_t>(phys_base); |
| // We create each range at 128KiB, then adjust the size to 64KiB by shortening at the end. We do |
| // this because the create doesn't report failure from the FW (legacy behavior) even if the number |
| // of REE-usable HW protection ranges is exhausted, while adjust does return failure. |
| constexpr uint32_t kStartingLength = 2 * kProtectionRangeGranularity; |
| constexpr uint32_t kHoldLength = kProtectionRangeGranularity; |
| uint32_t adjust_flags = 0; |
| adjust_flags |= kEnableFlag_SubCommand_Adjust << kEnableFlag_SubCommand_Shift; |
| // at_start is already 0 |
| // longer is already 0 |
| // we want 64KiB and the exponent is already 0 |
| uint32_t range_ordinal = 0; |
| for (range_ordinal = 0;; ++range_ordinal) { |
| if (start_0 + range_ordinal * kHoldLength + kStartingLength > phys_base + size_bytes) { |
| LOG(INFO, "range count capped due to size of secure heap (unexpected)"); |
| break; |
| } |
| TEEC_Result create_result = InvokeProtectMemory(start_0 + range_ordinal * kHoldLength, |
| kStartingLength, create_enable_flags); |
| if (create_result != TEEC_SUCCESS) { |
| LOG(INFO, "FW changed to ever report failure from HW protection range create? - result: 0x%x", |
| create_result); |
| break; |
| } |
| TEEC_Result adjust_result = |
| InvokeProtectMemory(start_0 + range_ordinal * kHoldLength, kStartingLength, adjust_flags); |
| if (adjust_result != TEEC_SUCCESS) { |
| // Normal when we've run out of REE-usable ranges, so don't log anything here. |
| break; |
| } |
| }; |
| uint32_t range_count = range_ordinal; |
| LOG(INFO, "range_count: %u", range_count); |
| uint32_t disable_flags = 0; |
| disable_flags |= kEnableFlag_SubCommand_Disable << kEnableFlag_SubCommand_Shift; |
| disable_flags |= kEnableFlag_EnableDisable_SkipDeviceSecureModeUpdate; |
| for (range_ordinal = 0; range_ordinal < range_count; ++range_ordinal) { |
| TEEC_Result disable_result = |
| InvokeProtectMemory(start_0 + range_ordinal * kHoldLength, kHoldLength, disable_flags); |
| // The FW never reports a failure from disable (legacy behavior). If this assert does fire, |
| // this process will exit, and sysmem will exit, and the device will reboot. |
| ZX_ASSERT_MSG(disable_result == TEEC_SUCCESS, "disable_result: 0x%x", disable_result); |
| } |
| return range_count; |
| } |
| |
| TEEC_Result SecmemSession::ProtectMemoryRange(uint32_t start, uint32_t length, |
| bool is_enable_protection) { |
| ZX_DEBUG_ASSERT(is_detect_called_); |
| ZX_DEBUG_ASSERT(start % kProtectionRangeGranularity == 0); |
| ZX_DEBUG_ASSERT(length % kProtectionRangeGranularity == 0); |
| ZX_DEBUG_ASSERT(length != 0); |
| |
| uint32_t enable_flags = 0; |
| static_assert(kEnableFlag_SubCommand_Shift == 0); |
| if (is_enable_protection) { |
| enable_flags |= kEnableFlag_SubCommand_Enable << kEnableFlag_SubCommand_Shift; |
| } else { |
| enable_flags |= kEnableFlag_SubCommand_Disable << kEnableFlag_SubCommand_Shift; |
| } |
| if (is_adjust_known_available_) { |
| enable_flags |= kEnableFlag_EnableDisable_SkipDeviceSecureModeUpdate; |
| } |
| |
| return InvokeProtectMemory(start, length, enable_flags); |
| } |
| |
| TEEC_Result SecmemSession::AdjustMemoryRange(uint32_t start, uint32_t length, |
| uint32_t adjustment_magnitude, bool at_start, |
| bool longer) { |
| ZX_DEBUG_ASSERT(is_adjust_known_available_); |
| ZX_DEBUG_ASSERT(start % kProtectionRangeGranularity == 0); |
| ZX_DEBUG_ASSERT(length % kProtectionRangeGranularity == 0); |
| ZX_DEBUG_ASSERT(length != 0); |
| ZX_DEBUG_ASSERT(adjustment_magnitude % kProtectionRangeGranularity == 0); |
| |
| // The available choices here are 64KiB, 256KiB, 1MiB, 4MiB. We don't want to zero too much per |
| // call since that could have us in the TEE long enough to cause trouble with scheduling. |
| constexpr uint32_t kMaxZeroingSizeInSingleCall = 1024u * 1024u; |
| |
| uint32_t enable_flags_base = 0; |
| static_assert(kEnableFlag_SubCommand_Shift == 0); |
| enable_flags_base |= kEnableFlag_SubCommand_Adjust << kEnableFlag_SubCommand_Shift; |
| if (at_start) { |
| enable_flags_base |= kEnableFlag_Adjust_RangeAtStart; |
| } |
| if (longer) { |
| enable_flags_base |= kEnableFlag_Adjust_RangeLonger; |
| } |
| |
| uint32_t adjustment_todo = adjustment_magnitude; |
| while (adjustment_todo != 0) { |
| uint32_t enable_flags = enable_flags_base; |
| uint32_t magnitude = 0; |
| int32_t value; |
| for (value = (kEnableFlag_Adjust_Size_Mask >> kEnableFlag_Adjust_Size_Shift); value >= 0; |
| --value) { |
| magnitude = kEnableFlag_Adjust_Size_Coefficient |
| << (value * kEnableFlag_Adjust_Size_ExponentMultiplier); |
| if (magnitude <= adjustment_todo && (longer || magnitude <= kMaxZeroingSizeInSingleCall)) { |
| break; |
| } |
| } |
| ZX_DEBUG_ASSERT(magnitude != 0); |
| ZX_DEBUG_ASSERT(magnitude <= adjustment_todo); |
| ZX_DEBUG_ASSERT(value >= 0 && static_cast<uint32_t>(value) <= (kEnableFlag_Adjust_Size_Mask >> |
| kEnableFlag_Adjust_Size_Shift)); |
| uint32_t to_adjust_this_time_magnitude = magnitude; |
| uint32_t to_adjust_this_time_value = value; |
| enable_flags |= (to_adjust_this_time_value << kEnableFlag_Adjust_Size_Shift); |
| |
| TEEC_Result adjust_result = InvokeProtectMemory(start, length, enable_flags); |
| if (adjust_result != TEEC_SUCCESS) { |
| LOG(WARNING, |
| "InvokeProtectMemory (adjust) failed - start: 0x%x length: 0x%x enable_flags: 0x%x " |
| "adjust_result: %x", |
| start, length, enable_flags, adjust_result); |
| if (adjustment_todo != adjustment_magnitude) { |
| // If this fails after making a partial adjustment, we don't have a way to report the actual |
| // current range to the layers above. In addition, this call to the TEE should _never_ |
| // fail, and the fact that it has failed is good evidence that the TEE has gotten into a |
| // broken state, which for security reasons is good justification for doing a hard reboot to |
| // get back to a functional TEE. We really can't be having range shortening or range |
| // deletion failing; that just can't really work from the user's point of view even if we |
| // could report the actual current range back to sysmem in this path. |
| ZX_PANIC("AdjustMemoryRange() failed - adjust_result: 0x%x", adjust_result); |
| } |
| return adjust_result; |
| } |
| uint32_t old_start = start; |
| uint32_t old_length = length; |
| adjustment_todo -= to_adjust_this_time_magnitude; |
| // We adjust the parameters so we can refer to the newly-adjusted range next iteration. |
| if (longer) { |
| length += to_adjust_this_time_magnitude; |
| if (at_start) { |
| start -= to_adjust_this_time_magnitude; |
| } |
| } else { |
| length -= to_adjust_this_time_magnitude; |
| if (at_start) { |
| start += to_adjust_this_time_magnitude; |
| } |
| } |
| uint32_t old_end = old_start + old_length; |
| uint32_t new_end = start + length; |
| ZX_DEBUG_ASSERT(start == old_start || new_end == old_end); |
| } |
| return TEEC_SUCCESS; |
| } |
| |
| TEEC_Result SecmemSession::ZeroSubRange(bool is_covering_range_explicit, uint32_t start, |
| uint32_t length) { |
| ZX_DEBUG_ASSERT(start % zx_system_get_page_size() == 0); |
| ZX_DEBUG_ASSERT(length % zx_system_get_page_size() == 0); |
| ZX_DEBUG_ASSERT(length != 0); |
| |
| // We're not restricted by the TEE API here but it's good to avoid zeroing too much in one call |
| // to the TEE. |
| constexpr uint32_t kMaxZeroingSizeInSingleCall = 1024u * 1024u; |
| |
| uint32_t enable_flags = 0; |
| static_assert(kEnableFlag_SubCommand_Shift == 0); |
| enable_flags |= kEnableFlag_SubCommand_ZeroSubRange << kEnableFlag_SubCommand_Shift; |
| if (is_covering_range_explicit) { |
| enable_flags |= kEnableFlag_ZeroSubRange_IsCoveringRangeExplicit; |
| } |
| |
| uint32_t end = start + length; |
| uint32_t todo_this_time; |
| for (uint32_t iter = start; iter != end; iter += todo_this_time) { |
| todo_this_time = std::min(end - iter, kMaxZeroingSizeInSingleCall); |
| TEEC_Result zero_result = InvokeProtectMemory(iter, todo_this_time, enable_flags); |
| if (zero_result != TEEC_SUCCESS) { |
| LOG(WARNING, |
| "InvokeProtectMemory() (zero) failed - start: 0x%x length: 0x%x enable_flags: 0x%x", iter, |
| todo_this_time, enable_flags); |
| return zero_result; |
| } |
| } |
| |
| return TEEC_SUCCESS; |
| } |
| |
| void SecmemSession::DumpRanges() { |
| uint32_t enable_flags = 0; |
| static_assert(kEnableFlag_SubCommand_Shift == 0); |
| enable_flags |= kEnableFlag_SubCommand_DumpRanges << kEnableFlag_SubCommand_Shift; |
| TEEC_Result dump_result = InvokeProtectMemory(0xFFFFFFFF, 0xFFFFFFFF, enable_flags); |
| if (dump_result != TEEC_SUCCESS) { |
| LOG(WARNING, "InvokeProtectMemory() (dump ranges) failed - dump_result: %d", dump_result); |
| ZX_ASSERT(dump_result == TEEC_SUCCESS); |
| return; |
| } |
| // done |
| } |
| |
| TEEC_Result SecmemSession::AllocateSecureMemory(uint32_t* start, uint32_t* length) { |
| // First, ask secmem TA for the max size of VDEC, then allocate that size. |
| |
| std::vector<uint8_t> cmd_buffer; |
| // Reserve room for 4 parameters. |
| cmd_buffer.reserve(kParameterAlignment * 4); |
| |
| // kSecmemCommandIdGetMemSize command first |
| PackUint32Parameter(kSecmemCommandIdGetMemSize, &cmd_buffer); |
| TEEC_Result tee_status = InvokeSecmemCommand(kSecmemCommandIdGetMemSize, &cmd_buffer); |
| if (tee_status != TEEC_SUCCESS) { |
| LOG(ERROR, "kSecmemCommandIdGetMemSize failed - TEEC_Result: %" PRIx32, tee_status); |
| return tee_status; |
| } |
| |
| size_t output_offset = 0; |
| fpromise::result<uint32_t> max_vdec_size_result = |
| UnpackUint32Parameter(cmd_buffer, &output_offset); |
| if (!max_vdec_size_result.is_ok()) { |
| LOG(ERROR, "UnpackUint32Parameter() after kSecmemCommandIdGetMemSize failed"); |
| return TEEC_ERROR_COMMUNICATION; |
| } |
| |
| // Reset for new command: kSecmemCommandIdAllocateSecureMemory. |
| cmd_buffer.clear(); |
| |
| PackUint32Parameter(kSecmemCommandIdAllocateSecureMemory, &cmd_buffer); |
| |
| // ignored |
| constexpr uint32_t kDbgLevel = 0; |
| PackUint32Parameter(kDbgLevel, &cmd_buffer); |
| |
| // We can pass false for is_vp9, even if later when we do |
| // kSecmemCommandIdGetVp9HeaderSize we start at exactly one AMLV header length |
| // into a page to avoid one frame/sub-frame being copied. |
| constexpr auto kIsVp9 = static_cast<uint32_t>(false); // 0 |
| PackUint32Parameter(kIsVp9, &cmd_buffer); |
| |
| PackUint32Parameter(max_vdec_size_result.value(), &cmd_buffer); |
| |
| tee_status = InvokeSecmemCommand(kSecmemCommandIdAllocateSecureMemory, &cmd_buffer); |
| if (tee_status != TEEC_SUCCESS) { |
| LOG(ERROR, "kSecmemCommandIdAllocateSecureMemory failed - TEEC_Result: %" PRIx32, tee_status); |
| return tee_status; |
| } |
| |
| output_offset = 0; |
| fpromise::result<uint32_t> vdec_paddr_result = UnpackUint32Parameter(cmd_buffer, &output_offset); |
| if (!vdec_paddr_result.is_ok()) { |
| LOG(ERROR, "UnpackUint32Parameter() after kSecmemCommandIdAllocateSecureMemory failed"); |
| return TEEC_ERROR_COMMUNICATION; |
| } |
| |
| *start = vdec_paddr_result.value(); |
| *length = max_vdec_size_result.value(); |
| |
| return TEEC_SUCCESS; |
| } |