Chromium Code Reviews| Index: base/memory/shared_memory_unittest.cc |
| diff --git a/base/memory/shared_memory_unittest.cc b/base/memory/shared_memory_unittest.cc |
| index 892fd7f1a590b0d20b8f9e6289b07a225c8676fa..d923b26a0ba445e90f7dd52e8c344582e1ea054d 100644 |
| --- a/base/memory/shared_memory_unittest.cc |
| +++ b/base/memory/shared_memory_unittest.cc |
| @@ -7,7 +7,9 @@ |
| #include "base/memory/shared_memory.h" |
| #include "base/process/kill.h" |
| #include "base/rand_util.h" |
| +#include "base/safe_numerics.h" |
| #include "base/strings/string_number_conversions.h" |
| +#include "base/strings/stringprintf.h" |
| #include "base/sys_info.h" |
| #include "base/test/multiprocess_test.h" |
| #include "base/threading/platform_thread.h" |
| @@ -20,6 +22,8 @@ |
| #endif |
| #if defined(OS_POSIX) |
| +#include <errno.h> |
| +#include <fcntl.h> |
| #include <sys/mman.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| @@ -361,6 +365,107 @@ TEST(SharedMemoryTest, AnonymousPrivate) { |
| } |
| } |
| +TEST(SharedMemoryTest, AnonymousReadOnly) { |
| + StringPiece contents = "Hello World"; |
| + scoped_ptr<SharedMemory> shmem( |
| + SharedMemory::NewAnonymousReadOnly("Hello World")); |
| + |
| + ASSERT_TRUE(shmem->Map(contents.size())); |
| + EXPECT_EQ( |
| + contents, |
| + StringPiece(static_cast<const char*>(shmem->memory()), contents.size())); |
| + |
| + // 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 = shmem->handle(); |
| +#if defined(OS_POSIX) |
| + |
| + EXPECT_EQ(O_RDONLY, fcntl(handle.fd, F_GETFL) & O_ACCMODE) |
| + << "The descriptor itself should be read-only."; |
| + |
| + errno = 0; |
| + void* writable = mmap(NULL, |
| + shmem->mapped_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); |
| + |
| + struct stat fd_stat; |
| + errno = 0; |
| + EXPECT_EQ(0, fstat(handle.fd, &fd_stat)) << strerror(errno); |
| + EXPECT_EQ(0400, checked_numeric_cast<int>(fd_stat.st_mode & 0777)) |
| + << "inode should be read-only"; |
| + EXPECT_EQ(0U, fd_stat.st_nlink) << "inode should be unlinked"; |
| + EXPECT_EQ(geteuid(), fd_stat.st_uid) |
| + << "inode should be owned by current user"; |
| + |
| + if (0 == access("/dev/fd", X_OK)) { |
| + // Try to re-open through /dev/fd. This is an end-run around the notion of |
| + // an FD as a capability. |
| + const std::string shmem_path = StringPrintf("/dev/fd/%d", handle.fd); |
| + errno = 0; |
| + int readable_fd = open(shmem_path.c_str(), O_RDONLY); |
| + EXPECT_NE(-1, readable_fd) << strerror(errno); |
| + close(readable_fd); |
| + |
| + errno = 0; |
| + int writable_fd = open(shmem_path.c_str(), O_WRONLY); |
| + int open_writable_errno = errno; |
| + EXPECT_EQ(-1, writable_fd); |
| + EXPECT_EQ(EACCES, open_writable_errno) << strerror(open_writable_errno); |
| + close(writable_fd); |
| + |
| + // However, if we explicitly make the entry in /dev/fd writable first, the |
| + // open() call successfully creates a writable file on Linux. The sandbox |
| + // has to prevent opening this path. TODO(jln): Write a test that attacks |
| + // this from inside the sandbox. |
| + errno = 0; |
| + EXPECT_EQ(0, fchmod(handle.fd, S_IRUSR | S_IWUSR)) << strerror(errno); |
| + |
| + errno = 0; |
| + writable_fd = open(shmem_path.c_str(), O_WRONLY); |
| + open_writable_errno = errno; |
| + // On Linux, opening the file /dev/fd/N where 'N' is a read-only file |
| + // descriptor, can produce a writable file descriptor if the inode is |
| + // writable (see the fchmod above). Mac appears to restrict the open() call |
| + // appropriately. Other systems might let the open() succeed but still |
| + // produce a read-only descriptor. |
| +#if !defined(OS_LINUX) |
| + EXPECT_EQ(-1, writable_fd); |
| + EXPECT_EQ(EACCES, open_writable_errno) << strerror(open_writable_errno); |
| +#endif |
| + close(writable_fd); |
| + } |
| + |
| +#elif defined(OS_WIN) |
| + EXPECT_EQ(NULL, MapViewOfFile(handle, FILE_MAP_WRITE, 0, 0, 0)) |
| + << "Shouldn't be able to map memory writable."; |
| + |
| + SharedMemoryHandle writable_handle = INVALID_HANDLE_VALUE; |
| + EXPECT_EQ(0, |
| + ::DuplicateHandle(GetCurrentProcess(), |
|
Will Harris
2013/10/16 17:01:03
should probably CloseHandle after this succeeds, o
Jeffrey Yasskin
2013/10/16 22:27:53
Thanks, done with ScopedHandle.
|
| + handle, |
| + GetCurrentProcess, |
| + &writable_handle, |
| + FILE_MAP_ALL_ACCESS, |
| + false, |
| + 0)) |
| + << "Shouldn't be able to duplicate the handle into a writable one."; |
| +#else |
| +#error Unexpected platform; write a test that tries to make 'handle' writable. |
| +#endif |
| +} |
| + |
| TEST(SharedMemoryTest, MapAt) { |
| ASSERT_TRUE(SysInfo::VMAllocationGranularity() >= sizeof(uint32)); |
| const size_t kCount = SysInfo::VMAllocationGranularity(); |