summaryrefslogtreecommitdiffhomepage
path: root/test
diff options
context:
space:
mode:
authorRahat Mahmood <rahat@google.com>2019-03-26 16:15:55 -0700
committerShentubot <shentubot@google.com>2019-03-26 16:16:57 -0700
commit06ec97a3f823f1f5d928fc9c2beb3a11c2c88487 (patch)
treea2b501718c82aede761d7235527492782ef65cc2 /test
parent79aca14a0cd70720e8a8f8bd6c1499ab1ffbd8d3 (diff)
Implement memfd_create.
Memfds are simply anonymous tmpfs files with no associated mounts. Also implementing file seals, which Linux only implements for memfds at the moment. PiperOrigin-RevId: 240450031 Change-Id: I31de78b950101ae8d7a13d0e93fe52d98ea06f2f
Diffstat (limited to 'test')
-rw-r--r--test/syscalls/linux/BUILD17
-rw-r--r--test/syscalls/linux/memfd.cc546
2 files changed, 563 insertions, 0 deletions
diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD
index 2c214925e..7dd63dd0a 100644
--- a/test/syscalls/linux/BUILD
+++ b/test/syscalls/linux/BUILD
@@ -3261,3 +3261,20 @@ cc_binary(
"@com_google_googletest//:gtest",
],
)
+
+cc_binary(
+ name = "memfd_test",
+ testonly = 1,
+ srcs = ["memfd.cc"],
+ linkstatic = 1,
+ deps = [
+ "//test/util:file_descriptor",
+ "//test/util:fs_util",
+ "//test/util:memory_util",
+ "//test/util:multiprocess_util",
+ "//test/util:temp_path",
+ "//test/util:test_main",
+ "//test/util:test_util",
+ "@com_google_googletest//:gtest",
+ ],
+)
diff --git a/test/syscalls/linux/memfd.cc b/test/syscalls/linux/memfd.cc
new file mode 100644
index 000000000..ccdddd4e5
--- /dev/null
+++ b/test/syscalls/linux/memfd.cc
@@ -0,0 +1,546 @@
+// 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));
+ pid_t pid = getpid();
+ const auto rest = [&] {
+ ASSERT_NO_ERRNO(
+ Open(absl::StrFormat("/proc/self/%d/%d", pid, memfd.get()), O_RDWR));
+ };
+ 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