blob: 0bcb2a52b6c38769a3808a750c0e230b831e6b02 [file] [log] [blame]
// Copyright 2023 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <fcntl.h>
#include <sys/uio.h>
#include <unistd.h>
#include <linux/memfd.h>
// Must be included after <linux/*.h> to avoid conflicts between Bionic UAPI and glibc headers.
// TODO(b/307959737): Build these tests without glibc.
#include <sys/mman.h>
#include <fbl/unique_fd.h>
#include <gtest/gtest.h>
#include "fault_test.h"
#include "fault_test_suite.h"
#include "src/starnix/tests/syscalls/cpp/test_helper.h"
#if !defined(__NR_memfd_create)
#if defined(__x86_64__)
#define __NR_memfd_create 319
#elif defined(__aarch64__) || defined(__riscv)
#define __NR_memfd_create 279
#elif defined(__arm__)
#define __NR_memfd_create 385
#else
#error Add support for this architecture
#endif
#endif // !defined(__NR_memfd_create)
#if !defined(MFD_ALLOW_SEALING)
#define MFD_ALLOW_SEALING 0x0002U
#endif
#if !defined(MFD_NOEXEC_SEAL)
#define MFD_NOEXEC_SEAL 0x0008U
#endif
#if !defined(MFD_EXEC)
#define MFD_EXEC 0x0010U
#endif
#if !defined(F_SEAL_FUTURE_WRITE)
#define F_SEAL_FUTURE_WRITE 0x0010
#endif
#if !defined(F_ADD_SEALS)
#define F_ADD_SEALS 1033
#endif
#if !defined(F_SEAL_EXEC)
#define F_SEAL_EXEC 0x0020
#endif
namespace {
int CreateMemFd() {
return static_cast<int>(syscall(__NR_memfd_create, "test_memfd", MFD_ALLOW_SEALING));
}
const int kPageSize = 4096;
class MemfdTest : public ::testing::Test {
void SetUp() override { ASSERT_TRUE(fd_ = fbl::unique_fd(CreateMemFd())) << strerror(errno); }
void TearDown() override {
if (addr_ != MAP_FAILED) {
ASSERT_EQ(munmap(addr_, kPageSize), 0);
}
fd_.reset();
}
protected:
fbl::unique_fd fd_;
void* addr_ = MAP_FAILED;
};
// These tests currently test only `F_SEAL_FUTURE_WRITE`. `F_SEAL_WRITE` is covered
// by GVisor tests.
TEST_F(MemfdTest, SealFutureWriteThenMmap) {
ASSERT_EQ(fcntl(fd_.get(), F_ADD_SEALS, F_SEAL_FUTURE_WRITE), 0);
// Readable mapping succeeds even when created from writable FD.
addr_ = mmap(0, kPageSize, PROT_READ, MAP_SHARED, fd_.get(), 0);
ASSERT_NE(addr_, MAP_FAILED);
// `mprotect()` should fail since the file was sealed for write.
ASSERT_EQ(mprotect(addr_, kPageSize, PROT_READ | PROT_WRITE), -1);
}
TEST_F(MemfdTest, MmapThenSealFutureWrite) {
addr_ = mmap(0, kPageSize, PROT_READ, MAP_SHARED, fd_.get(), 0);
ASSERT_NE(addr_, MAP_FAILED);
ASSERT_EQ(fcntl(fd_.get(), F_ADD_SEALS, F_SEAL_FUTURE_WRITE), 0);
// `mprotect()` still succeed since the mapping was created before the seal.
ASSERT_EQ(mprotect(addr_, kPageSize, PROT_READ | PROT_WRITE), 0);
}
// These tests currently test `F_SEAL_FUTURE_WRITE`. `F_SEAL_WRITE` is covered
// by GVisor tests.
TEST_F(MemfdTest, MapWritableThenSealFutureWrite) {
addr_ = mmap(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd_.get(), 0);
ASSERT_NE(addr_, MAP_FAILED);
// `F_SEAL_FUTURE_WRITE` should still succeed when there are writable mappings.
ASSERT_EQ(fcntl(fd_.get(), F_ADD_SEALS, F_SEAL_FUTURE_WRITE), 0);
}
TEST_F(MemfdTest, ChmodWithNOEXEC) {
auto fd = fbl::unique_fd(
static_cast<int>(syscall(__NR_memfd_create, "memfd_no_exec", MFD_NOEXEC_SEAL)));
// Setting the memfd that has the exec seal to executable should fail.
ASSERT_EQ(fchmod(fd.get(), S_IRWXU | S_IRWXG | S_IRWXO), -1);
// Setting the memfd that does not have the exec seal to executable should succeed.
ASSERT_EQ(fchmod(fd_.get(), S_IRWXU | S_IRWXG | S_IRWXO), 0);
}
TEST_F(MemfdTest, MapExecutableWithNOEXEC) {
auto fd = fbl::unique_fd(
static_cast<int>(syscall(__NR_memfd_create, "memfd_no_exec", MFD_NOEXEC_SEAL)));
// Map the fd with the noexec seal.
addr_ = mmap(0, kPageSize, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED, fd.get(), 0);
ASSERT_EQ(addr_, MAP_FAILED);
// Map the default memfd that does not have the exec seal.
addr_ = mmap(0, kPageSize, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED, fd_.get(), 0);
ASSERT_NE(addr_, MAP_FAILED);
}
TEST_F(MemfdTest, MapNonExecutableWithNOEXEC) {
if (!test_helper::IsStarnix()) {
GTEST_SKIP() << "This test does not work on host in CQ";
}
auto fd = fbl::unique_fd(
static_cast<int>(syscall(__NR_memfd_create, "memfd_no_exec", MFD_NOEXEC_SEAL)));
addr_ = mmap(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd.get(), 0);
ASSERT_NE(addr_, MAP_FAILED);
}
TEST_F(MemfdTest, CreateExecutableThenSealThenMap) {
if (!test_helper::IsStarnix()) {
GTEST_SKIP() << "This test does not work on host in CQ";
}
auto fd = fbl::unique_fd(
static_cast<int>(syscall(__NR_memfd_create, "memfd_no_exec", MFD_EXEC | MFD_ALLOW_SEALING)));
ASSERT_EQ(fcntl(fd.get(), F_ADD_SEALS, F_SEAL_EXEC), 0);
addr_ = mmap(0, kPageSize, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED, fd.get(), 0);
ASSERT_EQ(addr_, MAP_FAILED);
ASSERT_EQ(fchmod(fd.get(), S_IRWXU | S_IRWXG | S_IRWXO), -1);
}
INSTANTIATE_TEST_SUITE_P(MemfdFaultTest, FaultFileTest, ::testing::Values(CreateMemFd));
} // namespace