|  | // Copyright (c) 2012 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/shared_memory.h" | 
|  |  | 
|  | #include <stddef.h> | 
|  | #include <stdint.h> | 
|  |  | 
|  | #include <memory> | 
|  |  | 
|  | #include "base/atomicops.h" | 
|  | #include "base/base_switches.h" | 
|  | #include "base/bind.h" | 
|  | #include "base/command_line.h" | 
|  | #include "base/logging.h" | 
|  | #include "base/macros.h" | 
|  | #include "base/memory/shared_memory_handle.h" | 
|  | #include "base/process/kill.h" | 
|  | #include "base/rand_util.h" | 
|  | #include "base/strings/string_number_conversions.h" | 
|  | #include "base/strings/string_piece.h" | 
|  | #include "base/strings/string_util.h" | 
|  | #include "base/sys_info.h" | 
|  | #include "base/test/multiprocess_test.h" | 
|  | #include "base/threading/platform_thread.h" | 
|  | #include "base/time/time.h" | 
|  | #include "base/unguessable_token.h" | 
|  | #include "build/build_config.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  | #include "testing/multiprocess_func_list.h" | 
|  |  | 
|  | #if defined(OS_ANDROID) | 
|  | #include "base/callback.h" | 
|  | #endif | 
|  |  | 
|  | #if defined(OS_POSIX) | 
|  | #include <errno.h> | 
|  | #include <fcntl.h> | 
|  | #include <sys/mman.h> | 
|  | #include <sys/stat.h> | 
|  | #include <sys/types.h> | 
|  | #include <unistd.h> | 
|  | #endif | 
|  |  | 
|  | #if defined(OS_LINUX) | 
|  | #include <sys/syscall.h> | 
|  | #endif | 
|  |  | 
|  | #if defined(OS_WIN) | 
|  | #include "base/win/scoped_handle.h" | 
|  | #endif | 
|  |  | 
|  | #if defined(OS_FUCHSIA) | 
|  | #include <zircon/process.h> | 
|  | #include <zircon/syscalls.h> | 
|  | #include "base/fuchsia/scoped_zx_handle.h" | 
|  | #endif | 
|  |  | 
|  | namespace base { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | #if !defined(OS_MACOSX) && !defined(OS_FUCHSIA) | 
|  | // Each thread will open the shared memory.  Each thread will take a different 4 | 
|  | // byte int pointer, and keep changing it, with some small pauses in between. | 
|  | // Verify that each thread's value in the shared memory is always correct. | 
|  | class MultipleThreadMain : public PlatformThread::Delegate { | 
|  | public: | 
|  | explicit MultipleThreadMain(int16_t id) : id_(id) {} | 
|  | ~MultipleThreadMain() override = default; | 
|  |  | 
|  | static void CleanUp() { | 
|  | SharedMemory memory; | 
|  | memory.Delete(s_test_name_); | 
|  | } | 
|  |  | 
|  | // PlatformThread::Delegate interface. | 
|  | void ThreadMain() override { | 
|  | const uint32_t kDataSize = 1024; | 
|  | SharedMemory memory; | 
|  | bool rv = memory.CreateNamedDeprecated(s_test_name_, true, kDataSize); | 
|  | EXPECT_TRUE(rv); | 
|  | rv = memory.Map(kDataSize); | 
|  | EXPECT_TRUE(rv); | 
|  | int* ptr = static_cast<int*>(memory.memory()) + id_; | 
|  | EXPECT_EQ(0, *ptr); | 
|  |  | 
|  | for (int idx = 0; idx < 100; idx++) { | 
|  | *ptr = idx; | 
|  | PlatformThread::Sleep(TimeDelta::FromMilliseconds(1)); | 
|  | EXPECT_EQ(*ptr, idx); | 
|  | } | 
|  | // Reset back to 0 for the next test that uses the same name. | 
|  | *ptr = 0; | 
|  |  | 
|  | memory.Close(); | 
|  | } | 
|  |  | 
|  | private: | 
|  | int16_t id_; | 
|  |  | 
|  | static const char s_test_name_[]; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(MultipleThreadMain); | 
|  | }; | 
|  |  | 
|  | const char MultipleThreadMain::s_test_name_[] = | 
|  | "SharedMemoryOpenThreadTest"; | 
|  | #endif  // !defined(OS_MACOSX) && !defined(OS_FUCHSIA) | 
|  |  | 
|  | enum class Mode { | 
|  | Default, | 
|  | #if defined(OS_LINUX) && !defined(OS_CHROMEOS) | 
|  | DisableDevShm = 1, | 
|  | #endif | 
|  | }; | 
|  |  | 
|  | class SharedMemoryTest : public ::testing::TestWithParam<Mode> { | 
|  | public: | 
|  | void SetUp() override { | 
|  | switch (GetParam()) { | 
|  | case Mode::Default: | 
|  | break; | 
|  | #if defined(OS_LINUX) && !defined(OS_CHROMEOS) | 
|  | case Mode::DisableDevShm: | 
|  | CommandLine* cmdline = CommandLine::ForCurrentProcess(); | 
|  | cmdline->AppendSwitch(switches::kDisableDevShmUsage); | 
|  | break; | 
|  | #endif  // defined(OS_LINUX) && !defined(OS_CHROMEOS) | 
|  | } | 
|  | } | 
|  | }; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | // Android/Mac/Fuchsia doesn't support SharedMemory::Open/Delete/ | 
|  | // CreateNamedDeprecated(openExisting=true) | 
|  | #if !defined(OS_ANDROID) && !defined(OS_MACOSX) && !defined(OS_FUCHSIA) | 
|  |  | 
|  | TEST_P(SharedMemoryTest, OpenClose) { | 
|  | const uint32_t kDataSize = 1024; | 
|  | std::string test_name = "SharedMemoryOpenCloseTest"; | 
|  |  | 
|  | // Open two handles to a memory segment, confirm that they are mapped | 
|  | // separately yet point to the same space. | 
|  | SharedMemory memory1; | 
|  | bool rv = memory1.Delete(test_name); | 
|  | EXPECT_TRUE(rv); | 
|  | rv = memory1.Delete(test_name); | 
|  | EXPECT_TRUE(rv); | 
|  | rv = memory1.Open(test_name, false); | 
|  | EXPECT_FALSE(rv); | 
|  | rv = memory1.CreateNamedDeprecated(test_name, false, kDataSize); | 
|  | EXPECT_TRUE(rv); | 
|  | rv = memory1.Map(kDataSize); | 
|  | EXPECT_TRUE(rv); | 
|  | SharedMemory memory2; | 
|  | rv = memory2.Open(test_name, false); | 
|  | EXPECT_TRUE(rv); | 
|  | rv = memory2.Map(kDataSize); | 
|  | EXPECT_TRUE(rv); | 
|  | EXPECT_NE(memory1.memory(), memory2.memory());  // Compare the pointers. | 
|  |  | 
|  | // Make sure we don't segfault. (it actually happened!) | 
|  | ASSERT_NE(memory1.memory(), static_cast<void*>(nullptr)); | 
|  | ASSERT_NE(memory2.memory(), static_cast<void*>(nullptr)); | 
|  |  | 
|  | // Write data to the first memory segment, verify contents of second. | 
|  | memset(memory1.memory(), '1', kDataSize); | 
|  | EXPECT_EQ(memcmp(memory1.memory(), memory2.memory(), kDataSize), 0); | 
|  |  | 
|  | // Close the first memory segment, and verify the second has the right data. | 
|  | memory1.Close(); | 
|  | char* start_ptr = static_cast<char*>(memory2.memory()); | 
|  | char* end_ptr = start_ptr + kDataSize; | 
|  | for (char* ptr = start_ptr; ptr < end_ptr; ptr++) | 
|  | EXPECT_EQ(*ptr, '1'); | 
|  |  | 
|  | // Close the second memory segment. | 
|  | memory2.Close(); | 
|  |  | 
|  | rv = memory1.Delete(test_name); | 
|  | EXPECT_TRUE(rv); | 
|  | rv = memory2.Delete(test_name); | 
|  | EXPECT_TRUE(rv); | 
|  | } | 
|  |  | 
|  | TEST_P(SharedMemoryTest, OpenExclusive) { | 
|  | const uint32_t kDataSize = 1024; | 
|  | const uint32_t kDataSize2 = 2048; | 
|  | std::ostringstream test_name_stream; | 
|  | test_name_stream << "SharedMemoryOpenExclusiveTest." | 
|  | << Time::Now().ToDoubleT(); | 
|  | std::string test_name = test_name_stream.str(); | 
|  |  | 
|  | // Open two handles to a memory segment and check that | 
|  | // open_existing_deprecated works as expected. | 
|  | SharedMemory memory1; | 
|  | bool rv = memory1.CreateNamedDeprecated(test_name, false, kDataSize); | 
|  | EXPECT_TRUE(rv); | 
|  |  | 
|  | // Memory1 knows it's size because it created it. | 
|  | EXPECT_EQ(memory1.requested_size(), kDataSize); | 
|  |  | 
|  | rv = memory1.Map(kDataSize); | 
|  | EXPECT_TRUE(rv); | 
|  |  | 
|  | // The mapped memory1 must be at least the size we asked for. | 
|  | EXPECT_GE(memory1.mapped_size(), kDataSize); | 
|  |  | 
|  | // The mapped memory1 shouldn't exceed rounding for allocation granularity. | 
|  | EXPECT_LT(memory1.mapped_size(), | 
|  | kDataSize + SysInfo::VMAllocationGranularity()); | 
|  |  | 
|  | memset(memory1.memory(), 'G', kDataSize); | 
|  |  | 
|  | SharedMemory memory2; | 
|  | // Should not be able to create if openExisting is false. | 
|  | rv = memory2.CreateNamedDeprecated(test_name, false, kDataSize2); | 
|  | EXPECT_FALSE(rv); | 
|  |  | 
|  | // Should be able to create with openExisting true. | 
|  | rv = memory2.CreateNamedDeprecated(test_name, true, kDataSize2); | 
|  | EXPECT_TRUE(rv); | 
|  |  | 
|  | // Memory2 shouldn't know the size because we didn't create it. | 
|  | EXPECT_EQ(memory2.requested_size(), 0U); | 
|  |  | 
|  | // We should be able to map the original size. | 
|  | rv = memory2.Map(kDataSize); | 
|  | EXPECT_TRUE(rv); | 
|  |  | 
|  | // The mapped memory2 must be at least the size of the original. | 
|  | EXPECT_GE(memory2.mapped_size(), kDataSize); | 
|  |  | 
|  | // The mapped memory2 shouldn't exceed rounding for allocation granularity. | 
|  | EXPECT_LT(memory2.mapped_size(), | 
|  | kDataSize2 + SysInfo::VMAllocationGranularity()); | 
|  |  | 
|  | // Verify that opening memory2 didn't truncate or delete memory 1. | 
|  | char* start_ptr = static_cast<char*>(memory2.memory()); | 
|  | char* end_ptr = start_ptr + kDataSize; | 
|  | for (char* ptr = start_ptr; ptr < end_ptr; ptr++) { | 
|  | EXPECT_EQ(*ptr, 'G'); | 
|  | } | 
|  |  | 
|  | memory1.Close(); | 
|  | memory2.Close(); | 
|  |  | 
|  | rv = memory1.Delete(test_name); | 
|  | EXPECT_TRUE(rv); | 
|  | } | 
|  | #endif  // !defined(OS_ANDROID) && !defined(OS_MACOSX) && !defined(OS_FUCHSIA) | 
|  |  | 
|  | // Check that memory is still mapped after its closed. | 
|  | TEST_P(SharedMemoryTest, CloseNoUnmap) { | 
|  | const size_t kDataSize = 4096; | 
|  |  | 
|  | SharedMemory memory; | 
|  | ASSERT_TRUE(memory.CreateAndMapAnonymous(kDataSize)); | 
|  | char* ptr = static_cast<char*>(memory.memory()); | 
|  | ASSERT_NE(ptr, static_cast<void*>(nullptr)); | 
|  | memset(ptr, 'G', kDataSize); | 
|  |  | 
|  | memory.Close(); | 
|  |  | 
|  | EXPECT_EQ(ptr, memory.memory()); | 
|  | EXPECT_TRUE(!memory.handle().IsValid()); | 
|  |  | 
|  | for (size_t i = 0; i < kDataSize; i++) { | 
|  | EXPECT_EQ('G', ptr[i]); | 
|  | } | 
|  |  | 
|  | memory.Unmap(); | 
|  | EXPECT_EQ(nullptr, memory.memory()); | 
|  | } | 
|  |  | 
|  | #if !defined(OS_MACOSX) && !defined(OS_FUCHSIA) | 
|  | // Create a set of N threads to each open a shared memory segment and write to | 
|  | // it. Verify that they are always reading/writing consistent data. | 
|  | TEST_P(SharedMemoryTest, MultipleThreads) { | 
|  | const int kNumThreads = 5; | 
|  |  | 
|  | MultipleThreadMain::CleanUp(); | 
|  | // On POSIX we have a problem when 2 threads try to create the shmem | 
|  | // (a file) at exactly the same time, since create both creates the | 
|  | // file and zerofills it.  We solve the problem for this unit test | 
|  | // (make it not flaky) by starting with 1 thread, then | 
|  | // intentionally don't clean up its shmem before running with | 
|  | // kNumThreads. | 
|  |  | 
|  | int threadcounts[] = { 1, kNumThreads }; | 
|  | for (size_t i = 0; i < arraysize(threadcounts); i++) { | 
|  | int numthreads = threadcounts[i]; | 
|  | std::unique_ptr<PlatformThreadHandle[]> thread_handles; | 
|  | std::unique_ptr<MultipleThreadMain* []> thread_delegates; | 
|  |  | 
|  | thread_handles.reset(new PlatformThreadHandle[numthreads]); | 
|  | thread_delegates.reset(new MultipleThreadMain*[numthreads]); | 
|  |  | 
|  | // Spawn the threads. | 
|  | for (int16_t index = 0; index < numthreads; index++) { | 
|  | PlatformThreadHandle pth; | 
|  | thread_delegates[index] = new MultipleThreadMain(index); | 
|  | EXPECT_TRUE(PlatformThread::Create(0, thread_delegates[index], &pth)); | 
|  | thread_handles[index] = pth; | 
|  | } | 
|  |  | 
|  | // Wait for the threads to finish. | 
|  | for (int index = 0; index < numthreads; index++) { | 
|  | PlatformThread::Join(thread_handles[index]); | 
|  | delete thread_delegates[index]; | 
|  | } | 
|  | } | 
|  | MultipleThreadMain::CleanUp(); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | // Allocate private (unique) shared memory with an empty string for a | 
|  | // name.  Make sure several of them don't point to the same thing as | 
|  | // we might expect if the names are equal. | 
|  | TEST_P(SharedMemoryTest, AnonymousPrivate) { | 
|  | int i, j; | 
|  | int count = 4; | 
|  | bool rv; | 
|  | const uint32_t kDataSize = 8192; | 
|  |  | 
|  | std::unique_ptr<SharedMemory[]> memories(new SharedMemory[count]); | 
|  | std::unique_ptr<int* []> pointers(new int*[count]); | 
|  | ASSERT_TRUE(memories.get()); | 
|  | ASSERT_TRUE(pointers.get()); | 
|  |  | 
|  | for (i = 0; i < count; i++) { | 
|  | rv = memories[i].CreateAndMapAnonymous(kDataSize); | 
|  | EXPECT_TRUE(rv); | 
|  | int* ptr = static_cast<int*>(memories[i].memory()); | 
|  | EXPECT_TRUE(ptr); | 
|  | pointers[i] = ptr; | 
|  | } | 
|  |  | 
|  | for (i = 0; i < count; i++) { | 
|  | // zero out the first int in each except for i; for that one, make it 100. | 
|  | for (j = 0; j < count; j++) { | 
|  | if (i == j) | 
|  | pointers[j][0] = 100; | 
|  | else | 
|  | pointers[j][0] = 0; | 
|  | } | 
|  | // make sure there is no bleeding of the 100 into the other pointers | 
|  | for (j = 0; j < count; j++) { | 
|  | if (i == j) | 
|  | EXPECT_EQ(100, pointers[j][0]); | 
|  | else | 
|  | EXPECT_EQ(0, pointers[j][0]); | 
|  | } | 
|  | } | 
|  |  | 
|  | for (int i = 0; i < count; i++) { | 
|  | memories[i].Close(); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_P(SharedMemoryTest, GetReadOnlyHandle) { | 
|  | StringPiece contents = "Hello World"; | 
|  |  | 
|  | SharedMemory writable_shmem; | 
|  | SharedMemoryCreateOptions options; | 
|  | options.size = contents.size(); | 
|  | options.share_read_only = true; | 
|  | #if defined(OS_MACOSX) && !defined(OS_IOS) | 
|  | // The Mach functionality is tested in shared_memory_mac_unittest.cc. | 
|  | options.type = SharedMemoryHandle::POSIX; | 
|  | #endif | 
|  | ASSERT_TRUE(writable_shmem.Create(options)); | 
|  | ASSERT_TRUE(writable_shmem.Map(options.size)); | 
|  | memcpy(writable_shmem.memory(), contents.data(), contents.size()); | 
|  | EXPECT_TRUE(writable_shmem.Unmap()); | 
|  |  | 
|  | SharedMemoryHandle readonly_handle = writable_shmem.GetReadOnlyHandle(); | 
|  | EXPECT_EQ(writable_shmem.handle().GetGUID(), readonly_handle.GetGUID()); | 
|  | EXPECT_EQ(writable_shmem.handle().GetSize(), readonly_handle.GetSize()); | 
|  | ASSERT_TRUE(readonly_handle.IsValid()); | 
|  | SharedMemory readonly_shmem(readonly_handle, /*readonly=*/true); | 
|  |  | 
|  | ASSERT_TRUE(readonly_shmem.Map(contents.size())); | 
|  | EXPECT_EQ(contents, | 
|  | StringPiece(static_cast<const char*>(readonly_shmem.memory()), | 
|  | contents.size())); | 
|  | EXPECT_TRUE(readonly_shmem.Unmap()); | 
|  |  | 
|  | #if defined(OS_ANDROID) | 
|  | // On Android, mapping a region through a read-only descriptor makes the | 
|  | // region read-only. Any writable mapping attempt should fail. | 
|  | ASSERT_FALSE(writable_shmem.Map(contents.size())); | 
|  | #else | 
|  | // Make sure the writable instance is still writable. | 
|  | ASSERT_TRUE(writable_shmem.Map(contents.size())); | 
|  | StringPiece new_contents = "Goodbye"; | 
|  | memcpy(writable_shmem.memory(), new_contents.data(), new_contents.size()); | 
|  | EXPECT_EQ(new_contents, | 
|  | StringPiece(static_cast<const char*>(writable_shmem.memory()), | 
|  | new_contents.size())); | 
|  | #endif | 
|  |  | 
|  | // We'd like to check that if we send the read-only segment to another | 
|  | // process, then that other process can't reopen it read/write.  (Since that | 
|  | // would be a security hole.)  Setting up multiple processes is hard in a | 
|  | // unittest, so this test checks that the *current* process can't reopen the | 
|  | // segment read/write.  I think the test here is stronger than we actually | 
|  | // care about, but there's a remote possibility that sending a file over a | 
|  | // pipe would transform it into read/write. | 
|  | SharedMemoryHandle handle = readonly_shmem.handle(); | 
|  |  | 
|  | #if defined(OS_ANDROID) | 
|  | // The "read-only" handle is still writable on Android: | 
|  | // http://crbug.com/320865 | 
|  | (void)handle; | 
|  | #elif defined(OS_FUCHSIA) | 
|  | uintptr_t addr; | 
|  | EXPECT_NE(ZX_OK, zx_vmar_map(zx_vmar_root_self(), 0, handle.GetHandle(), 0, | 
|  | contents.size(), ZX_VM_FLAG_PERM_WRITE, &addr)) | 
|  | << "Shouldn't be able to map as writable."; | 
|  |  | 
|  | ScopedZxHandle duped_handle; | 
|  | EXPECT_NE(ZX_OK, zx_handle_duplicate(handle.GetHandle(), ZX_RIGHT_WRITE, | 
|  | duped_handle.receive())) | 
|  | << "Shouldn't be able to duplicate the handle into a writable one."; | 
|  |  | 
|  | EXPECT_EQ(ZX_OK, zx_handle_duplicate(handle.GetHandle(), ZX_RIGHT_READ, | 
|  | duped_handle.receive())) | 
|  | << "Should be able to duplicate the handle into a readable one."; | 
|  | #elif defined(OS_POSIX) | 
|  | int handle_fd = SharedMemory::GetFdFromSharedMemoryHandle(handle); | 
|  | EXPECT_EQ(O_RDONLY, fcntl(handle_fd, F_GETFL) & O_ACCMODE) | 
|  | << "The descriptor itself should be read-only."; | 
|  |  | 
|  | errno = 0; | 
|  | void* writable = mmap(nullptr, contents.size(), PROT_READ | PROT_WRITE, | 
|  | MAP_SHARED, handle_fd, 0); | 
|  | int mmap_errno = errno; | 
|  | EXPECT_EQ(MAP_FAILED, writable) | 
|  | << "It shouldn't be possible to re-mmap the descriptor writable."; | 
|  | EXPECT_EQ(EACCES, mmap_errno) << strerror(mmap_errno); | 
|  | if (writable != MAP_FAILED) | 
|  | EXPECT_EQ(0, munmap(writable, readonly_shmem.mapped_size())); | 
|  |  | 
|  | #elif defined(OS_WIN) | 
|  | EXPECT_EQ(NULL, MapViewOfFile(handle.GetHandle(), FILE_MAP_WRITE, 0, 0, 0)) | 
|  | << "Shouldn't be able to map memory writable."; | 
|  |  | 
|  | HANDLE temp_handle; | 
|  | BOOL rv = ::DuplicateHandle(GetCurrentProcess(), handle.GetHandle(), | 
|  | GetCurrentProcess(), &temp_handle, | 
|  | FILE_MAP_ALL_ACCESS, false, 0); | 
|  | EXPECT_EQ(FALSE, rv) | 
|  | << "Shouldn't be able to duplicate the handle into a writable one."; | 
|  | if (rv) | 
|  | win::ScopedHandle writable_handle(temp_handle); | 
|  | rv = ::DuplicateHandle(GetCurrentProcess(), handle.GetHandle(), | 
|  | GetCurrentProcess(), &temp_handle, FILE_MAP_READ, | 
|  | false, 0); | 
|  | EXPECT_EQ(TRUE, rv) | 
|  | << "Should be able to duplicate the handle into a readable one."; | 
|  | if (rv) | 
|  | win::ScopedHandle writable_handle(temp_handle); | 
|  | #else | 
|  | #error Unexpected platform; write a test that tries to make 'handle' writable. | 
|  | #endif  // defined(OS_POSIX) || defined(OS_WIN) | 
|  | } | 
|  |  | 
|  | TEST_P(SharedMemoryTest, ShareToSelf) { | 
|  | StringPiece contents = "Hello World"; | 
|  |  | 
|  | SharedMemory shmem; | 
|  | ASSERT_TRUE(shmem.CreateAndMapAnonymous(contents.size())); | 
|  | memcpy(shmem.memory(), contents.data(), contents.size()); | 
|  | EXPECT_TRUE(shmem.Unmap()); | 
|  |  | 
|  | SharedMemoryHandle shared_handle = shmem.handle().Duplicate(); | 
|  | ASSERT_TRUE(shared_handle.IsValid()); | 
|  | EXPECT_TRUE(shared_handle.OwnershipPassesToIPC()); | 
|  | EXPECT_EQ(shared_handle.GetGUID(), shmem.handle().GetGUID()); | 
|  | EXPECT_EQ(shared_handle.GetSize(), shmem.handle().GetSize()); | 
|  | SharedMemory shared(shared_handle, /*readonly=*/false); | 
|  |  | 
|  | ASSERT_TRUE(shared.Map(contents.size())); | 
|  | EXPECT_EQ( | 
|  | contents, | 
|  | StringPiece(static_cast<const char*>(shared.memory()), contents.size())); | 
|  |  | 
|  | shared_handle = shmem.handle().Duplicate(); | 
|  | ASSERT_TRUE(shared_handle.IsValid()); | 
|  | ASSERT_TRUE(shared_handle.OwnershipPassesToIPC()); | 
|  | SharedMemory readonly(shared_handle, /*readonly=*/true); | 
|  |  | 
|  | ASSERT_TRUE(readonly.Map(contents.size())); | 
|  | EXPECT_EQ(contents, | 
|  | StringPiece(static_cast<const char*>(readonly.memory()), | 
|  | contents.size())); | 
|  | } | 
|  |  | 
|  | TEST_P(SharedMemoryTest, ShareWithMultipleInstances) { | 
|  | static const StringPiece kContents = "Hello World"; | 
|  |  | 
|  | SharedMemory shmem; | 
|  | ASSERT_TRUE(shmem.CreateAndMapAnonymous(kContents.size())); | 
|  | // We do not need to unmap |shmem| to let |shared| map. | 
|  | const StringPiece shmem_contents(static_cast<const char*>(shmem.memory()), | 
|  | shmem.requested_size()); | 
|  |  | 
|  | SharedMemoryHandle shared_handle = shmem.handle().Duplicate(); | 
|  | ASSERT_TRUE(shared_handle.IsValid()); | 
|  | SharedMemory shared(shared_handle, /*readonly=*/false); | 
|  | ASSERT_TRUE(shared.Map(kContents.size())); | 
|  | // The underlying shared memory is created by |shmem|, so both | 
|  | // |shared|.requested_size() and |readonly|.requested_size() are zero. | 
|  | ASSERT_EQ(0U, shared.requested_size()); | 
|  | const StringPiece shared_contents(static_cast<const char*>(shared.memory()), | 
|  | shmem.requested_size()); | 
|  |  | 
|  | shared_handle = shmem.handle().Duplicate(); | 
|  | ASSERT_TRUE(shared_handle.IsValid()); | 
|  | ASSERT_TRUE(shared_handle.OwnershipPassesToIPC()); | 
|  | SharedMemory readonly(shared_handle, /*readonly=*/true); | 
|  | ASSERT_TRUE(readonly.Map(kContents.size())); | 
|  | ASSERT_EQ(0U, readonly.requested_size()); | 
|  | const StringPiece readonly_contents( | 
|  | static_cast<const char*>(readonly.memory()), | 
|  | shmem.requested_size()); | 
|  |  | 
|  | // |shmem| should be able to update the content. | 
|  | memcpy(shmem.memory(), kContents.data(), kContents.size()); | 
|  |  | 
|  | ASSERT_EQ(kContents, shmem_contents); | 
|  | ASSERT_EQ(kContents, shared_contents); | 
|  | ASSERT_EQ(kContents, readonly_contents); | 
|  |  | 
|  | // |shared| should also be able to update the content. | 
|  | memcpy(shared.memory(), ToLowerASCII(kContents).c_str(), kContents.size()); | 
|  |  | 
|  | ASSERT_EQ(StringPiece(ToLowerASCII(kContents)), shmem_contents); | 
|  | ASSERT_EQ(StringPiece(ToLowerASCII(kContents)), shared_contents); | 
|  | ASSERT_EQ(StringPiece(ToLowerASCII(kContents)), readonly_contents); | 
|  | } | 
|  |  | 
|  | TEST_P(SharedMemoryTest, MapAt) { | 
|  | ASSERT_TRUE(SysInfo::VMAllocationGranularity() >= sizeof(uint32_t)); | 
|  | const size_t kCount = SysInfo::VMAllocationGranularity(); | 
|  | const size_t kDataSize = kCount * sizeof(uint32_t); | 
|  |  | 
|  | SharedMemory memory; | 
|  | ASSERT_TRUE(memory.CreateAndMapAnonymous(kDataSize)); | 
|  | uint32_t* ptr = static_cast<uint32_t*>(memory.memory()); | 
|  | ASSERT_NE(ptr, static_cast<void*>(nullptr)); | 
|  |  | 
|  | for (size_t i = 0; i < kCount; ++i) { | 
|  | ptr[i] = i; | 
|  | } | 
|  |  | 
|  | memory.Unmap(); | 
|  |  | 
|  | off_t offset = SysInfo::VMAllocationGranularity(); | 
|  | ASSERT_TRUE(memory.MapAt(offset, kDataSize - offset)); | 
|  | offset /= sizeof(uint32_t); | 
|  | ptr = static_cast<uint32_t*>(memory.memory()); | 
|  | ASSERT_NE(ptr, static_cast<void*>(nullptr)); | 
|  | for (size_t i = offset; i < kCount; ++i) { | 
|  | EXPECT_EQ(ptr[i - offset], i); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_P(SharedMemoryTest, MapTwice) { | 
|  | const uint32_t kDataSize = 1024; | 
|  | SharedMemory memory; | 
|  | bool rv = memory.CreateAndMapAnonymous(kDataSize); | 
|  | EXPECT_TRUE(rv); | 
|  |  | 
|  | void* old_address = memory.memory(); | 
|  |  | 
|  | rv = memory.Map(kDataSize); | 
|  | EXPECT_FALSE(rv); | 
|  | EXPECT_EQ(old_address, memory.memory()); | 
|  | } | 
|  |  | 
|  | #if defined(OS_POSIX) | 
|  | // This test is not applicable for iOS (crbug.com/399384). | 
|  | #if !defined(OS_IOS) | 
|  | // Create a shared memory object, mmap it, and mprotect it to PROT_EXEC. | 
|  | TEST_P(SharedMemoryTest, AnonymousExecutable) { | 
|  | const uint32_t kTestSize = 1 << 16; | 
|  |  | 
|  | SharedMemory shared_memory; | 
|  | SharedMemoryCreateOptions options; | 
|  | options.size = kTestSize; | 
|  | options.executable = true; | 
|  | #if defined(OS_MACOSX) && !defined(OS_IOS) | 
|  | // The Mach functionality is tested in shared_memory_mac_unittest.cc. | 
|  | options.type = SharedMemoryHandle::POSIX; | 
|  | #endif | 
|  |  | 
|  | EXPECT_TRUE(shared_memory.Create(options)); | 
|  | EXPECT_TRUE(shared_memory.Map(shared_memory.requested_size())); | 
|  |  | 
|  | EXPECT_EQ(0, mprotect(shared_memory.memory(), shared_memory.requested_size(), | 
|  | PROT_READ | PROT_EXEC)); | 
|  | } | 
|  | #endif  // !defined(OS_IOS) | 
|  |  | 
|  | #if defined(OS_ANDROID) | 
|  | // This test is restricted to Android since there is no way on other platforms | 
|  | // to guarantee that a region can never be mapped with PROT_EXEC. E.g. on | 
|  | // Linux, anonymous shared regions come from /dev/shm which can be mounted | 
|  | // without 'noexec'. In this case, anything can perform an mprotect() to | 
|  | // change the protection mask of a given page. | 
|  | TEST(SharedMemoryTest, AnonymousIsNotExecutableByDefault) { | 
|  | const uint32_t kTestSize = 1 << 16; | 
|  |  | 
|  | SharedMemory shared_memory; | 
|  | SharedMemoryCreateOptions options; | 
|  | options.size = kTestSize; | 
|  |  | 
|  | EXPECT_TRUE(shared_memory.Create(options)); | 
|  | EXPECT_TRUE(shared_memory.Map(shared_memory.requested_size())); | 
|  |  | 
|  | errno = 0; | 
|  | EXPECT_EQ(-1, mprotect(shared_memory.memory(), shared_memory.requested_size(), | 
|  | PROT_READ | PROT_EXEC)); | 
|  | EXPECT_EQ(EACCES, errno); | 
|  | } | 
|  | #endif  // OS_ANDROID | 
|  |  | 
|  | // Android supports a different permission model than POSIX for its "ashmem" | 
|  | // shared memory implementation. So the tests about file permissions are not | 
|  | // included on Android. Fuchsia does not use a file-backed shared memory | 
|  | // implementation. | 
|  |  | 
|  | #if !defined(OS_ANDROID) && !defined(OS_FUCHSIA) | 
|  |  | 
|  | // Set a umask and restore the old mask on destruction. | 
|  | class ScopedUmaskSetter { | 
|  | public: | 
|  | explicit ScopedUmaskSetter(mode_t target_mask) { | 
|  | old_umask_ = umask(target_mask); | 
|  | } | 
|  | ~ScopedUmaskSetter() { umask(old_umask_); } | 
|  | private: | 
|  | mode_t old_umask_; | 
|  | DISALLOW_IMPLICIT_CONSTRUCTORS(ScopedUmaskSetter); | 
|  | }; | 
|  |  | 
|  | // Create a shared memory object, check its permissions. | 
|  | TEST_P(SharedMemoryTest, FilePermissionsAnonymous) { | 
|  | const uint32_t kTestSize = 1 << 8; | 
|  |  | 
|  | SharedMemory shared_memory; | 
|  | SharedMemoryCreateOptions options; | 
|  | options.size = kTestSize; | 
|  | #if defined(OS_MACOSX) && !defined(OS_IOS) | 
|  | // The Mach functionality is tested in shared_memory_mac_unittest.cc. | 
|  | options.type = SharedMemoryHandle::POSIX; | 
|  | #endif | 
|  | // Set a file mode creation mask that gives all permissions. | 
|  | ScopedUmaskSetter permissive_mask(S_IWGRP | S_IWOTH); | 
|  |  | 
|  | EXPECT_TRUE(shared_memory.Create(options)); | 
|  |  | 
|  | int shm_fd = | 
|  | SharedMemory::GetFdFromSharedMemoryHandle(shared_memory.handle()); | 
|  | struct stat shm_stat; | 
|  | EXPECT_EQ(0, fstat(shm_fd, &shm_stat)); | 
|  | // Neither the group, nor others should be able to read the shared memory | 
|  | // file. | 
|  | EXPECT_FALSE(shm_stat.st_mode & S_IRWXO); | 
|  | EXPECT_FALSE(shm_stat.st_mode & S_IRWXG); | 
|  | } | 
|  |  | 
|  | // Create a shared memory object, check its permissions. | 
|  | TEST_P(SharedMemoryTest, FilePermissionsNamed) { | 
|  | const uint32_t kTestSize = 1 << 8; | 
|  |  | 
|  | SharedMemory shared_memory; | 
|  | SharedMemoryCreateOptions options; | 
|  | options.size = kTestSize; | 
|  | #if defined(OS_MACOSX) && !defined(OS_IOS) | 
|  | // The Mach functionality is tested in shared_memory_mac_unittest.cc. | 
|  | options.type = SharedMemoryHandle::POSIX; | 
|  | #endif | 
|  |  | 
|  | // Set a file mode creation mask that gives all permissions. | 
|  | ScopedUmaskSetter permissive_mask(S_IWGRP | S_IWOTH); | 
|  |  | 
|  | EXPECT_TRUE(shared_memory.Create(options)); | 
|  |  | 
|  | int fd = SharedMemory::GetFdFromSharedMemoryHandle(shared_memory.handle()); | 
|  | struct stat shm_stat; | 
|  | EXPECT_EQ(0, fstat(fd, &shm_stat)); | 
|  | // Neither the group, nor others should have been able to open the shared | 
|  | // memory file while its name existed. | 
|  | EXPECT_FALSE(shm_stat.st_mode & S_IRWXO); | 
|  | EXPECT_FALSE(shm_stat.st_mode & S_IRWXG); | 
|  | } | 
|  | #endif  // !defined(OS_ANDROID) && !defined(OS_FUCHSIA) | 
|  |  | 
|  | #endif  // defined(OS_POSIX) | 
|  |  | 
|  | // Map() will return addresses which are aligned to the platform page size, this | 
|  | // varies from platform to platform though.  Since we'd like to advertise a | 
|  | // minimum alignment that callers can count on, test for it here. | 
|  | TEST_P(SharedMemoryTest, MapMinimumAlignment) { | 
|  | static const int kDataSize = 8192; | 
|  |  | 
|  | SharedMemory shared_memory; | 
|  | ASSERT_TRUE(shared_memory.CreateAndMapAnonymous(kDataSize)); | 
|  | EXPECT_EQ(0U, reinterpret_cast<uintptr_t>( | 
|  | shared_memory.memory()) & (SharedMemory::MAP_MINIMUM_ALIGNMENT - 1)); | 
|  | shared_memory.Close(); | 
|  | } | 
|  |  | 
|  | #if defined(OS_WIN) | 
|  | TEST_P(SharedMemoryTest, UnsafeImageSection) { | 
|  | const char kTestSectionName[] = "UnsafeImageSection"; | 
|  | wchar_t path[MAX_PATH]; | 
|  | EXPECT_GT(::GetModuleFileName(nullptr, path, arraysize(path)), 0U); | 
|  |  | 
|  | // Map the current executable image to save us creating a new PE file on disk. | 
|  | base::win::ScopedHandle file_handle(::CreateFile( | 
|  | path, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr)); | 
|  | EXPECT_TRUE(file_handle.IsValid()); | 
|  | base::win::ScopedHandle section_handle( | 
|  | ::CreateFileMappingA(file_handle.Get(), nullptr, | 
|  | PAGE_READONLY | SEC_IMAGE, 0, 0, kTestSectionName)); | 
|  | EXPECT_TRUE(section_handle.IsValid()); | 
|  |  | 
|  | // Check direct opening by name, from handle and duplicated from handle. | 
|  | SharedMemory shared_memory_open; | 
|  | EXPECT_TRUE(shared_memory_open.Open(kTestSectionName, true)); | 
|  | EXPECT_FALSE(shared_memory_open.Map(1)); | 
|  | EXPECT_EQ(nullptr, shared_memory_open.memory()); | 
|  |  | 
|  | SharedMemory shared_memory_handle_local( | 
|  | SharedMemoryHandle(section_handle.Take(), 1, UnguessableToken::Create()), | 
|  | true); | 
|  | EXPECT_FALSE(shared_memory_handle_local.Map(1)); | 
|  | EXPECT_EQ(nullptr, shared_memory_handle_local.memory()); | 
|  |  | 
|  | // Check that a handle without SECTION_QUERY also can't be mapped as it can't | 
|  | // be checked. | 
|  | SharedMemory shared_memory_handle_dummy; | 
|  | SharedMemoryCreateOptions options; | 
|  | options.size = 0x1000; | 
|  | EXPECT_TRUE(shared_memory_handle_dummy.Create(options)); | 
|  | HANDLE handle_no_query; | 
|  | EXPECT_TRUE(::DuplicateHandle( | 
|  | ::GetCurrentProcess(), shared_memory_handle_dummy.handle().GetHandle(), | 
|  | ::GetCurrentProcess(), &handle_no_query, FILE_MAP_READ, FALSE, 0)); | 
|  | SharedMemory shared_memory_handle_no_query( | 
|  | SharedMemoryHandle(handle_no_query, options.size, | 
|  | UnguessableToken::Create()), | 
|  | true); | 
|  | EXPECT_FALSE(shared_memory_handle_no_query.Map(1)); | 
|  | EXPECT_EQ(nullptr, shared_memory_handle_no_query.memory()); | 
|  | } | 
|  | #endif  // defined(OS_WIN) | 
|  |  | 
|  | // iOS does not allow multiple processes. | 
|  | // Android ashmem does not support named shared memory. | 
|  | // Fuchsia SharedMemory does not support named shared memory. | 
|  | // Mac SharedMemory does not support named shared memory. crbug.com/345734 | 
|  | #if !defined(OS_IOS) && !defined(OS_ANDROID) && !defined(OS_MACOSX) && \ | 
|  | !defined(OS_FUCHSIA) | 
|  | // On POSIX it is especially important we test shmem across processes, | 
|  | // not just across threads.  But the test is enabled on all platforms. | 
|  | class SharedMemoryProcessTest : public MultiProcessTest { | 
|  | public: | 
|  | static void CleanUp() { | 
|  | SharedMemory memory; | 
|  | memory.Delete(s_test_name_); | 
|  | } | 
|  |  | 
|  | static int TaskTestMain() { | 
|  | int errors = 0; | 
|  | SharedMemory memory; | 
|  | bool rv = memory.CreateNamedDeprecated(s_test_name_, true, s_data_size_); | 
|  | EXPECT_TRUE(rv); | 
|  | if (rv != true) | 
|  | errors++; | 
|  | rv = memory.Map(s_data_size_); | 
|  | EXPECT_TRUE(rv); | 
|  | if (rv != true) | 
|  | errors++; | 
|  | int* ptr = static_cast<int*>(memory.memory()); | 
|  |  | 
|  | // This runs concurrently in multiple processes. Writes need to be atomic. | 
|  | subtle::Barrier_AtomicIncrement(ptr, 1); | 
|  | memory.Close(); | 
|  | return errors; | 
|  | } | 
|  |  | 
|  | static const char s_test_name_[]; | 
|  | static const uint32_t s_data_size_; | 
|  | }; | 
|  |  | 
|  | const char SharedMemoryProcessTest::s_test_name_[] = "MPMem"; | 
|  | const uint32_t SharedMemoryProcessTest::s_data_size_ = 1024; | 
|  |  | 
|  | TEST_F(SharedMemoryProcessTest, SharedMemoryAcrossProcesses) { | 
|  | const int kNumTasks = 5; | 
|  |  | 
|  | SharedMemoryProcessTest::CleanUp(); | 
|  |  | 
|  | // Create a shared memory region. Set the first word to 0. | 
|  | SharedMemory memory; | 
|  | bool rv = memory.CreateNamedDeprecated(s_test_name_, true, s_data_size_); | 
|  | ASSERT_TRUE(rv); | 
|  | rv = memory.Map(s_data_size_); | 
|  | ASSERT_TRUE(rv); | 
|  | int* ptr = static_cast<int*>(memory.memory()); | 
|  | *ptr = 0; | 
|  |  | 
|  | // Start |kNumTasks| processes, each of which atomically increments the first | 
|  | // word by 1. | 
|  | Process processes[kNumTasks]; | 
|  | for (int index = 0; index < kNumTasks; ++index) { | 
|  | processes[index] = SpawnChild("SharedMemoryTestMain"); | 
|  | ASSERT_TRUE(processes[index].IsValid()); | 
|  | } | 
|  |  | 
|  | // Check that each process exited correctly. | 
|  | int exit_code = 0; | 
|  | for (int index = 0; index < kNumTasks; ++index) { | 
|  | EXPECT_TRUE(processes[index].WaitForExit(&exit_code)); | 
|  | EXPECT_EQ(0, exit_code); | 
|  | } | 
|  |  | 
|  | // Check that the shared memory region reflects |kNumTasks| increments. | 
|  | ASSERT_EQ(kNumTasks, *ptr); | 
|  |  | 
|  | memory.Close(); | 
|  | SharedMemoryProcessTest::CleanUp(); | 
|  | } | 
|  |  | 
|  | MULTIPROCESS_TEST_MAIN(SharedMemoryTestMain) { | 
|  | return SharedMemoryProcessTest::TaskTestMain(); | 
|  | } | 
|  | #endif  // !defined(OS_IOS) && !defined(OS_ANDROID) && !defined(OS_MACOSX) && | 
|  | // !defined(OS_FUCHSIA) | 
|  |  | 
|  | TEST_P(SharedMemoryTest, MappedId) { | 
|  | const uint32_t kDataSize = 1024; | 
|  | SharedMemory memory; | 
|  | SharedMemoryCreateOptions options; | 
|  | options.size = kDataSize; | 
|  | #if defined(OS_MACOSX) && !defined(OS_IOS) | 
|  | // The Mach functionality is tested in shared_memory_mac_unittest.cc. | 
|  | options.type = SharedMemoryHandle::POSIX; | 
|  | #endif | 
|  |  | 
|  | EXPECT_TRUE(memory.Create(options)); | 
|  | base::UnguessableToken id = memory.handle().GetGUID(); | 
|  | EXPECT_FALSE(id.is_empty()); | 
|  | EXPECT_TRUE(memory.mapped_id().is_empty()); | 
|  |  | 
|  | EXPECT_TRUE(memory.Map(kDataSize)); | 
|  | EXPECT_EQ(id, memory.mapped_id()); | 
|  |  | 
|  | memory.Close(); | 
|  | EXPECT_EQ(id, memory.mapped_id()); | 
|  |  | 
|  | memory.Unmap(); | 
|  | EXPECT_TRUE(memory.mapped_id().is_empty()); | 
|  | } | 
|  |  | 
|  | INSTANTIATE_TEST_CASE_P(Default, | 
|  | SharedMemoryTest, | 
|  | ::testing::Values(Mode::Default)); | 
|  | #if defined(OS_LINUX) && !defined(OS_CHROMEOS) | 
|  | INSTANTIATE_TEST_CASE_P(SkipDevShm, | 
|  | SharedMemoryTest, | 
|  | ::testing::Values(Mode::DisableDevShm)); | 
|  | #endif  // defined(OS_LINUX) && !defined(OS_CHROMEOS) | 
|  |  | 
|  | #if defined(OS_ANDROID) | 
|  | TEST(SharedMemoryTest, ReadOnlyRegions) { | 
|  | const uint32_t kDataSize = 1024; | 
|  | SharedMemory memory; | 
|  | SharedMemoryCreateOptions options; | 
|  | options.size = kDataSize; | 
|  | EXPECT_TRUE(memory.Create(options)); | 
|  |  | 
|  | EXPECT_FALSE(memory.handle().IsRegionReadOnly()); | 
|  |  | 
|  | // Check that it is possible to map the region directly from the fd. | 
|  | int region_fd = memory.handle().GetHandle(); | 
|  | EXPECT_GE(region_fd, 0); | 
|  | void* address = mmap(nullptr, kDataSize, PROT_READ | PROT_WRITE, MAP_SHARED, | 
|  | region_fd, 0); | 
|  | bool success = address && address != MAP_FAILED; | 
|  | ASSERT_TRUE(address); | 
|  | ASSERT_NE(address, MAP_FAILED); | 
|  | if (success) { | 
|  | EXPECT_EQ(0, munmap(address, kDataSize)); | 
|  | } | 
|  |  | 
|  | ASSERT_TRUE(memory.handle().SetRegionReadOnly()); | 
|  | EXPECT_TRUE(memory.handle().IsRegionReadOnly()); | 
|  |  | 
|  | // Check that it is no longer possible to map the region read/write. | 
|  | errno = 0; | 
|  | address = mmap(nullptr, kDataSize, PROT_READ | PROT_WRITE, MAP_SHARED, | 
|  | region_fd, 0); | 
|  | success = address && address != MAP_FAILED; | 
|  | ASSERT_FALSE(success); | 
|  | ASSERT_EQ(EPERM, errno); | 
|  | if (success) { | 
|  | EXPECT_EQ(0, munmap(address, kDataSize)); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(SharedMemoryTest, ReadOnlyDescriptors) { | 
|  | const uint32_t kDataSize = 1024; | 
|  | SharedMemory memory; | 
|  | SharedMemoryCreateOptions options; | 
|  | options.size = kDataSize; | 
|  | EXPECT_TRUE(memory.Create(options)); | 
|  |  | 
|  | EXPECT_FALSE(memory.handle().IsRegionReadOnly()); | 
|  |  | 
|  | // Getting a read-only descriptor should not make the region read-only itself. | 
|  | SharedMemoryHandle ro_handle = memory.GetReadOnlyHandle(); | 
|  | EXPECT_FALSE(memory.handle().IsRegionReadOnly()); | 
|  |  | 
|  | // Mapping a writable region from a read-only descriptor should not | 
|  | // be possible, it will DCHECK() in debug builds (see test below), | 
|  | // while returning false on release ones. | 
|  | { | 
|  | bool dcheck_fired = false; | 
|  | logging::ScopedLogAssertHandler log_assert( | 
|  | base::BindRepeating([](bool* flag, const char*, int, base::StringPiece, | 
|  | base::StringPiece) { *flag = true; }, | 
|  | base::Unretained(&dcheck_fired))); | 
|  |  | 
|  | SharedMemory rw_region(ro_handle.Duplicate(), /* read_only */ false); | 
|  | EXPECT_FALSE(rw_region.Map(kDataSize)); | 
|  | EXPECT_EQ(DCHECK_IS_ON() ? true : false, dcheck_fired); | 
|  | } | 
|  |  | 
|  | // Nor shall it turn the region read-only itself. | 
|  | EXPECT_FALSE(ro_handle.IsRegionReadOnly()); | 
|  |  | 
|  | // Mapping a read-only region from a read-only descriptor should work. | 
|  | SharedMemory ro_region(ro_handle.Duplicate(), /* read_only */ true); | 
|  | EXPECT_TRUE(ro_region.Map(kDataSize)); | 
|  |  | 
|  | // And it should turn the region read-only too. | 
|  | EXPECT_TRUE(ro_handle.IsRegionReadOnly()); | 
|  | EXPECT_TRUE(memory.handle().IsRegionReadOnly()); | 
|  | EXPECT_FALSE(memory.Map(kDataSize)); | 
|  |  | 
|  | ro_handle.Close(); | 
|  | } | 
|  |  | 
|  | #endif  // OS_ANDROID | 
|  |  | 
|  | }  // namespace base |