Bug 1881636 - Part 3: Implement IosProcessLauncher::DoLaunch. r=glandium,ipc-reviewers,jld

This uses BrowserEngineKit's ExtensionKit-based processes to start content
processes on iOS. These processes are started with an initial xpc connection,
which is then used to communicate a command line, initial file descriptors
and environment variables before invoking content_process_main.

The XPC connection is not used further after the bootstrap message, which seems
to roughly match how WebKit uses these APIs.

Differential Revision: https://phabricator.services.mozilla.com/D202525
This commit is contained in:
Nika Layzell 2024-03-26 22:56:27 +00:00
Родитель 1848f66b3b
Коммит ec4f15a4eb
9 изменённых файлов: 411 добавлений и 23 удалений

Просмотреть файл

@ -0,0 +1,80 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_ipc_ExtensionKitUtils_h
#define mozilla_ipc_ExtensionKitUtils_h
#include <functional>
#include <xpc/xpc.h>
#include "mozilla/DarwinObjectPtr.h"
#include "mozilla/Result.h"
#include "mozilla/ResultVariant.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/ipc/LaunchError.h"
namespace mozilla::ipc {
class BEProcessCapabilityGrantDeleter {
public:
void operator()(void* aGrant) const;
};
using UniqueBEProcessCapabilityGrant =
mozilla::UniquePtr<void, BEProcessCapabilityGrantDeleter>;
class ExtensionKitProcess {
public:
enum class Kind {
WebContent,
Networking,
Rendering,
};
// Called to start the process. The `aCompletion` function may be executed on
// a background libdispatch thread.
static void StartProcess(
Kind aKind,
const std::function<void(Result<ExtensionKitProcess, LaunchError>&&)>&
aCompletion);
// Get the kind of process being started.
Kind GetKind() const { return mKind; }
// Make an xpc_connection_t to this process. If an error is encountered,
// `aError` will be populated with the error.
//
// Ownership over the newly created connection is returned to the caller.
// The connection is returned in a suspended state, and must be resumed.
DarwinObjectPtr<xpc_connection_t> MakeLibXPCConnection();
UniqueBEProcessCapabilityGrant GrantForegroundCapability();
// Invalidate the process, indicating that it should be cleaned up &
// destroyed.
void Invalidate();
// Explicit copy constructors
ExtensionKitProcess(const ExtensionKitProcess&);
ExtensionKitProcess& operator=(const ExtensionKitProcess&);
// Release the object when completed.
~ExtensionKitProcess();
private:
ExtensionKitProcess(Kind aKind, void* aProcessObject)
: mKind(aKind), mProcessObject(aProcessObject) {}
// Type tag for `mProcessObject`.
Kind mKind;
// This is one of `BEWebContentProcess`, `BENetworkingProcess` or
// `BERenderingProcess`. It has been type erased to be usable from C++ code.
void* mProcessObject;
};
} // namespace mozilla::ipc
#endif // mozilla_ipc_ExtensionKitUtils_h

Просмотреть файл

