diff options
Diffstat (limited to 'test/syscalls/linux/mremap.cc')
-rw-r--r-- | test/syscalls/linux/mremap.cc | 503 |
1 files changed, 0 insertions, 503 deletions
diff --git a/test/syscalls/linux/mremap.cc b/test/syscalls/linux/mremap.cc deleted file mode 100644 index 64e435cb7..000000000 --- a/test/syscalls/linux/mremap.cc +++ /dev/null @@ -1,503 +0,0 @@ -// 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 <errno.h> -#include <string.h> -#include <sys/mman.h> - -#include <string> - -#include "gmock/gmock.h" -#include "absl/strings/string_view.h" -#include "test/util/file_descriptor.h" -#include "test/util/logging.h" -#include "test/util/memory_util.h" -#include "test/util/multiprocess_util.h" -#include "test/util/posix_error.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -using ::testing::_; - -namespace gvisor { -namespace testing { - -namespace { - -// Wrapper for mremap that returns a PosixErrorOr<>, since the return type of -// void* isn't directly compatible with SyscallSucceeds. -PosixErrorOr<void*> Mremap(void* old_address, size_t old_size, size_t new_size, - int flags, void* new_address) { - void* rv = mremap(old_address, old_size, new_size, flags, new_address); - if (rv == MAP_FAILED) { - return PosixError(errno, "mremap failed"); - } - return rv; -} - -// Fixture for mremap tests parameterized by mmap flags. -using MremapParamTest = ::testing::TestWithParam<int>; - -TEST_P(MremapParamTest, Noop) { - Mapping const m = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, GetParam())); - - ASSERT_THAT(Mremap(m.ptr(), kPageSize, kPageSize, 0, nullptr), - IsPosixErrorOkAndHolds(m.ptr())); - EXPECT_TRUE(IsMapped(m.addr())); -} - -TEST_P(MremapParamTest, InPlace_ShrinkingWholeVMA) { - Mapping const m = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(2 * kPageSize, PROT_NONE, GetParam())); - - const auto rest = [&] { - // N.B. we must be in a single-threaded subprocess to ensure a - // background thread doesn't concurrently map the second page. - void* addr = mremap(m.ptr(), 2 * kPageSize, kPageSize, 0, nullptr); - TEST_PCHECK_MSG(addr != MAP_FAILED, "mremap failed"); - TEST_CHECK(addr == m.ptr()); - MaybeSave(); - - TEST_CHECK(IsMapped(m.addr())); - TEST_CHECK(!IsMapped(m.addr() + kPageSize)); - }; - - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); -} - -TEST_P(MremapParamTest, InPlace_ShrinkingPartialVMA) { - Mapping const m = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(3 * kPageSize, PROT_NONE, GetParam())); - - const auto rest = [&] { - void* addr = mremap(m.ptr(), 2 * kPageSize, kPageSize, 0, nullptr); - TEST_PCHECK_MSG(addr != MAP_FAILED, "mremap failed"); - TEST_CHECK(addr == m.ptr()); - MaybeSave(); - - TEST_CHECK(IsMapped(m.addr())); - TEST_CHECK(!IsMapped(m.addr() + kPageSize)); - TEST_CHECK(IsMapped(m.addr() + 2 * kPageSize)); - }; - - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); -} - -TEST_P(MremapParamTest, InPlace_ShrinkingAcrossVMAs) { - Mapping const m = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(3 * kPageSize, PROT_READ, GetParam())); - // Changing permissions on the first page forces it to become a separate vma. - ASSERT_THAT(mprotect(m.ptr(), kPageSize, PROT_NONE), SyscallSucceeds()); - - const auto rest = [&] { - // Both old_size and new_size now span two vmas; mremap - // shouldn't care. - void* addr = mremap(m.ptr(), 3 * kPageSize, 2 * kPageSize, 0, nullptr); - TEST_PCHECK_MSG(addr != MAP_FAILED, "mremap failed"); - TEST_CHECK(addr == m.ptr()); - MaybeSave(); - - TEST_CHECK(IsMapped(m.addr())); - TEST_CHECK(IsMapped(m.addr() + kPageSize)); - TEST_CHECK(!IsMapped(m.addr() + 2 * kPageSize)); - }; - - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); -} - -TEST_P(MremapParamTest, InPlace_ExpansionSuccess) { - Mapping const m = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(2 * kPageSize, PROT_NONE, GetParam())); - - const auto rest = [&] { - // Unmap the second page so that the first can be expanded back into it. - // - // N.B. we must be in a single-threaded subprocess to ensure a - // background thread doesn't concurrently map this page. - TEST_PCHECK( - munmap(reinterpret_cast<void*>(m.addr() + kPageSize), kPageSize) == 0); - MaybeSave(); - - void* addr = mremap(m.ptr(), kPageSize, 2 * kPageSize, 0, nullptr); - TEST_PCHECK_MSG(addr != MAP_FAILED, "mremap failed"); - TEST_CHECK(addr == m.ptr()); - MaybeSave(); - - TEST_CHECK(IsMapped(m.addr())); - TEST_CHECK(IsMapped(m.addr() + kPageSize)); - }; - - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); -} - -TEST_P(MremapParamTest, InPlace_ExpansionFailure) { - Mapping const m = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(3 * kPageSize, PROT_NONE, GetParam())); - - const auto rest = [&] { - // Unmap the second page, leaving a one-page hole. Trying to expand the - // first page to three pages should fail since the original third page - // is still mapped. - TEST_PCHECK( - munmap(reinterpret_cast<void*>(m.addr() + kPageSize), kPageSize) == 0); - MaybeSave(); - - void* addr = mremap(m.ptr(), kPageSize, 3 * kPageSize, 0, nullptr); - TEST_CHECK_MSG(addr == MAP_FAILED, "mremap unexpectedly succeeded"); - TEST_PCHECK_MSG(errno == ENOMEM, "mremap failed with wrong errno"); - MaybeSave(); - - TEST_CHECK(IsMapped(m.addr())); - TEST_CHECK(!IsMapped(m.addr() + kPageSize)); - TEST_CHECK(IsMapped(m.addr() + 2 * kPageSize)); - }; - - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); -} - -TEST_P(MremapParamTest, MayMove_Expansion) { - Mapping const m = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(3 * kPageSize, PROT_NONE, GetParam())); - - const auto rest = [&] { - // Unmap the second page, leaving a one-page hole. Trying to expand the - // first page to three pages with MREMAP_MAYMOVE should force the - // mapping to be relocated since the original third page is still - // mapped. - TEST_PCHECK( - munmap(reinterpret_cast<void*>(m.addr() + kPageSize), kPageSize) == 0); - MaybeSave(); - - void* addr2 = - mremap(m.ptr(), kPageSize, 3 * kPageSize, MREMAP_MAYMOVE, nullptr); - TEST_PCHECK_MSG(addr2 != MAP_FAILED, "mremap failed"); - MaybeSave(); - - const Mapping m2 = Mapping(addr2, 3 * kPageSize); - TEST_CHECK(m.addr() != m2.addr()); - - TEST_CHECK(!IsMapped(m.addr())); - TEST_CHECK(!IsMapped(m.addr() + kPageSize)); - TEST_CHECK(IsMapped(m.addr() + 2 * kPageSize)); - TEST_CHECK(IsMapped(m2.addr())); - TEST_CHECK(IsMapped(m2.addr() + kPageSize)); - TEST_CHECK(IsMapped(m2.addr() + 2 * kPageSize)); - }; - - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); -} - -TEST_P(MremapParamTest, Fixed_SourceAndDestinationCannotOverlap) { - Mapping const m = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, GetParam())); - - ASSERT_THAT(Mremap(m.ptr(), kPageSize, kPageSize, - MREMAP_MAYMOVE | MREMAP_FIXED, m.ptr()), - PosixErrorIs(EINVAL, _)); - EXPECT_TRUE(IsMapped(m.addr())); -} - -TEST_P(MremapParamTest, Fixed_SameSize) { - Mapping const src = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, GetParam())); - Mapping const dst = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, GetParam())); - - const auto rest = [&] { - // Unmap dst to create a hole. - TEST_PCHECK(munmap(dst.ptr(), kPageSize) == 0); - MaybeSave(); - - void* addr = mremap(src.ptr(), kPageSize, kPageSize, - MREMAP_MAYMOVE | MREMAP_FIXED, dst.ptr()); - TEST_PCHECK_MSG(addr != MAP_FAILED, "mremap failed"); - TEST_CHECK(addr == dst.ptr()); - MaybeSave(); - - TEST_CHECK(!IsMapped(src.addr())); - TEST_CHECK(IsMapped(dst.addr())); - }; - - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); -} - -TEST_P(MremapParamTest, Fixed_SameSize_Unmapping) { - // Like the Fixed_SameSize case, but expect mremap to unmap the destination - // automatically. - Mapping const src = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, GetParam())); - Mapping const dst = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, GetParam())); - - const auto rest = [&] { - void* addr = mremap(src.ptr(), kPageSize, kPageSize, - MREMAP_MAYMOVE | MREMAP_FIXED, dst.ptr()); - TEST_PCHECK_MSG(addr != MAP_FAILED, "mremap failed"); - TEST_CHECK(addr == dst.ptr()); - MaybeSave(); - - TEST_CHECK(!IsMapped(src.addr())); - TEST_CHECK(IsMapped(dst.addr())); - }; - - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); -} - -TEST_P(MremapParamTest, Fixed_ShrinkingWholeVMA) { - Mapping const src = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(2 * kPageSize, PROT_NONE, GetParam())); - Mapping const dst = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(2 * kPageSize, PROT_NONE, GetParam())); - - const auto rest = [&] { - // Unmap dst so we can check that mremap does not keep the - // second page. - TEST_PCHECK(munmap(dst.ptr(), 2 * kPageSize) == 0); - MaybeSave(); - - void* addr = mremap(src.ptr(), 2 * kPageSize, kPageSize, - MREMAP_MAYMOVE | MREMAP_FIXED, dst.ptr()); - TEST_PCHECK_MSG(addr != MAP_FAILED, "mremap failed"); - TEST_CHECK(addr == dst.ptr()); - MaybeSave(); - - TEST_CHECK(!IsMapped(src.addr())); - TEST_CHECK(!IsMapped(src.addr() + kPageSize)); - TEST_CHECK(IsMapped(dst.addr())); - TEST_CHECK(!IsMapped(dst.addr() + kPageSize)); - }; - - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); -} - -TEST_P(MremapParamTest, Fixed_ShrinkingPartialVMA) { - Mapping const src = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(3 * kPageSize, PROT_NONE, GetParam())); - Mapping const dst = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(2 * kPageSize, PROT_NONE, GetParam())); - - const auto rest = [&] { - // Unmap dst so we can check that mremap does not keep the - // second page. - TEST_PCHECK(munmap(dst.ptr(), 2 * kPageSize) == 0); - MaybeSave(); - - void* addr = mremap(src.ptr(), 2 * kPageSize, kPageSize, - MREMAP_MAYMOVE | MREMAP_FIXED, dst.ptr()); - TEST_PCHECK_MSG(addr != MAP_FAILED, "mremap failed"); - TEST_CHECK(addr == dst.ptr()); - MaybeSave(); - - TEST_CHECK(!IsMapped(src.addr())); - TEST_CHECK(!IsMapped(src.addr() + kPageSize)); - TEST_CHECK(IsMapped(src.addr() + 2 * kPageSize)); - TEST_CHECK(IsMapped(dst.addr())); - TEST_CHECK(!IsMapped(dst.addr() + kPageSize)); - }; - - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); -} - -TEST_P(MremapParamTest, Fixed_ShrinkingAcrossVMAs) { - Mapping const src = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(3 * kPageSize, PROT_READ, GetParam())); - // Changing permissions on the first page forces it to become a separate vma. - ASSERT_THAT(mprotect(src.ptr(), kPageSize, PROT_NONE), SyscallSucceeds()); - Mapping const dst = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(2 * kPageSize, PROT_NONE, GetParam())); - - const auto rest = [&] { - // Unlike flags=0, MREMAP_FIXED requires that [old_address, - // old_address+new_size) only spans a single vma. - void* addr = mremap(src.ptr(), 3 * kPageSize, 2 * kPageSize, - MREMAP_MAYMOVE | MREMAP_FIXED, dst.ptr()); - TEST_CHECK_MSG(addr == MAP_FAILED, "mremap unexpectedly succeeded"); - TEST_PCHECK_MSG(errno == EFAULT, "mremap failed with wrong errno"); - MaybeSave(); - - TEST_CHECK(IsMapped(src.addr())); - TEST_CHECK(IsMapped(src.addr() + kPageSize)); - // Despite failing, mremap should have unmapped [old_address+new_size, - // old_address+old_size) (i.e. the third page). - TEST_CHECK(!IsMapped(src.addr() + 2 * kPageSize)); - // Despite failing, mremap should have unmapped the destination pages. - TEST_CHECK(!IsMapped(dst.addr())); - TEST_CHECK(!IsMapped(dst.addr() + kPageSize)); - }; - - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); -} - -TEST_P(MremapParamTest, Fixed_Expansion) { - Mapping const src = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, GetParam())); - Mapping const dst = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(2 * kPageSize, PROT_NONE, GetParam())); - - const auto rest = [&] { - // Unmap dst so we can check that mremap actually maps all pages - // at the destination. - TEST_PCHECK(munmap(dst.ptr(), 2 * kPageSize) == 0); - MaybeSave(); - - void* addr = mremap(src.ptr(), kPageSize, 2 * kPageSize, - MREMAP_MAYMOVE | MREMAP_FIXED, dst.ptr()); - TEST_PCHECK_MSG(addr != MAP_FAILED, "mremap failed"); - TEST_CHECK(addr == dst.ptr()); - MaybeSave(); - - TEST_CHECK(!IsMapped(src.addr())); - TEST_CHECK(IsMapped(dst.addr())); - TEST_CHECK(IsMapped(dst.addr() + kPageSize)); - }; - - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); -} - -INSTANTIATE_TEST_SUITE_P(PrivateShared, MremapParamTest, - ::testing::Values(MAP_PRIVATE, MAP_SHARED)); - -// mremap with old_size == 0 only works with MAP_SHARED after Linux 4.14 -// (dba58d3b8c50 "mm/mremap: fail map duplication attempts for private -// mappings"). - -TEST(MremapTest, InPlace_Copy) { - Mapping const m = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, MAP_SHARED)); - EXPECT_THAT(Mremap(m.ptr(), 0, kPageSize, 0, nullptr), - PosixErrorIs(ENOMEM, _)); -} - -TEST(MremapTest, MayMove_Copy) { - Mapping const m = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, MAP_SHARED)); - - // Remainder of this test executes in a subprocess to ensure that if mremap - // incorrectly removes m, it is not remapped by another thread. - const auto rest = [&] { - void* ptr = mremap(m.ptr(), 0, kPageSize, MREMAP_MAYMOVE, nullptr); - MaybeSave(); - TEST_PCHECK_MSG(ptr != MAP_FAILED, "mremap failed"); - TEST_CHECK(ptr != m.ptr()); - TEST_CHECK(IsMapped(m.addr())); - TEST_CHECK(IsMapped(reinterpret_cast<uintptr_t>(ptr))); - }; - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); -} - -TEST(MremapTest, MustMove_Copy) { - Mapping const src = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, MAP_SHARED)); - Mapping const dst = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, MAP_PRIVATE)); - - // Remainder of this test executes in a subprocess to ensure that if mremap - // incorrectly removes src, it is not remapped by another thread. - const auto rest = [&] { - void* ptr = mremap(src.ptr(), 0, kPageSize, MREMAP_MAYMOVE | MREMAP_FIXED, - dst.ptr()); - MaybeSave(); - TEST_PCHECK_MSG(ptr != MAP_FAILED, "mremap failed"); - TEST_CHECK(ptr == dst.ptr()); - TEST_CHECK(IsMapped(src.addr())); - TEST_CHECK(IsMapped(dst.addr())); - }; - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); -} - -void ExpectAllBytesAre(absl::string_view v, char c) { - for (size_t i = 0; i < v.size(); i++) { - ASSERT_EQ(v[i], c) << "at offset " << i; - } -} - -TEST(MremapTest, ExpansionPreservesCOWPagesAndExposesNewFilePages) { - // Create a file with 3 pages. The first is filled with 'a', the second is - // filled with 'b', and the third is filled with 'c'. - TempPath const file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR)); - ASSERT_THAT(WriteFd(fd.get(), std::string(kPageSize, 'a').c_str(), kPageSize), - SyscallSucceedsWithValue(kPageSize)); - ASSERT_THAT(WriteFd(fd.get(), std::string(kPageSize, 'b').c_str(), kPageSize), - SyscallSucceedsWithValue(kPageSize)); - ASSERT_THAT(WriteFd(fd.get(), std::string(kPageSize, 'c').c_str(), kPageSize), - SyscallSucceedsWithValue(kPageSize)); - - // Create a private mapping of the first 2 pages, and fill the second page - // with 'd'. - Mapping const src = ASSERT_NO_ERRNO_AND_VALUE(Mmap(nullptr, 2 * kPageSize, - PROT_READ | PROT_WRITE, - MAP_PRIVATE, fd.get(), 0)); - memset(reinterpret_cast<void*>(src.addr() + kPageSize), 'd', kPageSize); - MaybeSave(); - - // Move the mapping while expanding it to 3 pages. The resulting mapping - // should contain the original first page of the file (filled with 'a'), - // followed by the private copy of the second page (filled with 'd'), followed - // by the newly-mapped third page of the file (filled with 'c'). - Mapping const dst = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(3 * kPageSize, PROT_NONE, MAP_PRIVATE)); - ASSERT_THAT(Mremap(src.ptr(), 2 * kPageSize, 3 * kPageSize, - MREMAP_MAYMOVE | MREMAP_FIXED, dst.ptr()), - IsPosixErrorOkAndHolds(dst.ptr())); - auto const v = dst.view(); - ExpectAllBytesAre(v.substr(0, kPageSize), 'a'); - ExpectAllBytesAre(v.substr(kPageSize, kPageSize), 'd'); - ExpectAllBytesAre(v.substr(2 * kPageSize, kPageSize), 'c'); -} - -TEST(MremapDeathTest, SharedAnon) { - SetupGvisorDeathTest(); - - // Reserve 4 pages of address space. - Mapping const reserved = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(4 * kPageSize, PROT_NONE, MAP_PRIVATE)); - - // Create a 2-page shared anonymous mapping at the beginning of the - // reservation. Fill the first page with 'a' and the second with 'b'. - Mapping const m = ASSERT_NO_ERRNO_AND_VALUE( - Mmap(reserved.ptr(), 2 * kPageSize, PROT_READ | PROT_WRITE, - MAP_SHARED | MAP_ANONYMOUS | MAP_FIXED, -1, 0)); - memset(m.ptr(), 'a', kPageSize); - memset(reinterpret_cast<void*>(m.addr() + kPageSize), 'b', kPageSize); - MaybeSave(); - - // Shrink the mapping to 1 page in-place. - ASSERT_THAT(Mremap(m.ptr(), 2 * kPageSize, kPageSize, 0, m.ptr()), - IsPosixErrorOkAndHolds(m.ptr())); - - // Expand the mapping to 3 pages, moving it forward by 1 page in the process - // since the old and new mappings can't overlap. - void* const new_m = reinterpret_cast<void*>(m.addr() + kPageSize); - ASSERT_THAT(Mremap(m.ptr(), kPageSize, 3 * kPageSize, - MREMAP_MAYMOVE | MREMAP_FIXED, new_m), - IsPosixErrorOkAndHolds(new_m)); - - // The first 2 pages of the mapping should still contain the data we wrote - // (i.e. shrinking should not have discarded the second page's data), while - // touching the third page should raise SIGBUS. - auto const v = - absl::string_view(static_cast<char const*>(new_m), 3 * kPageSize); - ExpectAllBytesAre(v.substr(0, kPageSize), 'a'); - ExpectAllBytesAre(v.substr(kPageSize, kPageSize), 'b'); - EXPECT_EXIT(ExpectAllBytesAre(v.substr(2 * kPageSize, kPageSize), '\0'), - ::testing::KilledBySignal(SIGBUS), ""); -} - -} // namespace - -} // namespace testing -} // namespace gvisor |