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