/* * * Copyright 2021, 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 "guest_session.h" #include namespace android { namespace hardware { namespace confirmationui { namespace V1_0 { namespace implementation { using TeeuiRc = teeui::ResponseCode; GuestSession::ResultTriple GuestSession::PromptUserConfirmation() { std::unique_lock stateLock(listener_state_lock_); /* * This is the main listener thread function. The listener thread life cycle * is equivalent to the life cycle of a single confirmation request. The life * cycle is divided in four phases. * * The starting phase: * * Drives the cuttlefish confirmation UI session on the host side, too * * Note: During the starting phase the hwbinder service thread is blocked and * waiting for possible Errors. If the setup phase concludes successfully, the * hwbinder service thread gets unblocked and returns successfully. Errors * that occur after the first phase are delivered by callback interface. * * For cuttlefish, it means that the guest will conduct a blocking wait for * an ack to kStart. * * * The 2nd phase - non interactive phase * * After a grace period: * * guest will pick up cuttlefish host's ack to kStart * * * The 3rd phase - interactive phase * * We wait to any external event * * Abort * * Secure user input asserted * * The result is fetched from the TA. * * * The 4th phase - cleanup * * Sending the kStop command to the cuttlefish host, and wait for ack */ GuestSession::ResultTriple error; auto& error_rc = std::get(error); error_rc = ResponseCode::SystemError; CHECK(listener_state_ == ListenerState::Starting) << "ListenerState should be Starting"; // initiate prompt ConfUiLog(INFO) << "Initiating prompt"; const std::uint32_t payload_lower_bound = static_cast(prompt_text_.size() + extra_data_.size()); const std::uint32_t upper_bound = static_cast(cuttlefish::confui::kMaxMessageLength); if (payload_lower_bound > upper_bound) { ConfUiLog(INFO) << "UI message too long to send to the host"; // message is too long anyway, and don't send it to the host error_rc = ResponseCode::UIErrorMessageTooLong; return error; } SerializedSend(cuttlefish::confui::SendStartCmd, host_fd_, session_name_, prompt_text_, extra_data_, locale_, ui_options_); ConfUiLog(INFO) << "Session " << GetSessionId() << " started on both the guest and the host"; auto clean_up_and_get_first = [&]() -> std::unique_ptr { // blocking wait to get the first msg that belongs to this session while (true) { auto first_curr_session_msg = incoming_msg_queue_.Pop(); if (!first_curr_session_msg || first_curr_session_msg->GetSessionId() != GetSessionId()) { continue; } return std::move(first_curr_session_msg); } }; /* * Unconditionally wait ack, or host abort * * First couple of messages could be from the previous session. * We should clear them up. * * Even though the guest HAL sends kAbort to the host, the kAbort * does not happen immediately. Between the incoming_msg_queue_.FlushAll() * and the actual abort on the host, there could still be messages * sent from the host to the guest. As these lines are the first read * for the current session, we clear up the preceding messages * from the previous session until we see the message for the current * session. * * Note that abort() call puts the Abort command in the queue. So, * it will also show up in incoming_msg_queue_ * */ auto first_msg = std::move(clean_up_and_get_first()); cuttlefish::confui::ConfUiAckMessage& start_ack_msg = static_cast(*first_msg); if (!start_ack_msg.IsSuccess()) { // handle errors: MALFORMED_UTF8 or Message too long const std::string error_msg = start_ack_msg.GetStatusMessage(); if (error_msg == cuttlefish::confui::HostError::kMessageTooLongError) { ConfUiLog(ERROR) << "Message + Extra data + Meta info were too long"; error_rc = ResponseCode::UIErrorMessageTooLong; } if (error_msg == cuttlefish::confui::HostError::kIncorrectUTF8) { ConfUiLog(ERROR) << "Message is incorrectly UTF-encoded"; error_rc = ResponseCode::UIErrorMalformedUTF8Encoding; } return error; } // ############################## Start 2nd Phase ############################################# listener_state_ = ListenerState::SetupDone; ConfUiLog(INFO) << "Transition to SetupDone"; stateLock.unlock(); listener_state_condv_.notify_all(); // cuttlefish does not need the second phase to implement HAL APIs // input was already prepared before the confirmation UI screen was rendered // ############################## Start 3rd Phase - interactive phase ######################### stateLock.lock(); listener_state_ = ListenerState::Interactive; ConfUiLog(INFO) << "Transition to Interactive"; stateLock.unlock(); listener_state_condv_.notify_all(); // give deliverSecureInputEvent a chance to interrupt // wait for an input but should not block deliverSecureInputEvent or Abort // Thus, it should not hold the stateLock std::mutex input_ready_mtx; std::condition_variable input_ready_cv_; std::unique_lock input_ready_lock(input_ready_mtx); bool input_ready = false; auto wait_input_and_signal = [&]() -> std::unique_ptr { auto msg = incoming_msg_queue_.Pop(); { std::unique_lock lock(input_ready_mtx); input_ready = true; input_ready_cv_.notify_one(); } return msg; }; auto input_and_signal_future = std::async(std::launch::async, wait_input_and_signal); input_ready_cv_.wait(input_ready_lock, [&]() { return input_ready; }); // now an input is ready, so let's acquire the stateLock stateLock.lock(); auto user_or_abort = input_and_signal_future.get(); if (user_or_abort->GetType() == cuttlefish::confui::ConfUiCmd::kAbort) { ConfUiLog(ERROR) << "Abort called or the user/host aborted" << " while waiting user response"; return {ResponseCode::Aborted, {}, {}}; } if (user_or_abort->GetType() == cuttlefish::confui::ConfUiCmd::kCliAck) { auto& ack_msg = static_cast(*user_or_abort); if (ack_msg.IsSuccess()) { ConfUiLog(ERROR) << "When host failed, it is supposed to send " << "kCliAck with fail, but this is kCliAck with success"; } error_rc = ResponseCode::SystemError; return error; } cuttlefish::confui::ConfUiCliResponseMessage& user_response = static_cast(*user_or_abort); // pick, see if it is response, abort cmd // handle abort or error response here ConfUiLog(INFO) << "Making up the result"; // make up the result triple if (user_response.GetResponse() == cuttlefish::confui::UserResponse::kCancel) { SerializedSend(cuttlefish::confui::SendStopCmd, host_fd_, GetSessionId()); return {ResponseCode::Canceled, {}, {}}; } if (user_response.GetResponse() != cuttlefish::confui::UserResponse::kConfirm) { ConfUiLog(ERROR) << "Unexpected user response that is " << user_response.GetResponse(); return error; } SerializedSend(cuttlefish::confui::SendStopCmd, host_fd_, GetSessionId()); // ############################## Start 4th Phase - cleanup ################################## return {ResponseCode::OK, user_response.GetMessage(), user_response.GetSign()}; } Return GuestSession::DeliverSecureInputEvent( const android::hardware::keymaster::V4_0::HardwareAuthToken& auth_token) { ResponseCode rc = ResponseCode::Ignored; { /* * deliverSecureInputEvent is only used by the VTS test to mock human input. A correct * implementation responds with a mock confirmation token signed with a test key. The * problem is that the non interactive grace period was not formalized in the HAL spec, * so that the VTS test does not account for the grace period. (It probably should.) * This means we can only pass the VTS test if we block until the grace period is over * (SetupDone -> Interactive) before we deliver the input event. * * The true secure input is delivered by a different mechanism and gets ignored - * not queued - until the grace period is over. * */ std::unique_lock stateLock(listener_state_lock_); listener_state_condv_.wait(stateLock, [this] { return listener_state_ != ListenerState::SetupDone; }); if (listener_state_ != ListenerState::Interactive) return ResponseCode::Ignored; if (static_cast(auth_token.challenge) == TestModeCommands::OK_EVENT) { SerializedSend(cuttlefish::confui::SendUserSelection, host_fd_, GetSessionId(), cuttlefish::confui::UserResponse::kConfirm); } else { SerializedSend(cuttlefish::confui::SendUserSelection, host_fd_, GetSessionId(), cuttlefish::confui::UserResponse::kCancel); } rc = ResponseCode::OK; } listener_state_condv_.notify_all(); // VTS test expect an OK response if the event was successfully delivered. // But since the TA returns the callback response now, we have to translate // Canceled into OK. Canceled is only returned if the delivered event canceled // the operation, which means that the event was successfully delivered. Thus // we return OK. if (rc == ResponseCode::Canceled) return ResponseCode::OK; return rc; } Return GuestSession::Abort() { { std::unique_lock stateLock(listener_state_lock_); if (listener_state_ == ListenerState::SetupDone || listener_state_ == ListenerState::Interactive) { if (host_fd_->IsOpen()) { SerializedSend(cuttlefish::confui::SendAbortCmd, host_fd_, GetSessionId()); } using cuttlefish::confui::ConfUiAbortMessage; auto local_abort_cmd = std::make_unique(GetSessionId()); incoming_msg_queue_.Push(std::move(local_abort_cmd)); } } listener_state_condv_.notify_all(); return Void(); } } // namespace implementation } // namespace V1_0 } // namespace confirmationui } // namespace hardware } // namespace android