gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/syscalls/linux/madvise.cc (about) 1 // Copyright 2018 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #include <fcntl.h> 16 #include <stdlib.h> 17 #include <string.h> 18 #include <sys/mman.h> 19 #include <sys/stat.h> 20 #include <sys/types.h> 21 #include <sys/wait.h> 22 #include <unistd.h> 23 24 #include <string> 25 26 #include "gmock/gmock.h" 27 #include "gtest/gtest.h" 28 #include "test/util/file_descriptor.h" 29 #include "test/util/logging.h" 30 #include "test/util/memory_util.h" 31 #include "test/util/multiprocess_util.h" 32 #include "test/util/posix_error.h" 33 #include "test/util/temp_path.h" 34 #include "test/util/test_util.h" 35 36 namespace gvisor { 37 namespace testing { 38 39 namespace { 40 41 void ExpectAllMappingBytes(Mapping const& m, char c) { 42 auto const v = m.view(); 43 for (size_t i = 0; i < kPageSize; i++) { 44 ASSERT_EQ(v[i], c) << "at offset " << i; 45 } 46 } 47 48 // Equivalent to ExpectAllMappingBytes but async-signal-safe and with less 49 // helpful failure messages. 50 void CheckAllMappingBytes(Mapping const& m, char c) { 51 auto const v = m.view(); 52 for (size_t i = 0; i < kPageSize; i++) { 53 TEST_CHECK_MSG(v[i] == c, "mapping contains wrong value"); 54 } 55 } 56 57 TEST(MadviseDontneedTest, ZerosPrivateAnonPage) { 58 auto m = ASSERT_NO_ERRNO_AND_VALUE( 59 MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); 60 ExpectAllMappingBytes(m, 0); 61 memset(m.ptr(), 1, m.len()); 62 ExpectAllMappingBytes(m, 1); 63 ASSERT_THAT(madvise(m.ptr(), m.len(), MADV_DONTNEED), SyscallSucceeds()); 64 ExpectAllMappingBytes(m, 0); 65 } 66 67 TEST(MadviseDontneedTest, ZerosCOWAnonPageInCallerOnly) { 68 auto m = ASSERT_NO_ERRNO_AND_VALUE( 69 MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); 70 ExpectAllMappingBytes(m, 0); 71 memset(m.ptr(), 2, m.len()); 72 ExpectAllMappingBytes(m, 2); 73 74 // Do madvise in a child process. 75 pid_t pid = fork(); 76 CheckAllMappingBytes(m, 2); 77 if (pid == 0) { 78 TEST_PCHECK(madvise(m.ptr(), m.len(), MADV_DONTNEED) == 0); 79 CheckAllMappingBytes(m, 0); 80 _exit(0); 81 } 82 83 ASSERT_THAT(pid, SyscallSucceeds()); 84 85 int status = 0; 86 ASSERT_THAT(waitpid(-1, &status, 0), SyscallSucceedsWithValue(pid)); 87 EXPECT_TRUE(WIFEXITED(status)); 88 EXPECT_EQ(WEXITSTATUS(status), 0); 89 // The child's madvise should not have affected the parent. 90 ExpectAllMappingBytes(m, 2); 91 } 92 93 TEST(MadviseDontneedTest, DoesNotModifySharedAnonPage) { 94 auto m = ASSERT_NO_ERRNO_AND_VALUE( 95 MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED)); 96 ExpectAllMappingBytes(m, 0); 97 memset(m.ptr(), 3, m.len()); 98 ExpectAllMappingBytes(m, 3); 99 ASSERT_THAT(madvise(m.ptr(), m.len(), MADV_DONTNEED), SyscallSucceeds()); 100 ExpectAllMappingBytes(m, 3); 101 } 102 103 TEST(MadviseDontneedTest, CleansPrivateFilePage) { 104 TempPath f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( 105 /* parent = */ GetAbsoluteTestTmpdir(), 106 /* content = */ std::string(kPageSize, 4), TempPath::kDefaultFileMode)); 107 FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDWR)); 108 109 Mapping m = ASSERT_NO_ERRNO_AND_VALUE(Mmap( 110 nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd.get(), 0)); 111 ExpectAllMappingBytes(m, 4); 112 memset(m.ptr(), 5, m.len()); 113 ExpectAllMappingBytes(m, 5); 114 ASSERT_THAT(madvise(m.ptr(), m.len(), MADV_DONTNEED), SyscallSucceeds()); 115 ExpectAllMappingBytes(m, 4); 116 } 117 118 TEST(MadviseDontneedTest, DoesNotModifySharedFilePage) { 119 TempPath f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( 120 /* parent = */ GetAbsoluteTestTmpdir(), 121 /* content = */ std::string(kPageSize, 6), TempPath::kDefaultFileMode)); 122 FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDWR)); 123 124 Mapping m = ASSERT_NO_ERRNO_AND_VALUE(Mmap( 125 nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd.get(), 0)); 126 ExpectAllMappingBytes(m, 6); 127 memset(m.ptr(), 7, m.len()); 128 ExpectAllMappingBytes(m, 7); 129 ASSERT_THAT(madvise(m.ptr(), m.len(), MADV_DONTNEED), SyscallSucceeds()); 130 ExpectAllMappingBytes(m, 7); 131 } 132 133 TEST(MadviseDontneedTest, IgnoresPermissions) { 134 auto m = 135 ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, MAP_PRIVATE)); 136 EXPECT_THAT(madvise(m.ptr(), m.len(), MADV_DONTNEED), SyscallSucceeds()); 137 } 138 139 TEST(MadviseDontforkTest, AddressLength) { 140 auto m = 141 ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, MAP_PRIVATE)); 142 char* addr = static_cast<char*>(m.ptr()); 143 144 // Address must be page aligned. 145 EXPECT_THAT(madvise(addr + 1, kPageSize, MADV_DONTFORK), 146 SyscallFailsWithErrno(EINVAL)); 147 148 // Zero length madvise always succeeds. 149 EXPECT_THAT(madvise(addr, 0, MADV_DONTFORK), SyscallSucceeds()); 150 151 // Length must not roll over after rounding up. 152 size_t badlen = std::numeric_limits<std::size_t>::max() - (kPageSize / 2); 153 EXPECT_THAT(madvise(0, badlen, MADV_DONTFORK), SyscallFailsWithErrno(EINVAL)); 154 155 // Length need not be page aligned - it is implicitly rounded up. 156 EXPECT_THAT(madvise(addr, 1, MADV_DONTFORK), SyscallSucceeds()); 157 EXPECT_THAT(madvise(addr, kPageSize, MADV_DONTFORK), SyscallSucceeds()); 158 } 159 160 TEST(MadviseDontforkTest, DontforkShared) { 161 // Mmap two shared file-backed pages and MADV_DONTFORK the second page. 162 TempPath f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( 163 /* parent = */ GetAbsoluteTestTmpdir(), 164 /* content = */ std::string(kPageSize * 2, 2), 165 TempPath::kDefaultFileMode)); 166 FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDWR)); 167 168 Mapping m = ASSERT_NO_ERRNO_AND_VALUE(Mmap( 169 nullptr, kPageSize * 2, PROT_READ | PROT_WRITE, MAP_SHARED, fd.get(), 0)); 170 171 const Mapping ms1 = Mapping(reinterpret_cast<void*>(m.addr()), kPageSize); 172 const Mapping ms2 = 173 Mapping(reinterpret_cast<void*>(m.addr() + kPageSize), kPageSize); 174 m.release(); 175 176 ASSERT_THAT(madvise(ms2.ptr(), kPageSize, MADV_DONTFORK), SyscallSucceeds()); 177 178 const auto rest = [&] { 179 // First page is mapped in child and modifications are visible to parent 180 // via the shared mapping. 181 TEST_CHECK(IsMapped(ms1.addr())); 182 CheckAllMappingBytes(ms1, 2); 183 memset(ms1.ptr(), 1, kPageSize); 184 CheckAllMappingBytes(ms1, 1); 185 186 // Second page must not be mapped in child. 187 TEST_CHECK(!IsMapped(ms2.addr())); 188 }; 189 190 EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); 191 192 ExpectAllMappingBytes(ms1, 1); // page contents modified by child. 193 ExpectAllMappingBytes(ms2, 2); // page contents unchanged. 194 } 195 196 TEST(MadviseDontforkTest, DontforkAnonPrivate) { 197 // Mmap three anonymous pages and MADV_DONTFORK the middle page. 198 Mapping m = ASSERT_NO_ERRNO_AND_VALUE( 199 MmapAnon(kPageSize * 3, PROT_READ | PROT_WRITE, MAP_PRIVATE)); 200 const Mapping mp1 = Mapping(reinterpret_cast<void*>(m.addr()), kPageSize); 201 const Mapping mp2 = 202 Mapping(reinterpret_cast<void*>(m.addr() + kPageSize), kPageSize); 203 const Mapping mp3 = 204 Mapping(reinterpret_cast<void*>(m.addr() + 2 * kPageSize), kPageSize); 205 m.release(); 206 207 ASSERT_THAT(madvise(mp2.ptr(), kPageSize, MADV_DONTFORK), SyscallSucceeds()); 208 209 // Verify that all pages are zeroed and memset the first, second and third 210 // pages to 1, 2, and 3 respectively. 211 ExpectAllMappingBytes(mp1, 0); 212 memset(mp1.ptr(), 1, kPageSize); 213 214 ExpectAllMappingBytes(mp2, 0); 215 memset(mp2.ptr(), 2, kPageSize); 216 217 ExpectAllMappingBytes(mp3, 0); 218 memset(mp3.ptr(), 3, kPageSize); 219 220 const auto rest = [&] { 221 // Verify first page is mapped, verify its contents and then modify the 222 // page. The mapping is private so the modifications are not visible to 223 // the parent. 224 TEST_CHECK(IsMapped(mp1.addr())); 225 CheckAllMappingBytes(mp1, 1); 226 memset(mp1.ptr(), 11, kPageSize); 227 CheckAllMappingBytes(mp1, 11); 228 229 // Verify second page is not mapped. 230 TEST_CHECK(!IsMapped(mp2.addr())); 231 232 // Verify third page is mapped, verify its contents and then modify the 233 // page. The mapping is private so the modifications are not visible to 234 // the parent. 235 TEST_CHECK(IsMapped(mp3.addr())); 236 CheckAllMappingBytes(mp3, 3); 237 memset(mp3.ptr(), 13, kPageSize); 238 CheckAllMappingBytes(mp3, 13); 239 }; 240 EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); 241 242 // The fork and COW by child should not affect the parent mappings. 243 ExpectAllMappingBytes(mp1, 1); 244 ExpectAllMappingBytes(mp2, 2); 245 ExpectAllMappingBytes(mp3, 3); 246 } 247 248 } // namespace 249 250 } // namespace testing 251 } // namespace gvisor