chromium/mojo/proxy/main.cc

// 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 <string>
#include <utility>
#include <vector>

#include "base/at_exit.h"
#include "base/check.h"
#include "base/command_line.h"
#include "base/files/scoped_file.h"
#include "base/logging.h"
#include "base/message_loop/message_pump_type.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/task/single_thread_task_executor.h"
#include "mojo/core/embedder/embedder.h"
#include "mojo/core/embedder/scoped_ipc_support.h"
#include "mojo/core/ipcz_api.h"
#include "mojo/core/ipcz_driver/transport.h"
#include "mojo/core/scoped_ipcz_handle.h"
#include "mojo/proxy/node_proxy.h"
#include "mojo/proxy/portal_proxy.h"
#include "mojo/proxy/switches.h"
#include "mojo/public/cpp/platform/platform_channel_endpoint.h"
#include "mojo/public/cpp/platform/platform_handle.h"
#include "mojo/public/cpp/system/invitation.h"
#include "third_party/ipcz/include/ipcz/ipcz.h"

namespace mojo_proxy {

void RunProxy(int argc, char** argv) {
  CHECK(base::CommandLine::Init(argc, argv));
  base::AtExitManager at_exit;
  logging::InitLogging({});
  logging::SetLogItems(true, true, true, true);

  base::SingleThreadTaskExecutor io_task_executor(base::MessagePumpType::IO);

  // We initialize Mojo with ipcz disabled, since pre-ipcz Mojo Core only works
  // as a process-wide singleton. This means that all Mojo C APIs in this
  // process are wired to the old Mojo implementation and are therefore usable
  // to interface (exclusively) with the proxy's legacy client.
  //
  // We always operate as a broker on the legacy side based on the assumption
  // that all legacy clients are non-brokers. We're the only node the legacy
  // client communicates with.
  mojo::core::Configuration mojo_config;
  mojo_config.is_broker_process = true;
  mojo_config.disable_ipcz = true;
  mojo::core::Init(mojo_config);
  at_exit.RegisterTask(base::BindOnce(&mojo::core::ShutDown));
  auto ipc_support = std::make_unique<mojo::core::ScopedIPCSupport>(
      io_task_executor.task_runner(),
      mojo::core::ScopedIPCSupport::ShutdownPolicy::CLEAN);

  // Also initialize the global MojoIpcz node, but don't re-initialize Mojo
  // Core. Mojo C APIs therefore still point to the old Mojo implementation, and
  // any interaction with the MojoIpcz side of the proxy must be done direct
  // calls into either ipcz or the MojoIpcz driver.
  //
  // On the ipcz side we're a non-broker, based on the assumption that either
  // our ipcz client is a broker or (if --inherit-ipcz-broker is given) we can
  // inherit a broker from them.
  mojo::core::IpczNodeOptions ipcz_options{
      .is_broker = false,
      .use_local_shared_memory_allocation = true,
  };
  CHECK(mojo::core::InitializeIpczNodeForProcess(ipcz_options));
  const IpczHandle ipcz_node = mojo::core::GetIpczNode();

  int fd;
  const auto& command_line = *base::CommandLine::ForCurrentProcess();
  CHECK(base::StringToInt(
      command_line.GetSwitchValueASCII(switches::kLegacyClientFd), &fd));
  mojo::PlatformChannelEndpoint legacy_endpoint{
      mojo::PlatformHandle{base::ScopedFD{fd}}};
  CHECK(base::StringToInt(
      command_line.GetSwitchValueASCII(switches::kHostIpczTransportFd), &fd));
  mojo::PlatformChannelEndpoint ipcz_endpoint{
      mojo::PlatformHandle{base::ScopedFD{fd}}};

  // Some Mojo clients use free-form strings for attachment names, and some use
  // 64-bit integral, zero-based values. In general only the latter cases attach
  // multiple pipes to a single invitation. The chosen scheme influences how
  // MojoIpcz (and therefore how this proxy) maps attachment names from Mojo
  // APIs to an index into the portal array filled im by ipcz ConnectNode().
  std::vector<std::string> attachment_names;
  if (command_line.HasSwitch(switches::kAttachmentName)) {
    attachment_names.push_back(
        command_line.GetSwitchValueASCII(switches::kAttachmentName));
  } else if (command_line.HasSwitch(switches::kNumAttachments)) {
    uint64_t num_unnamed_attachments;
    CHECK(base::StringToUint64(
        command_line.GetSwitchValueASCII(switches::kNumAttachments),
        &num_unnamed_attachments));
    for (uint64_t i = 0; i < num_unnamed_attachments; ++i) {
      attachment_names.emplace_back(reinterpret_cast<const char*>(&i),
                                    sizeof(i));
    }
  }

  // Create an appropriate ipcz transport to connect back to the host.
  using Transport = mojo::core::ipcz_driver::Transport;
  const bool inherit_ipcz_broker =
      command_line.HasSwitch(switches::kInheritIpczBroker);
  const Transport::EndpointType ipcz_client_type =
      inherit_ipcz_broker ? Transport::kNonBroker : Transport::kBroker;
  auto ipcz_transport = Transport::Create(
      {.source = Transport::kNonBroker, .destination = ipcz_client_type},
      std::move(ipcz_endpoint), base::Process{});

  // Portal 0 is reserved (see below). The portals corresponding to invitation
  // attachments span indices [1, N].
  const IpczAPI& ipcz = mojo::core::GetIpczAPI();
  std::vector<IpczHandle> initial_portals(attachment_names.size() + 1);
  const IpczConnectNodeFlags connect_flags =
      inherit_ipcz_broker ? IPCZ_CONNECT_NODE_INHERIT_BROKER
                          : IPCZ_CONNECT_NODE_TO_BROKER;
  const IpczResult connect_result = ipcz.ConnectNode(
      ipcz_node, Transport::ReleaseAsHandle(std::move(ipcz_transport)),
      initial_portals.size(), connect_flags, nullptr, initial_portals.data());
  CHECK_EQ(IPCZ_RESULT_OK, connect_result);

  // Portal 0 is bound on the other end to an internal shared memory allocation
  // service by MojoIpcz. We don't need it.
  ipcz.Close(initial_portals[0], IPCZ_NO_FLAGS, nullptr);

  // Seed the server with proxies between each of the attached pipes on the
  // legacy invitation and their corresponding initial portals from the host
  // connection.
  base::RunLoop run_loop;
  NodeProxy proxy(ipcz, /*dead_callback=*/run_loop.QuitClosure());
  mojo::OutgoingInvitation invitation;
  for (size_t i = 0; i < attachment_names.size(); ++i) {
    proxy.AddPortalProxy(mojo::core::ScopedIpczHandle(initial_portals[i + 1]),
                         invitation.AttachMessagePipe(attachment_names[i]));
  }

  // After sending the legacy invitation, we wait until all proxies are dead.
  mojo::OutgoingInvitation::Send(std::move(invitation),
                                 base::kNullProcessHandle,
                                 std::move(legacy_endpoint));
  run_loop.Run();

  mojo::core::DestroyIpczNodeForProcess();
  ipc_support.reset();
}

}  // namespace mojo_proxy

int main(int argc, char** argv) {
  mojo_proxy::RunProxy(argc, argv);
  return 0;
}