gvisor/test/syscalls/linux/memfd.cc

549 lines
22 KiB
C++

// Copyright 2019 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 <fcntl.h>
#include <linux/magic.h>
#include <linux/memfd.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/statfs.h>
#include <sys/syscall.h>
#include <vector>
#include "gtest/gtest.h"
#include "test/util/file_descriptor.h"
#include "test/util/fs_util.h"
#include "test/util/memory_util.h"
#include "test/util/multiprocess_util.h"
#include "test/util/temp_path.h"
#include "test/util/test_util.h"
namespace gvisor {
namespace testing {
namespace {
// The header sys/memfd.h isn't available on all systems, so redefining some of
// the constants here.
#define F_LINUX_SPECIFIC_BASE 1024
#define F_ADD_SEALS (F_LINUX_SPECIFIC_BASE + 9)
#define F_GET_SEALS (F_LINUX_SPECIFIC_BASE + 10)
#define F_SEAL_SEAL 0x0001
#define F_SEAL_SHRINK 0x0002
#define F_SEAL_GROW 0x0004
#define F_SEAL_WRITE 0x0008
using ::testing::StartsWith;
const std::string kMemfdName = "some-memfd";
int memfd_create(const std::string& name, unsigned int flags) {
return syscall(__NR_memfd_create, name.c_str(), flags);
}
PosixErrorOr<FileDescriptor> MemfdCreate(const std::string& name, uint32_t flags) {
int fd = memfd_create(name, flags);
if (fd < 0) {
return PosixError(
errno, absl::StrFormat("memfd_create(\"%s\", %#x)", name, flags));
}
MaybeSave();
return FileDescriptor(fd);
}
// Procfs entries for memfds display the appropriate name.
TEST(MemfdTest, Name) {
const FileDescriptor memfd =
ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, 0));
const std::string proc_name = ASSERT_NO_ERRNO_AND_VALUE(
ReadLink(absl::StrFormat("/proc/self/fd/%d", memfd.get())));
EXPECT_THAT(proc_name, StartsWith("/memfd:" + kMemfdName));
}
// Memfds support read/write syscalls.
TEST(MemfdTest, WriteRead) {
const FileDescriptor memfd =
ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, 0));
// Write a random page of data to the memfd via write(2).
std::vector<char> buf(kPageSize);
RandomizeBuffer(buf.data(), buf.size());
ASSERT_THAT(write(memfd.get(), buf.data(), buf.size()),
SyscallSucceedsWithValue(kPageSize));
// Read back the same data and verify.
std::vector<char> buf2(kPageSize);
ASSERT_THAT(lseek(memfd.get(), 0, SEEK_SET), SyscallSucceeds());
EXPECT_THAT(read(memfd.get(), buf2.data(), buf2.size()),
SyscallSucceedsWithValue(kPageSize));
EXPECT_EQ(buf, buf2);
}
// Memfds can be mapped and used as usual.
TEST(MemfdTest, Mmap) {
const FileDescriptor memfd =
ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, 0));
const Mapping m1 = ASSERT_NO_ERRNO_AND_VALUE(Mmap(
nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, memfd.get(), 0));
// Write a random page of data to the memfd via mmap m1.
std::vector<char> buf(kPageSize);
RandomizeBuffer(buf.data(), buf.size());
ASSERT_THAT(ftruncate(memfd.get(), kPageSize), SyscallSucceeds());
memcpy(m1.ptr(), buf.data(), buf.size());
// Read the data back via a read syscall on the memfd.
std::vector<char> buf2(kPageSize);
EXPECT_THAT(read(memfd.get(), buf2.data(), buf2.size()),
SyscallSucceedsWithValue(kPageSize));
EXPECT_EQ(buf, buf2);
// The same data should be accessible via a new mapping m2.
const Mapping m2 = ASSERT_NO_ERRNO_AND_VALUE(Mmap(
nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, memfd.get(), 0));
EXPECT_EQ(0, memcmp(m1.ptr(), m2.ptr(), kPageSize));
}
TEST(MemfdTest, DuplicateFDsShareContent) {
const FileDescriptor memfd =
ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, 0));
const Mapping m1 = ASSERT_NO_ERRNO_AND_VALUE(Mmap(
nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, memfd.get(), 0));
const FileDescriptor memfd2 = ASSERT_NO_ERRNO_AND_VALUE(memfd.Dup());
// Write a random page of data to the memfd via mmap m1.
std::vector<char> buf(kPageSize);
RandomizeBuffer(buf.data(), buf.size());
ASSERT_THAT(ftruncate(memfd.get(), kPageSize), SyscallSucceeds());
memcpy(m1.ptr(), buf.data(), buf.size());
// Read the data back via a read syscall on a duplicate fd.
std::vector<char> buf2(kPageSize);
EXPECT_THAT(read(memfd2.get(), buf2.data(), buf2.size()),
SyscallSucceedsWithValue(kPageSize));
EXPECT_EQ(buf, buf2);
}
// File seals are disabled by default on memfds.
TEST(MemfdTest, SealingDisabledByDefault) {
const FileDescriptor memfd =
ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, 0));
EXPECT_THAT(fcntl(memfd.get(), F_GET_SEALS),
SyscallSucceedsWithValue(F_SEAL_SEAL));
// Attempting to set any seal should fail.
EXPECT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_WRITE),
SyscallFailsWithErrno(EPERM));
}
// Seals can be retrieved and updated for memfds.
TEST(MemfdTest, SealsGetSet) {
const FileDescriptor memfd =
ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING));
int seals;
ASSERT_THAT(seals = fcntl(memfd.get(), F_GET_SEALS), SyscallSucceeds());
// No seals are set yet.
EXPECT_EQ(0, seals);
// Set a seal and check that we can get it back.
ASSERT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_WRITE), SyscallSucceeds());
EXPECT_THAT(fcntl(memfd.get(), F_GET_SEALS),
SyscallSucceedsWithValue(F_SEAL_WRITE));
// Set some more seals and verify.
ASSERT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_GROW | F_SEAL_SHRINK),
SyscallSucceeds());
EXPECT_THAT(
fcntl(memfd.get(), F_GET_SEALS),
SyscallSucceedsWithValue(F_SEAL_WRITE | F_SEAL_GROW | F_SEAL_SHRINK));
// Attempting to set a seal that is already set is a no-op.
ASSERT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_WRITE), SyscallSucceeds());
EXPECT_THAT(
fcntl(memfd.get(), F_GET_SEALS),
SyscallSucceedsWithValue(F_SEAL_WRITE | F_SEAL_GROW | F_SEAL_SHRINK));
// Add remaining seals and verify.
ASSERT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_SEAL), SyscallSucceeds());
EXPECT_THAT(fcntl(memfd.get(), F_GET_SEALS),
SyscallSucceedsWithValue(F_SEAL_WRITE | F_SEAL_GROW |
F_SEAL_SHRINK | F_SEAL_SEAL));
}
// F_SEAL_GROW prevents a memfd from being grown using ftruncate.
TEST(MemfdTest, SealGrowWithTruncate) {
const FileDescriptor memfd =
ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING));
ASSERT_THAT(ftruncate(memfd.get(), kPageSize), SyscallSucceeds());
ASSERT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_GROW), SyscallSucceeds());
// Try grow the memfd by 1 page.
ASSERT_THAT(ftruncate(memfd.get(), kPageSize * 2),
SyscallFailsWithErrno(EPERM));
// Ftruncate calls that don't actually grow the memfd are allowed.
ASSERT_THAT(ftruncate(memfd.get(), kPageSize), SyscallSucceeds());
ASSERT_THAT(ftruncate(memfd.get(), kPageSize / 2), SyscallSucceeds());
// After shrinking, growing back is not allowed.
ASSERT_THAT(ftruncate(memfd.get(), kPageSize), SyscallFailsWithErrno(EPERM));
}
// F_SEAL_GROW prevents a memfd from being grown using the write syscall.
TEST(MemfdTest, SealGrowWithWrite) {
const FileDescriptor memfd =
ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING));
// Initially, writing to the memfd succeeds.
const std::vector<char> buf(kPageSize);
EXPECT_THAT(write(memfd.get(), buf.data(), buf.size()),
SyscallSucceedsWithValue(kPageSize));
// Apply F_SEAL_GROW, subsequent writes which extend the memfd should fail.
ASSERT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_GROW), SyscallSucceeds());
EXPECT_THAT(write(memfd.get(), buf.data(), buf.size()),
SyscallFailsWithErrno(EPERM));
// However, zero-length writes are ok since they don't grow the memfd.
EXPECT_THAT(write(memfd.get(), buf.data(), 0), SyscallSucceeds());
// Writing to existing parts of the memfd is also ok.
ASSERT_THAT(lseek(memfd.get(), 0, SEEK_SET), SyscallSucceeds());
EXPECT_THAT(write(memfd.get(), buf.data(), buf.size()),
SyscallSucceedsWithValue(kPageSize));
// Returning the end of the file and writing still not allowed.
EXPECT_THAT(write(memfd.get(), buf.data(), buf.size()),
SyscallFailsWithErrno(EPERM));
}
// F_SEAL_GROW causes writes which partially extend off the current EOF to
// partially succeed, up to the page containing the EOF.
TEST(MemfdTest, SealGrowPartialWriteTruncated) {
const FileDescriptor memfd =
ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING));
ASSERT_THAT(ftruncate(memfd.get(), kPageSize), SyscallSucceeds());
ASSERT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_GROW), SyscallSucceeds());
// FD offset: 1 page, EOF: 1 page.
ASSERT_THAT(lseek(memfd.get(), kPageSize * 3 / 4, SEEK_SET),
SyscallSucceeds());
// FD offset: 3/4 page. Writing a full page now should only write 1/4 page
// worth of data. This partially succeeds because the first page is entirely
// within the file and requires no growth, but attempting to write the final
// 3/4 page would require growing the file.
const std::vector<char> buf(kPageSize);
EXPECT_THAT(write(memfd.get(), buf.data(), buf.size()),
SyscallSucceedsWithValue(kPageSize / 4));
}
// F_SEAL_GROW causes writes which partially extend off the current EOF to fail
// in its entirety if the only data written would be to the page containing the
// EOF.
TEST(MemfdTest, SealGrowPartialWriteTruncatedSamePage) {
const FileDescriptor memfd =
ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING));
ASSERT_THAT(ftruncate(memfd.get(), kPageSize * 3 / 4), SyscallSucceeds());
ASSERT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_GROW), SyscallSucceeds());
// EOF: 3/4 page, writing 1/2 page starting at 1/2 page would cause the file
// to grow. Since this would require only the page containing the EOF to be
// modified, the write is rejected entirely.
const std::vector<char> buf(kPageSize / 2);
EXPECT_THAT(pwrite(memfd.get(), buf.data(), buf.size(), kPageSize / 2),
SyscallFailsWithErrno(EPERM));
// However, writing up to EOF is fine.
EXPECT_THAT(pwrite(memfd.get(), buf.data(), buf.size() / 2, kPageSize / 2),
SyscallSucceedsWithValue(kPageSize / 4));
}
// F_SEAL_SHRINK prevents a memfd from being shrunk using ftruncate.
TEST(MemfdTest, SealShrink) {
const FileDescriptor memfd =
ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING));
ASSERT_THAT(ftruncate(memfd.get(), kPageSize), SyscallSucceeds());
ASSERT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_SHRINK),
SyscallSucceeds());
// Shrink by half a page.
ASSERT_THAT(ftruncate(memfd.get(), kPageSize / 2),
SyscallFailsWithErrno(EPERM));
// Ftruncate calls that don't actually shrink the file are allowed.
ASSERT_THAT(ftruncate(memfd.get(), kPageSize), SyscallSucceeds());
ASSERT_THAT(ftruncate(memfd.get(), kPageSize * 2), SyscallSucceeds());
// After growing, shrinking is still not allowed.
ASSERT_THAT(ftruncate(memfd.get(), kPageSize), SyscallFailsWithErrno(EPERM));
}
// F_SEAL_WRITE prevents a memfd from being written to through a write
// syscall.
TEST(MemfdTest, SealWriteWithWrite) {
const FileDescriptor memfd =
ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING));
const std::vector<char> buf(kPageSize);
ASSERT_THAT(write(memfd.get(), buf.data(), buf.size()),
SyscallSucceedsWithValue(kPageSize));
ASSERT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_WRITE), SyscallSucceeds());
// Attemping to write at the end of the file fails.
EXPECT_THAT(write(memfd.get(), buf.data(), 1), SyscallFailsWithErrno(EPERM));
// Attemping to overwrite an existing part of the memfd fails.
EXPECT_THAT(pwrite(memfd.get(), buf.data(), 1, 0),
SyscallFailsWithErrno(EPERM));
EXPECT_THAT(pwrite(memfd.get(), buf.data(), buf.size() / 2, kPageSize / 2),
SyscallFailsWithErrno(EPERM));
EXPECT_THAT(pwrite(memfd.get(), buf.data(), buf.size(), kPageSize / 2),
SyscallFailsWithErrno(EPERM));
// Zero-length writes however do not fail.
EXPECT_THAT(write(memfd.get(), buf.data(), 0), SyscallSucceeds());
}
// F_SEAL_WRITE prevents a memfd from being written to through an mmap.
TEST(MemfdTest, SealWriteWithMmap) {
const FileDescriptor memfd =
ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING));
const std::vector<char> buf(kPageSize);
ASSERT_THAT(write(memfd.get(), buf.data(), buf.size()),
SyscallSucceedsWithValue(kPageSize));
ASSERT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_WRITE), SyscallSucceeds());
// Can't create a shared mapping with writes sealed.
void* ret = mmap(nullptr, kPageSize, PROT_WRITE, MAP_SHARED, memfd.get(), 0);
EXPECT_EQ(ret, MAP_FAILED);
EXPECT_EQ(errno, EPERM);
ret = mmap(nullptr, kPageSize, PROT_READ, MAP_SHARED, memfd.get(), 0);
EXPECT_EQ(ret, MAP_FAILED);
EXPECT_EQ(errno, EPERM);
// However, private mappings are ok.
EXPECT_NO_ERRNO(Mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE,
memfd.get(), 0));
}
// Adding F_SEAL_WRITE fails when there are outstanding writable mappings to a
// memfd.
TEST(MemfdTest, SealWriteWithOutstandingWritbleMapping) {
const FileDescriptor memfd =
ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING));
const std::vector<char> buf(kPageSize);
ASSERT_THAT(write(memfd.get(), buf.data(), buf.size()),
SyscallSucceedsWithValue(kPageSize));
// Attempting to add F_SEAL_WRITE with active shared mapping with any set of
// permissions fails.
// Read-only shared mapping.
{
const Mapping m = ASSERT_NO_ERRNO_AND_VALUE(
Mmap(nullptr, kPageSize, PROT_READ, MAP_SHARED, memfd.get(), 0));
EXPECT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_WRITE),
SyscallFailsWithErrno(EBUSY));
}
// Write-only shared mapping.
{
const Mapping m = ASSERT_NO_ERRNO_AND_VALUE(
Mmap(nullptr, kPageSize, PROT_WRITE, MAP_SHARED, memfd.get(), 0));
EXPECT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_WRITE),
SyscallFailsWithErrno(EBUSY));
}
// Read-write shared mapping.
{
const Mapping m = ASSERT_NO_ERRNO_AND_VALUE(
Mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED,
memfd.get(), 0));
EXPECT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_WRITE),
SyscallFailsWithErrno(EBUSY));
}
// F_SEAL_WRITE can be set with private mappings with any permissions.
{
const Mapping m = ASSERT_NO_ERRNO_AND_VALUE(
Mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE,
memfd.get(), 0));
EXPECT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_WRITE),
SyscallSucceeds());
}
}
// When applying F_SEAL_WRITE fails due to outstanding writable mappings, any
// additional seals passed to the same add seal call are also rejected.
TEST(MemfdTest, NoPartialSealApplicationWhenWriteSealRejected) {
const FileDescriptor memfd =
ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING));
const Mapping m = ASSERT_NO_ERRNO_AND_VALUE(Mmap(
nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, memfd.get(), 0));
// Try add some seals along with F_SEAL_WRITE. The seal application should
// fail since there exists an active shared mapping.
EXPECT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_WRITE | F_SEAL_GROW),
SyscallFailsWithErrno(EBUSY));
// None of the seals should be applied.
EXPECT_THAT(fcntl(memfd.get(), F_GET_SEALS), SyscallSucceedsWithValue(0));
}
// Seals are inode level properties, and apply to all file descriptors referring
// to a memfd.
TEST(MemfdTest, SealsAreInodeLevelProperties) {
const FileDescriptor memfd =
ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING));
const FileDescriptor memfd2 = ASSERT_NO_ERRNO_AND_VALUE(memfd.Dup());
// Add seal through the original memfd, and verify that it appears on the
// dupped fd.
ASSERT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_WRITE), SyscallSucceeds());
EXPECT_THAT(fcntl(memfd2.get(), F_GET_SEALS),
SyscallSucceedsWithValue(F_SEAL_WRITE));
// Verify the seal actually applies to both fds.
std::vector<char> buf(kPageSize);
EXPECT_THAT(write(memfd.get(), buf.data(), buf.size()),
SyscallFailsWithErrno(EPERM));
EXPECT_THAT(write(memfd2.get(), buf.data(), buf.size()),
SyscallFailsWithErrno(EPERM));
// Seals are enforced on new FDs that are dupped after the seal is already
// applied.
const FileDescriptor memfd3 = ASSERT_NO_ERRNO_AND_VALUE(memfd2.Dup());
EXPECT_THAT(write(memfd3.get(), buf.data(), buf.size()),
SyscallFailsWithErrno(EPERM));
// Try a new seal applied to one of the dupped fds.
ASSERT_THAT(fcntl(memfd3.get(), F_ADD_SEALS, F_SEAL_GROW), SyscallSucceeds());
EXPECT_THAT(ftruncate(memfd.get(), kPageSize), SyscallFailsWithErrno(EPERM));
EXPECT_THAT(ftruncate(memfd2.get(), kPageSize), SyscallFailsWithErrno(EPERM));
EXPECT_THAT(ftruncate(memfd3.get(), kPageSize), SyscallFailsWithErrno(EPERM));
}
PosixErrorOr<bool> IsTmpfs(const std::string& path) {
struct statfs stat;
if (statfs(path.c_str(), &stat)) {
if (errno == ENOENT) {
// Nothing at path, don't raise this as an error. Instead, just report no
// tmpfs at path.
return false;
}
return PosixError(errno,
absl::StrFormat("statfs(\"%s\", %#p)", path, &stat));
}
return stat.f_type == TMPFS_MAGIC;
}
// Tmpfs files also support seals, but are created with F_SEAL_SEAL.
TEST(MemfdTest, TmpfsFilesHaveSealSeal) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsTmpfs("/tmp")));
const TempPath tmpfs_file =
ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn("/tmp"));
const FileDescriptor fd =
ASSERT_NO_ERRNO_AND_VALUE(Open(tmpfs_file.path(), O_RDWR, 0644));
EXPECT_THAT(fcntl(fd.get(), F_GET_SEALS),
SyscallSucceedsWithValue(F_SEAL_SEAL));
}
// Can open a memfd from procfs and use as normal.
TEST(MemfdTest, CanOpenFromProcfs) {
const FileDescriptor memfd =
ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING));
// Write a random page of data to the memfd via write(2).
std::vector<char> buf(kPageSize);
RandomizeBuffer(buf.data(), buf.size());
ASSERT_THAT(write(memfd.get(), buf.data(), buf.size()),
SyscallSucceedsWithValue(kPageSize));
// Read back the same data from the fd obtained from procfs and verify.
const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(
Open(absl::StrFormat("/proc/self/fd/%d", memfd.get()), O_RDWR));
std::vector<char> buf2(kPageSize);
EXPECT_THAT(pread(fd.get(), buf2.data(), buf2.size(), 0),
SyscallSucceedsWithValue(kPageSize));
EXPECT_EQ(buf, buf2);
}
// Test that memfd permissions are set up correctly to allow another process to
// open it from procfs.
TEST(MemfdTest, OtherProcessCanOpenFromProcfs) {
const FileDescriptor memfd =
ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING));
const auto memfd_path =
absl::StrFormat("/proc/%d/fd/%d", getpid(), memfd.get());
const auto rest = [&] {
int fd = open(memfd_path.c_str(), O_RDWR);
TEST_PCHECK(fd >= 0);
TEST_PCHECK(close(fd) >= 0);
};
EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
}
// Test that only files opened as writable can have seals applied to them.
// Normally there's no way to specify file permissions on memfds, but we can
// obtain a read-only memfd by opening the corresponding procfs fd entry as
// read-only.
TEST(MemfdTest, MemfdMustBeWritableToModifySeals) {
const FileDescriptor memfd =
ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING));
// Initially adding a seal works.
EXPECT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_WRITE), SyscallSucceeds());
// Re-open the memfd as read-only from procfs.
const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(
Open(absl::StrFormat("/proc/self/fd/%d", memfd.get()), O_RDONLY));
// Can't add seals through an unwritable fd.
EXPECT_THAT(fcntl(fd.get(), F_ADD_SEALS, F_SEAL_GROW),
SyscallFailsWithErrno(EPERM));
}
// Test that the memfd implementation internally tracks potentially writable
// maps correctly.
TEST(MemfdTest, MultipleWritableAndNonWritableRefsToSameFileRegion) {
const FileDescriptor memfd =
ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, 0));
// Populate with a random page of data.
std::vector<char> buf(kPageSize);
RandomizeBuffer(buf.data(), buf.size());
ASSERT_THAT(write(memfd.get(), buf.data(), buf.size()),
SyscallSucceedsWithValue(kPageSize));
// Read-only map to the page. This should cause an initial mapping to be
// created.
Mapping m1 = ASSERT_NO_ERRNO_AND_VALUE(
Mmap(nullptr, kPageSize, PROT_READ, MAP_PRIVATE, memfd.get(), 0));
// Create a shared writable map to the page. This should cause the internal
// mapping to become potentially writable.
Mapping m2 = ASSERT_NO_ERRNO_AND_VALUE(Mmap(
nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, memfd.get(), 0));
// Drop the read-only mapping first. If writable-ness isn't tracked correctly,
// this can cause some misaccounting, which can trigger asserts internally.
m1.reset();
m2.reset();
}
} // namespace
} // namespace testing
} // namespace gvisor