xref: /aosp_15_r20/external/cronet/base/memory/shared_memory_switch.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2024 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 #include "base/memory/shared_memory_switch.h"
6 
7 #include <optional>
8 #include <string_view>
9 
10 #include "base/command_line.h"
11 #include "base/logging.h"
12 #include "base/memory/read_only_shared_memory_region.h"
13 #include "base/memory/unsafe_shared_memory_region.h"
14 #include "base/process/launch.h"
15 #include "base/process/process_handle.h"
16 #include "base/process/process_info.h"
17 #include "base/strings/strcat.h"
18 #include "base/strings/string_number_conversions.h"
19 #include "base/strings/string_split.h"
20 #include "base/types/expected.h"
21 #include "base/unguessable_token.h"
22 
23 // On Apple platforms, the shared memory handle is shared using a Mach port
24 // rendezvous key.
25 #if BUILDFLAG(IS_APPLE)
26 #include "base/mac/mach_port_rendezvous.h"
27 #endif
28 
29 // On POSIX, the shared memory handle is a file_descriptor mapped in the
30 // GlobalDescriptors table.
31 #if BUILDFLAG(IS_POSIX)
32 #include "base/posix/global_descriptors.h"
33 #endif
34 
35 #if BUILDFLAG(IS_WIN)
36 #include <windows.h>
37 
38 #include "base/win/win_util.h"
39 #endif
40 
41 #if BUILDFLAG(IS_FUCHSIA)
42 #include <lib/zx/vmo.h>
43 #include <zircon/process.h>
44 
45 #include "base/fuchsia/fuchsia_logging.h"
46 #endif
47 
48 // This file supports passing a read/write histogram shared memory region
49 // between a parent process and child process. The information about the
50 // shared memory region is encoded into a command-line switch value.
51 //
52 // Format: "handle,[irp],guid-high,guid-low,size".
53 //
54 // The switch value is composed of 5 segments, separated by commas:
55 //
56 // 1. The platform-specific handle id for the shared memory as a string.
57 // 2. [irp] to indicate whether the handle is inherited (i, most platforms),
58 //    sent via rendezvous (r, MacOS), or should be queried from the parent
59 //    (p, Windows).
60 // 3. The high 64 bits of the shared memory block GUID.
61 // 4. The low 64 bits of the shared memory block GUID.
62 // 5. The size of the shared memory segment as a string.
63 
64 namespace base {
65 namespace shared_memory {
66 namespace {
67 
68 using subtle::PlatformSharedMemoryRegion;
69 using subtle::ScopedPlatformSharedMemoryHandle;
70 
71 // The max shared memory size is artificially limited. This serves as a sanity
72 // check when serializing/deserializing the handle info. This value should be
73 // slightly larger than the largest shared memory size used in practice.
74 constexpr size_t kMaxSharedMemorySize = 8 << 20;  // 8 MiB
75 
76 // Return a scoped platform shared memory handle for |shmem_region|, possibly
77 // with permissions reduced to make the handle read-only.
GetPlatformHandle(PlatformSharedMemoryRegion & shmem_region,bool make_read_only)78 ScopedPlatformSharedMemoryHandle GetPlatformHandle(
79     PlatformSharedMemoryRegion& shmem_region,
80     [[maybe_unused]] bool make_read_only) {
81 #if BUILDFLAG(IS_FUCHSIA)
82   if (make_read_only) {
83     // For Fuchsia, ScopedPlatformSharedMemoryHandle <==> zx::vmo
84     zx::vmo scoped_handle;
85     zx_status_t status = shmem_region.GetPlatformHandle()->duplicate(
86         ZX_RIGHT_READ | ZX_RIGHT_MAP | ZX_RIGHT_TRANSFER |
87             ZX_RIGHT_GET_PROPERTY | ZX_RIGHT_DUPLICATE,
88         &scoped_handle);
89     ZX_CHECK(status == ZX_OK, status) << "zx_handle_duplicate";
90     return scoped_handle;
91   }
92 #endif  // BUILDFLAG(IS_FUCHSIA)
93   return shmem_region.PassPlatformHandle();
94 }
95 
96 // Serializes the shared memory region metadata to a string that can be added
97 // to the command-line of a child-process.
Serialize(PlatformSharedMemoryRegion shmem_region,bool is_read_only,MachPortsForRendezvous::key_type rendezvous_key,LaunchOptions * launch_options)98 std::string Serialize(PlatformSharedMemoryRegion shmem_region,
99                       bool is_read_only,
100 #if BUILDFLAG(IS_APPLE)
101                       MachPortsForRendezvous::key_type rendezvous_key,
102 #elif BUILDFLAG(IS_POSIX)
103                       GlobalDescriptors::Key descriptor_key,
104                       ScopedFD& descriptor_to_share,
105 #endif
106                       [[maybe_unused]] LaunchOptions* launch_options) {
107 #if !BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_APPLE)
108   CHECK(launch_options != nullptr);
109 #endif
110 
111   CHECK(shmem_region.IsValid());
112 
113   auto shmem_token = shmem_region.GetGUID();
114   auto shmem_size = shmem_region.GetSize();
115   auto shmem_handle = GetPlatformHandle(shmem_region, is_read_only);
116 
117   CHECK(shmem_token);
118   CHECK_NE(shmem_size, 0u);
119   CHECK_LE(shmem_size, kMaxSharedMemorySize);
120 
121   // Reserve memory for the serialized value.
122   // handle,method,hi,lo,size = 4 * 64-bit number + 1 char + 4 commas + NUL
123   //                          = (4 * 20-max decimal char) + 6 chars
124   //                          = 86 bytes
125   constexpr size_t kSerializedReservedSize = 86;
126 
127   std::string serialized;
128   serialized.reserve(kSerializedReservedSize);
129 
130 #if BUILDFLAG(IS_WIN)
131   // Ownership of the handle is passed to |launch_options|. We keep a non-
132   // owning alias for a moment, so we can serialize the handle's numeric
133   // value.
134   HANDLE handle = shmem_handle.release();
135   launch_options->handles_to_inherit.push_back(handle);
136 
137   // Tell the child process the name of the HANDLE and whether to handle can
138   // be inherited ('i') or must be duplicate from the parent process ('p').
139   StrAppend(&serialized, {NumberToString(win::HandleToUint32(handle)),
140                           (launch_options->elevated ? ",p," : ",i,")});
141 #elif BUILDFLAG(IS_APPLE)
142   // In the receiving child, the handle is looked up using the rendezvous key.
143   launch_options->mach_ports_for_rendezvous.emplace(
144       rendezvous_key, MachRendezvousPort(std::move(shmem_handle)));
145   StrAppend(&serialized, {NumberToString(rendezvous_key), ",r,"});
146 #elif BUILDFLAG(IS_FUCHSIA)
147   // The handle is passed via the handles to transfer launch options. The child
148   // will use the returned handle_id to lookup the handle. Ownership of the
149   // handle is transferred to |launch_options|.
150   uint32_t handle_id = LaunchOptions::AddHandleToTransfer(
151       &launch_options->handles_to_transfer, shmem_handle.release());
152   StrAppend(&serialized, {NumberToString(handle_id), ",i,"});
153 #elif BUILDFLAG(IS_POSIX)
154   // Serialize the key by which the child can lookup the shared memory handle.
155   // Ownership of the handle is transferred, via |descriptor_to_share|, to the
156   // caller, who is responsible for updating |launch_options| or the zygote
157   // launch parameters, as appropriate.
158   //
159   // TODO(crbug.com/1028263): Create a wrapper to release and return the primary
160   // descriptor for android (ScopedFD) vs non-android (ScopedFDPair).
161   //
162   // TODO(crbug.com/1028263): Get rid of |descriptor_to_share| and just populate
163   // |launch_options|. The caller should be responsible for translating between
164   // |launch_options| and zygote parameters as necessary.
165 #if BUILDFLAG(IS_ANDROID)
166   descriptor_to_share = std::move(shmem_handle);
167 #else
168   descriptor_to_share = std::move(shmem_handle.fd);
169 #endif
170   DVLOG(1) << "Sharing fd=" << descriptor_to_share.get()
171            << " with child process as fd_key=" << descriptor_key;
172   StrAppend(&serialized, {NumberToString(descriptor_key), ",i,"});
173 #else
174 #error "Unsupported OS"
175 #endif
176 
177   StrAppend(&serialized,
178             {NumberToString(shmem_token.GetHighForSerialization()), ",",
179              NumberToString(shmem_token.GetLowForSerialization()), ",",
180              NumberToString(shmem_size)});
181 
182   DCHECK_LT(serialized.size(), kSerializedReservedSize);
183   return serialized;
184 }
185 
186 // Deserialize |guid| from |hi_part| and |lo_part|, returning true on success.
DeserializeGUID(std::string_view hi_part,std::string_view lo_part)187 std::optional<UnguessableToken> DeserializeGUID(std::string_view hi_part,
188                                                 std::string_view lo_part) {
189   uint64_t hi = 0;
190   uint64_t lo = 0;
191   if (!StringToUint64(hi_part, &hi) || !StringToUint64(lo_part, &lo)) {
192     return std::nullopt;
193   }
194   return UnguessableToken::Deserialize(hi, lo);
195 }
196 
197 // Deserialize |switch_value| and return a corresponding writable shared memory
198 // region. On POSIX the handle is passed by |histogram_memory_descriptor_key|
199 // but |switch_value| is still required to describe the memory region.
Deserialize(std::string_view switch_value,PlatformSharedMemoryRegion::Mode mode)200 expected<PlatformSharedMemoryRegion, SharedMemoryError> Deserialize(
201     std::string_view switch_value,
202     PlatformSharedMemoryRegion::Mode mode) {
203   std::vector<std::string_view> tokens =
204       SplitStringPiece(switch_value, ",", KEEP_WHITESPACE, SPLIT_WANT_ALL);
205   if (tokens.size() != 5) {
206     return unexpected(SharedMemoryError::kUnexpectedTokensCount);
207   }
208 
209   // Parse the handle from tokens[0].
210   uint64_t shmem_handle = 0;
211   if (!StringToUint64(tokens[0], &shmem_handle)) {
212     return unexpected(SharedMemoryError::kParseInt0Failed);
213   }
214 
215   // token[1] has a fixed value but is ignored on all platforms except
216   // Windows, where it can be 'i' or 'p' to indicate that the handle is
217   // inherited or must be obtained from the parent.
218 #if BUILDFLAG(IS_WIN)
219   HANDLE handle = win::Uint32ToHandle(checked_cast<uint32_t>(shmem_handle));
220   if (tokens[1] == "p") {
221     DCHECK(IsCurrentProcessElevated());
222     // LaunchProcess doesn't have a way to duplicate the handle, but this
223     // process can since by definition it's not sandboxed.
224     win::ScopedHandle parent_handle(OpenProcess(
225         PROCESS_ALL_ACCESS, FALSE, GetParentProcessId(GetCurrentProcess())));
226     DuplicateHandle(parent_handle.get(), handle, GetCurrentProcess(), &handle,
227                     0, FALSE, DUPLICATE_SAME_ACCESS);
228   } else if (tokens[1] != "i") {
229     return unexpected(SharedMemoryError::kUnexpectedHandleType);
230   }
231   win::ScopedHandle scoped_handle(handle);
232 #elif BUILDFLAG(IS_APPLE)
233   DCHECK_EQ(tokens[1], "r");
234   auto* rendezvous = MachPortRendezvousClient::GetInstance();
235   if (!rendezvous) {
236     // Note: This matches mojo behavior in content/child/child_thread_impl.cc.
237     LOG(ERROR) << "No rendezvous client, terminating process (parent died?)";
238     Process::TerminateCurrentProcessImmediately(0);
239   }
240   apple::ScopedMachSendRight scoped_handle = rendezvous->TakeSendRight(
241       static_cast<MachPortsForRendezvous::key_type>(shmem_handle));
242   if (!scoped_handle.is_valid()) {
243     // Note: This matches mojo behavior in content/child/child_thread_impl.cc.
244     LOG(ERROR) << "Mach rendezvous failed, terminating process (parent died?)";
245     base::Process::TerminateCurrentProcessImmediately(0);
246   }
247 #elif BUILDFLAG(IS_FUCHSIA)
248   DCHECK_EQ(tokens[1], "i");
249   const uint32_t handle = checked_cast<uint32_t>(shmem_handle);
250   zx::vmo scoped_handle(zx_take_startup_handle(handle));
251   if (!scoped_handle.is_valid()) {
252     LOG(ERROR) << "Invalid shared mem handle: " << handle;
253     return unexpected(SharedMemoryError::kInvalidHandle);
254   }
255 #elif BUILDFLAG(IS_POSIX)
256   DCHECK_EQ(tokens[1], "i");
257   const int fd = GlobalDescriptors::GetInstance()->MaybeGet(
258       checked_cast<GlobalDescriptors::Key>(shmem_handle));
259   if (fd == -1) {
260     LOG(ERROR) << "Failed global descriptor lookup: " << shmem_handle;
261     return unexpected(SharedMemoryError::kGetFDFailed);
262   }
263   DVLOG(1) << "Opening shared memory handle " << fd << " shared as "
264            << shmem_handle;
265   ScopedFD scoped_handle(fd);
266 #else
267 #error Unsupported OS
268 #endif
269 
270   // Together, tokens[2] and tokens[3] encode the shared memory guid.
271   auto guid = DeserializeGUID(tokens[2], tokens[3]);
272   if (!guid.has_value()) {
273     return unexpected(SharedMemoryError::kDeserializeGUIDFailed);
274   }
275 
276   // The size is in tokens[4].
277   uint64_t size = 0;
278   if (!StringToUint64(tokens[4], &size)) {
279     return unexpected(SharedMemoryError::kParseInt4Failed);
280   }
281   if (size == 0 || size > kMaxSharedMemorySize) {
282     return unexpected(SharedMemoryError::kUnexpectedSize);
283   }
284 
285   // Resolve the handle to a shared memory region.
286   return PlatformSharedMemoryRegion::Take(
287       std::move(scoped_handle), mode, checked_cast<size_t>(size), guid.value());
288 }
289 
290 }  // namespace
291 
AddToLaunchParameters(std::string_view switch_name,ReadOnlySharedMemoryRegion read_only_memory_region,MachPortsForRendezvous::key_type rendezvous_key,CommandLine * command_line,LaunchOptions * launch_options)292 void AddToLaunchParameters(std::string_view switch_name,
293                            ReadOnlySharedMemoryRegion read_only_memory_region,
294 #if BUILDFLAG(IS_APPLE)
295                            MachPortsForRendezvous::key_type rendezvous_key,
296 #elif BUILDFLAG(IS_POSIX)
297                            GlobalDescriptors::Key descriptor_key,
298                            ScopedFD& out_descriptor_to_share,
299 #endif
300                            CommandLine* command_line,
301                            LaunchOptions* launch_options) {
302   std::string switch_value =
303       Serialize(ReadOnlySharedMemoryRegion::TakeHandleForSerialization(
304                     std::move(read_only_memory_region)),
305                 /*is_read_only=*/true,
306 #if BUILDFLAG(IS_APPLE)
307                 rendezvous_key,
308 #elif BUILDFLAG(IS_POSIX)
309                 descriptor_key, out_descriptor_to_share,
310 #endif
311                 launch_options);
312   command_line->AppendSwitchASCII(switch_name, switch_value);
313 }
314 
AddToLaunchParameters(std::string_view switch_name,UnsafeSharedMemoryRegion unsafe_memory_region,MachPortsForRendezvous::key_type rendezvous_key,CommandLine * command_line,LaunchOptions * launch_options)315 void AddToLaunchParameters(std::string_view switch_name,
316                            UnsafeSharedMemoryRegion unsafe_memory_region,
317 #if BUILDFLAG(IS_APPLE)
318                            MachPortsForRendezvous::key_type rendezvous_key,
319 #elif BUILDFLAG(IS_POSIX)
320                            GlobalDescriptors::Key descriptor_key,
321                            ScopedFD& out_descriptor_to_share,
322 #endif
323                            CommandLine* command_line,
324                            LaunchOptions* launch_options) {
325   std::string switch_value =
326       Serialize(UnsafeSharedMemoryRegion::TakeHandleForSerialization(
327                     std::move(unsafe_memory_region)),
328                 /*is_read_only=*/false,
329 #if BUILDFLAG(IS_APPLE)
330                 rendezvous_key,
331 #elif BUILDFLAG(IS_POSIX)
332                 descriptor_key, out_descriptor_to_share,
333 #endif
334                 launch_options);
335   command_line->AppendSwitchASCII(switch_name, switch_value);
336 }
337 
338 expected<UnsafeSharedMemoryRegion, SharedMemoryError>
UnsafeSharedMemoryRegionFrom(std::string_view switch_value)339 UnsafeSharedMemoryRegionFrom(std::string_view switch_value) {
340   auto platform_handle =
341       Deserialize(switch_value, PlatformSharedMemoryRegion::Mode::kUnsafe);
342   if (!platform_handle.has_value()) {
343     return unexpected(platform_handle.error());
344   }
345   auto shmem_region =
346       UnsafeSharedMemoryRegion::Deserialize(std::move(platform_handle).value());
347   if (!shmem_region.IsValid()) {
348     LOG(ERROR) << "Failed to deserialize writable memory handle";
349     return unexpected(SharedMemoryError::kDeserializeFailed);
350   }
351   return ok(std::move(shmem_region));
352 }
353 
354 expected<ReadOnlySharedMemoryRegion, SharedMemoryError>
ReadOnlySharedMemoryRegionFrom(std::string_view switch_value)355 ReadOnlySharedMemoryRegionFrom(std::string_view switch_value) {
356   auto platform_handle =
357       Deserialize(switch_value, PlatformSharedMemoryRegion::Mode::kReadOnly);
358   if (!platform_handle.has_value()) {
359     return unexpected(platform_handle.error());
360   }
361   auto shmem_region = ReadOnlySharedMemoryRegion::Deserialize(
362       std::move(platform_handle).value());
363   if (!shmem_region.IsValid()) {
364     LOG(ERROR) << "Faield to deserialize read-only memory handle";
365     return unexpected(SharedMemoryError::kDeserializeFailed);
366   }
367   return ok(std::move(shmem_region));
368 }
369 
370 }  // namespace shared_memory
371 }  // namespace base
372