| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/memory/platform_shared_memory_region.h" |
| |
| #include <mach/mach_vm.h> |
| |
| #include "base/mac/mach_logging.h" |
| #include "base/mac/scoped_mach_vm.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "build/build_config.h" |
| #include "starboard/types.h" |
| |
| #if defined(OS_IOS) |
| #error "MacOS only - iOS uses platform_shared_memory_region_posix.cc" |
| #endif |
| |
| namespace base { |
| namespace subtle { |
| |
| namespace { |
| |
| void LogCreateError(PlatformSharedMemoryRegion::CreateError error, |
| kern_return_t mac_error) { |
| UMA_HISTOGRAM_ENUMERATION("SharedMemory.CreateError", error); |
| if (mac_error != KERN_SUCCESS) |
| UmaHistogramSparse("SharedMemory.CreateMacError", mac_error); |
| } |
| |
| } // namespace |
| |
| // static |
| PlatformSharedMemoryRegion PlatformSharedMemoryRegion::Take( |
| mac::ScopedMachSendRight handle, |
| Mode mode, |
| size_t size, |
| const UnguessableToken& guid) { |
| if (!handle.is_valid()) |
| return {}; |
| |
| if (size == 0) |
| return {}; |
| |
| if (size > static_cast<size_t>(std::numeric_limits<int>::max())) |
| return {}; |
| |
| CHECK( |
| CheckPlatformHandlePermissionsCorrespondToMode(handle.get(), mode, size)); |
| |
| return PlatformSharedMemoryRegion(std::move(handle), mode, size, guid); |
| } |
| |
| // static |
| PlatformSharedMemoryRegion |
| PlatformSharedMemoryRegion::TakeFromSharedMemoryHandle( |
| const SharedMemoryHandle& handle, |
| Mode mode) { |
| CHECK(mode == Mode::kReadOnly || mode == Mode::kUnsafe); |
| CHECK(handle.GetType() == SharedMemoryHandle::MACH); |
| if (!handle.IsValid()) |
| return {}; |
| |
| return Take(base::mac::ScopedMachSendRight(handle.GetMemoryObject()), mode, |
| handle.GetSize(), handle.GetGUID()); |
| } |
| |
| mach_port_t PlatformSharedMemoryRegion::GetPlatformHandle() const { |
| return handle_.get(); |
| } |
| |
| bool PlatformSharedMemoryRegion::IsValid() const { |
| return handle_.is_valid(); |
| } |
| |
| PlatformSharedMemoryRegion PlatformSharedMemoryRegion::Duplicate() const { |
| if (!IsValid()) |
| return {}; |
| |
| CHECK_NE(mode_, Mode::kWritable) |
| << "Duplicating a writable shared memory region is prohibited"; |
| |
| // Increment the ref count. |
| kern_return_t kr = mach_port_mod_refs(mach_task_self(), handle_.get(), |
| MACH_PORT_RIGHT_SEND, 1); |
| if (kr != KERN_SUCCESS) { |
| MACH_DLOG(ERROR, kr) << "mach_port_mod_refs"; |
| return {}; |
| } |
| |
| return PlatformSharedMemoryRegion(mac::ScopedMachSendRight(handle_.get()), |
| mode_, size_, guid_); |
| } |
| |
| bool PlatformSharedMemoryRegion::ConvertToReadOnly() { |
| return ConvertToReadOnly(nullptr); |
| } |
| |
| bool PlatformSharedMemoryRegion::ConvertToReadOnly(void* mapped_addr) { |
| if (!IsValid()) |
| return false; |
| |
| CHECK_EQ(mode_, Mode::kWritable) |
| << "Only writable shared memory region can be converted to read-only"; |
| |
| mac::ScopedMachSendRight handle_copy(handle_.release()); |
| |
| void* temp_addr = mapped_addr; |
| mac::ScopedMachVM scoped_memory; |
| if (!temp_addr) { |
| // Intentionally lower current prot and max prot to |VM_PROT_READ|. |
| kern_return_t kr = mach_vm_map( |
| mach_task_self(), reinterpret_cast<mach_vm_address_t*>(&temp_addr), |
| size_, 0, VM_FLAGS_ANYWHERE, handle_copy.get(), 0, FALSE, VM_PROT_READ, |
| VM_PROT_READ, VM_INHERIT_NONE); |
| if (kr != KERN_SUCCESS) { |
| MACH_DLOG(ERROR, kr) << "mach_vm_map"; |
| return false; |
| } |
| scoped_memory.reset(reinterpret_cast<vm_address_t>(temp_addr), |
| mach_vm_round_page(size_)); |
| } |
| |
| // Make new memory object. |
| memory_object_size_t allocation_size = size_; |
| mac::ScopedMachSendRight named_right; |
| kern_return_t kr = mach_make_memory_entry_64( |
| mach_task_self(), &allocation_size, |
| reinterpret_cast<memory_object_offset_t>(temp_addr), VM_PROT_READ, |
| named_right.receive(), MACH_PORT_NULL); |
| if (kr != KERN_SUCCESS) { |
| MACH_DLOG(ERROR, kr) << "mach_make_memory_entry_64"; |
| return false; |
| } |
| DCHECK_GE(allocation_size, size_); |
| |
| handle_ = std::move(named_right); |
| mode_ = Mode::kReadOnly; |
| return true; |
| } |
| |
| bool PlatformSharedMemoryRegion::ConvertToUnsafe() { |
| if (!IsValid()) |
| return false; |
| |
| CHECK_EQ(mode_, Mode::kWritable) |
| << "Only writable shared memory region can be converted to unsafe"; |
| |
| mode_ = Mode::kUnsafe; |
| return true; |
| } |
| |
| bool PlatformSharedMemoryRegion::MapAtInternal(off_t offset, |
| size_t size, |
| void** memory, |
| size_t* mapped_size) const { |
| bool write_allowed = mode_ != Mode::kReadOnly; |
| vm_prot_t vm_prot_write = write_allowed ? VM_PROT_WRITE : 0; |
| kern_return_t kr = mach_vm_map( |
| mach_task_self(), |
| reinterpret_cast<mach_vm_address_t*>(memory), // Output parameter |
| size, |
| 0, // Alignment mask |
| VM_FLAGS_ANYWHERE, handle_.get(), offset, |
| FALSE, // Copy |
| VM_PROT_READ | vm_prot_write, // Current protection |
| VM_PROT_READ | vm_prot_write, // Maximum protection |
| VM_INHERIT_NONE); |
| if (kr != KERN_SUCCESS) { |
| MACH_DLOG(ERROR, kr) << "mach_vm_map"; |
| return false; |
| } |
| |
| *mapped_size = size; |
| return true; |
| } |
| |
| // static |
| PlatformSharedMemoryRegion PlatformSharedMemoryRegion::Create(Mode mode, |
| size_t size) { |
| if (size == 0) { |
| LogCreateError(CreateError::SIZE_ZERO, KERN_SUCCESS); |
| return {}; |
| } |
| |
| if (size > static_cast<size_t>(std::numeric_limits<int>::max())) { |
| LogCreateError(CreateError::SIZE_TOO_LARGE, KERN_SUCCESS); |
| return {}; |
| } |
| |
| CHECK_NE(mode, Mode::kReadOnly) << "Creating a region in read-only mode will " |
| "lead to this region being non-modifiable"; |
| |
| mach_vm_size_t vm_size = size; |
| mac::ScopedMachSendRight named_right; |
| kern_return_t kr = mach_make_memory_entry_64( |
| mach_task_self(), &vm_size, |
| 0, // Address. |
| MAP_MEM_NAMED_CREATE | VM_PROT_READ | VM_PROT_WRITE, |
| named_right.receive(), |
| MACH_PORT_NULL); // Parent handle. |
| if (kr != KERN_SUCCESS) |
| LogCreateError(CreateError::CREATE_FILE_MAPPING_FAILURE, kr); |
| // Crash as soon as shm allocation fails to debug the issue |
| // https://crbug.com/872237. |
| MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_make_memory_entry_64"; |
| DCHECK_GE(vm_size, size); |
| |
| LogCreateError(CreateError::SUCCESS, KERN_SUCCESS); |
| return PlatformSharedMemoryRegion(std::move(named_right), mode, size, |
| UnguessableToken::Create()); |
| } |
| |
| // static |
| bool PlatformSharedMemoryRegion::CheckPlatformHandlePermissionsCorrespondToMode( |
| PlatformHandle handle, |
| Mode mode, |
| size_t size) { |
| mach_vm_address_t temp_addr = 0; |
| kern_return_t kr = |
| mach_vm_map(mach_task_self(), &temp_addr, size, 0, VM_FLAGS_ANYWHERE, |
| handle, 0, FALSE, VM_PROT_READ | VM_PROT_WRITE, |
| VM_PROT_READ | VM_PROT_WRITE, VM_INHERIT_NONE); |
| if (kr == KERN_SUCCESS) { |
| kern_return_t kr_deallocate = |
| mach_vm_deallocate(mach_task_self(), temp_addr, size); |
| MACH_DLOG_IF(ERROR, kr_deallocate != KERN_SUCCESS, kr_deallocate) |
| << "mach_vm_deallocate"; |
| } else if (kr != KERN_INVALID_RIGHT) { |
| MACH_DLOG(ERROR, kr) << "mach_vm_map"; |
| return false; |
| } |
| |
| bool is_read_only = kr == KERN_INVALID_RIGHT; |
| bool expected_read_only = mode == Mode::kReadOnly; |
| |
| if (is_read_only != expected_read_only) { |
| DLOG(ERROR) << "VM region has a wrong protection mask: it is" |
| << (is_read_only ? " " : " not ") << "read-only but it should" |
| << (expected_read_only ? " " : " not ") << "be"; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| PlatformSharedMemoryRegion::PlatformSharedMemoryRegion( |
| mac::ScopedMachSendRight handle, |
| Mode mode, |
| size_t size, |
| const UnguessableToken& guid) |
| : handle_(std::move(handle)), mode_(mode), size_(size), guid_(guid) {} |
| |
| } // namespace subtle |
| } // namespace base |