| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "mojo/public/cpp/platform/platform_channel_endpoint.h" |
| |
| #include <string> |
| #include <string_view> |
| #include <tuple> |
| #include <utility> |
| |
| #include "base/command_line.h" |
| #include "base/logging.h" |
| #include "base/rand_util.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "build/build_config.h" |
| #include "mojo/public/cpp/platform/platform_channel.h" |
| |
| #if BUILDFLAG(MOJO_USE_APPLE_CHANNEL) |
| #include <mach/port.h> |
| |
| #include "base/apple/mach_port_rendezvous.h" |
| #include "base/apple/scoped_mach_port.h" |
| #elif BUILDFLAG(IS_FUCHSIA) |
| #include <lib/zx/handle.h> |
| #elif BUILDFLAG(IS_POSIX) |
| #include "base/files/scoped_file.h" |
| #include "base/posix/global_descriptors.h" |
| #elif BUILDFLAG(IS_WIN) |
| #include <windows.h> |
| |
| #include "base/win/scoped_handle.h" |
| #endif |
| |
| namespace mojo { |
| |
| namespace { |
| |
| #if BUILDFLAG(IS_ANDROID) |
| // Leave room for any other descriptors defined in content for example. |
| // TODO(crbug.com/40499227): Consider changing base::GlobalDescriptors to |
| // generate a key when setting the file descriptor. |
| constexpr int kAndroidClientHandleDescriptor = |
| base::GlobalDescriptors::kBaseDescriptor + 10000; |
| #elif BUILDFLAG(IS_POSIX) && !BUILDFLAG(MOJO_USE_APPLE_CHANNEL) |
| bool IsTargetDescriptorUsed(const base::FileHandleMappingVector& mapping, |
| int target_fd) { |
| for (auto& [i, fd] : mapping) { |
| if (fd == target_fd) |
| return true; |
| } |
| return false; |
| } |
| #endif |
| |
| } // namespace |
| |
| PlatformChannelEndpoint::PlatformChannelEndpoint() = default; |
| |
| PlatformChannelEndpoint::PlatformChannelEndpoint( |
| PlatformChannelEndpoint&& other) = default; |
| |
| PlatformChannelEndpoint::PlatformChannelEndpoint(PlatformHandle handle) |
| : handle_(std::move(handle)) {} |
| |
| PlatformChannelEndpoint::~PlatformChannelEndpoint() = default; |
| |
| PlatformChannelEndpoint& PlatformChannelEndpoint::operator=( |
| PlatformChannelEndpoint&& other) = default; |
| |
| void PlatformChannelEndpoint::reset() { |
| handle_.reset(); |
| } |
| |
| PlatformChannelEndpoint PlatformChannelEndpoint::Clone() const { |
| return PlatformChannelEndpoint(handle_.Clone()); |
| } |
| |
| void PlatformChannelEndpoint::PrepareToPass(HandlePassingInfo& info, |
| base::CommandLine& command_line) { |
| std::string value; |
| PrepareToPass(info, value); |
| if (!value.empty()) { |
| command_line.AppendSwitchASCII(PlatformChannel::kHandleSwitch, value); |
| } |
| } |
| |
| void PlatformChannelEndpoint::PrepareToPass(HandlePassingInfo& info, |
| std::string& value) { |
| DCHECK(is_valid()); |
| #if BUILDFLAG(IS_WIN) |
| info.push_back(platform_handle().GetHandle().Get()); |
| value = |
| base::NumberToString(HandleToLong(platform_handle().GetHandle().Get())); |
| #elif BUILDFLAG(IS_FUCHSIA) |
| const uint32_t id = base::LaunchOptions::AddHandleToTransfer( |
| &info, platform_handle().GetHandle().get()); |
| value = base::NumberToString(id); |
| #elif BUILDFLAG(IS_ANDROID) |
| int fd = platform_handle().GetFD().get(); |
| int mapped_fd = kAndroidClientHandleDescriptor + info.size(); |
| info.emplace_back(fd, mapped_fd); |
| value = base::NumberToString(mapped_fd); |
| #elif BUILDFLAG(MOJO_USE_APPLE_CHANNEL) |
| DCHECK(platform_handle().is_mach_receive()); |
| base::apple::ScopedMachReceiveRight receive_right = |
| TakePlatformHandle().TakeMachReceiveRight(); |
| base::MachPortsForRendezvous::key_type rendezvous_key = 0; |
| do { |
| rendezvous_key = static_cast<decltype(rendezvous_key)>(base::RandUint64()); |
| } while (info.find(rendezvous_key) != info.end()); |
| auto it = info.insert(std::make_pair( |
| rendezvous_key, base::MachRendezvousPort(std::move(receive_right)))); |
| DCHECK(it.second) << "Failed to insert port for rendezvous."; |
| value = base::NumberToString(rendezvous_key); |
| #elif BUILDFLAG(IS_POSIX) |
| // Arbitrary sanity check to ensure the loop below terminates reasonably |
| // quickly. |
| CHECK_LT(info.size(), 1000u); |
| |
| // Find a suitable FD to map the remote endpoint handle to in the child |
| // process. This has quadratic time complexity in the size of |*info|, but |
| // |*info| should be very small and is usually empty. |
| int target_fd = base::GlobalDescriptors::kBaseDescriptor; |
| while (IsTargetDescriptorUsed(info, target_fd)) { |
| ++target_fd; |
| } |
| info.emplace_back(platform_handle().GetFD().get(), target_fd); |
| value = base::NumberToString(target_fd); |
| #endif |
| } |
| |
| void PlatformChannelEndpoint::PrepareToPass(base::LaunchOptions& options, |
| base::CommandLine& command_line) { |
| const std::string value = PrepareToPass(options); |
| if (!value.empty()) { |
| command_line.AppendSwitchASCII(PlatformChannel::kHandleSwitch, value); |
| } |
| } |
| |
| std::string PlatformChannelEndpoint::PrepareToPass( |
| base::LaunchOptions& options) { |
| std::string value; |
| #if BUILDFLAG(IS_WIN) |
| PrepareToPass(options.handles_to_inherit, value); |
| #elif BUILDFLAG(IS_FUCHSIA) |
| PrepareToPass(options.handles_to_transfer, value); |
| #elif BUILDFLAG(MOJO_USE_APPLE_CHANNEL) |
| PrepareToPass(options.mach_ports_for_rendezvous, value); |
| #elif BUILDFLAG(IS_POSIX) |
| PrepareToPass(options.fds_to_remap, value); |
| #else |
| #error "Platform not supported." |
| #endif |
| return value; |
| } |
| |
| void PlatformChannelEndpoint::ProcessLaunchAttempted() { |
| #if BUILDFLAG(IS_FUCHSIA) |
| // Unlike other platforms, Fuchsia transfers handle ownership to the new |
| // process, rather than duplicating it. For consistency the process-launch |
| // call will have consumed the handle regardless of whether launch succeeded. |
| DCHECK(platform_handle().is_valid_handle()); |
| std::ignore = TakePlatformHandle().ReleaseHandle(); |
| #else |
| reset(); |
| #endif |
| } |
| |
| // static |
| PlatformChannelEndpoint PlatformChannelEndpoint::RecoverFromString( |
| std::string_view value) { |
| #if BUILDFLAG(IS_WIN) |
| int handle_value = 0; |
| if (value.empty() || !base::StringToInt(value, &handle_value)) { |
| DLOG(ERROR) << "Invalid PlatformChannel endpoint string."; |
| return PlatformChannelEndpoint(); |
| } |
| return PlatformChannelEndpoint( |
| PlatformHandle(base::win::ScopedHandle(LongToHandle(handle_value)))); |
| #elif BUILDFLAG(IS_FUCHSIA) |
| unsigned int handle_value = 0; |
| if (value.empty() || !base::StringToUint(value, &handle_value)) { |
| DLOG(ERROR) << "Invalid PlatformChannel endpoint string."; |
| return PlatformChannelEndpoint(); |
| } |
| return PlatformChannelEndpoint(PlatformHandle(zx::handle( |
| zx_take_startup_handle(base::checked_cast<uint32_t>(handle_value))))); |
| #elif BUILDFLAG(IS_ANDROID) |
| base::GlobalDescriptors::Key key = -1; |
| if (value.empty() || !base::StringToUint(value, &key)) { |
| DLOG(ERROR) << "Invalid PlatformChannel endpoint string."; |
| return PlatformChannelEndpoint(); |
| } |
| return PlatformChannelEndpoint(PlatformHandle( |
| base::ScopedFD(base::GlobalDescriptors::GetInstance()->Get(key)))); |
| #elif BUILDFLAG(MOJO_USE_APPLE_CHANNEL) |
| auto* client = base::MachPortRendezvousClient::GetInstance(); |
| if (!client) { |
| DLOG(ERROR) << "Mach rendezvous failed."; |
| return PlatformChannelEndpoint(); |
| } |
| uint32_t rendezvous_key = 0; |
| if (value.empty() || !base::StringToUint(value, &rendezvous_key)) { |
| DLOG(ERROR) << "Invalid PlatformChannel rendezvous key."; |
| return PlatformChannelEndpoint(); |
| } |
| auto receive = client->TakeReceiveRight(rendezvous_key); |
| if (!receive.is_valid()) { |
| DLOG(ERROR) << "Invalid PlatformChannel receive right."; |
| return PlatformChannelEndpoint(); |
| } |
| return PlatformChannelEndpoint(PlatformHandle(std::move(receive))); |
| #elif BUILDFLAG(IS_POSIX) |
| int fd = -1; |
| if (value.empty() || !base::StringToInt(value, &fd) || |
| fd < base::GlobalDescriptors::kBaseDescriptor) { |
| DLOG(ERROR) << "Invalid PlatformChannel endpoint string."; |
| return PlatformChannelEndpoint(); |
| } |
| return PlatformChannelEndpoint(PlatformHandle(base::ScopedFD(fd))); |
| #endif |
| } |
| |
| } // namespace mojo |