blob: 5f743996359c74cc09e7940a2709535c7ed49cdb [file] [edit]
// Copyright 2026 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/eventfd.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <fbl/unique_fd.h>
#include <gtest/gtest.h>
#include <linux/securebits.h>
#include "src/starnix/tests/syscalls/cpp/syscall_matchers.h"
#include "src/starnix/tests/syscalls/cpp/test_helper.h"
#include "third_party/android/platform/bionic/libc/kernel/uapi/linux/bpf.h"
#ifndef SYS_userfaultfd
#if defined(__x86_64__)
#define SYS_userfaultfd 323
#elif defined(__aarch64__)
#define SYS_userfaultfd 282
#elif defined(__riscv)
#define SYS_userfaultfd 282
#endif
#endif
#ifndef UFFD_USER_MODE_ONLY
#define UFFD_USER_MODE_ONLY 1
#endif
namespace {
constexpr uid_t kRootUid = 0;
constexpr gid_t kRootGid = 0;
constexpr uid_t kTestUid = 10001;
constexpr gid_t kTestGid = 10002;
struct AnonInodeTestParam {
std::string name;
std::function<fbl::unique_fd()> create_fd;
bool expect_shared;
};
class AnonInodeTest : public ::testing::TestWithParam<AnonInodeTestParam> {};
TEST_P(AnonInodeTest, ModeAndOwnership) {
if (!test_helper::HasSysAdmin()) {
GTEST_SKIP() << "This test requires CAP_SYS_ADMIN";
}
test_helper::ForkHelper helper;
helper.RunInForkedProcess([]() {
// Creating BPF nodes requires CAP_BPF so keep capabilities when changing eUID.
ASSERT_THAT(prctl(PR_SET_SECUREBITS, SECBIT_NO_SETUID_FIXUP), SyscallSucceeds());
ASSERT_THAT(setegid(kTestGid), SyscallSucceeds());
ASSERT_THAT(seteuid(kTestUid), SyscallSucceeds());
const AnonInodeTestParam& param = GetParam();
fbl::unique_fd fd = param.create_fd();
ASSERT_THAT(fd.get(), SyscallSucceeds());
struct stat st;
ASSERT_THAT(fstat(fd.get(), &st), SyscallSucceeds());
// All anonymous nodes should be readable and writable only by the owner.
EXPECT_EQ(st.st_mode, static_cast<mode_t>(0600));
if (param.expect_shared) {
// Shared inodes do not inherit ownership, they are owned by root.
EXPECT_EQ(st.st_uid, kRootUid);
EXPECT_EQ(st.st_gid, kRootGid);
} else {
// Unique inodes inherit ownership.
EXPECT_EQ(st.st_uid, kTestUid);
EXPECT_EQ(st.st_gid, kTestGid);
}
});
EXPECT_TRUE(helper.WaitForChildren());
}
TEST_P(AnonInodeTest, SingletonNode) {
if (!test_helper::HasSysAdmin()) {
GTEST_SKIP() << "This test requires CAP_SYS_ADMIN";
}
const AnonInodeTestParam& param = GetParam();
fbl::unique_fd fd1 = param.create_fd();
ASSERT_THAT(fd1.get(), SyscallSucceeds());
fbl::unique_fd fd2 = param.create_fd();
ASSERT_THAT(fd2.get(), SyscallSucceeds());
struct stat st1, st2;
ASSERT_THAT(fstat(fd1.get(), &st1), SyscallSucceeds());
ASSERT_THAT(fstat(fd2.get(), &st2), SyscallSucceeds());
if (param.expect_shared) {
EXPECT_EQ(st1.st_ino, st2.st_ino);
} else {
EXPECT_NE(st1.st_ino, st2.st_ino);
}
}
fbl::unique_fd CreateUserfaultfd() {
int flags = O_CLOEXEC | O_NONBLOCK | UFFD_USER_MODE_ONLY;
return fbl::unique_fd(static_cast<int>(syscall(SYS_userfaultfd, flags)));
}
fbl::unique_fd CreateEventfd() { return fbl::unique_fd(eventfd(0, 0)); }
fbl::unique_fd CreateBpfMap() {
union bpf_attr attr;
memset(&attr, 0, sizeof(attr));
attr.map_type = BPF_MAP_TYPE_ARRAY;
attr.key_size = sizeof(int);
attr.value_size = sizeof(int);
attr.max_entries = 1;
return fbl::unique_fd(static_cast<int>(syscall(SYS_bpf, BPF_MAP_CREATE, &attr, sizeof(attr))));
}
INSTANTIATE_TEST_SUITE_P(All, AnonInodeTest,
::testing::Values(
AnonInodeTestParam{
.name = "Userfaultfd",
.create_fd = CreateUserfaultfd,
.expect_shared = false,
},
AnonInodeTestParam{
.name = "Eventfd",
.create_fd = CreateEventfd,
.expect_shared = true,
},
AnonInodeTestParam{
.name = "BpfMap",
.create_fd = CreateBpfMap,
.expect_shared = true,
}),
[](const ::testing::TestParamInfo<AnonInodeTestParam>& info) {
return info.param.name;
});
} // namespace