// Copyright 2015 The Chromium 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 "components/arc/arc_session.h" #include #include #include #include #include #include #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/location.h" #include "base/macros.h" #include "base/memory/ptr_util.h" #include "base/posix/eintr_wrapper.h" #include "base/sys_info.h" #include "base/task_runner_util.h" #include "base/threading/thread_checker.h" #include "base/threading/thread_task_runner_handle.h" #include "chromeos/cryptohome/cryptohome_parameters.h" #include "chromeos/dbus/dbus_method_call_status.h" #include "chromeos/dbus/dbus_thread_manager.h" #include "chromeos/dbus/session_manager_client.h" #include "components/arc/arc_bridge_host_impl.h" #include "components/arc/arc_features.h" #include "components/user_manager/user_manager.h" #include "mojo/edk/embedder/embedder.h" #include "mojo/edk/embedder/named_platform_handle.h" #include "mojo/edk/embedder/named_platform_handle_utils.h" #include "mojo/edk/embedder/pending_process_connection.h" #include "mojo/edk/embedder/platform_channel_pair.h" #include "mojo/edk/embedder/platform_channel_utils_posix.h" #include "mojo/edk/embedder/platform_handle_vector.h" #include "mojo/edk/embedder/scoped_platform_handle.h" #include "mojo/public/cpp/bindings/binding.h" namespace arc { namespace { using StartArcInstanceResult = chromeos::SessionManagerClient::StartArcInstanceResult; const base::FilePath::CharType kArcBridgeSocketPath[] = FILE_PATH_LITERAL("/var/run/chrome/arc_bridge.sock"); const char kArcBridgeSocketGroup[] = "arc-bridge"; // This is called when StopArcInstance D-Bus method completes. Since we have the // ArcInstanceStopped() callback and are notified if StartArcInstance fails, we // don't need to do anything when StopArcInstance completes. void DoNothingInstanceStopped(bool) {} chromeos::SessionManagerClient* GetSessionManagerClient() { // If the DBusThreadManager or the SessionManagerClient aren't available, // there isn't much we can do. This should only happen when running tests. if (!chromeos::DBusThreadManager::IsInitialized() || !chromeos::DBusThreadManager::Get() || !chromeos::DBusThreadManager::Get()->GetSessionManagerClient()) return nullptr; return chromeos::DBusThreadManager::Get()->GetSessionManagerClient(); } // Creates a pipe. Returns true on success, otherwise false. // On success, |read_fd| will be set to the fd of the read side, and // |write_fd| will be set to the one of write side. bool CreatePipe(base::ScopedFD* read_fd, base::ScopedFD* write_fd) { int fds[2]; if (pipe2(fds, O_NONBLOCK | O_CLOEXEC) < 0) { PLOG(ERROR) << "pipe2()"; return false; } read_fd->reset(fds[0]); write_fd->reset(fds[1]); return true; } // Waits until |raw_socket_fd| is readable. // The operation may be cancelled originally triggered by user interaction to // disable ARC, or ARC instance is unexpectedly stopped (e.g. crash). // To notify such a situation, |raw_cancel_fd| is also passed to here, and the // write side will be closed in such a case. bool WaitForSocketReadable(int raw_socket_fd, int raw_cancel_fd) { struct pollfd fds[2] = { {raw_socket_fd, POLLIN, 0}, {raw_cancel_fd, POLLIN, 0}, }; if (HANDLE_EINTR(poll(fds, arraysize(fds), -1)) <= 0) { PLOG(ERROR) << "poll()"; return false; } if (fds[1].revents) { // Notified that Stop() is invoked. Cancel the Mojo connecting. VLOG(1) << "Stop() was called during ConnectMojo()"; return false; } DCHECK(fds[0].revents); return true; } // TODO(hidehiko): Refactor more to make this class unittest-able, for at least // state-machine part. class ArcSessionImpl : public ArcSession, public chromeos::SessionManagerClient::Observer { public: // The possible states of the session. In the normal flow, the state changes // in the following sequence: // // NOT_STARTED // Start() -> // CREATING_SOCKET // CreateSocket() -> OnSocketCreated() -> // STARTING_INSTANCE // -> OnInstanceStarted() -> // CONNECTING_MOJO // ConnectMojo() -> OnMojoConnected() -> // RUNNING // // At any state, Stop() can be called. It does not immediately stop the // instance, but will eventually stop it. // The actual stop will be notified via ArcSession::Observer::OnStopped(). // // When Stop() is called, it makes various behavior based on the current // phase. // // NOT_STARTED: // Do nothing. Immediately transition to the STOPPED state. // CREATING_SOCKET: // The main task of the phase runs on BlockingPool thread. So, Stop() just // sets the flag and return. On the main task completion, a callback // will run on the main (practically UI) thread, and the flag is checked // at the beginning of them. This should work under the assumption that // the main tasks do not block indefinitely. // STARTING_INSTANCE: // The ARC instance is starting via SessionManager. So, similar to // CREATING_SOCKET case, Stop() just sets the flag and return. In its // callback, it checks if ARC instance is successfully started or not. // In case of success, a request to stop the ARC instance is sent to // SessionManager. Its completion will be notified via ArcInstanceStopped. // Otherwise, it just turns into STOPPED state. // CONNECTING_MOJO: // The main task runs on BlockingPool thread, but it is blocking call. // So, Stop() sends a request to cancel the blocking by closing the pipe // whose read side is also polled. Then, in its callback, similar to // STARTING_INSTANCE, a request to stop the ARC instance is sent to // SessionManager, and ArcInstanceStopped handles remaining procedure. // RUNNING: // There is no more callback which runs on normal flow, so Stop() requests // to stop the ARC instance via SessionManager. // // Another trigger to change the state coming from outside of this class // is an event ArcInstanceStopped() sent from SessionManager, when ARC // instace unexpectedly terminates. ArcInstanceStopped() turns the state into // STOPPED immediately. // This happens only when STARTING_INSTANCE, CONNECTING_MOJO or RUNNING // state. // // STARTING_INSTANCE: // In OnInstanceStarted(), |state_| is checked at the beginning. If it is // STOPPED, then ArcInstanceStopped() is called. Do nothing in that case. // CONNECTING_MOJO: // Similar to Stop() case above, ArcInstanceStopped() also notifies to // BlockingPool thread to cancel it to unblock the thread. In // OnMojoConnected(), similar to OnInstanceStarted(), check if |state_| is // STOPPED, then do nothing. // RUNNING: // It is not necessary to do anything special here. // // In NOT_STARTED or STOPPED state, the instance can be safely destructed. // Specifically, in STOPPED state, there may be inflight operations or // pending callbacks. Though, what they do is just do-nothing conceptually // and they can be safely ignored. // // Note: Order of constants below matters. Please make sure to sort them // in chronological order. enum class State { // ARC is not yet started. NOT_STARTED, // An UNIX socket is being created. CREATING_SOCKET, // The request to start the instance has been sent. STARTING_INSTANCE, // The instance has started. Waiting for it to connect to the IPC bridge. CONNECTING_MOJO, // The instance is fully set up. RUNNING, // ARC is terminated. STOPPED, }; ArcSessionImpl(ArcBridgeService* arc_bridge_service, const scoped_refptr& blocking_task_runner); ~ArcSessionImpl() override; // ArcSession overrides: void Start() override; void Stop() override; void OnShutdown() override; private: // Creates the UNIX socket on a worker pool and then processes its file // descriptor. static mojo::edk::ScopedPlatformHandle CreateSocket(); void OnSocketCreated(mojo::edk::ScopedPlatformHandle fd); // DBus callback for StartArcInstance(). void OnInstanceStarted(mojo::edk::ScopedPlatformHandle socket_fd, StartArcInstanceResult result); // Synchronously accepts a connection on |socket_fd| and then processes the // connected socket's file descriptor. static mojo::ScopedMessagePipeHandle ConnectMojo( mojo::edk::ScopedPlatformHandle socket_fd, base::ScopedFD cancel_fd); void OnMojoConnected(mojo::ScopedMessagePipeHandle server_pipe); // Request to stop ARC instance via DBus. void StopArcInstance(); // chromeos::SessionManagerClient::Observer: void ArcInstanceStopped(bool clean) override; // Completes the termination procedure. void OnStopped(ArcStopReason reason); // Checks whether a function runs on the thread where the instance is // created. base::ThreadChecker thread_checker_; // Owned by ArcServiceManager. ArcBridgeService* const arc_bridge_service_; // Task runner to run a blocking tasks. scoped_refptr blocking_task_runner_; // The state of the session. State state_ = State::NOT_STARTED; // When Stop() is called, this flag is set. bool stop_requested_ = false; // In CONNECTING_MOJO state, this is set to the write side of the pipe // to notify cancelling of the procedure. base::ScopedFD accept_cancel_pipe_; // Mojo endpoint. std::unique_ptr arc_bridge_host_; // WeakPtrFactory to use callbacks. base::WeakPtrFactory weak_factory_; private: DISALLOW_COPY_AND_ASSIGN(ArcSessionImpl); }; ArcSessionImpl::ArcSessionImpl( ArcBridgeService* arc_bridge_service, const scoped_refptr& blocking_task_runner) : arc_bridge_service_(arc_bridge_service), blocking_task_runner_(blocking_task_runner), weak_factory_(this) { chromeos::SessionManagerClient* client = GetSessionManagerClient(); if (client == nullptr) return; client->AddObserver(this); } ArcSessionImpl::~ArcSessionImpl() { DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(state_ == State::NOT_STARTED || state_ == State::STOPPED); chromeos::SessionManagerClient* client = GetSessionManagerClient(); if (client == nullptr) return; client->RemoveObserver(this); } void ArcSessionImpl::Start() { DCHECK(thread_checker_.CalledOnValidThread()); DCHECK_EQ(state_, State::NOT_STARTED); VLOG(2) << "Starting ARC session."; VLOG(2) << "Creating socket..."; state_ = State::CREATING_SOCKET; base::PostTaskAndReplyWithResult( blocking_task_runner_.get(), FROM_HERE, base::Bind(&ArcSessionImpl::CreateSocket), base::Bind(&ArcSessionImpl::OnSocketCreated, weak_factory_.GetWeakPtr())); } // static mojo::edk::ScopedPlatformHandle ArcSessionImpl::CreateSocket() { base::FilePath socket_path(kArcBridgeSocketPath); mojo::edk::ScopedPlatformHandle socket_fd = mojo::edk::CreateServerHandle( mojo::edk::NamedPlatformHandle(socket_path.value())); if (!socket_fd.is_valid()) return socket_fd; // Change permissions on the socket. struct group arc_bridge_group; struct group* arc_bridge_group_res = nullptr; char buf[10000]; if (HANDLE_EINTR(getgrnam_r(kArcBridgeSocketGroup, &arc_bridge_group, buf, sizeof(buf), &arc_bridge_group_res)) < 0) { PLOG(ERROR) << "getgrnam_r"; return mojo::edk::ScopedPlatformHandle(); } if (!arc_bridge_group_res) { LOG(ERROR) << "Group '" << kArcBridgeSocketGroup << "' not found"; return mojo::edk::ScopedPlatformHandle(); } if (HANDLE_EINTR(chown(kArcBridgeSocketPath, -1, arc_bridge_group.gr_gid)) < 0) { PLOG(ERROR) << "chown"; return mojo::edk::ScopedPlatformHandle(); } if (!base::SetPosixFilePermissions(socket_path, 0660)) { PLOG(ERROR) << "Could not set permissions: " << socket_path.value(); return mojo::edk::ScopedPlatformHandle(); } return socket_fd; } void ArcSessionImpl::OnSocketCreated( mojo::edk::ScopedPlatformHandle socket_fd) { DCHECK(thread_checker_.CalledOnValidThread()); DCHECK_EQ(state_, State::CREATING_SOCKET); if (stop_requested_) { VLOG(1) << "Stop() called while connecting"; OnStopped(ArcStopReason::SHUTDOWN); return; } if (!socket_fd.is_valid()) { LOG(ERROR) << "ARC: Error creating socket"; OnStopped(ArcStopReason::GENERIC_BOOT_FAILURE); return; } VLOG(2) << "Socket is created. Starting ARC instance..."; state_ = State::STARTING_INSTANCE; user_manager::UserManager* user_manager = user_manager::UserManager::Get(); DCHECK(user_manager->GetPrimaryUser()); const cryptohome::Identification cryptohome_id( user_manager->GetPrimaryUser()->GetAccountId()); bool disable_boot_completed_broadcast = !base::FeatureList::IsEnabled(arc::kBootCompletedBroadcastFeature); chromeos::SessionManagerClient* session_manager_client = chromeos::DBusThreadManager::Get()->GetSessionManagerClient(); session_manager_client->StartArcInstance( cryptohome_id, disable_boot_completed_broadcast, base::Bind(&ArcSessionImpl::OnInstanceStarted, weak_factory_.GetWeakPtr(), base::Passed(&socket_fd))); } void ArcSessionImpl::OnInstanceStarted( mojo::edk::ScopedPlatformHandle socket_fd, StartArcInstanceResult result) { DCHECK(thread_checker_.CalledOnValidThread()); if (state_ == State::STOPPED) { // This is the case that error is notified via DBus before the // OnInstanceStarted() callback is invoked. The stopping procedure has // been run, so do nothing. return; } DCHECK_EQ(state_, State::STARTING_INSTANCE); if (stop_requested_) { if (result == StartArcInstanceResult::SUCCESS) { // The ARC instance has started to run. Request to stop. StopArcInstance(); return; } OnStopped(ArcStopReason::SHUTDOWN); return; } if (result != StartArcInstanceResult::SUCCESS) { LOG(ERROR) << "Failed to start ARC instance"; OnStopped(result == StartArcInstanceResult::LOW_FREE_DISK_SPACE ? ArcStopReason::LOW_DISK_SPACE : ArcStopReason::GENERIC_BOOT_FAILURE); return; } VLOG(2) << "ARC instance is successfully started. Connecting Mojo..."; state_ = State::CONNECTING_MOJO; // Prepare a pipe so that AcceptInstanceConnection can be interrupted on // Stop(). base::ScopedFD cancel_fd; if (!CreatePipe(&cancel_fd, &accept_cancel_pipe_)) { OnStopped(ArcStopReason::GENERIC_BOOT_FAILURE); return; } base::PostTaskAndReplyWithResult( blocking_task_runner_.get(), FROM_HERE, base::Bind(&ArcSessionImpl::ConnectMojo, base::Passed(&socket_fd), base::Passed(&cancel_fd)), base::Bind(&ArcSessionImpl::OnMojoConnected, weak_factory_.GetWeakPtr())); } // static mojo::ScopedMessagePipeHandle ArcSessionImpl::ConnectMojo( mojo::edk::ScopedPlatformHandle socket_fd, base::ScopedFD cancel_fd) { if (!WaitForSocketReadable(socket_fd.get().handle, cancel_fd.get())) { VLOG(1) << "Mojo connection was cancelled."; return mojo::ScopedMessagePipeHandle(); } mojo::edk::ScopedPlatformHandle scoped_fd; if (!mojo::edk::ServerAcceptConnection(socket_fd.get(), &scoped_fd, /* check_peer_user = */ false) || !scoped_fd.is_valid()) { return mojo::ScopedMessagePipeHandle(); } // Hardcode pid 0 since it is unused in mojo. const base::ProcessHandle kUnusedChildProcessHandle = 0; mojo::edk::PlatformChannelPair channel_pair; mojo::edk::PendingProcessConnection process; process.Connect(kUnusedChildProcessHandle, channel_pair.PassServerHandle()); mojo::edk::ScopedPlatformHandleVectorPtr handles( new mojo::edk::PlatformHandleVector{ channel_pair.PassClientHandle().release()}); std::string token; mojo::ScopedMessagePipeHandle pipe = process.CreateMessagePipe(&token); // We need to send the length of the message as a single byte, so make sure it // fits. DCHECK_LT(token.size(), 256u); uint8_t message_length = static_cast(token.size()); struct iovec iov[] = {{&message_length, sizeof(message_length)}, {const_cast(token.c_str()), token.size()}}; ssize_t result = mojo::edk::PlatformChannelSendmsgWithHandles( scoped_fd.get(), iov, sizeof(iov) / sizeof(iov[0]), handles->data(), handles->size()); if (result == -1) { PLOG(ERROR) << "sendmsg"; return mojo::ScopedMessagePipeHandle(); } return pipe; } void ArcSessionImpl::OnMojoConnected( mojo::ScopedMessagePipeHandle server_pipe) { DCHECK(thread_checker_.CalledOnValidThread()); if (state_ == State::STOPPED) { // This is the case that error is notified via DBus before the // OnMojoConnected() callback is invoked. The stopping procedure has // been run, so do nothing. return; } DCHECK_EQ(state_, State::CONNECTING_MOJO); accept_cancel_pipe_.reset(); if (stop_requested_) { StopArcInstance(); return; } if (!server_pipe.is_valid()) { LOG(ERROR) << "Invalid pipe"; StopArcInstance(); return; } mojom::ArcBridgeInstancePtr instance; instance.Bind(mojo::InterfacePtrInfo( std::move(server_pipe), 0u)); arc_bridge_host_ = base::MakeUnique(arc_bridge_service_, std::move(instance)); VLOG(2) << "Mojo is connected. ARC is running."; state_ = State::RUNNING; for (auto& observer : observer_list_) observer.OnSessionReady(); } void ArcSessionImpl::Stop() { DCHECK(thread_checker_.CalledOnValidThread()); VLOG(2) << "Stopping ARC session is requested."; // For second time or later, just do nothing. // It is already in the stopping phase. if (stop_requested_) return; stop_requested_ = true; arc_bridge_host_.reset(); switch (state_) { case State::NOT_STARTED: OnStopped(ArcStopReason::SHUTDOWN); return; case State::CREATING_SOCKET: case State::STARTING_INSTANCE: // Before starting the ARC instance, we do nothing here. // At some point, a callback will be invoked on UI thread, // and stopping procedure will be run there. // On Chrome shutdown, it is not the case because the message loop is // already stopped here. Practically, it is not a problem because; // - On socket creating, it is ok to simply ignore such cases, // because we no-longer continue the bootstrap procedure. // - On starting instance, the container instance can be leaked. // Practically it is not problematic because the session manager will // clean it up. return; case State::CONNECTING_MOJO: // Mojo connection is being waited on a BlockingPool thread. // Request to cancel it. Following stopping procedure will run // in its callback. accept_cancel_pipe_.reset(); return; case State::RUNNING: // Now ARC instance is running. Request to stop it. StopArcInstance(); return; case State::STOPPED: // The instance is already stopped. Do nothing. return; } } void ArcSessionImpl::StopArcInstance() { DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(state_ == State::STARTING_INSTANCE || state_ == State::CONNECTING_MOJO || state_ == State::RUNNING); // Notification will arrive through ArcInstanceStopped(). VLOG(2) << "Requesting to stop ARC instance"; chromeos::SessionManagerClient* session_manager_client = chromeos::DBusThreadManager::Get()->GetSessionManagerClient(); session_manager_client->StopArcInstance( base::Bind(&DoNothingInstanceStopped)); } void ArcSessionImpl::ArcInstanceStopped(bool clean) { DCHECK(thread_checker_.CalledOnValidThread()); VLOG(1) << "Notified that ARC instance is stopped " << (clean ? "cleanly" : "uncleanly"); // In case that crash happens during before the Mojo channel is connected, // unlock the BlockingPool thread. accept_cancel_pipe_.reset(); ArcStopReason reason; if (stop_requested_) { // If the ARC instance is stopped after its explicit request, // return SHUTDOWN. reason = ArcStopReason::SHUTDOWN; } else if (clean) { // If the ARC instance is stopped, but it is not explicitly requested, // then this is triggered by some failure during the starting procedure. // Return GENERIC_BOOT_FAILURE for the case. reason = ArcStopReason::GENERIC_BOOT_FAILURE; } else { // Otherwise, this is caused by CRASH occured inside of the ARC instance. reason = ArcStopReason::CRASH; } OnStopped(reason); } void ArcSessionImpl::OnStopped(ArcStopReason reason) { DCHECK(thread_checker_.CalledOnValidThread()); // OnStopped() should be called once per instance. DCHECK_NE(state_, State::STOPPED); VLOG(2) << "ARC session is stopped."; arc_bridge_host_.reset(); state_ = State::STOPPED; for (auto& observer : observer_list_) observer.OnSessionStopped(reason); } void ArcSessionImpl::OnShutdown() { DCHECK(thread_checker_.CalledOnValidThread()); stop_requested_ = true; if (state_ == State::STOPPED) return; // Here, the message loop is already stopped, and the Chrome will be soon // shutdown. Thus, it is not necessary to take care about restarting case. // If ArcSession is waiting for mojo connection, cancels it. The BlockingPool // will be joined later. accept_cancel_pipe_.reset(); // Stops the ARC instance to let it graceful shutdown. // Note that this may fail if ARC container is not actually running, but // ignore an error as described below. if (state_ == State::STARTING_INSTANCE || state_ == State::CONNECTING_MOJO || state_ == State::RUNNING) StopArcInstance(); // Directly set to the STOPPED stateby OnStopped(). Note that calling // StopArcInstance() may not work well. At least, because the UI thread is // already stopped here, ArcInstanceStopped() callback cannot be invoked. OnStopped(ArcStopReason::SHUTDOWN); } } // namespace ArcSession::ArcSession() = default; ArcSession::~ArcSession() = default; void ArcSession::AddObserver(Observer* observer) { observer_list_.AddObserver(observer); } void ArcSession::RemoveObserver(Observer* observer) { observer_list_.RemoveObserver(observer); } // static std::unique_ptr ArcSession::Create( ArcBridgeService* arc_bridge_service, const scoped_refptr& blocking_task_runner) { return base::MakeUnique(arc_bridge_service, blocking_task_runner); } } // namespace arc