xref: /aosp_15_r20/external/cronet/base/mac/mach_port_rendezvous.h (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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_MAC_MACH_PORT_RENDEZVOUS_H_
6 #define BASE_MAC_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 
17 #include "base/apple/dispatch_source_mach.h"
18 #include "base/apple/scoped_mach_port.h"
19 #include "base/base_export.h"
20 #include "base/synchronization/lock.h"
21 #include "base/thread_annotations.h"
22 #include "build/ios_buildflags.h"
23 
24 #if !BUILDFLAG(IS_IOS)
25 #include "base/apple/scoped_dispatch_object.h"
26 #endif
27 
28 namespace base {
29 
30 // Mach Port Rendezvous is a technique to exchange Mach port rights across
31 // child process creation. macOS does not provide a way to inherit Mach port
32 // rights, unlike what is possible with file descriptors. Port rendezvous
33 // enables a parent process to register Mach port rights for a nascent child,
34 // which the child can then retrieve using Mach IPC by looking up the endpoint
35 // in launchd's bootstrap namespace.
36 //
37 // The same mechanism is used on iOS but the Mach IPC endpoint is not found
38 // via launchd's bootstrap namespace but via an initial XPC connection.
39 //
40 // When launching a child process, the parent process' rendezvous server lets
41 // calling code register a collection of ports for the new child. In order to
42 // acquire the ports, a child looks up the rendezvous server in the bootstrap
43 // namespace, and it sends an IPC message to the server, the reply to which
44 // contains the registered ports.
45 //
46 // Port rendezvous is only permitted between a parent and its direct child
47 // process descendants.
48 
49 // A MachRendezvousPort contains a single Mach port to pass to the child
50 // process. The associated disposition controls how the reference count will
51 // be manipulated.
52 class BASE_EXPORT MachRendezvousPort {
53  public:
54   MachRendezvousPort() = default;
55   // Creates a rendezvous port that allows specifying the specific disposition.
56   MachRendezvousPort(mach_port_t name, mach_msg_type_name_t disposition);
57   // Creates a rendezvous port for MACH_MSG_TYPE_MOVE_SEND.
58   explicit MachRendezvousPort(apple::ScopedMachSendRight send_right);
59   // Creates a rendezvous port for MACH_MSG_TYPE_MOVE_RECEIVE.
60   explicit MachRendezvousPort(apple::ScopedMachReceiveRight receive_right);
61 
62   // Note that the destructor does not call Destroy() explicitly.
63   // To avoid leaking ports, either use dispositions that create rights during
64   // transit (MAKE or COPY), or use base::LaunchProcess, which will destroy
65   // rights on failure.
66   ~MachRendezvousPort();
67 
68   // Destroys the Mach port right type conveyed |disposition| named by |name|.
69   void Destroy();
70 
name()71   mach_port_t name() const { return name_; }
72 
disposition()73   mach_msg_type_name_t disposition() const { return disposition_; }
74 
75  private:
76   mach_port_t name_ = MACH_PORT_NULL;
77   mach_msg_type_name_t disposition_ = 0;
78   // Copy and assign allowed.
79 };
80 
81 // The collection of ports to pass to a child process. There are no restrictions
82 // regarding the keys of the map. Clients are responsible for avoiding
83 // collisions with other clients.
84 using MachPortsForRendezvous = std::map<uint32_t, MachRendezvousPort>;
85 
86 // Base class that runs a Mach message server, listening to requests on
87 // mach server port.
88 class BASE_EXPORT MachPortRendezvousServerBase {
89  protected:
90   MachPortRendezvousServerBase();
91   virtual ~MachPortRendezvousServerBase();
92 
93   // The Mach receive right for the server. A send right to this is port is
94   // registered in the bootstrap server.
95   apple::ScopedMachReceiveRight server_port_;
96 
97   // Mach message dispatch source for |server_port_|.
98   std::unique_ptr<apple::DispatchSourceMach> dispatch_source_;
99 
100   // Ask for the associated ports associated with `pid`. `pid` will
101   // be 0 for iOS.
102   virtual MachPortsForRendezvous PortsForPid(int pid) = 0;
103 
104   // The server-side Mach message handler. Called by |dispatch_source_| when a
105   // message is received.
106   void HandleRequest();
107 
108   // Returns a buffer containing a well-formed Mach message, destined for
109   // |reply_port| containing descriptors for the specified |ports|.
110   std::unique_ptr<uint8_t[]> CreateReplyMessage(
111       mach_port_t reply_port,
112       const MachPortsForRendezvous& ports);
113 };
114 
115 #if BUILDFLAG(IS_IOS)
116 // An implementation class that works for a single process. It is intended
117 // that each process spawned will create a corresponding instance and the
118 // mach send right of this server will be sent using XPC to the process.
119 class BASE_EXPORT MachPortRendezvousServer
120     : public MachPortRendezvousServerBase {
121  public:
122   MachPortRendezvousServer(const MachPortsForRendezvous& ports);
123   ~MachPortRendezvousServer() override;
124   MachPortRendezvousServer(const MachPortRendezvousServer&) = delete;
125   MachPortRendezvousServer& operator=(const MachPortRendezvousServer&) = delete;
126 
127   // Retrieve the send right to be sent to the process.
128   apple::ScopedMachSendRight GetMachSendRight();
129 
130  protected:
131   MachPortsForRendezvous PortsForPid(int pid) override;
132 
133  private:
134   apple::ScopedMachSendRight send_right_;
135   MachPortsForRendezvous ports_;
136 };
137 
138 #else
139 
140 // An implementation class that uses bootstrap to register ports to many
141 // processes.
142 class BASE_EXPORT MachPortRendezvousServer
143     : public MachPortRendezvousServerBase {
144  public:
145   // Returns the instance of the server. Upon the first call to this method,
146   // the server is created, which registers an endpoint in the Mach bootstrap
147   // namespace.
148   static MachPortRendezvousServer* GetInstance();
149 
150   MachPortRendezvousServer(const MachPortRendezvousServer&) = delete;
151   MachPortRendezvousServer& operator=(const MachPortRendezvousServer&) = delete;
152 
153   // Registers a collection of Mach ports |ports| to be acquirable by the
154   // process known by |pid|. This cannot be called again for the same |pid|
155   // until the process known by |pid| has either acquired the ports or died.
156   //
157   // This must be called with the lock from GetLock() held.
158   void RegisterPortsForPid(pid_t pid, const MachPortsForRendezvous& ports)
159       EXCLUSIVE_LOCKS_REQUIRED(GetLock());
160 
161   // Returns a lock on the internal port registration map. The parent process
162   // should hold this lock for the duration of launching a process, including
163   // after calling RegisterPortsForPid(). This ensures that a child process
164   // cannot race acquiring ports before they are registered. The lock should
165   // be released after the child process is launched and the ports are
166   // registered.
GetLock()167   Lock& GetLock() LOCK_RETURNED(lock_) { return lock_; }
168 
169  protected:
170   // Returns the registered collection of ports for the specified |pid|. An
171   // empty collection indicates no ports were found, as it is invalid to
172   // register with an empty collection. This claims the collection of ports
173   // and removes the entry from |client_data_|.
174   MachPortsForRendezvous PortsForPid(int pid) override;
175 
176  private:
177   friend class MachPortRendezvousServerTest;
178   friend struct MachPortRendezvousFuzzer;
179 
180   MachPortRendezvousServer();
181   ~MachPortRendezvousServer() override;
182 
183   struct ClientData {
184     ClientData(apple::ScopedDispatchObject<dispatch_source_t> exit_watcher,
185                MachPortsForRendezvous ports);
186     ClientData(ClientData&&);
187     ~ClientData();
188 
189     // A DISPATCH_SOURCE_TYPE_PROC / DISPATCH_PROC_EXIT dispatch source. When
190     // the source is triggered, it calls OnClientExited().
191     apple::ScopedDispatchObject<dispatch_source_t> exit_watcher;
192 
193     MachPortsForRendezvous ports;
194   };
195 
196   // Called by the ClientData::exit_watcher dispatch sources when a process
197   // for which ports have been registered exits. This releases port rights
198   // that are strongly owned, in the event that the child has not claimed them.
199   void OnClientExited(pid_t pid);
200 
201   Lock lock_;
202   // Association of pid-to-ports.
203   std::map<pid_t, ClientData> client_data_ GUARDED_BY(lock_);
204 };
205 #endif
206 
207 // Client class for accessing the memory object exposed by the
208 // MachPortRendezvousServer.
209 class BASE_EXPORT MachPortRendezvousClient {
210  public:
211   // Connects to the MachPortRendezvousServer and requests any registered Mach
212   // ports. This only performs the rendezvous once. Subsequent calls to this
213   // method return the same instance. If the rendezvous fails, which can happen
214   // if the server is not available, this returns null. Acquiring zero ports
215   // from the exchange is not considered a failure.
216   static MachPortRendezvousClient* GetInstance();
217 
218   MachPortRendezvousClient(const MachPortRendezvousClient&) = delete;
219   MachPortRendezvousClient& operator=(const MachPortRendezvousClient&) = delete;
220 
221   // Returns the Mach send right that was registered with |key|. If no such
222   // right exists, or it was already taken, returns an invalid right. Safe to
223   // call from any thread. DCHECKs if the right referenced by |key| is not a
224   // send or send-once right.
225   apple::ScopedMachSendRight TakeSendRight(
226       MachPortsForRendezvous::key_type key);
227 
228   // Returns the Mach receive right that was registered with |key|. If no such
229   // right exists, or it was already taken, returns an invalid right. Safe to
230   // call from any thread. DCHECKs if the right referenced by |key| is not a
231   // receive right.
232   apple::ScopedMachReceiveRight TakeReceiveRight(
233       MachPortsForRendezvous::key_type key);
234 
235   // Returns the number of ports in the client. After PerformRendezvous(), this
236   // reflects the number of ports acquired. But as rights are taken, this
237   // only reflects the number of remaining rights.
238   size_t GetPortCount();
239 
240 #if BUILDFLAG(IS_IOS)
241   // Initialize the MacPortRendezvousClient using `server_port`.
242   static bool Initialize(apple::ScopedMachSendRight server_port);
243 #else
244   // Returns the name of the server to look up in the bootstrap namespace.
245   static std::string GetBootstrapName();
246 #endif
247 
248  private:
249   MachPortRendezvousClient();
250   ~MachPortRendezvousClient();
251 
252   // Helper method to look up the server in the bootstrap namespace and send
253   // the acquisition request message.
254 #if BUILDFLAG(IS_IOS)
255   bool AcquirePorts(apple::ScopedMachSendRight server_port);
256 #else
257   bool AcquirePorts();
258 #endif
259 
260   // Sends the actual IPC message to |server_port| and parses the reply.
261   bool SendRequest(apple::ScopedMachSendRight server_port)
262       EXCLUSIVE_LOCKS_REQUIRED(lock_);
263 
264   // Returns a MachRendezvousPort for a given key and removes it from the
265   // |ports_| map. If an entry does not exist for that key, then a
266   // MachRendezvousPort with MACH_PORT_NULL is returned.
267   MachRendezvousPort PortForKey(MachPortsForRendezvous::key_type key);
268 
269   Lock lock_;
270   // The collection of ports that was acquired.
271   MachPortsForRendezvous ports_ GUARDED_BY(lock_);
272 };
273 
274 }  // namespace base
275 
276 #endif  // BASE_MAC_MACH_PORT_RENDEZVOUS_H_
277