975 lines
33 KiB
C++
975 lines
33 KiB
C++
// 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 <lib/zx/vmar.h>
|
|
#include <lib/zx/vmo.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::root_self()->map(
|
|
0, *zx::unowned_vmo(handle.GetHandle()), 0,
|
|
contents.size(), ZX_VM_FLAG_PERM_WRITE, &addr))
|
|
<< "Shouldn't be able to map as writable.";
|
|
|
|
zx::vmo duped_handle;
|
|
EXPECT_NE(ZX_OK, zx::unowned_vmo(handle.GetHandle())
|
|
->duplicate(ZX_RIGHT_WRITE, &duped_handle))
|
|
<< "Shouldn't be able to duplicate the handle into a writable one.";
|
|
|
|
EXPECT_EQ(ZX_OK, zx::unowned_vmo(handle.GetHandle())
|
|
->duplicate(ZX_RIGHT_READ, &duped_handle))
|
|
<< "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) {
|
|
#if defined(OS_LINUX)
|
|
// On Chromecast both /dev/shm and /tmp are mounted with 'noexec' option,
|
|
// which makes this test fail. But Chromecast doesn't use NaCL so we don't
|
|
// need this.
|
|
if (!IsPathExecutable(FilePath("/dev/shm")) &&
|
|
!IsPathExecutable(FilePath("/tmp"))) {
|
|
return;
|
|
}
|
|
#endif // OS_LINUX
|
|
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
|