blob: 7c413c5d521d8bdcda30a15147b877c14070b214 [file] [log] [blame]
Roger McFarlanec104d9402024-02-15 21:17:471// 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"
Roger McFarlane2f246882025-01-22 16:12:1412#include "base/memory/platform_shared_memory_handle.h"
Roger McFarlanec104d9402024-02-15 21:17:4713#include "base/memory/read_only_shared_memory_region.h"
14#include "base/memory/unsafe_shared_memory_region.h"
Gyuyoung Kim40cc667c2025-04-15 14:07:2615#include "base/notimplemented.h"
Roger McFarlanec104d9402024-02-15 21:17:4716#include "base/process/launch.h"
17#include "base/process/process_handle.h"
18#include "base/process/process_info.h"
19#include "base/strings/strcat.h"
20#include "base/strings/string_number_conversions.h"
21#include "base/strings/string_split.h"
Roger McFarlanec104d9402024-02-15 21:17:4722#include "base/unguessable_token.h"
23
24// On Apple platforms, the shared memory handle is shared using a Mach port
25// rendezvous key.
26#if BUILDFLAG(IS_APPLE)
Mark Roweced178d2024-10-08 21:16:0927#include "base/apple/mach_port_rendezvous.h"
Roger McFarlanec104d9402024-02-15 21:17:4728#endif
29
30// On POSIX, the shared memory handle is a file_descriptor mapped in the
31// GlobalDescriptors table.
32#if BUILDFLAG(IS_POSIX)
33#include "base/posix/global_descriptors.h"
34#endif
35
36#if BUILDFLAG(IS_WIN)
37#include <windows.h>
38
Alex Gough959bb3822025-04-07 03:32:4739#include "base/types/expected.h"
40#include "base/win/scoped_handle.h"
41#include "base/win/windows_handle_util.h"
Roger McFarlanec104d9402024-02-15 21:17:4742#endif
43
44#if BUILDFLAG(IS_FUCHSIA)
45#include <lib/zx/vmo.h>
46#include <zircon/process.h>
47
48#include "base/fuchsia/fuchsia_logging.h"
49#endif
50
51// This file supports passing a read/write histogram shared memory region
52// between a parent process and child process. The information about the
53// shared memory region is encoded into a command-line switch value.
54//
55// Format: "handle,[irp],guid-high,guid-low,size".
56//
57// The switch value is composed of 5 segments, separated by commas:
58//
59// 1. The platform-specific handle id for the shared memory as a string.
60// 2. [irp] to indicate whether the handle is inherited (i, most platforms),
61// sent via rendezvous (r, MacOS), or should be queried from the parent
62// (p, Windows).
63// 3. The high 64 bits of the shared memory block GUID.
64// 4. The low 64 bits of the shared memory block GUID.
65// 5. The size of the shared memory segment as a string.
Roger McFarlane2f246882025-01-22 16:12:1466//
67// TODO(crbug.com/389713696): Refactor the platform specific parts of this file.
Roger McFarlanec104d9402024-02-15 21:17:4768
Peter Kasting811504a72025-01-09 03:18:5069namespace base::shared_memory {
Roger McFarlanec104d9402024-02-15 21:17:4770namespace {
71
Roger McFarlane2f246882025-01-22 16:12:1472using base::subtle::PlatformSharedMemoryRegion;
73using base::subtle::ScopedPlatformSharedMemoryHandle;
Roger McFarlanec104d9402024-02-15 21:17:4774
75// The max shared memory size is artificially limited. This serves as a sanity
76// check when serializing/deserializing the handle info. This value should be
77// slightly larger than the largest shared memory size used in practice.
78constexpr size_t kMaxSharedMemorySize = 8 << 20; // 8 MiB
79
Roger McFarlane2f246882025-01-22 16:12:1480#if BUILDFLAG(IS_FUCHSIA)
Roger McFarlanec104d9402024-02-15 21:17:4781// Return a scoped platform shared memory handle for |shmem_region|, possibly
82// with permissions reduced to make the handle read-only.
Roger McFarlane2f246882025-01-22 16:12:1483// For Fuchsia:
84// * ScopedPlatformSharedMemoryHandle <==> zx::vmo
85zx::vmo GetFuchsiaHandle(ScopedPlatformSharedMemoryHandle shmem_handle,
86 bool make_read_only) {
87 if (!make_read_only) {
88 return shmem_handle;
Roger McFarlanec104d9402024-02-15 21:17:4789 }
Roger McFarlane2f246882025-01-22 16:12:1490 zx::vmo scoped_handle;
91 zx_status_t status =
92 shmem_handle.duplicate(ZX_RIGHT_READ | ZX_RIGHT_MAP | ZX_RIGHT_TRANSFER |
93 ZX_RIGHT_GET_PROPERTY | ZX_RIGHT_DUPLICATE,
94 &scoped_handle);
95 ZX_CHECK(status == ZX_OK, status) << "zx_handle_duplicate";
96 return scoped_handle;
Minoru Chikamune095b1222025-01-21 03:38:1597}
Roger McFarlane2f246882025-01-22 16:12:1498#endif // BUILDFLAG(IS_FUCHSIA)
Roger McFarlanec104d9402024-02-15 21:17:4799
100// Serializes the shared memory region metadata to a string that can be added
101// to the command-line of a child-process.
Roger McFarlane2f246882025-01-22 16:12:14102template <typename HandleType>
103std::string Serialize(HandleType shmem_handle,
104 const UnguessableToken& shmem_token,
105 size_t shmem_size,
106 [[maybe_unused]] bool is_read_only,
Roger McFarlanec104d9402024-02-15 21:17:47107#if BUILDFLAG(IS_APPLE)
Gyuyoung Kim40cc667c2025-04-15 14:07:26108 SharedMemoryMachPortRendezvousKey rendezvous_key,
Roger McFarlanec104d9402024-02-15 21:17:47109#elif BUILDFLAG(IS_POSIX)
110 GlobalDescriptors::Key descriptor_key,
111 ScopedFD& descriptor_to_share,
112#endif
113 [[maybe_unused]] LaunchOptions* launch_options) {
114#if !BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_APPLE)
115 CHECK(launch_options != nullptr);
116#endif
117
Roger McFarlanec104d9402024-02-15 21:17:47118 CHECK(shmem_token);
119 CHECK_NE(shmem_size, 0u);
120 CHECK_LE(shmem_size, kMaxSharedMemorySize);
121
122 // Reserve memory for the serialized value.
123 // handle,method,hi,lo,size = 4 * 64-bit number + 1 char + 4 commas + NUL
124 // = (4 * 20-max decimal char) + 6 chars
125 // = 86 bytes
126 constexpr size_t kSerializedReservedSize = 86;
127
128 std::string serialized;
129 serialized.reserve(kSerializedReservedSize);
130
131#if BUILDFLAG(IS_WIN)
Roger McFarlane2f246882025-01-22 16:12:14132 // If the child is launched in elevated privilege mode, it will query the
133 // parent for the handle. Otherwise, in non-elevated mode, the handle will
134 // be passed via process inheritance.
135 if (!launch_options->elevated) {
136 launch_options->handles_to_inherit.push_back(shmem_handle);
137 }
Roger McFarlanec104d9402024-02-15 21:17:47138
139 // Tell the child process the name of the HANDLE and whether to handle can
140 // be inherited ('i') or must be duplicate from the parent process ('p').
Roger McFarlane2f246882025-01-22 16:12:14141 StrAppend(&serialized, {NumberToString(win::HandleToUint32(shmem_handle)),
Roger McFarlanec104d9402024-02-15 21:17:47142 (launch_options->elevated ? ",p," : ",i,")});
143#elif BUILDFLAG(IS_APPLE)
Gyuyoung Kim40cc667c2025-04-15 14:07:26144#if !BUILDFLAG(IS_IOS_TVOS)
Roger McFarlanec104d9402024-02-15 21:17:47145 // In the receiving child, the handle is looked up using the rendezvous key.
146 launch_options->mach_ports_for_rendezvous.emplace(
147 rendezvous_key, MachRendezvousPort(std::move(shmem_handle)));
148 StrAppend(&serialized, {NumberToString(rendezvous_key), ",r,"});
Gyuyoung Kim40cc667c2025-04-15 14:07:26149#endif
Roger McFarlanec104d9402024-02-15 21:17:47150#elif BUILDFLAG(IS_FUCHSIA)
151 // The handle is passed via the handles to transfer launch options. The child
152 // will use the returned handle_id to lookup the handle. Ownership of the
153 // handle is transferred to |launch_options|.
Roger McFarlane2f246882025-01-22 16:12:14154 zx::vmo scoped_handle =
155 GetFuchsiaHandle(std::move(shmem_handle), is_read_only);
Roger McFarlanec104d9402024-02-15 21:17:47156 uint32_t handle_id = LaunchOptions::AddHandleToTransfer(
Roger McFarlane2f246882025-01-22 16:12:14157 &launch_options->handles_to_transfer, scoped_handle.release());
Roger McFarlanec104d9402024-02-15 21:17:47158 StrAppend(&serialized, {NumberToString(handle_id), ",i,"});
159#elif BUILDFLAG(IS_POSIX)
160 // Serialize the key by which the child can lookup the shared memory handle.
161 // Ownership of the handle is transferred, via |descriptor_to_share|, to the
162 // caller, who is responsible for updating |launch_options| or the zygote
163 // launch parameters, as appropriate.
164 //
Alison Gale59c007a2024-04-20 03:05:40165 // TODO(crbug.com/40109064): Create a wrapper to release and return the
166 // primary descriptor for android (ScopedFD) vs non-android (ScopedFDPair).
Roger McFarlanec104d9402024-02-15 21:17:47167 //
Alison Gale59c007a2024-04-20 03:05:40168 // TODO(crbug.com/40109064): Get rid of |descriptor_to_share| and just
169 // populate |launch_options|. The caller should be responsible for translating
170 // between |launch_options| and zygote parameters as necessary.
Roger McFarlanec104d9402024-02-15 21:17:47171#if BUILDFLAG(IS_ANDROID)
172 descriptor_to_share = std::move(shmem_handle);
173#else
174 descriptor_to_share = std::move(shmem_handle.fd);
175#endif
176 DVLOG(1) << "Sharing fd=" << descriptor_to_share.get()
177 << " with child process as fd_key=" << descriptor_key;
178 StrAppend(&serialized, {NumberToString(descriptor_key), ",i,"});
179#else
180#error "Unsupported OS"
181#endif
182
183 StrAppend(&serialized,
184 {NumberToString(shmem_token.GetHighForSerialization()), ",",
185 NumberToString(shmem_token.GetLowForSerialization()), ",",
186 NumberToString(shmem_size)});
187
188 DCHECK_LT(serialized.size(), kSerializedReservedSize);
189 return serialized;
190}
191
192// Deserialize |guid| from |hi_part| and |lo_part|, returning true on success.
193std::optional<UnguessableToken> DeserializeGUID(std::string_view hi_part,
194 std::string_view lo_part) {
195 uint64_t hi = 0;
196 uint64_t lo = 0;
197 if (!StringToUint64(hi_part, &hi) || !StringToUint64(lo_part, &lo)) {
198 return std::nullopt;
199 }
200 return UnguessableToken::Deserialize(hi, lo);
201}
202
203// Deserialize |switch_value| and return a corresponding writable shared memory
204// region. On POSIX the handle is passed by |histogram_memory_descriptor_key|
205// but |switch_value| is still required to describe the memory region.
206expected<PlatformSharedMemoryRegion, SharedMemoryError> Deserialize(
207 std::string_view switch_value,
208 PlatformSharedMemoryRegion::Mode mode) {
209 std::vector<std::string_view> tokens =
210 SplitStringPiece(switch_value, ",", KEEP_WHITESPACE, SPLIT_WANT_ALL);
211 if (tokens.size() != 5) {
212 return unexpected(SharedMemoryError::kUnexpectedTokensCount);
213 }
214
215 // Parse the handle from tokens[0].
216 uint64_t shmem_handle = 0;
217 if (!StringToUint64(tokens[0], &shmem_handle)) {
218 return unexpected(SharedMemoryError::kParseInt0Failed);
219 }
220
221 // token[1] has a fixed value but is ignored on all platforms except
222 // Windows, where it can be 'i' or 'p' to indicate that the handle is
223 // inherited or must be obtained from the parent.
224#if BUILDFLAG(IS_WIN)
225 HANDLE handle = win::Uint32ToHandle(checked_cast<uint32_t>(shmem_handle));
226 if (tokens[1] == "p") {
227 DCHECK(IsCurrentProcessElevated());
228 // LaunchProcess doesn't have a way to duplicate the handle, but this
229 // process can since by definition it's not sandboxed.
Greg Thompson1816ed92025-01-15 09:21:46230 if (ProcessId parent_pid = GetParentProcessId(GetCurrentProcessHandle());
231 parent_pid == kNullProcessId) {
232 return unexpected(SharedMemoryError::kInvalidHandle);
233 } else if (auto parent =
234 Process::OpenWithAccess(parent_pid, PROCESS_ALL_ACCESS);
235 !parent.IsValid() ||
236 !::DuplicateHandle(parent.Handle(), handle,
237 ::GetCurrentProcess(), &handle, 0, FALSE,
238 DUPLICATE_SAME_ACCESS)) {
239 return unexpected(SharedMemoryError::kInvalidHandle);
240 }
Roger McFarlanec104d9402024-02-15 21:17:47241 } else if (tokens[1] != "i") {
242 return unexpected(SharedMemoryError::kUnexpectedHandleType);
243 }
Greg Thompson1816ed92025-01-15 09:21:46244 // Under some situations, the handle value provided on the command line does
245 // not refer to a valid Section object. Fail gracefully rather than wrapping
246 // the value in a ScopedHandle, as that will lead to a crash when CloseHandle
247 // fails; see https://crbug.com/40071993.
248 win::ScopedHandle scoped_handle;
249 if (auto handle_or_error = win::TakeHandleOfType(
250 std::exchange(handle, kNullProcessHandle), L"Section");
251 !handle_or_error.has_value()) {
252 return unexpected(SharedMemoryError::kInvalidHandle);
253 } else {
254 scoped_handle = *std::move(handle_or_error);
255 }
Gyuyoung Kim40cc667c2025-04-15 14:07:26256#elif BUILDFLAG(IS_IOS_TVOS)
257 // Create an empty handle to prevent a build failure when returning a writable
258 // shared memory region at the end of the function.
259 apple::ScopedMachSendRight scoped_handle;
260 TVOS_NOT_YET_IMPLEMENTED();
Roger McFarlanec104d9402024-02-15 21:17:47261#elif BUILDFLAG(IS_APPLE)
262 DCHECK_EQ(tokens[1], "r");
263 auto* rendezvous = MachPortRendezvousClient::GetInstance();
264 if (!rendezvous) {
265 // Note: This matches mojo behavior in content/child/child_thread_impl.cc.
266 LOG(ERROR) << "No rendezvous client, terminating process (parent died?)";
267 Process::TerminateCurrentProcessImmediately(0);
268 }
269 apple::ScopedMachSendRight scoped_handle = rendezvous->TakeSendRight(
270 static_cast<MachPortsForRendezvous::key_type>(shmem_handle));
271 if (!scoped_handle.is_valid()) {
272 // Note: This matches mojo behavior in content/child/child_thread_impl.cc.
273 LOG(ERROR) << "Mach rendezvous failed, terminating process (parent died?)";
274 base::Process::TerminateCurrentProcessImmediately(0);
275 }
276#elif BUILDFLAG(IS_FUCHSIA)
277 DCHECK_EQ(tokens[1], "i");
278 const uint32_t handle = checked_cast<uint32_t>(shmem_handle);
279 zx::vmo scoped_handle(zx_take_startup_handle(handle));
280 if (!scoped_handle.is_valid()) {
281 LOG(ERROR) << "Invalid shared mem handle: " << handle;
282 return unexpected(SharedMemoryError::kInvalidHandle);
283 }
284#elif BUILDFLAG(IS_POSIX)
285 DCHECK_EQ(tokens[1], "i");
286 const int fd = GlobalDescriptors::GetInstance()->MaybeGet(
287 checked_cast<GlobalDescriptors::Key>(shmem_handle));
288 if (fd == -1) {
289 LOG(ERROR) << "Failed global descriptor lookup: " << shmem_handle;
290 return unexpected(SharedMemoryError::kGetFDFailed);
291 }
292 DVLOG(1) << "Opening shared memory handle " << fd << " shared as "
293 << shmem_handle;
294 ScopedFD scoped_handle(fd);
295#else
296#error Unsupported OS
297#endif
298
299 // Together, tokens[2] and tokens[3] encode the shared memory guid.
300 auto guid = DeserializeGUID(tokens[2], tokens[3]);
301 if (!guid.has_value()) {
302 return unexpected(SharedMemoryError::kDeserializeGUIDFailed);
303 }
304
305 // The size is in tokens[4].
306 uint64_t size = 0;
307 if (!StringToUint64(tokens[4], &size)) {
308 return unexpected(SharedMemoryError::kParseInt4Failed);
309 }
310 if (size == 0 || size > kMaxSharedMemorySize) {
311 return unexpected(SharedMemoryError::kUnexpectedSize);
312 }
313
314 // Resolve the handle to a shared memory region.
315 return PlatformSharedMemoryRegion::Take(
316 std::move(scoped_handle), mode, checked_cast<size_t>(size), guid.value());
317}
318
Roger McFarlane2f246882025-01-22 16:12:14319template <typename RegionType>
320void AddToLaunchParametersImpl(std::string_view switch_name,
321 const RegionType& memory_region,
Roger McFarlanec104d9402024-02-15 21:17:47322#if BUILDFLAG(IS_APPLE)
Gyuyoung Kim40cc667c2025-04-15 14:07:26323 SharedMemoryMachPortRendezvousKey rendezvous_key,
Roger McFarlanec104d9402024-02-15 21:17:47324#elif BUILDFLAG(IS_POSIX)
Roger McFarlane2f246882025-01-22 16:12:14325 GlobalDescriptors::Key descriptor_key,
326 ScopedFD& out_descriptor_to_share,
Roger McFarlanec104d9402024-02-15 21:17:47327#endif
Roger McFarlane2f246882025-01-22 16:12:14328 CommandLine* command_line,
329 LaunchOptions* launch_options) {
330#if BUILDFLAG(IS_WIN)
331 auto token = memory_region.GetGUID();
332 auto size = memory_region.GetSize();
333 auto handle = memory_region.GetPlatformHandle();
334#else
335 auto region =
336 RegionType::TakeHandleForSerialization(memory_region.Duplicate());
337 auto token = region.GetGUID();
338 auto size = region.GetSize();
339 auto handle = region.PassPlatformHandle();
340#endif // !BUILDFLAG(IS_WIN)
341 constexpr bool is_read_only =
342 std::is_same<RegionType, ReadOnlySharedMemoryRegion>::value;
Roger McFarlanec104d9402024-02-15 21:17:47343 std::string switch_value =
Roger McFarlane2f246882025-01-22 16:12:14344 Serialize(std::move(handle), token, size, is_read_only,
Roger McFarlanec104d9402024-02-15 21:17:47345#if BUILDFLAG(IS_APPLE)
346 rendezvous_key,
347#elif BUILDFLAG(IS_POSIX)
348 descriptor_key, out_descriptor_to_share,
349#endif
350 launch_options);
351 command_line->AppendSwitchASCII(switch_name, switch_value);
352}
353
Roger McFarlane2f246882025-01-22 16:12:14354} // namespace
355
356void AddToLaunchParameters(
357 std::string_view switch_name,
358 const ReadOnlySharedMemoryRegion& read_only_memory_region,
359#if BUILDFLAG(IS_APPLE)
Gyuyoung Kim40cc667c2025-04-15 14:07:26360 SharedMemoryMachPortRendezvousKey rendezvous_key,
Roger McFarlane2f246882025-01-22 16:12:14361#elif BUILDFLAG(IS_POSIX)
362 GlobalDescriptors::Key descriptor_key,
363 ScopedFD& out_descriptor_to_share,
364#endif
365 CommandLine* command_line,
366 LaunchOptions* launch_options) {
367 AddToLaunchParametersImpl(switch_name, read_only_memory_region,
368#if BUILDFLAG(IS_APPLE)
369 rendezvous_key,
370#elif BUILDFLAG(IS_POSIX)
371 descriptor_key, out_descriptor_to_share,
372#endif
373 command_line, launch_options);
374}
375
Roger McFarlanec104d9402024-02-15 21:17:47376void AddToLaunchParameters(std::string_view switch_name,
Roger McFarlane2f246882025-01-22 16:12:14377 const UnsafeSharedMemoryRegion& unsafe_memory_region,
Roger McFarlanec104d9402024-02-15 21:17:47378#if BUILDFLAG(IS_APPLE)
Gyuyoung Kim40cc667c2025-04-15 14:07:26379 SharedMemoryMachPortRendezvousKey rendezvous_key,
Roger McFarlanec104d9402024-02-15 21:17:47380#elif BUILDFLAG(IS_POSIX)
381 GlobalDescriptors::Key descriptor_key,
382 ScopedFD& out_descriptor_to_share,
383#endif
384 CommandLine* command_line,
385 LaunchOptions* launch_options) {
Roger McFarlane2f246882025-01-22 16:12:14386 AddToLaunchParametersImpl(switch_name, unsafe_memory_region,
Roger McFarlanec104d9402024-02-15 21:17:47387#if BUILDFLAG(IS_APPLE)
Roger McFarlane2f246882025-01-22 16:12:14388 rendezvous_key,
Roger McFarlanec104d9402024-02-15 21:17:47389#elif BUILDFLAG(IS_POSIX)
Roger McFarlane2f246882025-01-22 16:12:14390 descriptor_key, out_descriptor_to_share,
Roger McFarlanec104d9402024-02-15 21:17:47391#endif
Roger McFarlane2f246882025-01-22 16:12:14392 command_line, launch_options);
Roger McFarlanec104d9402024-02-15 21:17:47393}
394
395expected<UnsafeSharedMemoryRegion, SharedMemoryError>
396UnsafeSharedMemoryRegionFrom(std::string_view switch_value) {
397 auto platform_handle =
398 Deserialize(switch_value, PlatformSharedMemoryRegion::Mode::kUnsafe);
399 if (!platform_handle.has_value()) {
400 return unexpected(platform_handle.error());
401 }
402 auto shmem_region =
403 UnsafeSharedMemoryRegion::Deserialize(std::move(platform_handle).value());
404 if (!shmem_region.IsValid()) {
405 LOG(ERROR) << "Failed to deserialize writable memory handle";
406 return unexpected(SharedMemoryError::kDeserializeFailed);
407 }
408 return ok(std::move(shmem_region));
409}
410
411expected<ReadOnlySharedMemoryRegion, SharedMemoryError>
412ReadOnlySharedMemoryRegionFrom(std::string_view switch_value) {
413 auto platform_handle =
414 Deserialize(switch_value, PlatformSharedMemoryRegion::Mode::kReadOnly);
415 if (!platform_handle.has_value()) {
416 return unexpected(platform_handle.error());
417 }
418 auto shmem_region = ReadOnlySharedMemoryRegion::Deserialize(
419 std::move(platform_handle).value());
420 if (!shmem_region.IsValid()) {
Roger McFarlane2f246882025-01-22 16:12:14421 LOG(ERROR) << "Failed to deserialize read-only memory handle";
Roger McFarlanec104d9402024-02-15 21:17:47422 return unexpected(SharedMemoryError::kDeserializeFailed);
423 }
424 return ok(std::move(shmem_region));
425}
426
Peter Kasting811504a72025-01-09 03:18:50427} // namespace base::shared_memory