1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
base / apple / mach_port_rendezvous.h [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_APPLE_MACH_PORT_RENDEZVOUS_H_
#define BASE_APPLE_MACH_PORT_RENDEZVOUS_H_
#include <dispatch/dispatch.h>
#include <mach/mach.h>
#include <stdint.h>
#include <sys/types.h>
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "base/apple/dispatch_source_mach.h"
#include "base/apple/scoped_mach_port.h"
#include "base/base_export.h"
#include "base/containers/buffer_iterator.h"
#include "base/feature_list.h"
#include "base/synchronization/lock.h"
#include "base/thread_annotations.h"
#include "build/ios_buildflags.h"
#if BUILDFLAG(IS_MAC)
#include "base/environment.h"
#include "base/mac/process_requirement.h"
#endif
namespace base {
// Mach Port Rendezvous is a technique to exchange Mach port rights across
// child process creation. macOS does not provide a way to inherit Mach port
// rights, unlike what is possible with file descriptors. Port rendezvous
// enables a parent process to register Mach port rights for a nascent child,
// which the child can then retrieve using Mach IPC by looking up the endpoint
// in launchd's bootstrap namespace.
//
// The same mechanism is used on iOS but the Mach IPC endpoint is not found
// via launchd's bootstrap namespace but via an initial XPC connection.
//
// When launching a child process, the parent process' rendezvous server lets
// calling code register a collection of ports for the new child. In order to
// acquire the ports, a child looks up the rendezvous server in the bootstrap
// namespace, and it sends an IPC message to the server, the reply to which
// contains the registered ports.
//
// Port rendezvous is only permitted between a parent and its direct child
// process descendants.
// A MachRendezvousPort contains a single Mach port to pass to the child
// process. The associated disposition controls how the reference count will
// be manipulated.
class BASE_EXPORT MachRendezvousPort {
public:
MachRendezvousPort() = default;
// Creates a rendezvous port that allows specifying the specific disposition.
MachRendezvousPort(mach_port_t name, mach_msg_type_name_t disposition);
// Creates a rendezvous port for MACH_MSG_TYPE_MOVE_SEND.
explicit MachRendezvousPort(apple::ScopedMachSendRight send_right);
// Creates a rendezvous port for MACH_MSG_TYPE_MOVE_RECEIVE.
explicit MachRendezvousPort(apple::ScopedMachReceiveRight receive_right);
// Note that the destructor does not call Destroy() explicitly.
// To avoid leaking ports, either use dispositions that create rights during
// transit (MAKE or COPY), or use base::LaunchProcess, which will destroy
// rights on failure.
~MachRendezvousPort();
// Destroys the Mach port right type conveyed |disposition| named by |name|.
void Destroy();
mach_port_t name() const { return name_; }
mach_msg_type_name_t disposition() const { return disposition_; }
private:
mach_port_t name_ = MACH_PORT_NULL;
mach_msg_type_name_t disposition_ = 0;
// Copy and assign allowed.
};
// The collection of ports to pass to a child process. There are no restrictions
// regarding the keys of the map. Clients are responsible for avoiding
// collisions with other clients.
using MachPortsForRendezvous = std::map<uint32_t, MachRendezvousPort>;
// Base class that runs a Mach message server, listening to requests on
// mach server port.
class BASE_EXPORT MachPortRendezvousServerBase {
protected:
MachPortRendezvousServerBase();
virtual ~MachPortRendezvousServerBase();
// The Mach receive right for the server. A send right to this is port is
// registered in the bootstrap server.
apple::ScopedMachReceiveRight server_port_;
// Mach message dispatch source for |server_port_|.
std::unique_ptr<apple::DispatchSourceMach> dispatch_source_;
// Ask for the associated ports associated with `audit_token`.
// Return `std::nullopt` if the client is not authorized to
// retrieve ports.
virtual std::optional<MachPortsForRendezvous> PortsForClient(
audit_token_t audit_token) = 0;
// Return whether `msg_id` should be accepted along with the known
// message IDs. Platform-specific subclasses may return additional data
// based on the `msg_id` within `AdditionalDataForReply`.
virtual bool IsValidAdditionalMessageId(mach_msg_id_t msg_id) const = 0;
// Return additional data to be attached to a reply for `request`.
virtual std::vector<uint8_t> AdditionalDataForReply(
mach_msg_id_t request) const = 0;
// The server-side Mach message handler. Called by |dispatch_source_| when a
// message is received.
void HandleRequest();
// Returns a buffer containing a well-formed Mach message, destined for
// `reply_port` containing descriptors for the specified `ports` and
// `additional_data`.
std::unique_ptr<uint8_t[]> CreateReplyMessage(
mach_port_t reply_port,
const MachPortsForRendezvous& ports,
std::vector<uint8_t> additional_data);
};
#if BUILDFLAG(IS_IOS)
// An implementation class that works for a single process. It is intended
// that each process spawned will create a corresponding instance and the
// mach send right of this server will be sent using XPC to the process.
class BASE_EXPORT MachPortRendezvousServerIOS final
: public MachPortRendezvousServerBase {
public:
MachPortRendezvousServerIOS(const MachPortsForRendezvous& ports);
~MachPortRendezvousServerIOS() override;
MachPortRendezvousServerIOS(const MachPortRendezvousServerIOS&) = delete;
MachPortRendezvousServerIOS& operator=(const MachPortRendezvousServerIOS&) =
delete;
// Retrieve the send right to be sent to the process.
apple::ScopedMachSendRight GetMachSendRight();
protected:
std::optional<MachPortsForRendezvous> PortsForClient(audit_token_t) override;
bool IsValidAdditionalMessageId(mach_msg_id_t) const override;
std::vector<uint8_t> AdditionalDataForReply(mach_msg_id_t) const override;
private:
apple::ScopedMachSendRight send_right_;
MachPortsForRendezvous ports_;
};
#endif // BUILDFLAG(IS_IOS)
#if BUILDFLAG(IS_MAC)
// An implementation class that uses bootstrap to register ports to many
// processes.
class BASE_EXPORT MachPortRendezvousServerMac final
: public MachPortRendezvousServerBase {
public:
// Returns the instance of the server. Upon the first call to this method,
// the server is created, which registers an endpoint in the Mach bootstrap
// namespace.
static MachPortRendezvousServerMac* GetInstance();
// Add feature state to an environment variable that will be used when
// launching a child process. MachPortRendezvousClient is used during
// feature list initialization so any state it uses must be passed
// via a side channel.
// TODO(crbug.com/362302761): Remove once enforcement is enabled by default.
static void AddFeatureStateToEnvironment(EnvironmentMap& environment);
MachPortRendezvousServerMac(const MachPortRendezvousServerMac&) = delete;
MachPortRendezvousServerMac& operator=(const MachPortRendezvousServerMac&) =
delete;
// Registers a collection of Mach ports |ports| to be acquirable by the
// process known by |pid|. This cannot be called again for the same |pid|
// until the process known by |pid| has either acquired the ports or died.
//
// This must be called with the lock from GetLock() held.
void RegisterPortsForPid(pid_t pid, const MachPortsForRendezvous& ports)
EXCLUSIVE_LOCKS_REQUIRED(GetLock());
// Sets the process requirement that `pid` must match before it
// can acquire any Mach ports. This cannot be called again for the same `pid`
// until the process known by `pid` has acquired the ports or died.
//
// This must be called with the lock from GetLock() held.
void SetProcessRequirementForPid(pid_t pid,
mac::ProcessRequirement requirement)
EXCLUSIVE_LOCKS_REQUIRED(GetLock());
// Returns a lock on the internal port registration map. The parent process
// should hold this lock for the duration of launching a process, including
// after calling RegisterPortsForPid(). This ensures that a child process
// cannot race acquiring ports before they are registered. The lock should
// be released after the child process is launched and the ports are
// registered.
Lock& GetLock() LOCK_RETURNED(lock_) { return lock_; }
void ClearClientDataForTesting() EXCLUSIVE_LOCKS_REQUIRED(GetLock());
protected:
// Returns the registered collection of ports for the specified `audit_token`.
// `std::nullopt` indicates that the client is not authorized to retrieve the
// ports. This claims the collection of ports and removes the entry from
// `client_data_`.
std::optional<MachPortsForRendezvous> PortsForClient(
audit_token_t audit_token) override;
bool IsValidAdditionalMessageId(mach_msg_id_t) const override;
std::vector<uint8_t> AdditionalDataForReply(
mach_msg_id_t request) const override;
private:
friend class MachPortRendezvousServerTest;
friend struct MachPortRendezvousFuzzer;
MachPortRendezvousServerMac();
~MachPortRendezvousServerMac() override;
struct ClientData;
// Returns the `ClientData` for `pid`, creating it if necessary.
// It will be cleaned up automatically when `pid` exits.
ClientData& ClientDataForPid(int pid) EXCLUSIVE_LOCKS_REQUIRED(GetLock());
// Called by the ClientData::exit_watcher dispatch sources when a process
// for which ports have been registered exits. This releases port rights
// that are strongly owned, in the event that the child has not claimed them.
void OnClientExited(pid_t pid);
Lock lock_;
// Association of pid-to-ports.
std::map<pid_t, ClientData> client_data_ GUARDED_BY(lock_);
};
#endif
// Client class for accessing the memory object exposed by the
// MachPortRendezvousServer.
class BASE_EXPORT MachPortRendezvousClient {
public:
MachPortRendezvousClient(const MachPortRendezvousClient&) = delete;
MachPortRendezvousClient& operator=(const MachPortRendezvousClient&) = delete;
// Connects to the MachPortRendezvousServer and requests any registered Mach
// ports. This only performs the rendezvous once. Subsequent calls to this
// method return the same instance. If the rendezvous fails, which can happen
// if the server is not available or if the server fails the code signature
// validation and requirement check, this returns null. Acquiring zero ports
// from the exchange is not considered a failure.
static MachPortRendezvousClient* GetInstance();
// Returns the Mach send right that was registered with |key|. If no such
// right exists, or it was already taken, returns an invalid right. Safe to
// call from any thread. DCHECKs if the right referenced by |key| is not a
// send or send-once right.
apple::ScopedMachSendRight TakeSendRight(
MachPortsForRendezvous::key_type key);
// Returns the Mach receive right that was registered with |key|. If no such
// right exists, or it was already taken, returns an invalid right. Safe to
// call from any thread. DCHECKs if the right referenced by |key| is not a
// receive right.
apple::ScopedMachReceiveRight TakeReceiveRight(
MachPortsForRendezvous::key_type key);
// Returns the number of ports in the client. After PerformRendezvous(), this
// reflects the number of ports acquired. But as rights are taken, this
// only reflects the number of remaining rights.
size_t GetPortCount();
protected:
MachPortRendezvousClient();
virtual ~MachPortRendezvousClient();
// Perform platform-specific validation on a received message and the peer
// that sent it.
virtual bool ValidateMessage(mach_msg_base_t* message,
BufferIterator<uint8_t> body) = 0;
// Sends the actual IPC message to |server_port| and parses the reply.
bool SendRequest(apple::ScopedMachSendRight server_port,
mach_msg_id_t request_id,
size_t additional_response_data_size = 0)
EXCLUSIVE_LOCKS_REQUIRED(lock_);
// Returns a MachRendezvousPort for a given key and removes it from the
// |ports_| map. If an entry does not exist for that key, then a
// MachRendezvousPort with MACH_PORT_NULL is returned.
MachRendezvousPort PortForKey(MachPortsForRendezvous::key_type key);
Lock lock_;
// The collection of ports that was acquired.
MachPortsForRendezvous ports_ GUARDED_BY(lock_);
};
#if BUILDFLAG(IS_IOS)
BASE_EXPORT
class BASE_EXPORT MachPortRendezvousClientIOS final
: public MachPortRendezvousClient {
public:
// Initialize the MacPortRendezvousClient using `server_port`.
static bool Initialize(apple::ScopedMachSendRight server_port);
protected:
bool ValidateMessage(mach_msg_base_t* message,
BufferIterator<uint8_t> body) override;
private:
MachPortRendezvousClientIOS();
~MachPortRendezvousClientIOS() override;
// Helper method to look up the server in the bootstrap namespace and send
// the acquisition request message.
bool AcquirePorts(apple::ScopedMachSendRight server_port);
};
#endif
#if BUILDFLAG(IS_MAC)
// Describes how the `ProcessRequirement` should be used during Mach port
// rendezvous. The active policy is derived from the feature flags in the
// browser process and is passed via an environment variable to child processes.
// TODO(crbug.com/362302761): Remove this policy once enforcement is enabled by
// default.
enum class MachPortRendezvousPeerValidationPolicy {
// Do not validate the peer against a process requirement.
kNoValidation,
// Validate the peer against a process requirement, if specified, but do not
// abort rendezvous if validation fails. Used to gather success metrics during
// experiment rollout.
kValidateOnly,
// Validate the peer against a process requirement, if specified, and abort
// rendezvous if the validation fails.
kEnforce,
};
class BASE_EXPORT MachPortRendezvousClientMac final
: public MachPortRendezvousClient {
public:
// Set a ProcessRequirement that the server should be validated
// against before accepting any Mach ports from it.
//
// Must be called before `GetInstance` or this will have no effect.
static void SetServerProcessRequirement(mac::ProcessRequirement requirement);
// Get the peer validation policy that was derived from feature flags.
static MachPortRendezvousPeerValidationPolicy
PeerValidationPolicyForTesting();
protected:
// Validate the server against a process requirement if one was set via
// `SetServerProcessRequirement`.
bool ValidateMessage(mach_msg_base_t* message,
BufferIterator<uint8_t> body) override;
private:
friend class MachPortRendezvousClient;
MachPortRendezvousClientMac();
~MachPortRendezvousClientMac() override;
// Returns the name of the server to look up in the bootstrap namespace.
static std::string GetBootstrapName();
// Helper method to look up the server in the bootstrap namespace and send
// the acquisition request message.
bool AcquirePorts();
// Take ownership of the server process requirement, if any.
static std::optional<mac::ProcessRequirement>
TakeServerCodeSigningRequirement();
// Whether Info.plist data is needed from the server in order
// to validate `server_requirement_`.
bool NeedsInfoPlistData() const;
std::optional<mac::ProcessRequirement> server_requirement_;
};
// Whether any peer process requirements should be validated.
BASE_EXPORT BASE_DECLARE_FEATURE(kMachPortRendezvousValidatePeerRequirements);
// Whether a failure to validate a peer process against a requirement
// should result in aborting the rendezvous.
BASE_EXPORT BASE_DECLARE_FEATURE(kMachPortRendezvousEnforcePeerRequirements);
#endif
} // namespace base
#endif // BASE_APPLE_MACH_PORT_RENDEZVOUS_H_