summaryrefslogtreecommitdiffhomepage
path: root/test/syscalls/linux/mremap.cc
diff options
context:
space:
mode:
Diffstat (limited to 'test/syscalls/linux/mremap.cc')
-rw-r--r--test/syscalls/linux/mremap.cc514
1 files changed, 514 insertions, 0 deletions
diff --git a/test/syscalls/linux/mremap.cc b/test/syscalls/linux/mremap.cc
new file mode 100644
index 000000000..ededab336
--- /dev/null
+++ b/test/syscalls/linux/mremap.cc
@@ -0,0 +1,514 @@
+// Copyright 2018 Google LLC
+//
+// 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;
+}
+
+// Returns true if the page containing addr is mapped.
+bool IsMapped(uintptr_t addr) {
+ int const rv = msync(reinterpret_cast<void*>(addr & ~(kPageSize - 1)),
+ kPageSize, MS_ASYNC);
+ if (rv == 0) {
+ return true;
+ }
+ TEST_PCHECK_MSG(errno == ENOMEM, "msync failed with unexpected errno");
+ return false;
+}
+
+// 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_CASE_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