blob: 6cf594e1976ee35f72e9e127d7e55e54c449f7a5 [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/epoll.h>
#include <sys/eventfd.h>
#include <sys/fsuid.h>
#include <sys/inotify.h>
#include <sys/prctl.h>
#include <sys/signalfd.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/timerfd.h>
#include <unistd.h>
#include <format>
#include <fbl/unique_fd.h>
#include <gtest/gtest.h>
#include <linux/capability.h>
#include <linux/perf_event.h>
#include "src/lib/files/directory.h"
#include "src/lib/files/file.h"
#include "src/lib/fxl/strings/string_printf.h"
#include "src/starnix/tests/syscalls/cpp/capabilities_helper.h"
#include "src/starnix/tests/syscalls/cpp/proc_test_base.h"
#include "src/starnix/tests/syscalls/cpp/test_helper.h"
namespace {
using testing::AnyOf;
using testing::ContainsRegex;
using testing::Eq;
/// Check if the procfs status file shows the correct fsuid number.
void AssertFsuidInProcfsStatus(uid_t fsuid) {
std::string status_string;
ASSERT_TRUE(files::ReadFileToString("/proc/self/status", &status_string));
ASSERT_THAT(status_string,
testing::ContainsRegex(std::format("Uid:\t[0-9]+\t[0-9]+\t[0-9]+\t{}\n", fsuid)));
}
class ProcUptimeTest : public ProcTestBase {
protected:
void SetUp() override {
ProcTestBase::SetUp();
Open();
}
void Close() { fd_.reset(); }
void Open() {
std::string path = proc_path() + "/uptime";
fd_.reset(open(path.c_str(), O_RDONLY));
ASSERT_TRUE(fd_.is_valid());
}
double Parse(const char* buf) {
double uptime, idle;
int s = sscanf(buf, "%lf %lf\n", &uptime, &idle);
EXPECT_EQ(s, 2);
// On Linux `idle` value may decrease, i.e. we cannot expect it to
// increase with `uptime` (see https://fxbug.dev/42080772). Ignore it.
return uptime;
}
double Read() {
char buf[100];
long r = read(fd_.get(), buf, sizeof(buf));
EXPECT_GT(r, 0);
return Parse(buf);
}
~ProcUptimeTest() override { Close(); }
fbl::unique_fd fd_;
};
TEST_F(ProcUptimeTest, UptimeRead) { Read(); }
TEST_F(ProcUptimeTest, UptimeProgressReopen) {
auto v1 = Read();
Close();
sleep(1);
Open();
auto v2 = Read();
EXPECT_GT(v2, v1);
}
// Verify that the reported value is updated after seeking /proc/uptime to the beginning.
TEST_F(ProcUptimeTest, UptimeProgressSeek) {
auto v1 = Read();
off_t pos = lseek(fd_.get(), 0, SEEK_SET);
ASSERT_EQ(pos, 0);
sleep(1);
auto v2 = Read();
EXPECT_GT(v2, v1);
}
// Verify that a valid value is produced even when reading by single char.
TEST_F(ProcUptimeTest, UptimeByChar) {
auto v1 = Read();
Open();
std::string buf;
char c;
ssize_t r = read(fd_.get(), &c, 1);
ASSERT_EQ(r, 1);
buf.push_back(c);
// Keep the FD, and then read from a new FD.
fbl::unique_fd old_fd = std::move(fd_);
Open();
auto v2 = Read();
// Wait for a bit and then read the old FD to the end.
sleep(1);
while ((r = read(old_fd.get(), &c, 1)) == 1) {
buf.push_back(c);
}
auto v3 = Parse(buf.c_str());
// `v3` should be between `v1` and `v2`.
EXPECT_LE(v1, v3);
EXPECT_LE(v3, v2);
}
class ProcSysNetTest : public ProcTestBase,
public ::testing::WithParamInterface<std::tuple<const char*, const char*>> {
protected:
void SetUp() override {
ProcTestBase::SetUp();
// Required to open the path below for writing.
// TODO(https://fxbug.dev/317285180) don't skip on baseline
if (!test_helper::HasSysAdmin()) {
GTEST_SKIP() << "Not running with sysadmin capabilities, skipping.";
}
auto const& [dev, path_fmt] = GetParam();
char buf[100] = {};
sprintf(buf, path_fmt, dev);
std::string path = proc_path() + "/sys/net" + buf;
fd_.reset(open(path.c_str(), O_RDWR));
ASSERT_TRUE(fd_.is_valid()) << "path: " + path + "; " + strerror(errno);
}
fbl::unique_fd fd_;
};
TEST_P(ProcSysNetTest, Write) {
constexpr uint8_t kWriteBuf = 127;
ASSERT_EQ(write(fd_.get(), &kWriteBuf, sizeof(kWriteBuf)),
static_cast<ssize_t>(sizeof(kWriteBuf)))
<< strerror(errno);
}
INSTANTIATE_TEST_SUITE_P(
ProcSysNetTest, ProcSysNetTest,
::testing::Combine(
::testing::Values("all", "default"),
::testing::Values("/ipv4/neigh/%s/ucast_solicit", "/ipv4/neigh/%s/retrans_time_ms",
"/ipv4/neigh/%s/mcast_resolicit", "/ipv6/conf/%s/accept_ra",
"/ipv6/conf/%s/dad_transmits", "/ipv6/conf/%s/use_tempaddr",
"/ipv6/conf/%s/addr_gen_mode", "/ipv6/conf/%s/stable_secret",
"/ipv6/conf/%s/disable_ipv6", "/ipv6/neigh/%s/ucast_solicit",
"/ipv6/neigh/%s/retrans_time_ms", "/ipv6/neigh/%s/mcast_resolicit")));
using ProcTest = ProcTestBase;
// Test that after forking without execing, /proc/self/cmdline still works.
TEST_F(ProcTest, CmdlineAfterFork) {
char cmdline[100];
int cmdline_fd = open("/proc/self/cmdline", O_RDONLY);
ASSERT_GT(cmdline_fd, 0) << strerror(errno);
ssize_t cmdline_len = read(cmdline_fd, cmdline, sizeof(cmdline));
ASSERT_GT(cmdline_len, 0) << strerror(errno);
close(cmdline_fd);
test_helper::ForkHelper helper;
helper.RunInForkedProcess([&] {
char child_cmdline[100];
int cmdline_fd = open("/proc/self/cmdline", O_RDONLY);
ASSERT_GT(cmdline_fd, 0) << strerror(errno);
ssize_t child_cmdline_len = read(cmdline_fd, child_cmdline, sizeof(child_cmdline));
ASSERT_GT(child_cmdline_len, 0) << strerror(errno);
close(cmdline_fd);
ASSERT_EQ(cmdline_len, child_cmdline_len);
ASSERT_TRUE(memcmp(cmdline, child_cmdline, cmdline_len) == 0);
});
ASSERT_TRUE(helper.WaitForChildren());
}
class ProcTaskDirTest : public ProcTestBase {
protected:
void SetUp() override { ProcTestBase::SetUp(); }
};
bool ends_with(const std::string& haystack, const std::string needle) {
return haystack.rfind(needle) == (haystack.size() - needle.size());
}
std::string ProcSelfDirName() {
std::string proc_self = fxl::StringPrintf("/proc/%d", getpid());
struct stat statbuf;
SAFE_SYSCALL(stat(proc_self.c_str(), &statbuf));
return proc_self;
}
// Ensure that entries in /proc/pid/. have the correct ownership. proc(2) says
// that entries are owned by the effective uid of the task. This doesn't seem to
// be exactly what Linux does - Linux seems to assign most directories to the
// euid, and everything else to the real id - but it's not clear exactly what
// Linux is doing from looking at its behavior, so we hew to the man page.
//
// This test ensures that the proc directories *are* set to be owned by the euid
// of the task.
TEST_F(ProcTaskDirTest, PidDirCorrectUidIsEuid) {
// TODO(https://fxbug.dev/317285180) don't skip on baseline
if (!test_helper::HasSysAdmin() || !test_helper::IsStarnix()) {
GTEST_SKIP() << "PidDirCorrectUid requires root access (to change euid), "
<< "and currently only works on Starnix";
}
test_helper::ForkHelper helper;
helper.RunInForkedProcess([] {
std::string proc_path = ProcSelfDirName();
int dirfd;
struct stat pre_stat;
struct stat euid_stat;
// Get the original (presumably real) ownership
ASSERT_NE(-1, dirfd = open(proc_path.c_str(), O_RDONLY))
<< "Error trying to open " << proc_path << ": " << strerror(errno);
SAFE_SYSCALL(fstat(dirfd, &pre_stat));
SAFE_SYSCALL(close(dirfd));
// Set the effective uid.
uid_t newuid = geteuid() + 1;
SAFE_SYSCALL(seteuid(newuid));
// From proc(5): The files inside each /proc/pid directory are normally
// owned by the effective user and effective group ID of the process.
// However, as a security measure, the ownership is made root:root
// if the process's "dumpable" attribute is set to a value other than 1.
SAFE_SYSCALL(prctl(PR_SET_DUMPABLE, 1));
// Make sure the effective uid appears to own /proc/self.
SAFE_SYSCALL(dirfd = open(proc_path.c_str(), O_RDONLY));
SAFE_SYSCALL(fstat(dirfd, &euid_stat));
SAFE_SYSCALL(close(dirfd));
EXPECT_EQ(euid_stat.st_uid, newuid)
<< "owner for " << proc_path << " did not change to correct value";
std::vector<std::string> dirs;
files::ReadDirContents(proc_path, &dirs);
for (const auto& entry : dirs) {
if ((entry == ".") || (entry == "..")) {
continue;
}
struct stat info;
auto fname =
(entry[0] == '/') ? entry : fxl::StringPrintf("%s/%s", proc_path.c_str(), entry.c_str());
ASSERT_NE(-1, stat(fname.c_str(), &info))
<< "Error reading " << fname << ": " << strerror(errno);
// Links to other parts of the fs have their original ownership. S_ISLNK does
// not currently work on these files. See https://fxbug.dev/331990255.
if (ends_with(fname, "/cwd") || ends_with(fname, "/root") || ends_with(fname, "/exe")) {
continue;
}
EXPECT_EQ(info.st_uid, euid_stat.st_uid) << "Wrong owner for file " << fname;
}
});
}
// This test ensures that the proc directories aren't set to be owned by the
// fsuid of the task. This is separate from PidDirCorrectUidIsEuid because
// setting the euid implicitly sets the fsuid. We want to check we're not
// accidentally relying on the fsuid.
TEST_F(ProcTaskDirTest, PidDirSetFsuidDoesntChangeOwnership) {
// TODO(https://fxbug.dev/317285180) don't skip on baseline
if (!test_helper::HasSysAdmin() || !test_helper::IsStarnix()) {
GTEST_SKIP() << "PidDirCorrectUid requires root access (to change euid), "
<< "and currently only works on Starnix";
}
test_helper::ForkHelper helper;
helper.RunInForkedProcess([] {
std::string proc_path = ProcSelfDirName();
int dirfd;
struct stat pre_stat;
struct stat fsuid_stat;
uid_t newuid;
// Get the original (presumably real) ownership
ASSERT_NE(-1, dirfd = open(proc_path.c_str(), O_RDONLY))
<< "Error trying to open " << proc_path << ": " << strerror(errno);
SAFE_SYSCALL(fstat(dirfd, &pre_stat));
SAFE_SYSCALL(close(dirfd));
// Set the fsuid.
newuid = pre_stat.st_uid + 1;
ASSERT_EQ(static_cast<const int>(pre_stat.st_uid), setfsuid(newuid)) << "Unexpected fsuid";
// This is how you check to see if a call to setfsuid worked correctly.
ASSERT_EQ(static_cast<const int>(newuid), setfsuid(-1)) << "setfsuid not supported";
AssertFsuidInProcfsStatus(newuid);
// Make sure that the current owner has *not* changed to the fsuid
SAFE_SYSCALL(dirfd = open(proc_path.c_str(), O_RDONLY));
SAFE_SYSCALL(fstat(dirfd, &fsuid_stat));
SAFE_SYSCALL(close(dirfd));
EXPECT_NE(fsuid_stat.st_uid, newuid) << "fsuid seen to change incorrectly";
// Revert the fsuid
ASSERT_EQ(static_cast<const int>(newuid), setfsuid(pre_stat.st_uid));
AssertFsuidInProcfsStatus(pre_stat.st_uid);
});
}
TEST_F(ProcTaskDirTest, PidDirCorrectIno) {
const char kProcPath[] = "/proc/self/status";
int fd;
struct stat pre_stat;
struct stat post_stat;
SAFE_SYSCALL(fd = open(kProcPath, O_RDONLY));
SAFE_SYSCALL(fstat(fd, &pre_stat));
SAFE_SYSCALL(close(fd));
SAFE_SYSCALL(fd = open(kProcPath, O_RDONLY));
SAFE_SYSCALL(fstat(fd, &post_stat));
SAFE_SYSCALL(close(fd));
ASSERT_EQ(pre_stat.st_ino, post_stat.st_ino) << "Inode number incorrectly seen to change";
}
TEST_F(ProcTaskDirTest, SelfAuxvIsNotEmpty) {
std::string contents;
ASSERT_TRUE(files::ReadFileToString("/proc/self/auxv", &contents));
ASSERT_THAT(contents.size(), testing::Gt(0));
}
TEST_F(ProcTaskDirTest, KthreadAuxvIsEmpty) {
if (!test_helper::HasSysAdmin()) {
GTEST_SKIP() << "Not running with sysadmin capabilities, skipping.";
}
std::string contents;
ASSERT_TRUE(files::ReadFileToString("/proc/2/auxv", &contents));
ASSERT_EQ(contents.size(), 0ul);
}
TEST_F(ProcTaskDirTest, SelfCmdlineIsNotEmpty) {
std::string contents;
ASSERT_TRUE(files::ReadFileToString("/proc/self/cmdline", &contents));
ASSERT_THAT(contents.size(), testing::Gt(0));
}
TEST_F(ProcTaskDirTest, KthreadCmdlineIsEmpty) {
if (!test_helper::HasSysAdmin()) {
GTEST_SKIP() << "Not running with sysadmin capabilities, skipping.";
}
std::string contents;
ASSERT_TRUE(files::ReadFileToString("/proc/2/cmdline", &contents));
ASSERT_EQ(contents.size(), 0ul);
}
TEST_F(ProcTaskDirTest, SelfEnvironIsNotEmpty) {
std::string contents;
ASSERT_TRUE(files::ReadFileToString("/proc/self/environ", &contents));
ASSERT_THAT(contents.size(), testing::Gt(0));
}
TEST_F(ProcTaskDirTest, KthreadEnvironIsEmpty) {
if (!test_helper::HasSysAdmin()) {
GTEST_SKIP() << "Not running with sysadmin capabilities, skipping.";
}
std::string contents;
ASSERT_TRUE(files::ReadFileToString("/proc/2/environ", &contents));
ASSERT_EQ(contents.size(), 0ul);
}
TEST_F(ProcTaskDirTest, SelfExeSymlinkIsValid) {
char buf[1000];
ASSERT_THAT(readlink("/proc/self/exe", buf, 1000), testing::Gt(0));
}
TEST_F(ProcTaskDirTest, KthreadExeSymlinkIsNotValid) {
if (!test_helper::HasSysAdmin()) {
GTEST_SKIP() << "Not running with sysadmin capabilities, skipping.";
}
char buf[1000];
ASSERT_EQ(readlink("/proc/2/exe", buf, 1000), -1);
}
TEST_F(ProcTaskDirTest, SelfSmapsIsNotEmpty) {
std::string contents;
ASSERT_TRUE(files::ReadFileToString("/proc/self/smaps", &contents));
ASSERT_THAT(contents.size(), testing::Gt(0));
}
TEST_F(ProcTaskDirTest, KthreadSmapsIsEmpty) {
if (!test_helper::HasSysAdmin()) {
GTEST_SKIP() << "Not running with sysadmin capabilities, skipping.";
}
std::string contents;
ASSERT_TRUE(files::ReadFileToString("/proc/2/smaps", &contents));
ASSERT_EQ(contents.size(), 0ul);
}
TEST_F(ProcTaskDirTest, SelfStatIsNotEmpty) {
std::string contents;
ASSERT_TRUE(files::ReadFileToString("/proc/self/stat", &contents));
ASSERT_THAT(contents.size(), testing::Gt(0));
}
TEST_F(ProcTaskDirTest, KthreadStatIsNotEmpty) {
if (!test_helper::HasSysAdmin()) {
GTEST_SKIP() << "Not running with sysadmin capabilities, skipping.";
}
std::string contents;
ASSERT_TRUE(files::ReadFileToString("/proc/2/stat", &contents));
ASSERT_THAT(contents.size(), testing::Gt(0));
}
TEST_F(ProcTaskDirTest, SelfStatmIsNotEmpty) {
std::string contents;
ASSERT_TRUE(files::ReadFileToString("/proc/self/statm", &contents));
ASSERT_THAT(contents.size(), testing::Gt(0));
}
TEST_F(ProcTaskDirTest, KthreadStatmIsAllZeroes) {
if (!test_helper::HasSysAdmin()) {
GTEST_SKIP() << "Not running with sysadmin capabilities, skipping.";
}
std::string contents;
ASSERT_TRUE(files::ReadFileToString("/proc/2/statm", &contents));
ASSERT_EQ(contents, "0 0 0 0 0 0 0\n");
}
TEST_F(ProcTaskDirTest, SelfStatusSensibleOutput) {
std::string contents;
ASSERT_TRUE(files::ReadFileToString("/proc/self/status", &contents));
EXPECT_THAT(contents, testing::HasSubstr("Name:"));
EXPECT_THAT(contents, testing::HasSubstr("\nUmask:"));
EXPECT_THAT(contents, testing::HasSubstr("\nSigBlk:"));
EXPECT_THAT(contents, testing::HasSubstr("\nSigPnd:"));
EXPECT_THAT(contents, testing::HasSubstr("\nShdPnd:"));
EXPECT_THAT(contents, testing::HasSubstr("\nState:"));
EXPECT_THAT(contents, testing::HasSubstr("\nTgid:"));
EXPECT_THAT(contents, testing::HasSubstr("\nPid:"));
EXPECT_THAT(contents, testing::HasSubstr("\nPPid:"));
EXPECT_THAT(contents, testing::HasSubstr("\nUid:"));
EXPECT_THAT(contents, testing::HasSubstr("\nGid:"));
EXPECT_THAT(contents, testing::HasSubstr("\nGroups:"));
EXPECT_THAT(contents, testing::HasSubstr("\nVmSize:"));
EXPECT_THAT(contents, testing::HasSubstr("\nVmLck:"));
EXPECT_THAT(contents, testing::HasSubstr("\nVmRSS:"));
EXPECT_THAT(contents, testing::HasSubstr("\nRssAnon:"));
EXPECT_THAT(contents, testing::HasSubstr("\nRssFile:"));
EXPECT_THAT(contents, testing::HasSubstr("\nRssShmem:"));
EXPECT_THAT(contents, testing::HasSubstr("\nRssShmem:"));
EXPECT_THAT(contents, testing::HasSubstr("\nVmData:"));
EXPECT_THAT(contents, testing::HasSubstr("\nVmStk:"));
EXPECT_THAT(contents, testing::HasSubstr("\nVmExe:"));
EXPECT_THAT(contents, testing::HasSubstr("\nVmSwap:"));
EXPECT_THAT(contents, testing::HasSubstr("\nThreads:"));
EXPECT_THAT(contents, testing::HasSubstr("\nVmHWM:"));
}
TEST_F(ProcTaskDirTest, KthreadStatusSensibleOutput) {
if (!test_helper::HasSysAdmin()) {
GTEST_SKIP() << "Not running with sysadmin capabilities, skipping.";
}
std::string contents;
ASSERT_TRUE(files::ReadFileToString("/proc/2/status", &contents));
EXPECT_THAT(contents, testing::HasSubstr("Name:"));
EXPECT_THAT(contents, testing::HasSubstr("\nUmask:"));
EXPECT_THAT(contents, testing::HasSubstr("\nSigBlk:"));
EXPECT_THAT(contents, testing::HasSubstr("\nSigPnd:"));
EXPECT_THAT(contents, testing::HasSubstr("\nShdPnd:"));
EXPECT_THAT(contents, testing::HasSubstr("\nState:"));
EXPECT_THAT(contents, testing::HasSubstr("\nTgid:"));
EXPECT_THAT(contents, testing::HasSubstr("\nPid:"));
EXPECT_THAT(contents, testing::HasSubstr("\nPPid:"));
EXPECT_THAT(contents, testing::HasSubstr("\nUid:"));
EXPECT_THAT(contents, testing::HasSubstr("\nGid:"));
EXPECT_THAT(contents, testing::HasSubstr("\nGroups:"));
EXPECT_THAT(contents, Not(testing::HasSubstr("\nVmSize:")));
EXPECT_THAT(contents, Not(testing::HasSubstr("\nVmRSS:")));
EXPECT_THAT(contents, Not(testing::HasSubstr("\nRssAnon:")));
EXPECT_THAT(contents, Not(testing::HasSubstr("\nRssFile:")));
EXPECT_THAT(contents, Not(testing::HasSubstr("\nRssShmem:")));
EXPECT_THAT(contents, Not(testing::HasSubstr("\nRssShmem:")));
EXPECT_THAT(contents, Not(testing::HasSubstr("\nVmData:")));
EXPECT_THAT(contents, Not(testing::HasSubstr("\nVmStk:")));
EXPECT_THAT(contents, Not(testing::HasSubstr("\nVmExe:")));
EXPECT_THAT(contents, Not(testing::HasSubstr("\nVmSwap:")));
EXPECT_THAT(contents, testing::HasSubstr("\nThreads:"));
}
constexpr char kProcSelfFdPath[] = "/proc/self/fd/%d";
TEST_F(ProcTaskDirTest, FdOpath) {
test_helper::ScopedTempFD temp_file;
ASSERT_TRUE(temp_file);
fbl::unique_fd opath_fd(SAFE_SYSCALL(open(temp_file.name().c_str(), O_RDONLY | O_PATH)));
ASSERT_TRUE(opath_fd.is_valid());
std::string opath_fd_path = fxl::StringPrintf(kProcSelfFdPath, opath_fd.get());
fbl::unique_fd regular_fd(SAFE_SYSCALL(open(opath_fd_path.c_str(), O_RDONLY)));
ASSERT_TRUE(regular_fd.is_valid());
}
TEST_F(ProcTaskDirTest, FdOpathSymlink) {
test_helper::ScopedTempFD temp_file;
ASSERT_TRUE(temp_file);
test_helper::ScopedTempSymlink temp_symlink(temp_file.name().c_str());
ASSERT_TRUE(temp_symlink);
fbl::unique_fd symlink_fd(SAFE_SYSCALL(open(temp_symlink.path().c_str(), O_PATH | O_NOFOLLOW)));
ASSERT_TRUE(symlink_fd.is_valid());
std::string symlink_fd_path = fxl::StringPrintf(kProcSelfFdPath, symlink_fd.get());
ASSERT_THAT(open(symlink_fd_path.c_str(), O_RDONLY), SyscallFailsWithErrno(ELOOP));
}
TEST_F(ProcTaskDirTest, TimerslackNsSelf) {
// We do not need CAP_SYS_NICE to access our own timerslack_ns file.
test_helper::UnsetCapabilityEffective(CAP_SYS_NICE);
std::string initial_contents;
ASSERT_TRUE(files::ReadFileToString("/proc/self/timerslack_ns", &initial_contents));
std::string new_contents = "1" + initial_contents;
fbl::unique_fd fd(open("/proc/self/timerslack_ns", O_RDWR));
if (!fd.is_valid()) {
if (errno == EROFS) {
GTEST_SKIP() << "/proc is not writable, skipping";
} else {
ADD_FAILURE() << "Failed to open /proc/self/timerslack_ns for writing: " << strerror(errno);
return;
}
}
ASSERT_NE(write(fd.get(), new_contents.c_str(), new_contents.size()), -1) << errno;
fd.reset();
std::string read_contents;
ASSERT_TRUE(files::ReadFileToString("/proc/self/timerslack_ns", &read_contents));
EXPECT_EQ(read_contents, new_contents);
// Writing zero resets to the default value.
ASSERT_TRUE(files::WriteFile("/proc/self/timerslack_ns", "0"));
ASSERT_TRUE(files::ReadFileToString("/proc/self/timerslack_ns", &read_contents));
EXPECT_EQ(read_contents, initial_contents);
}
TEST_F(ProcTaskDirTest, TimerslackNsOtherNoAccess) {
pid_t parent_pid = getpid();
test_helper::ForkHelper helper;
helper.RunInForkedProcess([&] {
// Ensure we don't have CAP_SYS_NICE, otherwise we would have access.
test_helper::DropAllCapabilities();
std::string path = fxl::StringPrintf("/proc/%d/timerslack_ns", parent_pid);
// Open succeeds even though the file will not be readable.
fbl::unique_fd fd(open(path.c_str(), O_RDONLY));
ASSERT_TRUE(fd.is_valid());
// But reading fails.
char buf[256];
EXPECT_EQ(read(fd.get(), buf, sizeof(buf)), -1);
EXPECT_EQ(errno, EPERM);
});
ASSERT_TRUE(helper.WaitForChildren());
}
TEST_F(ProcTaskDirTest, TimerslackNsOtherAccessWithCap) {
if (!test_helper::HasCapabilityPermitted(CAP_SYS_NICE)) {
GTEST_SKIP() << "Needs the CAP_SYS_NICE capability.";
}
pid_t parent_pid = getpid();
test_helper::ForkHelper helper;
helper.RunInForkedProcess([&] {
// We need to have the effective CAP_SYS_NICE capability.
test_helper::SetCapabilityEffective(CAP_SYS_NICE);
std::string path = fxl::StringPrintf("/proc/%d/timerslack_ns", parent_pid);
std::string contents;
EXPECT_TRUE(files::ReadFileToString(path, &contents));
});
ASSERT_TRUE(helper.WaitForChildren());
}
class ProcfsTest : public ProcTestBase {
protected:
// Verifies whether the input string is a valid UUID in hyphenated format
// Example:
// af0af413-58e1-4210-bb57-bc9a9d3ca44a
// Segment lengths:
// 8 | 4 | 4 | 4 | 12
void is_valid_uuid(std::string in) {
size_t pos = 0;
std::string token;
std::array<size_t, 5> lengths = {8, 4, 4, 4, 12}; // Lengths of tokens
// Some Linux kernels have a newline character at the end
if (in[in.size() - 1] == '\n') {
in.pop_back();
}
in.append("-");
// For each hyphen-delimited token of the UUID string
for (size_t length : lengths) {
// Grab token
ASSERT_TRUE((pos = in.find("-")) != std::string::npos);
token = in.substr(0, pos);
in.erase(0, pos + 1);
ASSERT_TRUE(length == token.size());
// All characters are hexadecimal digits
ASSERT_TRUE(std::all_of(token.begin(), token.end(), [](char c) { return std::isxdigit(c); }));
}
}
};
// Verify /proc/sys/kernel/random/boot_id exists and has the boot UUID
TEST_F(ProcfsTest, ProcSysKernelRandomBootIdExists) {
std::string uuid;
EXPECT_EQ(0, access("/proc/sys/kernel/random/boot_id", R_OK));
EXPECT_TRUE(files::ReadFileToString("/proc/sys/kernel/random/boot_id", &uuid));
is_valid_uuid(uuid);
}
// Verify that /proc/zoneinfo contains something reasonable.
TEST_F(ProcfsTest, ZoneInfo) {
auto path = "/proc/zoneinfo";
EXPECT_EQ(0, access(path, R_OK));
std::string content;
ASSERT_TRUE(files::ReadFileToString(path, &content));
// Ensures that one node has `nr_inactive_file` and `nr_inactive_file`.
EXPECT_THAT(content, ContainsRegex("(\n|^)Node [0-9]+, zone +[a-zA-Z]+\n"
" per-node stats\n"
"( {6}.*\n)*"
" nr_inactive_file [0-9]+\n"
" nr_active_file [0-9]+\n"));
// Ensures that one node has `free`, `min`, `low`, `high`, `present`, among others.
EXPECT_THAT(content, ContainsRegex("(\n|^)Node [0-9]+, zone +[a-zA-Z]+\n"
"( .*\n)*"
" pages free [0-9]+\n"
"( .*\n)*"
" min [0-9]+\n"
"( .*\n)*"
" low [0-9]+\n"
"( .*\n)*"
" high [0-9]+\n"
"( .*\n)*"
" present [0-9]+\n"
"( .*\n)*"
" pagesets\n"));
}
// Verify that /proc/vmstat shape is reasonable.
TEST_F(ProcfsTest, VmStatFile) {
auto path = "/proc/vmstat";
EXPECT_EQ(0, access(path, R_OK));
std::string content;
ASSERT_TRUE(files::ReadFileToString(path, &content));
// Ensures that one node has `nr_inactive_file` and `nr_inactive_file`.
EXPECT_THAT(content, ContainsRegex("(\n|^)workingset_refault_file [0-9]+\n"));
EXPECT_THAT(content, ContainsRegex("(\n|^)nr_inactive_file [0-9]+\n"));
EXPECT_THAT(content, ContainsRegex("(\n|^)nr_active_file [0-9]+\n"));
EXPECT_THAT(content, ContainsRegex("(\n|^)pgscan_direct [0-9]+\n"));
EXPECT_THAT(content, ContainsRegex("(\n|^)pgscan_kswapd [0-9]+\n"));
}
class ProcSelfFdTest : public ProcTestBase {
protected:
std::string read_fd_link(int fd) {
constexpr char kProcSelfFdFormat[] = "/proc/self/fd/%d";
std::string path = fxl::StringPrintf(kProcSelfFdFormat, fd);
std::string links_to(PATH_MAX, 0);
ssize_t result = SAFE_SYSCALL(readlink(path.c_str(), links_to.data(), links_to.capacity()));
if (result < 0) {
return fxl::StringPrintf("readlink() failed: %s", strerror(errno));
}
links_to.resize(result);
return links_to;
}
std::string expected_name(const char* kind, int fd) {
struct stat stat_buf;
if (SAFE_SYSCALL(fstat(fd, &stat_buf)) != 0) {
return fxl::StringPrintf("fstat() failed: %s", strerror(errno));
}
return fxl::StringPrintf("%s:[%lu]", kind, stat_buf.st_ino);
}
};
// Validate naming of anonymous pipes as reported via "/proc/self/fd".
TEST_F(ProcSelfFdTest, AnonymousPipeFdName) {
int pipes[2];
ASSERT_EQ(SAFE_SYSCALL(pipe2(pipes, 0)), 0) << "pipe() failed:" << strerror(errno);
fbl::unique_fd pipe_a(pipes[0]);
fbl::unique_fd pipe_b(pipes[1]);
EXPECT_EQ(read_fd_link(pipe_a.get()), expected_name("pipe", pipe_a.get()));
EXPECT_EQ(read_fd_link(pipe_b.get()), expected_name("pipe", pipe_b.get()));
}
// Validate naming of sockets as reported via "/proc/self/fd".
TEST_F(ProcSelfFdTest, SocketFdName) {
fbl::unique_fd sock(SAFE_SYSCALL(socket(AF_UNIX, SOCK_STREAM, 0)));
ASSERT_TRUE(sock.is_valid()) << "socket() failed:" << strerror(errno);
EXPECT_EQ(read_fd_link(sock.get()), expected_name("socket", sock.get()));
}
// Validate naming of memory file descriptors as reported via "/proc/self/fd".
TEST_F(ProcSelfFdTest, MemFdName) {
constexpr char kMemFdName[] = "just_a_test_mem_fd";
fbl::unique_fd mem_fd(SAFE_SYSCALL(memfd_create(kMemFdName, 0)));
ASSERT_TRUE(mem_fd.is_valid()) << "memfd_create() failed:" << strerror(errno);
EXPECT_EQ(read_fd_link(mem_fd.get()), fxl::StringPrintf("/memfd:%s (deleted)", kMemFdName));
}
// Validate naming of timerfd descriptors as reported via "/proc/self/fd".
TEST_F(ProcSelfFdTest, TimerFdName) {
fbl::unique_fd timer_fd(SAFE_SYSCALL(timerfd_create(CLOCK_MONOTONIC, 0)));
ASSERT_TRUE(timer_fd.is_valid()) << "timerfd_create() failed:" << strerror(errno);
EXPECT_EQ(read_fd_link(timer_fd.get()), "anon_inode:[timerfd]");
}
// Validate naming of signalfd descriptors as reported via "/proc/self/fd".
TEST_F(ProcSelfFdTest, SignalFdName) {
sigset_t mask;
ASSERT_EQ(sigemptyset(&mask), 0);
ASSERT_EQ(sigaddset(&mask, SIGHUP), 0);
fbl::unique_fd signal_fd(SAFE_SYSCALL(signalfd(-1, &mask, 0)));
ASSERT_TRUE(signal_fd.is_valid()) << "signalfd_create() failed:" << strerror(errno);
EXPECT_EQ(read_fd_link(signal_fd.get()), "anon_inode:[signalfd]");
}
// Validate naming of pidfd descriptors as reported via "/proc/self/fd".
TEST_F(ProcSelfFdTest, PidFdName) {
fbl::unique_fd pid_fd(SAFE_SYSCALL(static_cast<int>(syscall(SYS_pidfd_open, getpid(), 0))));
ASSERT_TRUE(pid_fd.is_valid()) << "syscall(SYS_pidfd_open) failed:" << strerror(errno);
std::string result = read_fd_link(pid_fd.get());
EXPECT_THAT(result, AnyOf(Eq(expected_name("pidfd", pid_fd.get())), Eq("anon_inode:[pidfd]")));
}
// Validate naming of inotify descriptors as reported via "/proc/self/fd".
TEST_F(ProcSelfFdTest, InotifyFdName) {
fbl::unique_fd inotify_fd(SAFE_SYSCALL(inotify_init()));
ASSERT_TRUE(inotify_fd.is_valid()) << "inotify_init() failed:" << strerror(errno);
EXPECT_EQ(read_fd_link(inotify_fd.get()), "anon_inode:inotify");
}
// Validate naming of epoll descriptors as reported via "/proc/self/fd".
TEST_F(ProcSelfFdTest, EpollFdName) {
fbl::unique_fd epoll_fd(SAFE_SYSCALL(epoll_create1(0)));
ASSERT_TRUE(epoll_fd.is_valid()) << "epoll_create1() failed:" << strerror(errno);
EXPECT_EQ(read_fd_link(epoll_fd.get()), "anon_inode:[eventpoll]");
}
// Validate naming of eventfd descriptors as reported via "/proc/self/fd".
TEST_F(ProcSelfFdTest, EventFdName) {
fbl::unique_fd event_fd(SAFE_SYSCALL(eventfd(0, 0)));
ASSERT_TRUE(event_fd.is_valid()) << "eventfd() failed:" << strerror(errno);
EXPECT_EQ(read_fd_link(event_fd.get()), "anon_inode:[eventfd]");
}
// Validate naming of normal (still file-system linked) descriptors as reported via "/proc/self/fd".
TEST_F(ProcSelfFdTest, PathFdName) {
constexpr char kTmpPath[] = "/tmp";
fbl::unique_fd tmp_fd(SAFE_SYSCALL(open(kTmpPath, O_RDONLY)));
ASSERT_TRUE(tmp_fd.is_valid()) << "open(tmp) failed:" << strerror(errno);
EXPECT_EQ(read_fd_link(tmp_fd.get()), kTmpPath);
}
// Validate naming of O_TMPFILE descriptors as reported via "/proc/self/fd".
TEST_F(ProcSelfFdTest, TmpFileFdName) {
constexpr char kTmpPath[] = "/tmp";
fbl::unique_fd tmpfile_fd(SAFE_SYSCALL(open(kTmpPath, O_RDWR | O_TMPFILE)));
ASSERT_TRUE(tmpfile_fd.is_valid()) << "open(tmpfile) failed:" << strerror(errno);
std::string result = read_fd_link(tmpfile_fd.get());
EXPECT_TRUE(result.starts_with(kTmpPath)) << " target: " << result;
EXPECT_TRUE(result.ends_with(" (deleted)")) << " target: " << result;
}
// Validate naming of O_TMPFILE descriptors, and the naming of the file that it is linked into.
TEST_F(ProcSelfFdTest, TmpFileLinkIntoAfterFdName) {
// CAP_DAC_READ_SEARCH capability is required to use AT_EMPTY_PATH with linkat
if (!test_helper::HasCapability(CAP_DAC_READ_SEARCH)) {
GTEST_SKIP() << "Not running with CAP_DAC_READ_SEARCH capabilities, skipping.";
}
constexpr char kTmpPath[] = "/tmp";
fbl::unique_fd tmpfile_fd(SAFE_SYSCALL(open(kTmpPath, O_RDWR | O_TMPFILE)));
ASSERT_TRUE(tmpfile_fd.is_valid()) << "open(tmpfile) failed:" << strerror(errno);
std::string filename("/tmp/procfs_test_file");
SAFE_SYSCALL(linkat(tmpfile_fd.get(), "", AT_FDCWD, filename.c_str(), AT_EMPTY_PATH));
fbl::unique_fd linked_file_fd(open(filename.c_str(), O_RDONLY));
ASSERT_TRUE(linked_file_fd.is_valid()) << "Failed to open file:" << strerror(errno);
std::string result_linked_file = read_fd_link(linked_file_fd.get());
EXPECT_EQ(result_linked_file, filename);
std::string result_tmp_fd = read_fd_link(tmpfile_fd.get());
EXPECT_TRUE(result_tmp_fd.starts_with(kTmpPath)) << " target: " << result_tmp_fd;
EXPECT_TRUE(result_tmp_fd.ends_with(" (deleted)")) << " target: " << result_tmp_fd;
SAFE_SYSCALL(unlink(filename.c_str()));
}
// Validate naming of a file that is created, and opened, but then unlinked.
TEST_F(ProcSelfFdTest, OpenedAndUnlinkedFileFdName) {
std::string filename("/tmp/procfs_test_file:XXXXXX");
fbl::unique_fd fd(SAFE_SYSCALL(mkstemp(filename.data())));
ASSERT_TRUE(fd.is_valid()) << "mkstemp() failed:" << strerror(errno);
std::string result = read_fd_link(fd.get());
EXPECT_EQ(result, filename);
ASSERT_EQ(SAFE_SYSCALL(unlink(filename.data())), 0) << "unlink() failed:" << strerror(errno);
result = read_fd_link(fd.get());
EXPECT_EQ(result, filename + " (deleted)");
}
// Validate naming of a file that is created, and opened, but then unlinked, and is also
// unreachable.
TEST_F(ProcSelfFdTest, DeletedAndUnreachable) {
if (!test_helper::HasCapability(CAP_SYS_CHROOT)) {
GTEST_SKIP() << "Not running with chroot capability, skipping";
}
std::string filename("/tmp/procfs_test_file:XXXXXX");
fbl::unique_fd fd(SAFE_SYSCALL(mkstemp(filename.data())));
ASSERT_TRUE(fd.is_valid()) << "mkstemp() failed:" << strerror(errno);
unlink(filename.c_str());
test_helper::ForkHelper helper;
helper.RunInForkedProcess([&] {
SAFE_SYSCALL(chroot("/proc/self"));
std::string path = fxl::StringPrintf("/fd/%d", fd.get());
std::string links_to(PATH_MAX, 0);
ssize_t result = SAFE_SYSCALL(readlink(path.c_str(), links_to.data(), links_to.capacity()));
if (result < 0) {
return;
}
links_to.resize(result);
EXPECT_EQ(links_to, filename + " (deleted)");
});
ASSERT_TRUE(helper.WaitForChildren());
}
// Validate naming of perf_event_open descriptors as reported via "/proc/self/fd".
TEST_F(ProcSelfFdTest, PerfEventFdName) {
if (!test_helper::HasSysAdmin()) {
GTEST_SKIP() << "Require CAP_SYS_ADMIN to test perf_event_open(), Skipping.";
}
perf_event_attr attr{};
attr.type = PERF_TYPE_SOFTWARE;
attr.size = sizeof(attr);
attr.config = PERF_COUNT_SW_CPU_CLOCK;
attr.sample_type = PERF_SAMPLE_IP;
attr.disabled = true;
attr.exclude_kernel = true;
attr.exclude_hv = true;
attr.exclude_idle = true;
fbl::unique_fd perf_event_fd(SAFE_SYSCALL(static_cast<int>(
syscall(SYS_perf_event_open, &attr, /*pid=*/0, /*cpu=*/-1, /*group_fd=*/-1, /*flags=*/0))));
ASSERT_TRUE(perf_event_fd.is_valid()) << "perf_event_open() failed:" << strerror(errno);
EXPECT_EQ(read_fd_link(perf_event_fd.get()), "anon_inode:[perf_event]");
}
} // namespace