| // 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 "sysmem-secure-mem-server.h" |
| |
| #include <lib/async-loop/default.h> |
| #include <lib/fidl-async/cpp/bind.h> |
| #include <lib/fit/defer.h> |
| |
| #include <cinttypes> |
| |
| #include <safemath/safe_math.h> |
| |
| #include "log.h" |
| |
| SysmemSecureMemServer::SysmemSecureMemServer(thrd_t ddk_dispatcher_thread, |
| zx::channel tee_client_channel) |
| : ddk_dispatcher_thread_(ddk_dispatcher_thread), |
| loop_(&kAsyncLoopConfigNoAttachToCurrentThread) { |
| ZX_DEBUG_ASSERT(tee_client_channel); |
| tee_connection_.Bind(std::move(tee_client_channel)); |
| } |
| |
| SysmemSecureMemServer::~SysmemSecureMemServer() { |
| ZX_DEBUG_ASSERT(thrd_current() == ddk_dispatcher_thread_); |
| ZX_DEBUG_ASSERT(!is_protect_memory_range_active_); |
| ZX_DEBUG_ASSERT(is_loop_done_ || !was_thread_started_); |
| if (was_thread_started_) { |
| // Call StopAsync() first, and only do ~SysmemSecureMemServer() when secure_mem_server_done has |
| // been called. |
| ZX_DEBUG_ASSERT(loop_.GetState() == ASYNC_LOOP_QUIT); |
| ZX_DEBUG_ASSERT(is_loop_done_); |
| // EnsureLoopDone() was already previously called. |
| ZX_DEBUG_ASSERT(!secure_mem_server_done_); |
| loop_.JoinThreads(); |
| // This will cancel the wait, which will run EnsureLoopDone(), but since is_loop_done_ is |
| // already true, the EnsureLoopDone() that runs here won't do anything. This call to Shutdown() |
| // is necessary to complete the llcpp unbind. |
| loop_.Shutdown(); |
| } |
| } |
| |
| zx_status_t SysmemSecureMemServer::BindAsync(zx::channel sysmem_secure_mem_server, |
| thrd_t* sysmem_secure_mem_server_thread, |
| SecureMemServerDone secure_mem_server_done) { |
| ZX_DEBUG_ASSERT(sysmem_secure_mem_server); |
| ZX_DEBUG_ASSERT(sysmem_secure_mem_server_thread); |
| ZX_DEBUG_ASSERT(secure_mem_server_done); |
| ZX_DEBUG_ASSERT(thrd_current() == ddk_dispatcher_thread_); |
| zx_status_t status = loop_.StartThread("sysmem_secure_mem_server_loop", &loop_thread_); |
| if (status != ZX_OK) { |
| LOG(ERROR, "loop_.StartThread() failed - status: %d", status); |
| return status; |
| } |
| was_thread_started_ = true; |
| // This probably goes without saying, but it's worth pointing out that the loop_thread_ must be |
| // separate from the ddk_dispatcher_thread_ so that TEEC_* calls made on the loop_thread_ can be |
| // served by the ddk_dispatcher_thread_ without deadlock. |
| ZX_DEBUG_ASSERT(ddk_dispatcher_thread_ != loop_thread_); |
| closure_queue_.SetDispatcher(loop_.dispatcher(), loop_thread_); |
| *sysmem_secure_mem_server_thread = loop_thread_; |
| secure_mem_server_done_ = std::move(secure_mem_server_done); |
| PostToLoop([this, sysmem_secure_mem_server = std::move(sysmem_secure_mem_server)]() mutable { |
| ZX_DEBUG_ASSERT(thrd_current() == loop_thread_); |
| zx_status_t status = fidl::Bind<SysmemSecureMemServer>( |
| loop_.dispatcher(), std::move(sysmem_secure_mem_server), this, |
| [this](SysmemSecureMemServer* sysmem_secure_mem_server) { |
| // This can get called from the ddk_dispatcher_thread_ if |
| // we're doing loop_.Shutdown() to unbind an llcpp server (so |
| // far the only way to unbind an llcpp server). However, in |
| // this case the call to EnsureLoopDone() will idempotently do |
| // nothing because is_loop_done_ is already true. |
| ZX_DEBUG_ASSERT(thrd_current() == loop_thread_ || is_loop_done_); |
| // If secure_mem_server_done_ still set by this point, !is_success. |
| EnsureLoopDone(false); |
| }); |
| if (status != ZX_OK) { |
| LOG(ERROR, "fidl::Bind() failed - status: %d", status); |
| ZX_DEBUG_ASSERT(secure_mem_server_done_); |
| EnsureLoopDone(false); |
| } |
| }); |
| return ZX_OK; |
| } |
| |
| void SysmemSecureMemServer::StopAsync() { |
| // The only way to unbind an llcpp server is to Shutdown() the loop, but before we can do that we |
| // have to Quit() the loop. |
| ZX_DEBUG_ASSERT(thrd_current() == ddk_dispatcher_thread_); |
| ZX_DEBUG_ASSERT(was_thread_started_); |
| PostToLoop([this] { |
| ZX_DEBUG_ASSERT(thrd_current() == loop_thread_); |
| // Stopping the loop intentionally is considered is_success, if that happens before channel |
| // failure. EnsureLoopDone() is idempotent so it'll early out if already called previously. |
| EnsureLoopDone(true); |
| }); |
| } |
| |
| void SysmemSecureMemServer::GetPhysicalSecureHeaps( |
| llcpp::fuchsia::sysmem::SecureMem::Interface::GetPhysicalSecureHeapsCompleter::Sync completer) { |
| ZX_DEBUG_ASSERT(thrd_current() == loop_thread_); |
| llcpp::fuchsia::sysmem::PhysicalSecureHeaps heaps; |
| zx_status_t status = GetPhysicalSecureHeapsInternal(&heaps); |
| if (status != ZX_OK) { |
| LOG(ERROR, "GetPhysicalSecureHeapsInternal() failed - status: %d", status); |
| completer.ReplyError(status); |
| return; |
| } |
| completer.ReplySuccess(std::move(heaps)); |
| } |
| |
| void SysmemSecureMemServer::SetPhysicalSecureHeaps( |
| llcpp::fuchsia::sysmem::PhysicalSecureHeaps heaps, |
| llcpp::fuchsia::sysmem::SecureMem::Interface::SetPhysicalSecureHeapsCompleter::Sync completer) { |
| ZX_DEBUG_ASSERT(thrd_current() == loop_thread_); |
| // must out-live |complete| |
| fidl::aligned<llcpp::fuchsia::sysmem::SecureMem_SetPhysicalSecureHeaps_Response> response; |
| // must out-live |complete| |
| llcpp::fuchsia::sysmem::SecureMem_SetPhysicalSecureHeaps_Result result; |
| // ~complete before ~result or ~response |
| auto complete = fit::defer([&completer, &result] { |
| ZX_DEBUG_ASSERT(!result.has_invalid_tag()); |
| completer.Reply(std::move(result)); |
| }); |
| zx_status_t status = SetPhysicalSecureHeapsInternal(heaps); |
| if (status != ZX_OK) { |
| LOG(ERROR, "SetPhysicalSecureHeapsInternal() failed - status: %d", status); |
| result.set_err(fidl::unowned_ptr(&status)); |
| return; |
| } |
| result.set_response(fidl::unowned_ptr(&response)); |
| // ~complete, ~result, ~response in that order |
| } |
| |
| void SysmemSecureMemServer::PostToLoop(fit::closure to_run) { |
| // For now this is only expected to be called from ddk_dispatcher_thread_. |
| ZX_DEBUG_ASSERT(thrd_current() == ddk_dispatcher_thread_); |
| closure_queue_.Enqueue(std::move(to_run)); |
| } |
| |
| bool SysmemSecureMemServer::TrySetupSecmemSession() { |
| ZX_DEBUG_ASSERT(thrd_current() == loop_thread_); |
| // We only try this once; if it doesn't work the first time, it's very |
| // unlikely to work on retry anyway, and this avoids some retry complexity. |
| if (!has_attempted_secmem_session_connection_) { |
| ZX_DEBUG_ASSERT(tee_connection_.is_bound()); |
| ZX_DEBUG_ASSERT(!secmem_session_.has_value()); |
| |
| has_attempted_secmem_session_connection_ = true; |
| |
| auto session_result = SecmemSession::TryOpen(std::move(tee_connection_)); |
| if (!session_result.is_ok()) { |
| // Logging handled in `SecmemSession::TryOpen` |
| tee_connection_ = session_result.take_error(); |
| return false; |
| } |
| |
| secmem_session_.emplace(session_result.take_value()); |
| LOG(INFO, "Successfully connected to secmem session"); |
| return true; |
| } |
| |
| return secmem_session_.has_value(); |
| } |
| |
| void SysmemSecureMemServer::EnsureLoopDone(bool is_success) { |
| if (is_loop_done_) { |
| return; |
| } |
| // We can't assert this any sooner, because when unbinding llcpp server using loop_.Shutdown() |
| // we'll be on ddk_dispatcher_thread_. But in that case, the first run of EnsureLoopDone() |
| // happened on the loop_thread_. |
| ZX_DEBUG_ASSERT(thrd_current() == loop_thread_); |
| is_loop_done_ = true; |
| closure_queue_.StopAndClear(); |
| loop_.Quit(); |
| if (has_attempted_secmem_session_connection_ && secmem_session_.has_value()) { |
| ZX_DEBUG_ASSERT(thrd_current() == loop_thread_); |
| if (is_protect_memory_range_active_) { |
| TEEC_Result tee_status = |
| secmem_session_->ProtectMemoryRange(protect_start_, protect_length_, false); |
| if (tee_status != TEEC_SUCCESS) { |
| LOG(ERROR, "SecmemSession::ProtectMemoryRange(false) failed - TEEC_Result %d", tee_status); |
| ZX_PANIC("SecmemSession::ProtectMemoryRange(false) failed - TEEC_Result %d", tee_status); |
| } |
| is_protect_memory_range_active_ = false; |
| } |
| secmem_session_.reset(); |
| } else { |
| // We could be running on the loop_thread_ or the ddk_dispatcher_thread_ in this case. |
| ZX_DEBUG_ASSERT(!secmem_session_); |
| } |
| if (secure_mem_server_done_) { |
| secure_mem_server_done_(is_success); |
| } |
| } |
| |
| zx_status_t SysmemSecureMemServer::GetPhysicalSecureHeapsInternal( |
| llcpp::fuchsia::sysmem::PhysicalSecureHeaps* heaps) { |
| ZX_DEBUG_ASSERT(thrd_current() == loop_thread_); |
| |
| if (is_get_physical_secure_heaps_called_) { |
| LOG(ERROR, "GetPhysicalSecureHeaps may only be called at most once - reply status: %d", |
| ZX_ERR_BAD_STATE); |
| return ZX_ERR_BAD_STATE; |
| } |
| is_get_physical_secure_heaps_called_ = true; |
| |
| if (!TrySetupSecmemSession()) { |
| // Logging handled in `TrySetupSecmemSession` |
| return ZX_ERR_INTERNAL; |
| } |
| |
| uint64_t vdec_phys_base; |
| size_t vdec_size; |
| zx_status_t status = SetupVdec(&vdec_phys_base, &vdec_size); |
| if (status != ZX_OK) { |
| LOG(ERROR, "SetupVdec failed - status: %d", status); |
| return status; |
| } |
| |
| heaps->heaps_count = 1; |
| heaps->heaps[0].heap = llcpp::fuchsia::sysmem::HeapType::AMLOGIC_SECURE_VDEC; |
| heaps->heaps[0].physical_address = vdec_phys_base; |
| heaps->heaps[0].size_bytes = static_cast<uint64_t>(vdec_size); |
| return ZX_OK; |
| } |
| |
| zx_status_t SysmemSecureMemServer::SetPhysicalSecureHeapsInternal( |
| llcpp::fuchsia::sysmem::PhysicalSecureHeaps heaps) { |
| ZX_DEBUG_ASSERT(thrd_current() == loop_thread_); |
| if (is_set_physical_secure_heaps_called_) { |
| LOG(ERROR, "SetPhysicalSecureHeaps may only be called at most once - reply status: %d", |
| ZX_ERR_BAD_STATE); |
| return ZX_ERR_BAD_STATE; |
| } |
| is_set_physical_secure_heaps_called_ = true; |
| |
| if (!TrySetupSecmemSession()) { |
| // Logging handled in `TrySetupSecmemSession` |
| return ZX_ERR_INTERNAL; |
| } |
| |
| // This implementation is amlogic-specific; we expect exactly 1 heap which is |
| // AMLOGIC_SECURE, only. |
| if (heaps.heaps_count != 1) { |
| LOG(ERROR, "heaps.heaps_count != 1"); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| const llcpp::fuchsia::sysmem::PhysicalSecureHeap& heap = heaps.heaps[0]; |
| if (heap.heap != llcpp::fuchsia::sysmem::HeapType::AMLOGIC_SECURE) { |
| LOG(ERROR, "heap != AMLOGIC_SECURE"); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| zx_status_t status = ProtectMemoryRange(heap.physical_address, heap.size_bytes); |
| if (status != ZX_OK) { |
| LOG(ERROR, "ProtectMemoryRange() failed - status: %d", status); |
| return status; |
| } |
| |
| LOG(INFO, "Succeeded protecting memory range 0x%lx 0x%lx", heap.physical_address, |
| heap.size_bytes); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t SysmemSecureMemServer::SetupVdec(uint64_t* physical_address, size_t* size_bytes) { |
| ZX_DEBUG_ASSERT(thrd_current() == loop_thread_); |
| ZX_DEBUG_ASSERT(has_attempted_secmem_session_connection_); |
| ZX_DEBUG_ASSERT(secmem_session_.has_value()); |
| uint32_t start; |
| uint32_t length; |
| TEEC_Result tee_status = secmem_session_->AllocateSecureMemory(&start, &length); |
| if (tee_status != TEEC_SUCCESS) { |
| LOG(ERROR, "SecmemSession::AllocateSecureMemory() failed - TEEC_Result %" PRIu32, tee_status); |
| return ZX_ERR_INTERNAL; |
| } |
| *physical_address = start; |
| *size_bytes = length; |
| return ZX_OK; |
| } |
| |
| zx_status_t SysmemSecureMemServer::ProtectMemoryRange(uint64_t physical_address, |
| size_t size_bytes) { |
| ZX_DEBUG_ASSERT(thrd_current() == loop_thread_); |
| ZX_DEBUG_ASSERT(has_attempted_secmem_session_connection_); |
| ZX_DEBUG_ASSERT(secmem_session_.has_value()); |
| if (!safemath::IsValueInRangeForNumericType<uint32_t>(physical_address)) { |
| LOG(ERROR, "heap.physical_address > 0xFFFFFFFF"); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| if (!safemath::IsValueInRangeForNumericType<uint32_t>(size_bytes)) { |
| LOG(ERROR, "heap.size_bytes > 0xFFFFFFFF"); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| if (!safemath::CheckAdd(physical_address, size_bytes).IsValid<uint32_t>()) { |
| LOG(ERROR, "start + size overflow"); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| auto start = static_cast<uint32_t>(physical_address); |
| auto length = static_cast<uint32_t>(size_bytes); |
| TEEC_Result tee_status = secmem_session_->ProtectMemoryRange(start, length, true); |
| if (tee_status != TEEC_SUCCESS) { |
| LOG(ERROR, "SecmemSession::ProtectMemoryRange() failed - TEEC_Result %d returning status: %d", |
| tee_status, ZX_ERR_INTERNAL); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| // Stash these so we can clean up later during DdkSuspend(), so mexec can work. |
| protect_start_ = start; |
| protect_length_ = length; |
| is_protect_memory_range_active_ = true; |
| |
| return ZX_OK; |
| } |