blob: 6e20be8504ed5fb67639629326da616bbfcf6fea [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "remoting/host/mac/agent_process_broker.h"
#include <mach/message.h>
#include <stddef.h>
#include <sys/sysctl.h>
#include <algorithm>
#include <memory>
#include <ranges>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/process/process_handle.h"
#include "base/sequence_checker.h"
#include "base/strings/stringprintf.h"
#include "components/named_mojo_ipc_server/connection_info.h"
#include "components/named_mojo_ipc_server/endpoint_options.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "remoting/base/logging.h"
#include "remoting/host/ipc_constants.h"
#include "remoting/host/mac/agent_process_broker_constants.h"
#include "remoting/host/mojo_caller_security_checker.h"
#include "remoting/host/mojom/agent_process_broker.mojom.h"
#include "remoting/host/mojom/remoting_host.mojom.h"
namespace remoting {
namespace {
bool IsRootProcess(audit_token_t audit_token) {
return audit_token_to_ruid(audit_token) == 0;
}
} // namespace
AgentProcessBroker::AgentProcess::AgentProcess(
size_t reference_id,
base::ProcessId pid,
mojo::Remote<mojom::AgentProcess> agent_process_remote,
mojo::Remote<mojom::RemotingHostControl> remoting_host_control_remote,
bool is_root,
bool is_active)
: reference_id(reference_id),
pid(pid),
agent_process_remote(std::move(agent_process_remote)),
remoting_host_control_remote(std::move(remoting_host_control_remote)),
is_root(is_root),
is_active(is_active) {}
AgentProcessBroker::AgentProcess::AgentProcess(AgentProcess&&) = default;
AgentProcessBroker::AgentProcess::~AgentProcess() = default;
AgentProcessBroker::AgentProcess& AgentProcessBroker::AgentProcess::operator=(
AgentProcess&&) = default;
void AgentProcessBroker::AgentProcess::ResumeProcess() {
if (is_active) {
return;
}
agent_process_remote->ResumeProcess();
is_active = true;
HOST_LOG << GetAgentProcessLogString("resumed");
}
void AgentProcessBroker::AgentProcess::SuspendProcess() {
if (!is_active) {
return;
}
agent_process_remote->SuspendProcess();
is_active = false;
HOST_LOG << GetAgentProcessLogString("suspended");
}
void AgentProcessBroker::AgentProcess::TerminateProcess() {
agent_process_remote.ResetWithReason(
kTerminateAgentProcessBrokerReason,
"Agent process requested to be terminated by the broker.");
HOST_LOG << GetAgentProcessLogString("terminated");
}
std::string AgentProcessBroker::AgentProcess::GetAgentProcessLogString(
std::string_view state) const {
return base::StringPrintf("Agent process %d (PID: %d, %s) %s", reference_id,
pid, is_root ? "root" : "user", state.data());
}
AgentProcessBroker::AgentProcessBroker()
: AgentProcessBroker(GetAgentProcessBrokerServerName(),
base::BindRepeating(IsTrustedMojoEndpoint),
base::BindRepeating(IsRootProcess)) {}
AgentProcessBroker::AgentProcessBroker(
const mojo::NamedPlatformChannel::ServerName& server_name,
Validator validator,
IsRootProcessGetter is_root_process)
: server_(named_mojo_ipc_server::EndpointOptions(
server_name,
kAgentProcessBrokerMessagePipeId),
validator.Then(base::BindRepeating(
[](mojom::AgentProcessBroker* interface, bool is_valid) {
return is_valid ? interface : nullptr;
},
this))),
is_root_process_(std::move(is_root_process)) {
chromoting_host_services_server_ =
std::make_unique<ChromotingHostServicesServer>(
base::BindRepeating(&AgentProcessBroker::BindChromotingHostServices,
base::Unretained(this)));
}
AgentProcessBroker::~AgentProcessBroker() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void AgentProcessBroker::Start() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
server_.StartServer();
chromoting_host_services_server_->StartServer();
HOST_LOG << "Agent process broker has started.";
}
void AgentProcessBroker::OnAgentProcessLaunched(
mojo::PendingRemote<mojom::AgentProcess> pending_agent_process) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto& connection_info = server_.current_connection_info();
bool is_root = is_root_process_.Run(connection_info.audit_token);
mojo::Remote<mojom::AgentProcess> process_remote{
std::move(pending_agent_process)};
process_remote.set_disconnect_handler(
base::BindOnce(&AgentProcessBroker::OnAgentProcessDisconnected,
base::Unretained(this), next_reference_id_));
mojo::Remote<mojom::RemotingHostControl> remoting_host_control_remote;
process_remote->BindRemotingHostControl(
remoting_host_control_remote.BindNewPipeAndPassReceiver());
auto result = agent_processes_.emplace(
next_reference_id_,
AgentProcess{next_reference_id_, connection_info.pid,
std::move(process_remote),
std::move(remoting_host_control_remote), is_root,
/* is_active= */ false});
DCHECK(result.second); // Assert success.
HOST_LOG << result.first->second.GetAgentProcessLogString("launched");
next_reference_id_++;
BrokerAgentProcesses();
if (on_agent_process_launched_) {
std::move(on_agent_process_launched_).Run();
}
}
void AgentProcessBroker::BindChromotingHostServices(
mojo::PendingReceiver<mojom::ChromotingHostServices> receiver,
base::ProcessId peer_pid) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto active_iter = std::ranges::find_if(
agent_processes_,
[](const auto& process) { return process.second.is_active; });
if (active_iter == std::ranges::end(agent_processes_)) {
LOG(WARNING) << "Binding rejected. No active agent process is found.";
return;
}
AgentProcess& process = active_iter->second;
process.remoting_host_control_remote->BindChromotingHostServices(
std::move(receiver), peer_pid);
HOST_LOG << process.GetAgentProcessLogString(base::StringPrintf(
"bound ChromotingHostServices for peer PID %d", peer_pid));
}
void AgentProcessBroker::OnAgentProcessDisconnected(size_t reference_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto it = agent_processes_.find(reference_id);
if (it != agent_processes_.end()) {
HOST_LOG << it->second.GetAgentProcessLogString("disconnected");
agent_processes_.erase(it);
BrokerAgentProcesses();
} else {
LOG(WARNING) << "Agent process ID " << reference_id << " not found.";
}
}
void AgentProcessBroker::BrokerAgentProcesses() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Goal: There can be up to one root process and one user process running.
// When both the root process and the user process are running, the root
// process must be suspended to give way to the user process.
std::vector<AgentProcess*> root_processes;
std::vector<AgentProcess*> user_processes;
for (auto& pair : agent_processes_) {
if (pair.second.is_root) {
root_processes.push_back(&pair.second);
} else {
user_processes.push_back(&pair.second);
}
}
TrimProcessList(root_processes);
TrimProcessList(user_processes);
if (!user_processes.empty()) {
// Suspend the root process if it's running, then resume the user process,
// since the latter has higher priority.
if (!root_processes.empty()) {
root_processes.front()->SuspendProcess();
}
user_processes.front()->ResumeProcess();
return;
}
if (!root_processes.empty()) {
// There are no user processes, so resume the root process.
root_processes.front()->ResumeProcess();
}
}
void AgentProcessBroker::TrimProcessList(
std::vector<AgentProcess*>& processes) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (processes.size() < 2) {
return;
}
// Sort processes. Active processes come first so that they are the last to be
// removed.
std::sort(processes.begin(), processes.end(),
[](const AgentProcess* p1, const AgentProcess* p2) {
return p1->is_active && !p2->is_active;
});
while (processes.size() > 1) {
AgentProcess* process = processes.back();
process->TerminateProcess();
// Note: this will invalidate the storage that `processes.back()` references
// to.
agent_processes_.erase(process->reference_id);
processes.pop_back();
}
}
} // namespace remoting