/* * * Copyright 2019, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "TrustyConfirmationUI.h" #include #include namespace aidl::android::hardware::confirmationui { using ::teeui::MsgString; using ::teeui::MsgVector; using TeeuiRc = ::teeui::ResponseCode; namespace { teeui::UIOption convertUIOption(UIOption uio) { static_assert(uint32_t(UIOption::ACCESSIBILITY_INVERTED) == uint32_t(teeui::UIOption::AccessibilityInverted) && uint32_t(UIOption::ACCESSIBILITY_MAGNIFIED) == uint32_t(teeui::UIOption::AccessibilityMagnified), "teeui::UIOPtion and ::android::hardware::confirmationui::V1_0::UIOption " "are out of sync"); return teeui::UIOption(uio); } inline MsgString str2MsgString(const string& s) { return {s.c_str(), s.c_str() + s.size()}; } template inline MsgVector vec2MsgVector(const vector& v) { return {v}; } inline MsgVector vec2MsgVector(const vector& v) { MsgVector result(v.size()); for (unsigned int i = 0; i < v.size(); ++i) { result[i] = convertUIOption(v[i]); } return result; } } // namespace const char* TrustyConfirmationUI::GetVirtioConsoleDevicePath() { static char device_path[] = "/dev/hvc8"; return device_path; } TrustyConfirmationUI::TrustyConfirmationUI() : listener_state_(ListenerState::None), prompt_result_(IConfirmationUI::IGNORED), current_session_id_{10} { host_fd_ = cuttlefish::SharedFD::Open(GetVirtioConsoleDevicePath(), O_RDWR); CHECK(host_fd_->IsOpen()) << "ConfUI: " << GetVirtioConsoleDevicePath() << " is not open."; CHECK(host_fd_->SetTerminalRaw() >= 0) << "ConfUI: " << GetVirtioConsoleDevicePath() << " fail in SetTerminalRaw()"; constexpr static const auto enable_confirmationui_property = "ro.boot.enable_confirmationui"; const auto arg = property_get_int32(enable_confirmationui_property, -1); is_supported_vm_ = (arg == 1); if (host_fd_->IsOpen()) { auto fetching_cmd = [this]() { HostMessageFetcherLoop(); }; host_cmd_fetcher_thread_ = std::thread(fetching_cmd); } } TrustyConfirmationUI::~TrustyConfirmationUI() { if (host_fd_->IsOpen()) { host_fd_->Close(); } if (host_cmd_fetcher_thread_.joinable()) { host_cmd_fetcher_thread_.join(); } if (listener_state_ != ListenerState::None) { callback_thread_.join(); } } void TrustyConfirmationUI::HostMessageFetcherLoop() { while (true) { if (!host_fd_->IsOpen()) { // this happens when TrustyConfirmationUI is destroyed ConfUiLog(ERROR) << "host_fd_ is not open"; return; } ConfUiLog(INFO) << "Trying to fetch command"; auto msg = cuttlefish::confui::RecvConfUiMsg(host_fd_); ConfUiLog(INFO) << "RecvConfUiMsg() returned"; if (!msg) { // virtio-console is broken for now ConfUiLog(ERROR) << "received message was null"; return; } { std::unique_lock lk(current_session_lock_); if (!current_session_ || msg->GetSessionId() != current_session_->GetSessionId()) { if (!current_session_) { ConfUiLog(ERROR) << "msg is received but session is null"; continue; } ConfUiLog(ERROR) << "session id mismatch, so ignored" << "Received for " << msg->GetSessionId() << " but currently running " << current_session_->GetSessionId(); continue; } current_session_->Push(std::move(msg)); } listener_state_condv_.notify_all(); } } void TrustyConfirmationUI::RunSession(shared_ptr resultCB, string promptText, vector extraData, string locale, vector uiOptions) { cuttlefish::SharedFD fd = host_fd_; // ownership of the fd is passed to GuestSession { std::unique_lock lk(current_session_lock_); current_session_ = std::make_unique( current_session_id_, listener_state_, listener_state_lock_, listener_state_condv_, fd, str2MsgString(promptText), vec2MsgVector(extraData), str2MsgString(locale), vec2MsgVector(uiOptions)); } auto [rc, msg, token] = current_session_->PromptUserConfirmation(); std::unique_lock lock(listener_state_lock_); // for listener_state_ bool do_callback = (listener_state_ == ListenerState::Interactive || listener_state_ == ListenerState::SetupDone) && resultCB; prompt_result_ = rc; listener_state_ = ListenerState::Terminating; lock.unlock(); if (do_callback) { auto error = resultCB->result(prompt_result_, msg, token); if (!error.isOk()) { if (error.getExceptionCode() == EX_SERVICE_SPECIFIC) { ConfUiLog(ERROR) << "Result callback failed error: " << error.getServiceSpecificError(); } else { ConfUiLog(ERROR) << "Result callback failed error: " << error.getStatus(); } } ConfUiLog(INFO) << "Result callback returned."; } else { listener_state_condv_.notify_all(); } } // Methods from ::android::hardware::confirmationui::V1_0::IConfirmationUI // follow. ::ndk::ScopedAStatus TrustyConfirmationUI::promptUserConfirmation( const shared_ptr& resultCB, const vector& promptTextBytes, const vector& extraData, const string& locale, const vector& uiOptions) { std::unique_lock stateLock(listener_state_lock_, std::defer_lock); ConfUiLog(INFO) << "promptUserConfirmation is called"; string promptText(promptTextBytes.begin(), promptTextBytes.end()); if (!is_supported_vm_) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(IConfirmationUI::UNIMPLEMENTED)); } if (!stateLock.try_lock()) { return ndk::ScopedAStatus( AStatus_fromServiceSpecificError(IConfirmationUI::OPERATION_PENDING)); } switch (listener_state_) { case ListenerState::None: break; case ListenerState::Starting: case ListenerState::SetupDone: case ListenerState::Interactive: return ndk::ScopedAStatus( AStatus_fromServiceSpecificError(IConfirmationUI::OPERATION_PENDING)); case ListenerState::Terminating: callback_thread_.join(); listener_state_ = ListenerState::None; break; default: return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(IConfirmationUI::UNEXPECTED)); } assert(listener_state_ == ListenerState::None); listener_state_ = ListenerState::Starting; current_session_id_++; auto worker = [this](const shared_ptr& resultCB, const string& promptText, const vector& extraData, const string& locale, const vector& uiOptions) { RunSession(resultCB, promptText, extraData, locale, uiOptions); }; callback_thread_ = std::thread(worker, resultCB, promptText, extraData, locale, uiOptions); listener_state_condv_.wait(stateLock, [this] { return listener_state_ == ListenerState::SetupDone || listener_state_ == ListenerState::Interactive || listener_state_ == ListenerState::Terminating; }); if (listener_state_ == ListenerState::Terminating) { callback_thread_.join(); listener_state_ = ListenerState::None; if (prompt_result_ == IConfirmationUI::CANCELED) { // VTS expects this return ndk::ScopedAStatus::ok(); } return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(prompt_result_)); } return ndk::ScopedAStatus::ok(); } ::ndk::ScopedAStatus TrustyConfirmationUI::deliverSecureInputEvent(const HardwareAuthToken& auth_token) { ConfUiLog(INFO) << "deliverSecureInputEvent is called"; int rc = IConfirmationUI::IGNORED; if (!is_supported_vm_) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(IConfirmationUI::UNIMPLEMENTED)); } { std::unique_lock lock(current_session_lock_); if (!current_session_) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(rc)); } rc = current_session_->DeliverSecureInputEvent(auth_token); if (rc != IConfirmationUI::OK) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(rc)); } } return ndk::ScopedAStatus::ok(); } ::ndk::ScopedAStatus TrustyConfirmationUI::abort() { if (!is_supported_vm_) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(IConfirmationUI::UNIMPLEMENTED)); } std::unique_lock lock(current_session_lock_); if (!current_session_) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(IConfirmationUI::IGNORED)); } current_session_->Abort(); return ndk::ScopedAStatus::ok(); } } // namespace aidl::android::hardware::confirmationui