Skip to content

Commit

Permalink
test: add PageTableLeak test
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 643437188
  • Loading branch information
nixprime authored and gvisor-bot committed Jun 14, 2024
1 parent 8a83d9f commit 2069e86
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 1 deletion.
6 changes: 6 additions & 0 deletions test/syscalls/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,12 @@ syscall_test(
test = "//test/syscalls/linux:mlock_test",
)

syscall_test(
timeout = "eternal", # YES_I_REALLY_NEED_AN_ETERNAL_TEST
save = False, # save tests incorrectly shorten timeout to "long"
test = "//test/syscalls/linux:mmap_eternal_test",
)

syscall_test(
size = "medium",
shard_count = more_shards,
Expand Down
17 changes: 17 additions & 0 deletions test/syscalls/linux/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -1381,6 +1381,23 @@ cc_binary(
],
)

cc_binary(
name = "mmap_eternal_test",
testonly = 1,
srcs = ["mmap_eternal.cc"],
linkstatic = 1,
malloc = "//test/util:errno_safe_allocator",
deps = select_gtest() + [
"//test/util:logging",
"//test/util:memory_util",
"//test/util:multiprocess_util",
"//test/util:posix_error",
"//test/util:save_util",
"//test/util:test_main",
"//test/util:test_util",
],
)

cc_binary(
name = "mmap_test",
testonly = 1,
Expand Down
85 changes: 85 additions & 0 deletions test/syscalls/linux/mmap_eternal.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright 2024 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.

// mmap tests that often take longer than 900s to run and thus may be skipped
// by test automation.

#include <stddef.h>

#include "gmock/gmock.h"
#include "gtest/gtest.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/save_util.h"
#include "test/util/test_util.h"

namespace gvisor {
namespace testing {

namespace {

// Tests that when using one entry per page table leaf page at a time, page
// table pages that become empty do not accumulate.
TEST(MmapEternalTest, PageTableLeak) {
// Skip this test on platforms where app page tables are managed as for
// ordinary processes by the host kernel, both because there's relatively
// little value in exercising this behavior (separately from
// Platform::kNative) and because MM can be slow enough on such platforms to
// cause the test to time out.
SKIP_IF(GvisorPlatform() == Platform::kPtrace ||
GvisorPlatform() == Platform::kSystrap);

// Guess how much virtual address space we need.
constexpr size_t kMemoryLimitBytes = 12L << 30;
const size_t kMemoryLimitPages = kMemoryLimitBytes / kPageSize;
const size_t kEntriesPerPageTablePage = kPageSize / sizeof(void*);
const size_t kMemoryPerPageTableLeafPage =
kPageSize * kEntriesPerPageTablePage;
const size_t kMemorySizeBytes =
kMemoryLimitPages * kMemoryPerPageTableLeafPage;

// Reserve virtual address space.
Mapping m = ASSERT_NO_ERRNO_AND_VALUE(
MmapAnon(kMemorySizeBytes, PROT_NONE, MAP_PRIVATE));

// Map and unmap one page at a time. This uses a subprocess since the
// existence of our reservation VMA interferes with page table freeing;
// forking ensures that there are no other threads in the subprocess,
// allowing us to safely unmap the reservation.
const DisableSave ds;
const auto rest = [&] {
char* ptr = static_cast<char*>(m.ptr());
char const* const end = static_cast<char*>(m.endptr());
m.reset();
while (ptr < end) {
TEST_PCHECK(
MmapSafe(ptr, kPageSize, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED | MAP_POPULATE, -1,
0) == ptr);
TEST_PCHECK(MunmapSafe(ptr, kPageSize) == 0);
ptr += kMemoryPerPageTableLeafPage;
}
};

// The test passes if this does not result in an OOM kill (of the subprocess
// or the sandbox).
EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
}

} // namespace

} // namespace testing
} // namespace gvisor
9 changes: 8 additions & 1 deletion test/util/memory_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@
namespace gvisor {
namespace testing {

// Async-signal-safe version of mmap(2).
inline void* MmapSafe(void* addr, size_t length, int prot, int flags, int fd,
off_t offset) {
return reinterpret_cast<void*>(
syscall(SYS_mmap, addr, length, prot, flags, fd, offset));
}

// Async-signal-safe version of munmap(2).
inline int MunmapSafe(void* addr, size_t length) {
return syscall(SYS_munmap, addr, length);
Expand Down Expand Up @@ -110,7 +117,7 @@ class Mapping {
// Wrapper around mmap(2) that returns a Mapping.
inline PosixErrorOr<Mapping> Mmap(void* addr, size_t length, int prot,
int flags, int fd, off_t offset) {
void* ptr = mmap(addr, length, prot, flags, fd, offset);
void* ptr = MmapSafe(addr, length, prot, flags, fd, offset);
if (ptr == MAP_FAILED) {
return PosixError(
errno, absl::StrFormat("mmap(%p, %d, %x, %x, %d, %d)", addr, length,
Expand Down

0 comments on commit 2069e86

Please sign in to comment.