// 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 "ash/components/arc/session/mojo_invitation_manager.h"
#include <string>
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "base/process/process.h"
#include "base/rand_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "mojo/core/embedder/embedder.h"
#include "mojo/proxy/switches.h"
#include "mojo/public/cpp/platform/platform_channel.h"
#include "mojo/public/cpp/system/invitation.h"
#include "mojo/public/cpp/system/message_pipe.h"
namespace arc {
namespace {
std::string GenerateRandomToken() {
uint8_t random_bytes[16];
base::RandBytes(random_bytes);
return base::HexEncode(random_bytes);
}
base::FilePath GetMojoProxyPath() {
base::FilePath mojo_proxy_path;
CHECK(base::PathService::Get(base::DIR_EXE, &mojo_proxy_path));
return mojo_proxy_path.Append("mojo_proxy");
}
} // namespace
MojoInvitationManager::MojoInvitationManager()
: MojoInvitationManager(GetMojoProxyPath()) {}
// Generate an arbitrary 32-byte string. ARC uses this length as a protocol
// version identifier.
MojoInvitationManager::MojoInvitationManager(const base::FilePath& proxy_path)
: proxy_path_(proxy_path), token_(GenerateRandomToken()) {}
MojoInvitationManager::~MojoInvitationManager() {
if (proxy_process_.IsValid()) {
// Ensure proxy process is terminated.
base::ThreadPool::PostTask(
FROM_HERE,
{base::WithBaseSyncPrimitives(),
base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
base::BindOnce(&CollectMojoProxyProcess, std::move(proxy_process_)));
}
}
base::Process MojoInvitationManager::LaunchMojoProxy(
mojo::PlatformChannel& channel,
mojo::PlatformChannel& proxy_channel) {
base::ScopedFD target_fd =
channel.TakeLocalEndpoint().TakePlatformHandle().TakeFD();
auto proxy_remote_endpoint = proxy_channel.TakeRemoteEndpoint();
base::ScopedFD proxy_fd = proxy_remote_endpoint.TakePlatformHandle().TakeFD();
constexpr int kLegacyClientFdValue = STDERR_FILENO + 1;
constexpr int kHostIpczTransportFdValue = kLegacyClientFdValue + 1;
base::LaunchOptions proxy_launch_options;
proxy_launch_options.fds_to_remap.emplace_back(target_fd.get(),
kLegacyClientFdValue);
proxy_launch_options.fds_to_remap.emplace_back(proxy_fd.get(),
kHostIpczTransportFdValue);
base::CommandLine proxy_command_line(proxy_path_);
proxy_command_line.AppendSwitchASCII(
switches::kLegacyClientFd, base::NumberToString(kLegacyClientFdValue));
proxy_command_line.AppendSwitchASCII(
switches::kHostIpczTransportFd,
base::NumberToString(kHostIpczTransportFdValue));
proxy_command_line.AppendSwitchASCII(switches::kAttachmentName, token_);
proxy_command_line.AppendSwitch(switches::kInheritIpczBroker);
base::Process proxy_process =
base::LaunchProcess(proxy_command_line, proxy_launch_options);
proxy_remote_endpoint.ProcessLaunchAttempted();
return proxy_process;
}
void MojoInvitationManager::SendInvitation(mojo::PlatformChannel& channel) {
mojo::OutgoingInvitation invitation;
pipe_ = invitation.AttachMessagePipe(token_);
if (mojo::core::IsMojoIpczEnabled()) {
// ARCVM containers still use legacy Mojo Core. If IPCZ is enabled, we
// spawn an instance of Mojo Proxy which acts as a IPCZ <=> Mojo Core
// translation layer between Ash Chrome and ARCVM.
mojo::PlatformChannel proxy_channel;
proxy_process_ = LaunchMojoProxy(channel, proxy_channel);
DCHECK(proxy_process_.IsValid());
invitation.set_extra_flags(MOJO_SEND_INVITATION_FLAG_SHARE_BROKER);
mojo::OutgoingInvitation::Send(std::move(invitation),
proxy_process_.Handle(),
proxy_channel.TakeLocalEndpoint());
} else {
mojo::OutgoingInvitation::Send(std::move(invitation),
base::kNullProcessHandle,
channel.TakeLocalEndpoint());
}
}
// static
void MojoInvitationManager::CollectMojoProxyProcess(
base::Process proxy_process) {
// Wait for some time until the proxy process terminates.
// In the common case, Mojo Proxy will exit cleanly once
// all portals are closed.
constexpr base::TimeDelta timeout = base::Milliseconds(2500);
if (proxy_process.WaitForExitWithTimeout(timeout, nullptr)) {
return;
}
// Otherwise, terminate the process forcefully.
bool success = proxy_process.Terminate(/*exit_code=*/0, /*wait=*/true);
LOG_IF(ERROR, !success) << "Failed to terminate Mojo Proxy process";
}
} // namespace arc