// Copyright 2018 The gVisor Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/strings/escaping.h" #include "absl/strings/str_split.h" #include "test/util/cleanup.h" #include "test/util/file_descriptor.h" #include "test/util/fs_util.h" #include "test/util/memory_util.h" #include "test/util/multiprocess_util.h" #include "test/util/temp_path.h" #include "test/util/test_util.h" using ::testing::AnyOf; using ::testing::Eq; using ::testing::Gt; namespace gvisor { namespace testing { namespace { PosixErrorOr VirtualMemorySize() { ASSIGN_OR_RETURN_ERRNO(auto contents, GetContents("/proc/self/statm")); std::vector parts = absl::StrSplit(contents, ' '); if (parts.empty()) { return PosixError(EINVAL, "Unable to parse /proc/self/statm"); } ASSIGN_OR_RETURN_ERRNO(auto pages, Atoi(parts[0])); return pages * getpagesize(); } class MMapTest : public ::testing::Test { protected: // Unmap mapping, if one was made. void TearDown() override { if (addr_) { EXPECT_THAT(Unmap(), SyscallSucceeds()); } } // Remembers mapping, so it can be automatically unmapped. uintptr_t Map(uintptr_t addr, size_t length, int prot, int flags, int fd, off_t offset) { void* ret = mmap(reinterpret_cast(addr), length, prot, flags, fd, offset); if (ret != MAP_FAILED) { addr_ = ret; length_ = length; } return reinterpret_cast(ret); } // Unmap previous mapping int Unmap() { if (!addr_) { return -1; } int ret = munmap(addr_, length_); addr_ = nullptr; length_ = 0; return ret; } // Msync the mapping. int Msync() { return msync(addr_, length_, MS_SYNC); } // Mlock the mapping. int Mlock() { return mlock(addr_, length_); } // Munlock the mapping. int Munlock() { return munlock(addr_, length_); } int Protect(uintptr_t addr, size_t length, int prot) { return mprotect(reinterpret_cast(addr), length, prot); } void* addr_ = nullptr; size_t length_ = 0; }; // Matches if arg contains the same contents as string str. MATCHER_P(EqualsMemory, str, "") { if (0 == memcmp(arg, str.c_str(), str.size())) { return true; } *result_listener << "Memory did not match. Got:\n" << absl::BytesToHexString( std::string(static_cast(arg), str.size())) << "Want:\n" << absl::BytesToHexString(str); return false; } // We can't map pipes, but for different reasons. TEST_F(MMapTest, MapPipe) { int fds[2]; ASSERT_THAT(pipe(fds), SyscallSucceeds()); EXPECT_THAT(Map(0, kPageSize, PROT_READ, MAP_PRIVATE, fds[0], 0), SyscallFailsWithErrno(ENODEV)); EXPECT_THAT(Map(0, kPageSize, PROT_READ, MAP_PRIVATE, fds[1], 0), SyscallFailsWithErrno(EACCES)); ASSERT_THAT(close(fds[0]), SyscallSucceeds()); ASSERT_THAT(close(fds[1]), SyscallSucceeds()); } // It's very common to mmap /dev/zero because anonymous mappings aren't part // of POSIX although they are widely supported. So a zero initialized memory // region would actually come from a "file backed" /dev/zero mapping. TEST_F(MMapTest, MapDevZeroShared) { // This test will verify that we're able to map a page backed by /dev/zero // as MAP_SHARED. const FileDescriptor dev_zero = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_RDWR)); // Test that we can create a RW SHARED mapping of /dev/zero. ASSERT_THAT( Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, dev_zero.get(), 0), SyscallSucceeds()); } TEST_F(MMapTest, MapDevZeroPrivate) { // This test will verify that we're able to map a page backed by /dev/zero // as MAP_PRIVATE. const FileDescriptor dev_zero = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_RDWR)); // Test that we can create a RW SHARED mapping of /dev/zero. ASSERT_THAT( Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE, dev_zero.get(), 0), SyscallSucceeds()); } TEST_F(MMapTest, MapDevZeroNoPersistence) { // This test will verify that two independent mappings of /dev/zero do not // appear to reference the same "backed file." const FileDescriptor dev_zero1 = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_RDWR)); const FileDescriptor dev_zero2 = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_RDWR)); ASSERT_THAT( Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, dev_zero1.get(), 0), SyscallSucceeds()); // Create a second mapping via the second /dev/zero fd. void* psec_map = mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, dev_zero2.get(), 0); ASSERT_THAT(reinterpret_cast(psec_map), SyscallSucceeds()); // Always unmap. auto cleanup_psec_map = Cleanup( [&] { EXPECT_THAT(munmap(psec_map, kPageSize), SyscallSucceeds()); }); // Verify that we have independently addressed pages. ASSERT_NE(psec_map, addr_); std::string buf_zero(kPageSize, 0x00); std::string buf_ones(kPageSize, 0xFF); // Verify the first is actually all zeros after mmap. EXPECT_THAT(addr_, EqualsMemory(buf_zero)); // Let's fill in the first mapping with 0xFF. memcpy(addr_, buf_ones.data(), kPageSize); // Verify that the memcpy actually stuck in the page. EXPECT_THAT(addr_, EqualsMemory(buf_ones)); // Verify that it didn't affect the second page which should be all zeros. EXPECT_THAT(psec_map, EqualsMemory(buf_zero)); } TEST_F(MMapTest, MapDevZeroSharedMultiplePages) { // This will test that we're able to map /dev/zero over multiple pages. const FileDescriptor dev_zero = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_RDWR)); // Test that we can create a RW SHARED mapping of /dev/zero. ASSERT_THAT(Map(0, kPageSize * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE, dev_zero.get(), 0), SyscallSucceeds()); std::string buf_zero(kPageSize * 2, 0x00); std::string buf_ones(kPageSize * 2, 0xFF); // Verify the two pages are actually all zeros after mmap. EXPECT_THAT(addr_, EqualsMemory(buf_zero)); // Fill out the pages with all ones. memcpy(addr_, buf_ones.data(), kPageSize * 2); // Verify that the memcpy actually stuck in the pages. EXPECT_THAT(addr_, EqualsMemory(buf_ones)); } TEST_F(MMapTest, MapDevZeroSharedFdNoPersistence) { // This test will verify that two independent mappings of /dev/zero do not // appear to reference the same "backed file" even when mapped from the // same initial fd. const FileDescriptor dev_zero = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_RDWR)); ASSERT_THAT( Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, dev_zero.get(), 0), SyscallSucceeds()); // Create a second mapping via the same fd. void* psec_map = mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, dev_zero.get(), 0); ASSERT_THAT(reinterpret_cast(psec_map), SyscallSucceeds()); // Always unmap. auto cleanup_psec_map = Cleanup( [&] { ASSERT_THAT(munmap(psec_map, kPageSize), SyscallSucceeds()); }); // Verify that we have independently addressed pages. ASSERT_NE(psec_map, addr_); std::string buf_zero(kPageSize, 0x00); std::string buf_ones(kPageSize, 0xFF); // Verify the first is actually all zeros after mmap. EXPECT_THAT(addr_, EqualsMemory(buf_zero)); // Let's fill in the first mapping with 0xFF. memcpy(addr_, buf_ones.data(), kPageSize); // Verify that the memcpy actually stuck in the page. EXPECT_THAT(addr_, EqualsMemory(buf_ones)); // Verify that it didn't affect the second page which should be all zeros. EXPECT_THAT(psec_map, EqualsMemory(buf_zero)); } TEST_F(MMapTest, MapDevZeroSegfaultAfterUnmap) { SetupGvisorDeathTest(); // This test will verify that we're able to map a page backed by /dev/zero // as MAP_SHARED and after it's unmapped any access results in a SIGSEGV. // This test is redundant but given the special nature of /dev/zero mappings // it doesn't hurt. const FileDescriptor dev_zero = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_RDWR)); const auto rest = [&] { // Test that we can create a RW SHARED mapping of /dev/zero. TEST_PCHECK(Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, dev_zero.get(), 0) != reinterpret_cast(MAP_FAILED)); // Confirm that accesses after the unmap result in a SIGSEGV. // // N.B. We depend on this process being single-threaded to ensure there // can't be another mmap to map addr before the dereference below. void* addr_saved = addr_; // Unmap resets addr_. TEST_PCHECK(Unmap() == 0); *reinterpret_cast(addr_saved) = 0xFF; }; EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(AnyOf(Eq(W_EXITCODE(0, SIGSEGV)), Eq(W_EXITCODE(0, 128 + SIGSEGV))))); } TEST_F(MMapTest, MapDevZeroUnaligned) { const FileDescriptor dev_zero = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_RDWR)); const size_t size = kPageSize + kPageSize / 2; const std::string buf_zero(size, 0x00); ASSERT_THAT( Map(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, dev_zero.get(), 0), SyscallSucceeds()); EXPECT_THAT(addr_, EqualsMemory(buf_zero)); ASSERT_THAT(Unmap(), SyscallSucceeds()); ASSERT_THAT( Map(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, dev_zero.get(), 0), SyscallSucceeds()); EXPECT_THAT(addr_, EqualsMemory(buf_zero)); } // We can't map _some_ character devices. TEST_F(MMapTest, MapCharDevice) { const FileDescriptor cdevfd = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/random", 0, 0)); EXPECT_THAT(Map(0, kPageSize, PROT_READ, MAP_PRIVATE, cdevfd.get(), 0), SyscallFailsWithErrno(ENODEV)); } // We can't map directories. TEST_F(MMapTest, MapDirectory) { const FileDescriptor dirfd = ASSERT_NO_ERRNO_AND_VALUE(Open(GetAbsoluteTestTmpdir(), 0, 0)); EXPECT_THAT(Map(0, kPageSize, PROT_READ, MAP_PRIVATE, dirfd.get(), 0), SyscallFailsWithErrno(ENODEV)); } // We can map *something* TEST_F(MMapTest, MapAnything) { EXPECT_THAT(Map(0, kPageSize, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), SyscallSucceedsWithValue(Gt(0))); } // Map length < PageSize allowed TEST_F(MMapTest, SmallMap) { EXPECT_THAT(Map(0, 128, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), SyscallSucceeds()); } // Hint address doesn't break anything. // Note: there is no requirement we actually get the hint address TEST_F(MMapTest, HintAddress) { EXPECT_THAT( Map(0x30000000, kPageSize, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), SyscallSucceeds()); } // MAP_FIXED gives us exactly the requested address TEST_F(MMapTest, MapFixed) { EXPECT_THAT(Map(0x30000000, kPageSize, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0), SyscallSucceedsWithValue(0x30000000)); } // 64-bit addresses work too #if defined(__x86_64__) || defined(__aarch64__) TEST_F(MMapTest, MapFixed64) { EXPECT_THAT(Map(0x300000000000, kPageSize, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0), SyscallSucceedsWithValue(0x300000000000)); } #endif // MAP_STACK allowed. // There isn't a good way to verify it did anything. TEST_F(MMapTest, MapStack) { EXPECT_THAT(Map(0, kPageSize, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0), SyscallSucceeds()); } // MAP_LOCKED allowed. // There isn't a good way to verify it did anything. TEST_F(MMapTest, MapLocked) { EXPECT_THAT(Map(0, kPageSize, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_LOCKED, -1, 0), SyscallSucceeds()); } // MAP_PRIVATE or MAP_SHARED must be passed TEST_F(MMapTest, NotPrivateOrShared) { EXPECT_THAT(Map(0, kPageSize, PROT_NONE, MAP_ANONYMOUS, -1, 0), SyscallFailsWithErrno(EINVAL)); } // Only one of MAP_PRIVATE or MAP_SHARED may be passed TEST_F(MMapTest, PrivateAndShared) { EXPECT_THAT(Map(0, kPageSize, PROT_NONE, MAP_PRIVATE | MAP_SHARED | MAP_ANONYMOUS, -1, 0), SyscallFailsWithErrno(EINVAL)); } TEST_F(MMapTest, FixedAlignment) { // Addr must be page aligned (MAP_FIXED) EXPECT_THAT(Map(0x30000001, kPageSize, PROT_NONE, MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS, -1, 0), SyscallFailsWithErrno(EINVAL)); } // Non-MAP_FIXED address does not need to be page aligned TEST_F(MMapTest, NonFixedAlignment) { EXPECT_THAT( Map(0x30000001, kPageSize, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), SyscallSucceeds()); } // Length = 0 results in EINVAL. TEST_F(MMapTest, InvalidLength) { EXPECT_THAT(Map(0, 0, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), SyscallFailsWithErrno(EINVAL)); } // Bad fd not allowed. TEST_F(MMapTest, BadFd) { EXPECT_THAT(Map(0, kPageSize, PROT_NONE, MAP_PRIVATE, 999, 0), SyscallFailsWithErrno(EBADF)); } // Mappings are writable. TEST_F(MMapTest, ProtWrite) { uint64_t addr; constexpr uint8_t kFirstWord[] = {42, 42, 42, 42}; EXPECT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), SyscallSucceeds()); // This shouldn't cause a SIGSEGV. memset(reinterpret_cast(addr), 42, kPageSize); // The written data should actually be there. EXPECT_EQ( 0, memcmp(reinterpret_cast(addr), kFirstWord, sizeof(kFirstWord))); } // "Write-only" mappings are writable *and* readable. TEST_F(MMapTest, ProtWriteOnly) { uint64_t addr; constexpr uint8_t kFirstWord[] = {42, 42, 42, 42}; EXPECT_THAT( addr = Map(0, kPageSize, PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), SyscallSucceeds()); // This shouldn't cause a SIGSEGV. memset(reinterpret_cast(addr), 42, kPageSize); // The written data should actually be there. EXPECT_EQ( 0, memcmp(reinterpret_cast(addr), kFirstWord, sizeof(kFirstWord))); } // "Write-only" mappings are readable. // // This is distinct from above to ensure the page is accessible even if the // initial fault is a write fault. TEST_F(MMapTest, ProtWriteOnlyReadable) { uint64_t addr; constexpr uint64_t kFirstWord = 0; EXPECT_THAT( addr = Map(0, kPageSize, PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), SyscallSucceeds()); EXPECT_EQ(0, memcmp(reinterpret_cast(addr), &kFirstWord, sizeof(kFirstWord))); } // Mappings are writable after mprotect from PROT_NONE to PROT_READ|PROT_WRITE. TEST_F(MMapTest, ProtectProtWrite) { uint64_t addr; constexpr uint8_t kFirstWord[] = {42, 42, 42, 42}; EXPECT_THAT( addr = Map(0, kPageSize, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), SyscallSucceeds()); ASSERT_THAT(Protect(addr, kPageSize, PROT_READ | PROT_WRITE), SyscallSucceeds()); // This shouldn't cause a SIGSEGV. memset(reinterpret_cast(addr), 42, kPageSize); // The written data should actually be there. EXPECT_EQ( 0, memcmp(reinterpret_cast(addr), kFirstWord, sizeof(kFirstWord))); } // SIGSEGV raised when reading PROT_NONE memory TEST_F(MMapTest, ProtNoneDeath) { SetupGvisorDeathTest(); uintptr_t addr; ASSERT_THAT( addr = Map(0, kPageSize, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), SyscallSucceeds()); EXPECT_EXIT(*reinterpret_cast(addr), ::testing::KilledBySignal(SIGSEGV), ""); } // SIGSEGV raised when writing PROT_READ only memory TEST_F(MMapTest, ReadOnlyDeath) { SetupGvisorDeathTest(); uintptr_t addr; ASSERT_THAT( addr = Map(0, kPageSize, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), SyscallSucceeds()); EXPECT_EXIT(*reinterpret_cast(addr) = 42, ::testing::KilledBySignal(SIGSEGV), ""); } // Writable mapping mprotect'd to read-only should not be writable. TEST_F(MMapTest, MprotectReadOnlyDeath) { SetupGvisorDeathTest(); uintptr_t addr; ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), SyscallSucceeds()); volatile int* val = reinterpret_cast(addr); // Copy to ensure page is mapped in. *val = 42; ASSERT_THAT(Protect(addr, kPageSize, PROT_READ), SyscallSucceeds()); // Now it shouldn't be writable. EXPECT_EXIT(*val = 0, ::testing::KilledBySignal(SIGSEGV), ""); } // Verify that calling mprotect an address that's not page aligned fails. TEST_F(MMapTest, MprotectNotPageAligned) { uintptr_t addr; ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), SyscallSucceeds()); ASSERT_THAT(Protect(addr + 1, kPageSize - 1, PROT_READ), SyscallFailsWithErrno(EINVAL)); } // Verify that calling mprotect with an absurdly huge length fails. TEST_F(MMapTest, MprotectHugeLength) { uintptr_t addr; ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), SyscallSucceeds()); ASSERT_THAT(Protect(addr, static_cast(-1), PROT_READ), SyscallFailsWithErrno(ENOMEM)); } #if defined(__x86_64__) || defined(__i386__) // This code is equivalent in 32 and 64-bit mode const uint8_t machine_code[] = { 0xb8, 0x2a, 0x00, 0x00, 0x00, // movl $42, %eax 0xc3, // retq }; #elif defined(__aarch64__) const uint8_t machine_code[] = { 0x40, 0x05, 0x80, 0x52, // mov w0, #42 0xc0, 0x03, 0x5f, 0xd6, // ret }; #endif // PROT_EXEC allows code execution TEST_F(MMapTest, ProtExec) { uintptr_t addr; uint32_t (*func)(void); EXPECT_THAT(addr = Map(0, kPageSize, PROT_EXEC | PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), SyscallSucceeds()); memcpy(reinterpret_cast(addr), machine_code, sizeof(machine_code)); #if defined(__aarch64__) // We use this as a memory barrier for Arm64. ASSERT_THAT(Protect(addr, kPageSize, PROT_READ | PROT_EXEC), SyscallSucceeds()); #endif func = reinterpret_cast(addr); EXPECT_EQ(42, func()); } // No PROT_EXEC disallows code execution TEST_F(MMapTest, NoProtExecDeath) { SetupGvisorDeathTest(); uintptr_t addr; uint32_t (*func)(void); EXPECT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), SyscallSucceeds()); memcpy(reinterpret_cast(addr), machine_code, sizeof(machine_code)); func = reinterpret_cast(addr); EXPECT_EXIT(func(), ::testing::KilledBySignal(SIGSEGV), ""); } TEST_F(MMapTest, NoExceedLimitData) { void* prevbrk; void* target_brk; struct rlimit setlim; prevbrk = sbrk(0); ASSERT_NE(-1, reinterpret_cast(prevbrk)); target_brk = reinterpret_cast(prevbrk) + 1; setlim.rlim_cur = RLIM_INFINITY; setlim.rlim_max = RLIM_INFINITY; ASSERT_THAT(setrlimit(RLIMIT_DATA, &setlim), SyscallSucceeds()); EXPECT_THAT(brk(target_brk), SyscallSucceedsWithValue(0)); } TEST_F(MMapTest, ExceedLimitData) { // To unit test this more precisely, we'd need access to the mm's start_brk // and end_brk, which we don't have direct access to :/ void* prevbrk; void* target_brk; struct rlimit setlim; prevbrk = sbrk(0); ASSERT_NE(-1, reinterpret_cast(prevbrk)); target_brk = reinterpret_cast(prevbrk) + 8192; setlim.rlim_cur = 0; setlim.rlim_max = RLIM_INFINITY; // Set RLIMIT_DATA very low so any subsequent brk() calls fail. // Reset RLIMIT_DATA during teardown step. ASSERT_THAT(setrlimit(RLIMIT_DATA, &setlim), SyscallSucceeds()); EXPECT_THAT(brk(target_brk), SyscallFailsWithErrno(ENOMEM)); // Teardown step... setlim.rlim_cur = RLIM_INFINITY; ASSERT_THAT(setrlimit(RLIMIT_DATA, &setlim), SyscallSucceeds()); } TEST_F(MMapTest, ExceedLimitDataPrlimit) { // To unit test this more precisely, we'd need access to the mm's start_brk // and end_brk, which we don't have direct access to :/ void* prevbrk; void* target_brk; struct rlimit setlim; prevbrk = sbrk(0); ASSERT_NE(-1, reinterpret_cast(prevbrk)); target_brk = reinterpret_cast(prevbrk) + 8192; setlim.rlim_cur = 0; setlim.rlim_max = RLIM_INFINITY; // Set RLIMIT_DATA very low so any subsequent brk() calls fail. // Reset RLIMIT_DATA during teardown step. ASSERT_THAT(prlimit(0, RLIMIT_DATA, &setlim, nullptr), SyscallSucceeds()); EXPECT_THAT(brk(target_brk), SyscallFailsWithErrno(ENOMEM)); // Teardown step... setlim.rlim_cur = RLIM_INFINITY; ASSERT_THAT(setrlimit(RLIMIT_DATA, &setlim), SyscallSucceeds()); } TEST_F(MMapTest, ExceedLimitDataPrlimitPID) { // To unit test this more precisely, we'd need access to the mm's start_brk // and end_brk, which we don't have direct access to :/ void* prevbrk; void* target_brk; struct rlimit setlim; prevbrk = sbrk(0); ASSERT_NE(-1, reinterpret_cast(prevbrk)); target_brk = reinterpret_cast(prevbrk) + 8192; setlim.rlim_cur = 0; setlim.rlim_max = RLIM_INFINITY; // Set RLIMIT_DATA very low so any subsequent brk() calls fail. // Reset RLIMIT_DATA during teardown step. ASSERT_THAT(prlimit(syscall(__NR_gettid), RLIMIT_DATA, &setlim, nullptr), SyscallSucceeds()); EXPECT_THAT(brk(target_brk), SyscallFailsWithErrno(ENOMEM)); // Teardown step... setlim.rlim_cur = RLIM_INFINITY; ASSERT_THAT(setrlimit(RLIMIT_DATA, &setlim), SyscallSucceeds()); } TEST_F(MMapTest, NoExceedLimitAS) { constexpr uint64_t kAllocBytes = 200 << 20; // Add some headroom to the AS limit in case of e.g. unexpected stack // expansion. constexpr uint64_t kExtraASBytes = kAllocBytes + (20 << 20); static_assert(kAllocBytes < kExtraASBytes, "test depends on allocation not exceeding AS limit"); auto vss = ASSERT_NO_ERRNO_AND_VALUE(VirtualMemorySize()); struct rlimit setlim; setlim.rlim_cur = vss + kExtraASBytes; setlim.rlim_max = RLIM_INFINITY; ASSERT_THAT(setrlimit(RLIMIT_AS, &setlim), SyscallSucceeds()); EXPECT_THAT( Map(0, kAllocBytes, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), SyscallSucceedsWithValue(Gt(0))); } TEST_F(MMapTest, ExceedLimitAS) { constexpr uint64_t kAllocBytes = 200 << 20; // Add some headroom to the AS limit in case of e.g. unexpected stack // expansion. constexpr uint64_t kExtraASBytes = 20 << 20; static_assert(kAllocBytes > kExtraASBytes, "test depends on allocation exceeding AS limit"); auto vss = ASSERT_NO_ERRNO_AND_VALUE(VirtualMemorySize()); struct rlimit setlim; setlim.rlim_cur = vss + kExtraASBytes; setlim.rlim_max = RLIM_INFINITY; ASSERT_THAT(setrlimit(RLIMIT_AS, &setlim), SyscallSucceeds()); EXPECT_THAT( Map(0, kAllocBytes, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), SyscallFailsWithErrno(ENOMEM)); } // Tests that setting an anonymous mmap to PROT_NONE doesn't free the memory. TEST_F(MMapTest, SettingProtNoneDoesntFreeMemory) { uintptr_t addr; constexpr uint8_t kFirstWord[] = {42, 42, 42, 42}; EXPECT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), SyscallSucceedsWithValue(Gt(0))); memset(reinterpret_cast(addr), 42, kPageSize); ASSERT_THAT(Protect(addr, kPageSize, PROT_NONE), SyscallSucceeds()); ASSERT_THAT(Protect(addr, kPageSize, PROT_READ | PROT_WRITE), SyscallSucceeds()); // The written data should still be there. EXPECT_EQ( 0, memcmp(reinterpret_cast(addr), kFirstWord, sizeof(kFirstWord))); } constexpr char kFileContents[] = "Hello World!"; class MMapFileTest : public MMapTest { protected: FileDescriptor fd_; std::string filename_; // Open a file for read/write void SetUp() override { MMapTest::SetUp(); filename_ = NewTempAbsPath(); fd_ = ASSERT_NO_ERRNO_AND_VALUE(Open(filename_, O_CREAT | O_RDWR, 0644)); // Extend file so it can be written once mapped. Deliberately make the file // only half a page in size, so we can test what happens when we access the // second half. // Use ftruncate(2) once the sentry supports it. char zero = 0; size_t count = 0; do { const DisableSave ds; // saving 2048 times is slow and useless. Write(&zero, 1), SyscallSucceedsWithValue(1); } while (++count < (kPageSize / 2)); ASSERT_THAT(lseek(fd_.get(), 0, SEEK_SET), SyscallSucceedsWithValue(0)); } // Close and delete file void TearDown() override { MMapTest::TearDown(); fd_.reset(); // Make sure the files is closed before we unlink it. ASSERT_THAT(unlink(filename_.c_str()), SyscallSucceeds()); } bool FSSupportsMap() const { bool supported = true; void* ret = mmap(nullptr, 1, PROT_NONE, MAP_PRIVATE, fd_.get(), 0); if (ret == MAP_FAILED && errno != ENODEV) { supported = false; } if (ret != MAP_FAILED) { munmap(ret, 1); } return supported; } ssize_t Read(char* buf, size_t count) { ssize_t len = 0; do { ssize_t ret = read(fd_.get(), buf, count); if (ret < 0) { return ret; } else if (ret == 0) { return len; } len += ret; buf += ret; } while (len < static_cast(count)); return len; } ssize_t Write(const char* buf, size_t count) { ssize_t len = 0; do { ssize_t ret = write(fd_.get(), buf, count); if (ret < 0) { return ret; } else if (ret == 0) { return len; } len += ret; buf += ret; } while (len < static_cast(count)); return len; } }; class MMapFileParamTest : public MMapFileTest, public ::testing::WithParamInterface> { protected: int prot() const { return std::get<0>(GetParam()); } int flags() const { return std::get<1>(GetParam()); } }; // MAP_POPULATE allowed. // There isn't a good way to verify it actually did anything. TEST_P(MMapFileParamTest, MapPopulate) { SKIP_IF(!FSSupportsMap()); ASSERT_THAT(Map(0, kPageSize, prot(), flags() | MAP_POPULATE, fd_.get(), 0), SyscallSucceeds()); } // MAP_POPULATE on a short file. TEST_P(MMapFileParamTest, MapPopulateShort) { SKIP_IF(!FSSupportsMap()); ASSERT_THAT( Map(0, 2 * kPageSize, prot(), flags() | MAP_POPULATE, fd_.get(), 0), SyscallSucceeds()); } // Read contents from mapped file. TEST_F(MMapFileTest, Read) { SKIP_IF(!FSSupportsMap()); size_t len = strlen(kFileContents); ASSERT_EQ(len, Write(kFileContents, len)); uintptr_t addr; ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ, MAP_PRIVATE, fd_.get(), 0), SyscallSucceeds()); EXPECT_THAT(reinterpret_cast(addr), EqualsMemory(std::string(kFileContents))); } // Map at an offset. TEST_F(MMapFileTest, MapOffset) { SKIP_IF(!FSSupportsMap()); ASSERT_THAT(lseek(fd_.get(), kPageSize, SEEK_SET), SyscallSucceeds()); size_t len = strlen(kFileContents); ASSERT_EQ(len, Write(kFileContents, len)); uintptr_t addr; ASSERT_THAT( addr = Map(0, kPageSize, PROT_READ, MAP_PRIVATE, fd_.get(), kPageSize), SyscallSucceeds()); EXPECT_THAT(reinterpret_cast(addr), EqualsMemory(std::string(kFileContents))); } TEST_F(MMapFileTest, MapOffsetBeyondEnd) { SKIP_IF(!FSSupportsMap()); SetupGvisorDeathTest(); uintptr_t addr; ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd_.get(), 10 * kPageSize), SyscallSucceeds()); // Touching the memory causes SIGBUS. size_t len = strlen(kFileContents); EXPECT_EXIT(std::copy(kFileContents, kFileContents + len, reinterpret_cast(addr)), ::testing::KilledBySignal(SIGBUS), ""); } TEST_F(MMapFileTest, MapSecondToLastPositivePage) { SKIP_IF(!FSSupportsMap()); EXPECT_THAT( Map(0, kPageSize, PROT_READ, MAP_SHARED, fd_.get(), (std::numeric_limits::max() - kPageSize) & ~(kPageSize - 1)), SyscallSucceeds()); } TEST_F(MMapFileTest, MapLastPositivePage) { SKIP_IF(!FSSupportsMap()); // For regular files, this should fail due to integer overflow of the end // offset. EXPECT_THAT(Map(0, kPageSize, PROT_READ, MAP_SHARED, fd_.get(), std::numeric_limits::max() & ~(kPageSize - 1)), SyscallFailsWithErrno(EOVERFLOW)); } TEST_F(MMapFileTest, MapFirstNegativePage) { SKIP_IF(!FSSupportsMap()); EXPECT_THAT(Map(0, kPageSize, PROT_READ, MAP_SHARED, fd_.get(), std::numeric_limits::min()), SyscallFailsWithErrno(EOVERFLOW)); } TEST_F(MMapFileTest, MapSecondToLastNegativePage) { SKIP_IF(!FSSupportsMap()); EXPECT_THAT( Map(0, kPageSize, PROT_READ, MAP_SHARED, fd_.get(), -(2 * kPageSize)), SyscallFailsWithErrno(EOVERFLOW)); } TEST_F(MMapFileTest, MapLastNegativePage) { SKIP_IF(!FSSupportsMap()); EXPECT_THAT(Map(0, kPageSize, PROT_READ, MAP_SHARED, fd_.get(), -kPageSize), SyscallFailsWithErrno(EOVERFLOW)); } // MAP_PRIVATE PROT_WRITE is allowed on read-only FDs. TEST_F(MMapFileTest, WritePrivateOnReadOnlyFd) { SKIP_IF(!FSSupportsMap()); const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(filename_, O_RDONLY)); uintptr_t addr; EXPECT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd.get(), 0), SyscallSucceeds()); // Touch the page to ensure the kernel didn't lie about writability. size_t len = strlen(kFileContents); std::copy(kFileContents, kFileContents + len, reinterpret_cast(addr)); } // MAP_SHARED PROT_WRITE not allowed on read-only FDs. TEST_F(MMapFileTest, WriteSharedOnReadOnlyFd) { SKIP_IF(!FSSupportsMap()); const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(filename_, O_RDONLY)); uintptr_t addr; EXPECT_THAT( addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd.get(), 0), SyscallFailsWithErrno(EACCES)); } // Mmap not allowed on O_PATH FDs. TEST_F(MMapFileTest, MmapFileWithOpath) { SKIP_IF(!FSSupportsMap()); SKIP_IF(IsRunningWithVFS1()); const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH)); uintptr_t addr; EXPECT_THAT(addr = Map(0, kPageSize, PROT_READ, MAP_PRIVATE, fd.get(), 0), SyscallFailsWithErrno(EBADF)); } // The FD must be readable. TEST_P(MMapFileParamTest, WriteOnlyFd) { SKIP_IF(!FSSupportsMap()); const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(filename_, O_WRONLY)); uintptr_t addr; EXPECT_THAT(addr = Map(0, kPageSize, prot(), flags(), fd.get(), 0), SyscallFailsWithErrno(EACCES)); } // Overwriting the contents of a file mapped MAP_SHARED PROT_READ // should cause the new data to be reflected in the mapping. TEST_F(MMapFileTest, ReadSharedConsistentWithOverwrite) { SKIP_IF(!FSSupportsMap()); // Start from scratch. EXPECT_THAT(ftruncate(fd_.get(), 0), SyscallSucceeds()); // Expand the file to two pages and dirty them. std::string bufA(kPageSize, 'a'); ASSERT_THAT(Write(bufA.c_str(), bufA.size()), SyscallSucceedsWithValue(bufA.size())); std::string bufB(kPageSize, 'b'); ASSERT_THAT(Write(bufB.c_str(), bufB.size()), SyscallSucceedsWithValue(bufB.size())); // Map the page. uintptr_t addr; ASSERT_THAT(addr = Map(0, 2 * kPageSize, PROT_READ, MAP_SHARED, fd_.get(), 0), SyscallSucceeds()); // Check that the mapping contains the right file data. EXPECT_EQ(0, memcmp(reinterpret_cast(addr), bufA.c_str(), kPageSize)); EXPECT_EQ(0, memcmp(reinterpret_cast(addr + kPageSize), bufB.c_str(), kPageSize)); // Start at the beginning of the file. ASSERT_THAT(lseek(fd_.get(), 0, SEEK_SET), SyscallSucceedsWithValue(0)); // Swap the write pattern. ASSERT_THAT(Write(bufB.c_str(), bufB.size()), SyscallSucceedsWithValue(bufB.size())); ASSERT_THAT(Write(bufA.c_str(), bufA.size()), SyscallSucceedsWithValue(bufA.size())); // Check that the mapping got updated. EXPECT_EQ(0, memcmp(reinterpret_cast(addr), bufB.c_str(), kPageSize)); EXPECT_EQ(0, memcmp(reinterpret_cast(addr + kPageSize), bufA.c_str(), kPageSize)); } // Partially overwriting a file mapped MAP_SHARED PROT_READ should be reflected // in the mapping. TEST_F(MMapFileTest, ReadSharedConsistentWithPartialOverwrite) { SKIP_IF(!FSSupportsMap()); // Start from scratch. EXPECT_THAT(ftruncate(fd_.get(), 0), SyscallSucceeds()); // Expand the file to two pages and dirty them. std::string bufA(kPageSize, 'a'); ASSERT_THAT(Write(bufA.c_str(), bufA.size()), SyscallSucceedsWithValue(bufA.size())); std::string bufB(kPageSize, 'b'); ASSERT_THAT(Write(bufB.c_str(), bufB.size()), SyscallSucceedsWithValue(bufB.size())); // Map the page. uintptr_t addr; ASSERT_THAT(addr = Map(0, 2 * kPageSize, PROT_READ, MAP_SHARED, fd_.get(), 0), SyscallSucceeds()); // Check that the mapping contains the right file data. EXPECT_EQ(0, memcmp(reinterpret_cast(addr), bufA.c_str(), kPageSize)); EXPECT_EQ(0, memcmp(reinterpret_cast(addr + kPageSize), bufB.c_str(), kPageSize)); // Start at the beginning of the file. ASSERT_THAT(lseek(fd_.get(), 0, SEEK_SET), SyscallSucceedsWithValue(0)); // Do a partial overwrite, spanning both pages. std::string bufC(kPageSize + (kPageSize / 2), 'c'); ASSERT_THAT(Write(bufC.c_str(), bufC.size()), SyscallSucceedsWithValue(bufC.size())); // Check that the mapping got updated. EXPECT_EQ(0, memcmp(reinterpret_cast(addr), bufC.c_str(), kPageSize + (kPageSize / 2))); EXPECT_EQ(0, memcmp(reinterpret_cast(addr + kPageSize + (kPageSize / 2)), bufB.c_str(), kPageSize / 2)); } // Overwriting a file mapped MAP_SHARED PROT_READ should be reflected in the // mapping and the file. TEST_F(MMapFileTest, ReadSharedConsistentWithWriteAndFile) { SKIP_IF(!FSSupportsMap()); // Start from scratch. EXPECT_THAT(ftruncate(fd_.get(), 0), SyscallSucceeds()); // Expand the file to two full pages and dirty it. std::string bufA(2 * kPageSize, 'a'); ASSERT_THAT(Write(bufA.c_str(), bufA.size()), SyscallSucceedsWithValue(bufA.size())); // Map only the first page. uintptr_t addr; ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ, MAP_SHARED, fd_.get(), 0), SyscallSucceeds()); // Prepare to overwrite the file contents. ASSERT_THAT(lseek(fd_.get(), 0, SEEK_SET), SyscallSucceedsWithValue(0)); // Overwrite everything, beyond the mapped portion. std::string bufB(2 * kPageSize, 'b'); ASSERT_THAT(Write(bufB.c_str(), bufB.size()), SyscallSucceedsWithValue(bufB.size())); // What the mapped portion should now look like. std::string bufMapped(kPageSize, 'b'); // Expect that the mapped portion is consistent. EXPECT_EQ( 0, memcmp(reinterpret_cast(addr), bufMapped.c_str(), kPageSize)); // Prepare to read the entire file contents. ASSERT_THAT(lseek(fd_.get(), 0, SEEK_SET), SyscallSucceedsWithValue(0)); // Expect that the file was fully updated. std::vector bufFile(2 * kPageSize); ASSERT_THAT(Read(bufFile.data(), bufFile.size()), SyscallSucceedsWithValue(bufFile.size())); // Cast to void* to avoid EXPECT_THAT assuming bufFile.data() is a // NUL-terminated C std::string. EXPECT_THAT will try to print a char* as a C // std::string, possibly overruning the buffer. EXPECT_THAT(reinterpret_cast(bufFile.data()), EqualsMemory(bufB)); } // Write data to mapped file. TEST_F(MMapFileTest, WriteShared) { SKIP_IF(!FSSupportsMap()); uintptr_t addr; ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd_.get(), 0), SyscallSucceeds()); size_t len = strlen(kFileContents); memcpy(reinterpret_cast(addr), kFileContents, len); // The file may not actually be updated until munmap is called. ASSERT_THAT(Unmap(), SyscallSucceeds()); std::vector buf(len); ASSERT_THAT(Read(buf.data(), buf.size()), SyscallSucceedsWithValue(buf.size())); // Cast to void* to avoid EXPECT_THAT assuming buf.data() is a // NUL-terminated C string. EXPECT_THAT will try to print a char* as a C // string, possibly overruning the buffer. EXPECT_THAT(reinterpret_cast(buf.data()), EqualsMemory(std::string(kFileContents))); } // Write data to portion of mapped page beyond the end of the file. // These writes are not reflected in the file. TEST_F(MMapFileTest, WriteSharedBeyondEnd) { SKIP_IF(!FSSupportsMap()); // The file is only half of a page. We map an entire page. Writes to the // end of the mapping must not be reflected in the file. uintptr_t addr; ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd_.get(), 0), SyscallSucceeds()); // First half; this is reflected in the file. std::string first(kPageSize / 2, 'A'); memcpy(reinterpret_cast(addr), first.c_str(), first.size()); // Second half; this is not reflected in the file. std::string second(kPageSize / 2, 'B'); memcpy(reinterpret_cast(addr + kPageSize / 2), second.c_str(), second.size()); // The file may not actually be updated until munmap is called. ASSERT_THAT(Unmap(), SyscallSucceeds()); // Big enough to fit the entire page, if the writes are mistakenly written to // the file. std::vector buf(kPageSize); // Only the first half is in the file. ASSERT_THAT(Read(buf.data(), buf.size()), SyscallSucceedsWithValue(first.size())); // Cast to void* to avoid EXPECT_THAT assuming buf.data() is a // NUL-terminated C string. EXPECT_THAT will try to print a char* as a C // NUL-terminated C std::string. EXPECT_THAT will try to print a char* as a C // std::string, possibly overruning the buffer. EXPECT_THAT(reinterpret_cast(buf.data()), EqualsMemory(first)); } // The portion of a mapped page that becomes part of the file after a truncate // is reflected in the file. TEST_F(MMapFileTest, WriteSharedTruncateUp) { SKIP_IF(!FSSupportsMap()); // The file is only half of a page. We map an entire page. Writes to the // end of the mapping must not be reflected in the file. uintptr_t addr; ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd_.get(), 0), SyscallSucceeds()); // First half; this is reflected in the file. std::string first(kPageSize / 2, 'A'); memcpy(reinterpret_cast(addr), first.c_str(), first.size()); // Second half; this is not reflected in the file now (see // WriteSharedBeyondEnd), but will be after the truncate. std::string second(kPageSize / 2, 'B'); memcpy(reinterpret_cast(addr + kPageSize / 2), second.c_str(), second.size()); // Extend the file to a full page. The second half of the page will be // reflected in the file. EXPECT_THAT(ftruncate(fd_.get(), kPageSize), SyscallSucceeds()); // The file may not actually be updated until munmap is called. ASSERT_THAT(Unmap(), SyscallSucceeds()); // The whole page is in the file. std::vector buf(kPageSize); ASSERT_THAT(Read(buf.data(), buf.size()), SyscallSucceedsWithValue(buf.size())); // Cast to void* to avoid EXPECT_THAT assuming buf.data() is a // NUL-terminated C string. EXPECT_THAT will try to print a char* as a C // string, possibly overruning the buffer. EXPECT_THAT(reinterpret_cast(buf.data()), EqualsMemory(first)); EXPECT_THAT(reinterpret_cast(buf.data() + kPageSize / 2), EqualsMemory(second)); } TEST_F(MMapFileTest, ReadSharedTruncateDownThenUp) { SKIP_IF(!FSSupportsMap()); // Start from scratch. EXPECT_THAT(ftruncate(fd_.get(), 0), SyscallSucceeds()); // Expand the file to a full page and dirty it. std::string buf(kPageSize, 'a'); ASSERT_THAT(Write(buf.c_str(), buf.size()), SyscallSucceedsWithValue(buf.size())); // Map the page. uintptr_t addr; ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ, MAP_SHARED, fd_.get(), 0), SyscallSucceeds()); // Check that the memory contains the file data. EXPECT_EQ(0, memcmp(reinterpret_cast(addr), buf.c_str(), kPageSize)); // Truncate down, then up. EXPECT_THAT(ftruncate(fd_.get(), 0), SyscallSucceeds()); EXPECT_THAT(ftruncate(fd_.get(), kPageSize), SyscallSucceeds()); // Check that the memory was zeroed. std::string zeroed(kPageSize, '\0'); EXPECT_EQ(0, memcmp(reinterpret_cast(addr), zeroed.c_str(), kPageSize)); // The file may not actually be updated until msync is called. ASSERT_THAT(Msync(), SyscallSucceeds()); // Prepare to read the entire file contents. ASSERT_THAT(lseek(fd_.get(), 0, SEEK_SET), SyscallSucceedsWithValue(0)); // Expect that the file is fully updated. std::vector bufFile(kPageSize); ASSERT_THAT(Read(bufFile.data(), bufFile.size()), SyscallSucceedsWithValue(bufFile.size())); EXPECT_EQ(0, memcmp(bufFile.data(), zeroed.c_str(), kPageSize)); } TEST_F(MMapFileTest, WriteSharedTruncateDownThenUp) { SKIP_IF(!FSSupportsMap()); // The file is only half of a page. We map an entire page. Writes to the // end of the mapping must not be reflected in the file. uintptr_t addr; ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd_.get(), 0), SyscallSucceeds()); // First half; this will be deleted by truncate(0). std::string first(kPageSize / 2, 'A'); memcpy(reinterpret_cast(addr), first.c_str(), first.size()); // Truncate down, then up. EXPECT_THAT(ftruncate(fd_.get(), 0), SyscallSucceeds()); EXPECT_THAT(ftruncate(fd_.get(), kPageSize), SyscallSucceeds()); // The whole page is zeroed in memory. std::string zeroed(kPageSize, '\0'); EXPECT_EQ(0, memcmp(reinterpret_cast(addr), zeroed.c_str(), kPageSize)); // The file may not actually be updated until munmap is called. ASSERT_THAT(Unmap(), SyscallSucceeds()); // The whole file is also zeroed. std::vector buf(kPageSize); ASSERT_THAT(Read(buf.data(), buf.size()), SyscallSucceedsWithValue(buf.size())); // Cast to void* to avoid EXPECT_THAT assuming buf.data() is a // NUL-terminated C string. EXPECT_THAT will try to print a char* as a C // string, possibly overruning the buffer. EXPECT_THAT(reinterpret_cast(buf.data()), EqualsMemory(zeroed)); } TEST_F(MMapFileTest, ReadSharedTruncateSIGBUS) { SKIP_IF(!FSSupportsMap()); SetupGvisorDeathTest(); // Start from scratch. EXPECT_THAT(ftruncate(fd_.get(), 0), SyscallSucceeds()); // Expand the file to a full page and dirty it. std::string buf(kPageSize, 'a'); ASSERT_THAT(Write(buf.c_str(), buf.size()), SyscallSucceedsWithValue(buf.size())); // Map the page. uintptr_t addr; ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ, MAP_SHARED, fd_.get(), 0), SyscallSucceeds()); // Check that the mapping contains the file data. EXPECT_EQ(0, memcmp(reinterpret_cast(addr), buf.c_str(), kPageSize)); // Truncate down. EXPECT_THAT(ftruncate(fd_.get(), 0), SyscallSucceeds()); // Accessing the truncated region should cause a SIGBUS. std::vector in(kPageSize); EXPECT_EXIT( std::copy(reinterpret_cast(addr), reinterpret_cast(addr) + kPageSize, in.data()), ::testing::KilledBySignal(SIGBUS), ""); } TEST_F(MMapFileTest, WriteSharedTruncateSIGBUS) { SKIP_IF(!FSSupportsMap()); SetupGvisorDeathTest(); uintptr_t addr; ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd_.get(), 0), SyscallSucceeds()); // Touch the memory to be sure it really is mapped. size_t len = strlen(kFileContents); memcpy(reinterpret_cast(addr), kFileContents, len); // Truncate down. EXPECT_THAT(ftruncate(fd_.get(), 0), SyscallSucceeds()); // Accessing the truncated file should cause a SIGBUS. EXPECT_EXIT(std::copy(kFileContents, kFileContents + len, reinterpret_cast(addr)), ::testing::KilledBySignal(SIGBUS), ""); } TEST_F(MMapFileTest, ReadSharedTruncatePartialPage) { SKIP_IF(!FSSupportsMap()); // Start from scratch. EXPECT_THAT(ftruncate(fd_.get(), 0), SyscallSucceeds()); // Dirty the file. std::string buf(kPageSize, 'a'); ASSERT_THAT(Write(buf.c_str(), buf.size()), SyscallSucceedsWithValue(buf.size())); // Map a page. uintptr_t addr; ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ, MAP_SHARED, fd_.get(), 0), SyscallSucceeds()); // Truncate to half of the page. EXPECT_THAT(ftruncate(fd_.get(), kPageSize / 2), SyscallSucceeds()); // First half of the page untouched. EXPECT_EQ(0, memcmp(reinterpret_cast(addr), buf.data(), kPageSize / 2)); // Second half is zeroed. std::string zeroed(kPageSize / 2, '\0'); EXPECT_EQ(0, memcmp(reinterpret_cast(addr + kPageSize / 2), zeroed.c_str(), kPageSize / 2)); } // Page can still be accessed and contents are intact after truncating a partial // page. TEST_F(MMapFileTest, WriteSharedTruncatePartialPage) { SKIP_IF(!FSSupportsMap()); // Expand the file to a full page. EXPECT_THAT(ftruncate(fd_.get(), kPageSize), SyscallSucceeds()); uintptr_t addr; ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd_.get(), 0), SyscallSucceeds()); // Fill the entire page. std::string contents(kPageSize, 'A'); memcpy(reinterpret_cast(addr), contents.c_str(), contents.size()); // Truncate half of the page. EXPECT_THAT(ftruncate(fd_.get(), kPageSize / 2), SyscallSucceeds()); // First half of the page untouched. EXPECT_EQ(0, memcmp(reinterpret_cast(addr), contents.c_str(), kPageSize / 2)); // Second half zeroed. std::string zeroed(kPageSize / 2, '\0'); EXPECT_EQ(0, memcmp(reinterpret_cast(addr + kPageSize / 2), zeroed.c_str(), kPageSize / 2)); } // MAP_PRIVATE writes are not carried through to the underlying file. TEST_F(MMapFileTest, WritePrivate) { SKIP_IF(!FSSupportsMap()); uintptr_t addr; ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd_.get(), 0), SyscallSucceeds()); size_t len = strlen(kFileContents); memcpy(reinterpret_cast(addr), kFileContents, len); // The file should not be updated, but if it mistakenly is, it may not be // until after munmap is called. ASSERT_THAT(Unmap(), SyscallSucceeds()); std::vector buf(len); ASSERT_THAT(Read(buf.data(), buf.size()), SyscallSucceedsWithValue(buf.size())); // Cast to void* to avoid EXPECT_THAT assuming buf.data() is a // NUL-terminated C string. EXPECT_THAT will try to print a char* as a C // string, possibly overruning the buffer. EXPECT_THAT(reinterpret_cast(buf.data()), EqualsMemory(std::string(len, '\0'))); } // SIGBUS raised when reading or writing past end of a mapped file. TEST_P(MMapFileParamTest, SigBusDeath) { SKIP_IF(!FSSupportsMap()); SetupGvisorDeathTest(); uintptr_t addr; ASSERT_THAT(addr = Map(0, 2 * kPageSize, prot(), flags(), fd_.get(), 0), SyscallSucceeds()); auto* start = reinterpret_cast(addr + kPageSize); // MMapFileTest makes a file kPageSize/2 long. The entire first page should be // accessible, but anything beyond it should not. if (prot() & PROT_WRITE) { // Write beyond first page. size_t len = strlen(kFileContents); EXPECT_EXIT(std::copy(kFileContents, kFileContents + len, start), ::testing::KilledBySignal(SIGBUS), ""); } else { // Read beyond first page. std::vector in(kPageSize); EXPECT_EXIT(std::copy(start, start + kPageSize, in.data()), ::testing::KilledBySignal(SIGBUS), ""); } } // Tests that SIGBUS is not raised when reading or writing to a file-mapped // page before EOF, even if part of the mapping extends beyond EOF. // // See b/27877699. TEST_P(MMapFileParamTest, NoSigBusOnPagesBeforeEOF) { SKIP_IF(!FSSupportsMap()); uintptr_t addr; ASSERT_THAT(addr = Map(0, 2 * kPageSize, prot(), flags(), fd_.get(), 0), SyscallSucceeds()); // The test passes if this survives. auto* start = reinterpret_cast(addr + (kPageSize / 2) + 1); size_t len = strlen(kFileContents); if (prot() & PROT_WRITE) { std::copy(kFileContents, kFileContents + len, start); } else { std::vector in(len); std::copy(start, start + len, in.data()); } } // Tests that SIGBUS is not raised when reading or writing from a file-mapped // page containing EOF, *after* the EOF. TEST_P(MMapFileParamTest, NoSigBusOnPageContainingEOF) { SKIP_IF(!FSSupportsMap()); uintptr_t addr; ASSERT_THAT(addr = Map(0, 2 * kPageSize, prot(), flags(), fd_.get(), 0), SyscallSucceeds()); // The test passes if this survives. (Technically addr+kPageSize/2 is already // beyond EOF, but +1 to check for fencepost errors.) auto* start = reinterpret_cast(addr + (kPageSize / 2) + 1); size_t len = strlen(kFileContents); if (prot() & PROT_WRITE) { std::copy(kFileContents, kFileContents + len, start); } else { std::vector in(len); std::copy(start, start + len, in.data()); } } // Tests that reading from writable shared file-mapped pages succeeds. // // On most platforms this is trivial, but when the file is mapped via the sentry // page cache (which does not yet support writing to shared mappings), a bug // caused reads to fail unnecessarily on such mappings. See b/28913513. TEST_F(MMapFileTest, ReadingWritableSharedFilePageSucceeds) { SKIP_IF(!FSSupportsMap()); uintptr_t addr; size_t len = strlen(kFileContents); ASSERT_THAT(addr = Map(0, 2 * kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd_.get(), 0), SyscallSucceeds()); std::vector buf(kPageSize); // The test passes if this survives. std::copy(reinterpret_cast(addr), reinterpret_cast(addr) + len, buf.data()); } // Tests that EFAULT is returned when invoking a syscall that requires the OS to // read past end of file (resulting in a fault in sentry context in the gVisor // case). See b/28913513. TEST_F(MMapFileTest, InternalSigBus) { SKIP_IF(!FSSupportsMap()); uintptr_t addr; ASSERT_THAT(addr = Map(0, 2 * kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd_.get(), 0), SyscallSucceeds()); // This depends on the fact that gVisor implements pipes internally. int pipefd[2]; ASSERT_THAT(pipe(pipefd), SyscallSucceeds()); EXPECT_THAT( write(pipefd[1], reinterpret_cast(addr + kPageSize), kPageSize), SyscallFailsWithErrno(EFAULT)); EXPECT_THAT(close(pipefd[0]), SyscallSucceeds()); EXPECT_THAT(close(pipefd[1]), SyscallSucceeds()); } // Like InternalSigBus, but test the WriteZerosAt path by reading from // /dev/zero to a shared mapping (so that the SIGBUS isn't caught during // copy-on-write breaking). TEST_F(MMapFileTest, InternalSigBusZeroing) { SKIP_IF(!FSSupportsMap()); uintptr_t addr; ASSERT_THAT(addr = Map(0, 2 * kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd_.get(), 0), SyscallSucceeds()); const FileDescriptor dev_zero = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_RDONLY)); EXPECT_THAT(read(dev_zero.get(), reinterpret_cast(addr + kPageSize), kPageSize), SyscallFailsWithErrno(EFAULT)); } // Checks that mmaps with a length of uint64_t(-PAGE_SIZE + 1) or greater do not // induce a sentry panic (due to "rounding up" to 0). TEST_F(MMapTest, HugeLength) { EXPECT_THAT(Map(0, static_cast(-kPageSize + 1), PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), SyscallFailsWithErrno(ENOMEM)); } // Tests for a specific gVisor MM caching bug. TEST_F(MMapTest, AccessCOWInvalidatesCachedSegments) { auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDWR)); auto zero_fd = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_RDONLY)); // Get a two-page private mapping and fill it with 1s. uintptr_t addr; ASSERT_THAT(addr = Map(0, 2 * kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), SyscallSucceeds()); memset(addr_, 1, 2 * kPageSize); MaybeSave(); // Fork to make the mapping copy-on-write. pid_t const pid = fork(); if (pid == 0) { // The child process waits for the parent to SIGKILL it. while (true) { pause(); } } ASSERT_THAT(pid, SyscallSucceeds()); auto cleanup_child = Cleanup([&] { EXPECT_THAT(kill(pid, SIGKILL), SyscallSucceeds()); int status; EXPECT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid)); }); // Induce a read-only Access of the first page of the mapping, which will not // cause a copy. The usermem.Segment should be cached. ASSERT_THAT(PwriteFd(fd.get(), addr_, kPageSize, 0), SyscallSucceedsWithValue(kPageSize)); // Induce a writable Access of both pages of the mapping. This should // invalidate the cached Segment. ASSERT_THAT(PreadFd(zero_fd.get(), addr_, 2 * kPageSize, 0), SyscallSucceedsWithValue(2 * kPageSize)); // Induce a read-only Access of the first page of the mapping again. It should // read the 0s that were stored in the mapping by the read from /dev/zero. If // the read failed to invalidate the cached Segment, it will instead read the // 1s in the stale page. ASSERT_THAT(PwriteFd(fd.get(), addr_, kPageSize, 0), SyscallSucceedsWithValue(kPageSize)); std::vector buf(kPageSize); ASSERT_THAT(PreadFd(fd.get(), buf.data(), kPageSize, 0), SyscallSucceedsWithValue(kPageSize)); for (size_t i = 0; i < kPageSize; i++) { ASSERT_EQ(0, buf[i]) << "at offset " << i; } } TEST_F(MMapTest, NoReserve) { const size_t kSize = 10 * 1 << 20; // 10M uintptr_t addr; ASSERT_THAT(addr = Map(0, kSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0), SyscallSucceeds()); EXPECT_GT(addr, 0); // Check that every page can be read/written. Technically, writing to memory // could SIGSEGV in case there is no more memory available. In gVisor it // would never happen though because NORESERVE is ignored. In Linux, it's // possible to fail, but allocation is small enough that it's highly likely // to succeed. for (size_t j = 0; j < kSize; j += kPageSize) { EXPECT_EQ(0, reinterpret_cast(addr)[j]); reinterpret_cast(addr)[j] = j; } } // Map more than the gVisor page-cache map unit (64k) and ensure that // it is consistent with reading from the file. TEST_F(MMapFileTest, Bug38498194) { SKIP_IF(!FSSupportsMap()); // Choose a sufficiently large map unit. constexpr int kSize = 4 * 1024 * 1024; EXPECT_THAT(ftruncate(fd_.get(), kSize), SyscallSucceeds()); // Map a large enough region so that multiple internal segments // are created to back the mapping. uintptr_t addr; ASSERT_THAT( addr = Map(0, kSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd_.get(), 0), SyscallSucceeds()); std::vector expect(kSize, 'a'); std::copy(expect.data(), expect.data() + expect.size(), reinterpret_cast(addr)); // Trigger writeback for gVisor. In Linux pages stay cached until // it can't hold onto them anymore. ASSERT_THAT(Unmap(), SyscallSucceeds()); std::vector buf(kSize); ASSERT_THAT(Read(buf.data(), buf.size()), SyscallSucceedsWithValue(buf.size())); EXPECT_EQ(buf, expect) << std::string(buf.data(), buf.size()); } // Tests that reading from a file to a memory mapping of the same file does not // deadlock. See b/34813270. TEST_F(MMapFileTest, SelfRead) { SKIP_IF(!FSSupportsMap()); uintptr_t addr; ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd_.get(), 0), SyscallSucceeds()); EXPECT_THAT(Read(reinterpret_cast(addr), kPageSize / 2), SyscallSucceedsWithValue(kPageSize / 2)); // The resulting file contents are poorly-specified and irrelevant. } // Tests that writing to a file from a memory mapping of the same file does not // deadlock. Regression test for b/34813270. TEST_F(MMapFileTest, SelfWrite) { SKIP_IF(!FSSupportsMap()); uintptr_t addr; ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ, MAP_SHARED, fd_.get(), 0), SyscallSucceeds()); EXPECT_THAT(Write(reinterpret_cast(addr), kPageSize / 2), SyscallSucceedsWithValue(kPageSize / 2)); // The resulting file contents are poorly-specified and irrelevant. } TEST(MMapDeathTest, TruncateAfterCOWBreak) { SetupGvisorDeathTest(); // Create and map a single-page file. auto const temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); auto const fd = ASSERT_NO_ERRNO_AND_VALUE(Open(temp_file.path(), O_RDWR)); ASSERT_THAT(ftruncate(fd.get(), kPageSize), SyscallSucceeds()); auto maybe_mapping = Mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd.get(), 0); // Does FS support mmap? SKIP_IF(maybe_mapping.error().errno_value() == ENODEV); auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(std::move(maybe_mapping)); // Write to this mapping, causing the page to be copied for write. memset(mapping.ptr(), 'a', mapping.len()); MaybeSave(); // Trigger a co-operative save cycle. // Truncate the file and expect it to invalidate the copied page. ASSERT_THAT(ftruncate(fd.get(), 0), SyscallSucceeds()); EXPECT_EXIT(*reinterpret_cast(mapping.ptr()), ::testing::KilledBySignal(SIGBUS), ""); } // Regression test for #147. TEST(MMapNoFixtureTest, MapReadOnlyAfterCreateWriteOnly) { std::string filename = NewTempAbsPath(); // We have to create the file O_RDONLY to reproduce the bug because // fsgofer.localFile.Create() silently upgrades O_WRONLY to O_RDWR, causing // the cached "write-only" FD to be read/write and therefore usable by mmap(). auto const ro_fd = ASSERT_NO_ERRNO_AND_VALUE( Open(filename, O_RDONLY | O_CREAT | O_EXCL, 0666)); // Get a write-only FD for the same file, which should be ignored by mmap() // (but isn't in #147). auto const wo_fd = ASSERT_NO_ERRNO_AND_VALUE(Open(filename, O_WRONLY)); ASSERT_THAT(ftruncate(wo_fd.get(), kPageSize), SyscallSucceeds()); auto maybe_mapping = Mmap(nullptr, kPageSize, PROT_READ, MAP_SHARED, ro_fd.get(), 0); // Does FS support mmap? SKIP_IF(maybe_mapping.error().errno_value() == ENODEV); auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(std::move(maybe_mapping)); std::vector buf(kPageSize); // The test passes if this survives. std::copy(static_cast(mapping.ptr()), static_cast(mapping.endptr()), buf.data()); } // Conditional on MAP_32BIT. // This flag is supported only on x86-64, for 64-bit programs. #ifdef __x86_64__ TEST(MMapNoFixtureTest, Map32Bit) { auto const mapping = ASSERT_NO_ERRNO_AND_VALUE( MmapAnon(kPageSize, PROT_NONE, MAP_PRIVATE | MAP_32BIT)); EXPECT_LT(mapping.addr(), static_cast(1) << 32); EXPECT_LE(mapping.endaddr(), static_cast(1) << 32); } #endif // defined(__x86_64__) INSTANTIATE_TEST_SUITE_P( ReadWriteSharedPrivate, MMapFileParamTest, ::testing::Combine(::testing::ValuesIn({ PROT_READ, PROT_WRITE, PROT_READ | PROT_WRITE, }), ::testing::ValuesIn({MAP_SHARED, MAP_PRIVATE}))); } // namespace } // namespace testing } // namespace gvisor