// 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 <sys/mman.h> #include <sys/resource.h> #include <sys/syscall.h> #include <unistd.h> #include <cerrno> #include <cstring> #include "gmock/gmock.h" #include "test/util/capability_util.h" #include "test/util/cleanup.h" #include "test/util/memory_util.h" #include "test/util/multiprocess_util.h" #include "test/util/rlimit_util.h" #include "test/util/test_util.h" using ::testing::_; namespace gvisor { namespace testing { namespace { PosixErrorOr<bool> CanMlock() { struct rlimit rlim; if (getrlimit(RLIMIT_MEMLOCK, &rlim) < 0) { return PosixError(errno, "getrlimit(RLIMIT_MEMLOCK)"); } if (rlim.rlim_cur != 0) { return true; } return HaveCapability(CAP_IPC_LOCK); } // Returns true if the page containing addr is mlocked. bool IsPageMlocked(uintptr_t addr) { // This relies on msync(MS_INVALIDATE) interacting correctly with mlocked // pages, which is tested for by the MsyncInvalidate case below. int const rv = msync(reinterpret_cast<void*>(addr & ~(kPageSize - 1)), kPageSize, MS_ASYNC | MS_INVALIDATE); if (rv == 0) { return false; } // This uses TEST_PCHECK_MSG since it's used in subprocesses. TEST_PCHECK_MSG(errno == EBUSY, "msync failed with unexpected errno"); return true; } TEST(MlockTest, Basic) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock())); auto const mapping = ASSERT_NO_ERRNO_AND_VALUE( MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); EXPECT_FALSE(IsPageMlocked(mapping.addr())); ASSERT_THAT(mlock(mapping.ptr(), mapping.len()), SyscallSucceeds()); EXPECT_TRUE(IsPageMlocked(mapping.addr())); } TEST(MlockTest, ProtNone) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock())); auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, MAP_PRIVATE)); EXPECT_FALSE(IsPageMlocked(mapping.addr())); ASSERT_THAT(mlock(mapping.ptr(), mapping.len()), SyscallFailsWithErrno(ENOMEM)); // ENOMEM is returned because mlock can't populate the page, but it's still // considered locked. EXPECT_TRUE(IsPageMlocked(mapping.addr())); } TEST(MlockTest, MadviseDontneed) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock())); auto const mapping = ASSERT_NO_ERRNO_AND_VALUE( MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); ASSERT_THAT(mlock(mapping.ptr(), mapping.len()), SyscallSucceeds()); EXPECT_THAT(madvise(mapping.ptr(), mapping.len(), MADV_DONTNEED), SyscallFailsWithErrno(EINVAL)); } TEST(MlockTest, MsyncInvalidate) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock())); auto const mapping = ASSERT_NO_ERRNO_AND_VALUE( MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); ASSERT_THAT(mlock(mapping.ptr(), mapping.len()), SyscallSucceeds()); EXPECT_THAT(msync(mapping.ptr(), mapping.len(), MS_ASYNC | MS_INVALIDATE), SyscallFailsWithErrno(EBUSY)); EXPECT_THAT(msync(mapping.ptr(), mapping.len(), MS_SYNC | MS_INVALIDATE), SyscallFailsWithErrno(EBUSY)); } TEST(MlockTest, Fork) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock())); auto const mapping = ASSERT_NO_ERRNO_AND_VALUE( MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); EXPECT_FALSE(IsPageMlocked(mapping.addr())); ASSERT_THAT(mlock(mapping.ptr(), mapping.len()), SyscallSucceeds()); EXPECT_TRUE(IsPageMlocked(mapping.addr())); EXPECT_THAT( InForkedProcess([&] { TEST_CHECK(!IsPageMlocked(mapping.addr())); }), IsPosixErrorOkAndHolds(0)); } TEST(MlockTest, RlimitMemlockZero) { if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_IPC_LOCK))) { ASSERT_NO_ERRNO(SetCapability(CAP_IPC_LOCK, false)); } Cleanup reset_rlimit = ASSERT_NO_ERRNO_AND_VALUE(ScopedSetSoftRlimit(RLIMIT_MEMLOCK, 0)); auto const mapping = ASSERT_NO_ERRNO_AND_VALUE( MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); EXPECT_FALSE(IsPageMlocked(mapping.addr())); ASSERT_THAT(mlock(mapping.ptr(), mapping.len()), SyscallFailsWithErrno(EPERM)); } TEST(MlockTest, RlimitMemlockInsufficient) { if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_IPC_LOCK))) { ASSERT_NO_ERRNO(SetCapability(CAP_IPC_LOCK, false)); } Cleanup reset_rlimit = ASSERT_NO_ERRNO_AND_VALUE(ScopedSetSoftRlimit(RLIMIT_MEMLOCK, kPageSize)); auto const mapping = ASSERT_NO_ERRNO_AND_VALUE( MmapAnon(2 * kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); EXPECT_FALSE(IsPageMlocked(mapping.addr())); ASSERT_THAT(mlock(mapping.ptr(), mapping.len()), SyscallFailsWithErrno(ENOMEM)); } TEST(MunlockTest, Basic) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock())); auto const mapping = ASSERT_NO_ERRNO_AND_VALUE( MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); EXPECT_FALSE(IsPageMlocked(mapping.addr())); ASSERT_THAT(mlock(mapping.ptr(), mapping.len()), SyscallSucceeds()); EXPECT_TRUE(IsPageMlocked(mapping.addr())); ASSERT_THAT(munlock(mapping.ptr(), mapping.len()), SyscallSucceeds()); EXPECT_FALSE(IsPageMlocked(mapping.addr())); } TEST(MunlockTest, NotLocked) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock())); auto const mapping = ASSERT_NO_ERRNO_AND_VALUE( MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); EXPECT_FALSE(IsPageMlocked(mapping.addr())); EXPECT_THAT(munlock(mapping.ptr(), mapping.len()), SyscallSucceeds()); EXPECT_FALSE(IsPageMlocked(mapping.addr())); } // There is currently no test for mlockall(MCL_CURRENT) because the default // RLIMIT_MEMLOCK of 64 KB is insufficient to actually invoke // mlockall(MCL_CURRENT). TEST(MlockallTest, Future) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock())); // Run this test in a separate (single-threaded) subprocess to ensure that a // background thread doesn't try to mmap a large amount of memory, fail due // to hitting RLIMIT_MEMLOCK, and explode the process violently. auto const do_test = [] { auto const mapping = MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE).ValueOrDie(); TEST_CHECK(!IsPageMlocked(mapping.addr())); TEST_PCHECK(mlockall(MCL_FUTURE) == 0); // Ensure that mlockall(MCL_FUTURE) is turned off before the end of the // test, as otherwise mmaps may fail unexpectedly. Cleanup do_munlockall([] { TEST_PCHECK(munlockall() == 0); }); auto const mapping2 = MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE).ValueOrDie(); TEST_CHECK(IsPageMlocked(mapping2.addr())); // Fire munlockall() and check that it disables mlockall(MCL_FUTURE). do_munlockall.Release()(); auto const mapping3 = MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE).ValueOrDie(); TEST_CHECK(!IsPageMlocked(mapping2.addr())); }; EXPECT_THAT(InForkedProcess(do_test), IsPosixErrorOkAndHolds(0)); } TEST(MunlockallTest, Basic) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock())); auto const mapping = ASSERT_NO_ERRNO_AND_VALUE( MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_LOCKED)); EXPECT_TRUE(IsPageMlocked(mapping.addr())); ASSERT_THAT(munlockall(), SyscallSucceeds()); EXPECT_FALSE(IsPageMlocked(mapping.addr())); } #ifndef SYS_mlock2 #ifdef __x86_64__ #define SYS_mlock2 325 #endif #endif #ifndef MLOCK_ONFAULT #define MLOCK_ONFAULT 0x01 // Linux: include/uapi/asm-generic/mman-common.h #endif #ifdef SYS_mlock2 int mlock2(void const* addr, size_t len, int flags) { return syscall(SYS_mlock2, addr, len, flags); } TEST(Mlock2Test, NoFlags) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock())); auto const mapping = ASSERT_NO_ERRNO_AND_VALUE( MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); EXPECT_FALSE(IsPageMlocked(mapping.addr())); ASSERT_THAT(mlock2(mapping.ptr(), mapping.len(), 0), SyscallSucceeds()); EXPECT_TRUE(IsPageMlocked(mapping.addr())); } TEST(Mlock2Test, MlockOnfault) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock())); auto const mapping = ASSERT_NO_ERRNO_AND_VALUE( MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); EXPECT_FALSE(IsPageMlocked(mapping.addr())); ASSERT_THAT(mlock2(mapping.ptr(), mapping.len(), MLOCK_ONFAULT), SyscallSucceeds()); EXPECT_TRUE(IsPageMlocked(mapping.addr())); } TEST(Mlock2Test, UnknownFlags) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock())); auto const mapping = ASSERT_NO_ERRNO_AND_VALUE( MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); EXPECT_THAT(mlock2(mapping.ptr(), mapping.len(), ~0), SyscallFailsWithErrno(EINVAL)); } #endif // defined(SYS_mlock2) TEST(MapLockedTest, Basic) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock())); auto const mapping = ASSERT_NO_ERRNO_AND_VALUE( MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_LOCKED)); EXPECT_TRUE(IsPageMlocked(mapping.addr())); EXPECT_THAT(munlock(mapping.ptr(), mapping.len()), SyscallSucceeds()); EXPECT_FALSE(IsPageMlocked(mapping.addr())); } TEST(MapLockedTest, RlimitMemlockZero) { if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_IPC_LOCK))) { ASSERT_NO_ERRNO(SetCapability(CAP_IPC_LOCK, false)); } Cleanup reset_rlimit = ASSERT_NO_ERRNO_AND_VALUE(ScopedSetSoftRlimit(RLIMIT_MEMLOCK, 0)); EXPECT_THAT( MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_LOCKED), PosixErrorIs(EPERM, _)); } TEST(MapLockedTest, RlimitMemlockInsufficient) { if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_IPC_LOCK))) { ASSERT_NO_ERRNO(SetCapability(CAP_IPC_LOCK, false)); } Cleanup reset_rlimit = ASSERT_NO_ERRNO_AND_VALUE(ScopedSetSoftRlimit(RLIMIT_MEMLOCK, kPageSize)); EXPECT_THAT( MmapAnon(2 * kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_LOCKED), PosixErrorIs(EAGAIN, _)); } TEST(MremapLockedTest, Basic) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock())); auto mapping = ASSERT_NO_ERRNO_AND_VALUE( MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_LOCKED)); EXPECT_TRUE(IsPageMlocked(mapping.addr())); void* addr = mremap(mapping.ptr(), mapping.len(), 2 * mapping.len(), MREMAP_MAYMOVE, nullptr); if (addr == MAP_FAILED) { FAIL() << "mremap failed: " << errno << " (" << strerror(errno) << ")"; } mapping.release(); mapping.reset(addr, 2 * mapping.len()); EXPECT_TRUE(IsPageMlocked(reinterpret_cast<uintptr_t>(addr))); } TEST(MremapLockedTest, RlimitMemlockZero) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock())); auto mapping = ASSERT_NO_ERRNO_AND_VALUE( MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_LOCKED)); EXPECT_TRUE(IsPageMlocked(mapping.addr())); if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_IPC_LOCK))) { ASSERT_NO_ERRNO(SetCapability(CAP_IPC_LOCK, false)); } Cleanup reset_rlimit = ASSERT_NO_ERRNO_AND_VALUE(ScopedSetSoftRlimit(RLIMIT_MEMLOCK, 0)); void* addr = mremap(mapping.ptr(), mapping.len(), 2 * mapping.len(), MREMAP_MAYMOVE, nullptr); EXPECT_TRUE(addr == MAP_FAILED && errno == EAGAIN) << "addr = " << addr << ", errno = " << errno; } TEST(MremapLockedTest, RlimitMemlockInsufficient) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock())); auto mapping = ASSERT_NO_ERRNO_AND_VALUE( MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_LOCKED)); EXPECT_TRUE(IsPageMlocked(mapping.addr())); if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_IPC_LOCK))) { ASSERT_NO_ERRNO(SetCapability(CAP_IPC_LOCK, false)); } Cleanup reset_rlimit = ASSERT_NO_ERRNO_AND_VALUE( ScopedSetSoftRlimit(RLIMIT_MEMLOCK, mapping.len())); void* addr = mremap(mapping.ptr(), mapping.len(), 2 * mapping.len(), MREMAP_MAYMOVE, nullptr); EXPECT_TRUE(addr == MAP_FAILED && errno == EAGAIN) << "addr = " << addr << ", errno = " << errno; } } // namespace } // namespace testing } // namespace gvisor