@ -0,0 +1,127 @@
/* -*- Mode: c++; c-basic-offset: 2; tab-width: 4; indent-tabs-mode: nil; -*-
* vim: set sw=2 ts=4 expandtab:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ExtensionKitUtils.h"
#include "LaunchError.h"
#import <BrowserEngineKit/BrowserEngineKit.h>
namespace mozilla::ipc {
void BEProcessCapabilityGrantDeleter::operator()(void* aGrant) const {
auto grant = static_cast<id<BEProcessCapabilityGrant>>(aGrant);
[grant invalidate];
[grant release];
}
void ExtensionKitProcess::StartProcess(
Kind aKind,
const std::function<void(Result<ExtensionKitProcess, LaunchError>&&)>&
aCompletion) {
auto callCompletion = [=](auto* aProcess, NSError* aError) {
if (aProcess) {
[aProcess retain];
aCompletion(ExtensionKitProcess(aKind, aProcess));
} else {
NSLog(@"Error launching process, description '%@', reason '%@'",
[aError localizedDescription], [aError localizedFailureReason]);
aCompletion(Err(LaunchError("ExtensionKitProcess::StartProcess")));
}
};
switch (aKind) {
case Kind::WebContent: {
[BEWebContentProcess
webContentProcessWithInterruptionHandler:^{
}
completion:^(BEWebContentProcess* process, NSError* error) {
callCompletion(process, error);
}];
return;
}
case Kind::Networking: {
[BENetworkingProcess
networkProcessWithInterruptionHandler:^{
}
completion:^(BENetworkingProcess* process, NSError* error) {
callCompletion(process, error);
}];
return;
}
case Kind::Rendering: {
[BERenderingProcess
renderingProcessWithInterruptionHandler:^{
}
completion:^(BERenderingProcess* process, NSError* error) {
callCompletion(process, error);
}];
return;
}
}
}
template <typename F>
static void SwitchObject(ExtensionKitProcess::Kind aKind, void* aProcessObject,
F&& aMatcher) {
switch (aKind) {
case ExtensionKitProcess::Kind::WebContent:
aMatcher(static_cast<BEWebContentProcess*>(aProcessObject));
break;
case ExtensionKitProcess::Kind::Networking:
aMatcher(static_cast<BENetworkingProcess*>(aProcessObject));
break;
case ExtensionKitProcess::Kind::Rendering:
aMatcher(static_cast<BERenderingProcess*>(aProcessObject));
break;
}
}
DarwinObjectPtr<xpc_connection_t> ExtensionKitProcess::MakeLibXPCConnection() {
NSError* error = nullptr;
DarwinObjectPtr<xpc_connection_t> xpcConnection;
SwitchObject(mKind, mProcessObject, [&](auto* aProcessObject) {
xpcConnection = [aProcessObject makeLibXPCConnectionError:&error];
});
return xpcConnection;
}
void ExtensionKitProcess::Invalidate() {
SwitchObject(mKind, mProcessObject,
[&](auto* aProcessObject) { [aProcessObject invalidate]; });
}
UniqueBEProcessCapabilityGrant ExtensionKitProcess::GrantForegroundCapability() {
NSError* error = nullptr;
BEProcessCapability* cap = [BEProcessCapability foreground];
id<BEProcessCapabilityGrant> grant = nil;
SwitchObject(mKind, mProcessObject, [&](auto* aProcessObject) {
grant = [aProcessObject grantCapability:cap error:&error];
});
return UniqueBEProcessCapabilityGrant(grant ? [grant retain] : nil);
}
ExtensionKitProcess::ExtensionKitProcess(const ExtensionKitProcess& aOther)
: mKind(aOther.mKind), mProcessObject(aOther.mProcessObject) {
SwitchObject(mKind, mProcessObject,
[&](auto* aProcessObject) { [aProcessObject retain]; });
}
ExtensionKitProcess& ExtensionKitProcess::operator=(const ExtensionKitProcess& aOther) {
Kind oldKind = std::exchange(mKind, aOther.mKind);
void* oldProcessObject = std::exchange(mProcessObject, aOther.mProcessObject);
SwitchObject(mKind, mProcessObject,
[&](auto* aProcessObject) { [aProcessObject retain]; });
SwitchObject(oldKind, oldProcessObject,
[&](auto* aProcessObject) { [aProcessObject release]; });
return *this;
}
ExtensionKitProcess::~ExtensionKitProcess() {
SwitchObject(mKind, mProcessObject,
[&](auto* aProcessObject) { [aProcessObject release]; });
}
} // namespace mozilla::ipc

Просмотреть файл

@ -13,15 +13,16 @@
#include "base/task.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/process_watcher.h"
#include "mozilla/ProcessType.h"
#ifdef MOZ_WIDGET_COCOA
# include <bsm/libbsm.h>
#ifdef XP_DARWIN
# include <mach/mach_traps.h>
# include <servers/bootstrap.h>
# include "SharedMemoryBasic.h"
# include "base/rand_util.h"
# include "chrome/common/mach_ipc_mac.h"
# include "mozilla/StaticPrefs_media.h"
#endif
#ifdef MOZ_WIDGET_COCOA
# include <bsm/libbsm.h>
# include <servers/bootstrap.h>
# include "nsILocalFileMac.h"
#endif
@ -129,9 +130,14 @@ namespace ipc {
struct LaunchResults {
base::ProcessHandle mHandle = 0;
#ifdef XP_MACOSX
#ifdef XP_DARWIN
task_t mChildTask = MACH_PORT_NULL;
#endif
#ifdef XP_IOS
Maybe<ExtensionKitProcess> mExtensionKitProcess;
DarwinObjectPtr<xpc_connection_t> mXPCConnection;
UniqueBEProcessCapabilityGrant mForegroundCapabilityGrant;
#endif
#if defined(XP_WIN) && defined(MOZ_SANDBOX)
RefPtr<AbstractSandboxBroker> mSandboxBroker;
#endif
@ -358,9 +364,9 @@ class IosProcessLauncher : public PosixProcessLauncher {
: PosixProcessLauncher(aHost, std::move(aExtraOpts)) {}
protected:
virtual RefPtr<ProcessHandlePromise> DoLaunch() override {
MOZ_CRASH("IosProcessLauncher::DoLaunch not implemented");
}
virtual RefPtr<ProcessHandlePromise> DoLaunch() override;
DarwinObjectPtr<xpc_object_t> mBootstrapMessage;
};
typedef IosProcessLauncher ProcessLauncher;
# else
@ -394,7 +400,7 @@ GeckoChildProcessHost::GeckoChildProcessHost(GeckoProcessType aProcessType,
#endif
mHandleLock("mozilla.ipc.GeckoChildProcessHost.mHandleLock"),
mChildProcessHandle(0),
#if defined(MOZ_WIDGET_COCOA)
#if defined(XP_DARWIN)
mChildTask(MACH_PORT_NULL),
#endif
#if defined(MOZ_SANDBOX) && defined(XP_MACOSX)
@ -447,11 +453,22 @@ GeckoChildProcessHost::~GeckoChildProcessHost()
{
mozilla::AutoWriteLock hLock(mHandleLock);
#if defined(MOZ_WIDGET_COCOA)
#if defined(XP_DARWIN)
if (mChildTask != MACH_PORT_NULL) {
mach_port_deallocate(mach_task_self(), mChildTask);
}
#endif
#if defined(XP_IOS)
if (mForegroundCapabilityGrant) {
mForegroundCapabilityGrant.reset();
}
if (mExtensionKitProcess) {
mExtensionKitProcess->Invalidate();
}
if (mXPCConnection) {
xpc_connection_cancel(mXPCConnection.get());
}
#endif
if (mChildProcessHandle != 0) {
ProcessWatcher::EnsureProcessTerminated(
@ -487,7 +504,7 @@ base::ProcessId GeckoChildProcessHost::GetChildProcessId() {
return base::GetProcId(mChildProcessHandle);
}
#ifdef XP_MACOSX
#ifdef XP_DARWIN
task_t GeckoChildProcessHost::GetChildTask() {
mozilla::AutoReadLock handleLock(mHandleLock);
return mChildTask;
@ -769,14 +786,20 @@ bool GeckoChildProcessHost::AsyncLaunch(std::vector<std::string> aExtraOpts) {
// "safe" invalid value to use in places like this.
aResults.mHandle = 0;
#ifdef XP_MACOSX
#ifdef XP_DARWIN
this->mChildTask = aResults.mChildTask;
#endif
#ifdef XP_IOS
this->mExtensionKitProcess = aResults.mExtensionKitProcess;
this->mXPCConnection = aResults.mXPCConnection;
this->mForegroundCapabilityGrant =
std::move(aResults.mForegroundCapabilityGrant);
#endif
if (mNodeChannel) {
mNodeChannel->SetOtherPid(
base::GetProcId(this->mChildProcessHandle));
#ifdef XP_MACOSX
#ifdef XP_DARWIN
mNodeChannel->SetMachTaskPort(this->mChildTask);
#endif
}
@ -802,7 +825,8 @@ bool GeckoChildProcessHost::AsyncLaunch(std::vector<std::string> aExtraOpts) {
CHROMIUM_LOG(ERROR)
<< "Failed to launch "
<< XRE_GeckoProcessTypeToString(mProcessType)
<< " subprocess";
<< " subprocess @" << aError.FunctionName()
<< " (Error:" << aError.ErrorCode() << ")";
Telemetry::Accumulate(
Telemetry::SUBPROCESS_LAUNCH_FAILURE,
nsDependentCString(
@ -1357,6 +1381,143 @@ RefPtr<ProcessHandlePromise> PosixProcessLauncher::DoLaunch() {
}
#endif // XP_UNIX
#ifdef XP_IOS
RefPtr<ProcessHandlePromise> IosProcessLauncher::DoLaunch() {
MOZ_RELEASE_ASSERT(mLaunchOptions->fds_to_remap.size() == 3,
"Unexpected fds_to_remap on iOS");
ExtensionKitProcess::Kind kind = ExtensionKitProcess::Kind::WebContent;
if (mProcessType == GeckoProcessType_GPU) {
kind = ExtensionKitProcess::Kind::Rendering;
} else if (mProcessType == GeckoProcessType_Socket) {
kind = ExtensionKitProcess::Kind::Networking;
}
DarwinObjectPtr<xpc_object_t> bootstrapMessage =
AdoptDarwinObject(xpc_dictionary_create_empty());
xpc_dictionary_set_string(bootstrapMessage.get(), "message-name",
"bootstrap");
DarwinObjectPtr<xpc_object_t> environDict =
AdoptDarwinObject(xpc_dictionary_create_empty());
for (auto& [envKey, envValue] : mLaunchOptions->env_map) {
xpc_dictionary_set_string(environDict.get(), envKey.c_str(),
envValue.c_str());
}
xpc_dictionary_set_value(bootstrapMessage.get(), "environ",
environDict.get());
// XXX: this processing depends entirely on the internals of
// ContentParent::LaunchSubprocess()
// GeckoChildProcessHost::PerformAsyncLaunch(), and the order in
// which they append to fds_to_remap. There must be a better way to do it.
// See bug 1440207.
int prefsFd = mLaunchOptions->fds_to_remap[0].first;
int prefMapFd = mLaunchOptions->fds_to_remap[1].first;
int ipcFd = mLaunchOptions->fds_to_remap[2].first;
xpc_dictionary_set_fd(bootstrapMessage.get(), "prefs", prefsFd);
xpc_dictionary_set_fd(bootstrapMessage.get(), "prefmap", prefMapFd);
xpc_dictionary_set_fd(bootstrapMessage.get(), "channel", ipcFd);
// Setup stdout and stderr to inherit.
xpc_dictionary_set_fd(bootstrapMessage.get(), "stdout", STDOUT_FILENO);
xpc_dictionary_set_fd(bootstrapMessage.get(), "stderr", STDERR_FILENO);
DarwinObjectPtr<xpc_object_t> argsArray =
AdoptDarwinObject(xpc_array_create_empty());
for (auto& argv : mChildArgv) {
xpc_array_set_string(argsArray.get(), XPC_ARRAY_APPEND, argv.c_str());
}
MOZ_ASSERT(xpc_array_get_count(argsArray.get()) == mChildArgv.size());
xpc_dictionary_set_value(bootstrapMessage.get(), "argv", argsArray.get());
auto promise = MakeRefPtr<ProcessHandlePromise::Private>(__func__);
ExtensionKitProcess::StartProcess(kind, [self = RefPtr{this}, promise,
bootstrapMessage =
std::move(bootstrapMessage)](
Result<ExtensionKitProcess,
LaunchError>&& result) {
if (result.isErr()) {
CHROMIUM_LOG(ERROR) << "ExtensionKitProcess::StartProcess failed";
promise->Reject(result.unwrapErr(), __func__);
return;
}
auto process = result.unwrap();
self->mResults.mForegroundCapabilityGrant =
process.GrantForegroundCapability();
self->mResults.mXPCConnection = process.MakeLibXPCConnection();
self->mResults.mExtensionKitProcess = Some(std::move(process));
// We don't actually use the event handler for anything other than
// watching for errors. Once the promise is resolved, this becomes a
// no-op.
xpc_connection_set_event_handler(self->mResults.mXPCConnection.get(), ^(
xpc_object_t event) {
if (!event || xpc_get_type(event) == XPC_TYPE_ERROR) {
CHROMIUM_LOG(WARNING) << "XPC connection received encountered an error";
promise->Reject(LaunchError("xpc_connection_event_handler"), __func__);
}
});
xpc_connection_resume(self->mResults.mXPCConnection.get());
// Send our bootstrap message to the content and wait for it to reply with
// the task port before resolving.
// FIXME: Should we have a time-out for if the child process doesn't respond
// in time? The main thread may be blocked while we're starting this
// process.
xpc_connection_send_message_with_reply(
self->mResults.mXPCConnection.get(), bootstrapMessage.get(), nullptr,
^(xpc_object_t reply) {
if (xpc_get_type(reply) == XPC_TYPE_ERROR) {
CHROMIUM_LOG(ERROR)
<< "Got error sending XPC bootstrap message to child";
promise->Reject(
LaunchError("xpc_connection_send_message_with_reply error"),
__func__);
return;
}
if (xpc_get_type(reply) != XPC_TYPE_DICTIONARY) {
CHROMIUM_LOG(ERROR)
<< "Unexpected reply type for bootstrap message from child";
promise->Reject(
LaunchError(
"xpc_connection_send_message_with_reply non-dictionary"),
__func__);
return;
}
// FIXME: We have to trust the child to tell us its pid & mach task.
// WebKit uses `xpc_connection_get_pid` to get the pid, however this
// is marked as unavailable on iOS.
//
// Given how the process is started, however, validating this
// information it sends us this early during startup may be
// unnecessary.
self->mResults.mChildTask =
xpc_dictionary_copy_mach_send(reply, "task");
pid_t pid =
static_cast<pid_t>(xpc_dictionary_get_int64(reply, "pid"));
CHROMIUM_LOG(INFO) << "ExtensionKit process started, task: "
<< self->mResults.mChildTask << ", pid: " << pid;
pid_t taskPid;
kern_return_t kr = pid_for_task(self->mResults.mChildTask, &taskPid);
if (kr != KERN_SUCCESS || pid != taskPid) {
CHROMIUM_LOG(ERROR) << "Could not validate child task matches pid";
promise->Reject(LaunchError("pid_for_task mismatch"), __func__);
return;
}
promise->Resolve(pid, __func__);
});
});
return promise;
}
#endif
#ifdef XP_MACOSX
Result<Ok, LaunchError> MacProcessLauncher::DoFinishLaunch() {
Result<Ok, LaunchError> aError = PosixProcessLauncher::DoFinishLaunch();
@ -1742,7 +1903,7 @@ RefPtr<ProcessLaunchPromise> BaseProcessLauncher::FinishLaunch() {
Telemetry::AccumulateTimeDelta(Telemetry::CHILD_PROCESS_LAUNCH_MS,
mStartTimeStamp);
return ProcessLaunchPromise::CreateAndResolve(mResults, __func__);
return ProcessLaunchPromise::CreateAndResolve(std::move(mResults), __func__);
}
bool GeckoChildProcessHost::OpenPrivilegedHandle(base::ProcessId aPid) {

Просмотреть файл

@ -33,6 +33,10 @@
#include "nsXULAppAPI.h" // for GeckoProcessType
#include "nsString.h"
#if defined(XP_IOS)
# include "mozilla/ipc/ExtensionKitUtils.h"
#endif
#if defined(XP_WIN) && defined(MOZ_SANDBOX)
# include "sandboxBroker.h"
#endif
@ -151,7 +155,7 @@ class GeckoChildProcessHost : public SupportsWeakPtr,
GeckoProcessType GetProcessType() { return mProcessType; }
#ifdef XP_MACOSX
#ifdef XP_DARWIN
task_t GetChildTask();
#endif
@ -265,6 +269,12 @@ class GeckoChildProcessHost : public SupportsWeakPtr,
ProcessHandle mChildProcessHandle MOZ_GUARDED_BY(mHandleLock);
#if defined(XP_DARWIN)
task_t mChildTask MOZ_GUARDED_BY(mHandleLock);
#endif
#if defined(MOZ_WIDGET_UIKIT)
Maybe<ExtensionKitProcess> mExtensionKitProcess MOZ_GUARDED_BY(mHandleLock);
DarwinObjectPtr<xpc_connection_t> mXPCConnection MOZ_GUARDED_BY(mHandleLock);
UniqueBEProcessCapabilityGrant mForegroundCapabilityGrant
MOZ_GUARDED_BY(mHandleLock);
#endif
RefPtr<ProcessHandlePromise> mHandlePromise;

Просмотреть файл

@ -112,7 +112,7 @@ void NodeChannel::SetOtherPid(base::ProcessId aNewPid) {
mChannel->SetOtherPid(aNewPid);
}
#ifdef XP_MACOSX
#ifdef XP_DARWIN
void NodeChannel::SetMachTaskPort(task_t aTask) {
AssertIOThread();

Просмотреть файл

@ -118,7 +118,7 @@ class NodeChannel final : public IPC::Channel::Listener {
// THREAD.
void SetOtherPid(base::ProcessId aNewPid);
#ifdef XP_MACOSX
#ifdef XP_DARWIN
// Called by the GeckoChildProcessHost to provide the task_t for the peer
// process. MUST BE CALLED FROM THE IO THREAD.
void SetMachTaskPort(task_t aTask);

Просмотреть файл

@ -315,7 +315,7 @@ void NodeController::ForwardEvent(const NodeName& aNode,
// On Windows and macOS, messages holding HANDLEs or mach ports must be
// relayed via the broker process so it can transfer ownership.
bool needsRelay = false;
#if defined(XP_WIN) || defined(XP_MACOSX)
#if defined(XP_WIN) || defined(XP_DARWIN)
if (!IsBroker() && aNode != kBrokerNodeName &&
aEvent->type() == Event::kUserMessage) {
auto* userEvent = static_cast<UserMessageEvent*>(aEvent.get());
@ -439,7 +439,7 @@ void NodeController::OnEventMessage(const NodeName& aFromNode,
}
NodeName fromNode = aFromNode;
#if defined(XP_WIN) || defined(XP_MACOSX)
#if defined(XP_WIN) || defined(XP_DARWIN)
if (isRelay) {
if (event->type() != Event::kUserMessage) {
NODECONTROLLER_WARNING(

Просмотреть файл

@ -195,8 +195,8 @@ static const int kJSInitFileDescriptor = 11;
void ExportSharedJSInit(mozilla::ipc::GeckoChildProcessHost& procHost,
std::vector<std::string>& aExtraOpts) {
#ifdef ANDROID
// The code to support Android is added in a follow-up patch.
#if defined(ANDROID) || defined(XP_IOS)
// The code to support Android/iOS is added in a follow-up patch.
return;
#else
auto& shmem = xpc::SelfHostedShmem::GetSingleton();

Просмотреть файл

@ -153,6 +153,14 @@ if CONFIG["OS_ARCH"] != "WINNT":
"FileDescriptorShuffle.cpp",
]
if CONFIG["TARGET_OS"] == "iOS":
EXPORTS.mozilla.ipc += [
"ExtensionKitUtils.h",
]
UNIFIED_SOURCES += [
"ExtensionKitUtils.mm",
]
EXPORTS.ipc += [
"EnumSerializer.h",
"IPCMessageUtils.h",
@ -284,8 +292,10 @@ include("/ipc/chromium/chromium-config.mozbuild")
FINAL_LIBRARY = "xul"
if CONFIG["OS_ARCH"] == "Darwin":
if CONFIG["TARGET_OS"] == "OSX":
OS_LIBS += ["bsm"] # for audit_token_to_pid
elif CONFIG["TARGET_OS"] == "iOS":
OS_LIBS += ["-framework BrowserEngineKit"]
for var in (
"MOZ_CHILD_PROCESS_NAME",