blob: a7442d5c1e8ef08e1ed6bd1d3342cc1636c23252 [file] [log] [blame]
// Copyright 2024 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 <errno.h>
#include <fcntl.h>
#include <grp.h>
#include <lib/fit/defer.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mount.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/xattr.h>
#include <filesystem>
#include <fbl/unique_fd.h>
#include <gtest/gtest-spi.h>
#include <gtest/gtest.h>
#include <linux/capability.h>
#include <linux/prctl.h>
#include <linux/securebits.h>
#include "src/lib/fxl/strings/string_printf.h"
#include "src/starnix/tests/syscalls/cpp/capabilities_helper.h"
#include "src/starnix/tests/syscalls/cpp/test_helper.h"
namespace {
struct capability_t {
int cap_num;
int effective;
int permitted;
int inheritable;
int bounding;
int ambient;
};
static char kPrintHelperBinary[] = "print_helper";
constexpr size_t kRootUid = 0;
constexpr size_t kRootGid = 0;
constexpr size_t kUser1Uid = 65533;
constexpr size_t kUser1Gid = 65534;
// Runs a program with a single argument in a childe process, wiring stdout to a
// given file descriptor. Runs the given prelude code inside the child process
// before calling execve.
testing::AssertionResult RunSimpleProgram(std::function<void()> prelude, std::string program_path,
std::string argv1, int stdout_fd = 1) {
::testing::AssertionResult result = ::testing::AssertionSuccess();
pid_t pid = SAFE_SYSCALL(fork());
if (pid == 0) {
prelude();
if (stdout_fd != 1) {
SAFE_SYSCALL(dup2(stdout_fd, 1));
}
char *const argv[] = {const_cast<char *>(program_path.c_str()),
const_cast<char *>(argv1.c_str()), nullptr};
char *const envp[] = {nullptr};
SAFE_SYSCALL(execve(program_path.c_str(), argv, envp));
_exit(EXIT_FAILURE);
}
int wstatus = 0;
SAFE_SYSCALL(waitpid(pid, &wstatus, 0));
if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0) {
result = ::testing::AssertionFailure()
<< "wait_status: WIFEXITED(wstatus) = " << WIFEXITED(wstatus)
<< ", WEXITSTATUS(wstatus) = " << WEXITSTATUS(wstatus)
<< ", WTERMSIG(wstatus) = " << WTERMSIG(wstatus);
}
return result;
}
std::string GetTestResourcePath(const std::string &resource) {
std::filesystem::path test_file = std::filesystem::path("data/tests/deps") / resource;
std::error_code ec;
bool file_exists = std::filesystem::exists(test_file, ec);
EXPECT_FALSE(ec) << "failed to check if file exists: " << ec;
if (!file_exists) {
char self_path[PATH_MAX];
realpath("/proc/self/exe", self_path);
std::filesystem::path directory = std::filesystem::path(self_path).parent_path();
return directory / resource;
}
return test_file;
}
class CapsExecTest : public ::testing::Test {
protected:
void SetUp() {
if (!test_helper::HasSysAdmin()) {
GTEST_SKIP() << "Not running with sysadmin capabilities. skipping.";
}
// The securebits flag should have no bits set. Individual tests can enable
// what they use.
ASSERT_EQ(SAFE_SYSCALL(prctl(PR_GET_SECUREBITS)), 0);
std::error_code ec;
std::filesystem::path root = std::filesystem::temp_directory_path(ec);
ASSERT_FALSE(ec) << "failed to get temp dir: " << ec;
std::string path_template = root / "capexectest.XXXXXX";
std::vector<char> mut_path_template(path_template.size() + 1, '\0');
strncpy(mut_path_template.data(), path_template.c_str(), mut_path_template.size());
char *tmpdir = mkdtemp(mut_path_template.data());
ASSERT_NE(tmpdir, nullptr) << "mkdtemp failed: " << strerror(errno);
path_ = std::string(tmpdir);
// The test should be able to control the mount flags.
SAFE_SYSCALL(mount(nullptr, path_.c_str(), "tmpfs", 0, nullptr));
constexpr int kDirPerms = S_IRWXU | S_IXGRP | S_IXOTH;
SAFE_SYSCALL(chmod(path_.c_str(), kDirPerms));
// Copy out the test binary into the temporary directory.
std::filesystem::path print_helper_binary = GetTestResourcePath(kPrintHelperBinary);
print_helper_ = path_ / kPrintHelperBinary;
std::filesystem::copy_file(print_helper_binary.c_str(), print_helper_.c_str(), ec);
ASSERT_FALSE(ec) << "failed to copy file: " << ec;
SAFE_SYSCALL(chown(print_helper_.c_str(), kUser1Uid, kUser1Gid));
SAFE_SYSCALL(chmod(print_helper_.c_str(), S_IRWXU | S_IXGRP | S_IXOTH));
// The file has no capabilities.
ASSERT_EQ(getxattr(print_helper_.c_str(), "security.capability", nullptr, 0), -1);
ASSERT_EQ(errno, ENODATA);
FILE *fp = fopen("/proc/sys/kernel/cap_last_cap", "r");
ASSERT_NE(fp, nullptr);
int n = fscanf(fp, "%d\n", &cap_last_cap_);
ASSERT_EQ(n, 1);
fclose(fp);
}
void TearDown() {
if (IsSkipped() && !test_helper::HasSysAdmin()) {
// We shouldn't run the cleanup step if we skipped the SetUp step.
return;
}
SAFE_SYSCALL(umount2(path_.c_str(), MNT_DETACH));
std::error_code ec;
std::filesystem::remove_all(path_, ec);
ASSERT_FALSE(ec) << "failed to remove temp dir at " << path_ << ": " << ec;
}
testing::AssertionResult RunPrintSecurebits(std::function<void()> prelude,
unsigned int *securebits) {
fbl::unique_fd stdout_fd(SAFE_SYSCALL(test_helper::MemFdCreate("output", 0)));
testing::AssertionResult result =
RunSimpleProgram(prelude, print_helper_, "securebits", stdout_fd.get());
if (!result) {
return result;
}
SAFE_SYSCALL(lseek(stdout_fd.get(), 0, SEEK_SET));
FILE *fp = fdopen(stdout_fd.release(), "r");
if (fp == nullptr) {
return ::testing::AssertionFailure() << "failed to open output fd\n" << strerror(errno);
}
auto cleanup = fit::defer([fp]() { SAFE_SYSCALL(fclose(fp)); });
if (fscanf(fp, "%x\n", securebits) != 1) {
return ::testing::AssertionFailure() << "failed to read securebits\n";
}
return ::testing::AssertionSuccess();
}
testing::AssertionResult RunPrintCapabilities(std::function<void()> prelude,
std::vector<capability_t> &capabilities) {
capabilities.clear();
::testing::AssertionResult result = ::testing::AssertionSuccess();
fbl::unique_fd stdout_fd(SAFE_SYSCALL(test_helper::MemFdCreate("output", 0)));
result = RunSimpleProgram(prelude, print_helper_, "capabilities", stdout_fd.get());
if (!result) {
return result;
}
SAFE_SYSCALL(lseek(stdout_fd.get(), 0, SEEK_SET));
FILE *fp = fdopen(stdout_fd.release(), "r");
if (fp == nullptr) {
return ::testing::AssertionFailure() << "failed to open output fd\n" << strerror(errno);
}
auto cleanup = fit::defer([fp]() { SAFE_SYSCALL(fclose(fp)); });
char buf[100] = {0};
int n = fscanf(fp, "%99s\n", &buf[0]);
if (n != 1) {
return ::testing::AssertionFailure() << "failed to read result header";
}
if (strcmp(buf, "CAP_NUM,EFFECTIVE,PERMITTED,INHERITABLE,BOUNDING,AMBIENT") != 0) {
return ::testing::AssertionFailure() << "Header doesn't match expected value. Got: " << buf;
}
for (int expected_cap = 0; expected_cap <= cap_last_cap_; expected_cap++) {
capability_t capability{};
int n = fscanf(fp, "%d,%d,%d,%d,%d,%d\n", &capability.cap_num, &capability.effective,
&capability.permitted, &capability.inheritable, &capability.bounding,
&capability.ambient);
if (n != 6) {
return ::testing::AssertionFailure() << "invalid row";
}
if (capability.cap_num != expected_cap) {
return ::testing::AssertionFailure() << "Unexpected capability number";
}
auto is_valid = [](int val) { return (val == 1 || val == 0); };
if (!is_valid(capability.effective) || !is_valid(capability.permitted) ||
!is_valid(capability.inheritable) || !is_valid(capability.bounding) ||
!is_valid(capability.ambient)) {
return ::testing::AssertionFailure() << "Invalid capability value";
}
capabilities.push_back(capability);
}
return result;
}
std::filesystem::path path_;
std::filesystem::path print_helper_;
int cap_last_cap_;
};
} // namespace
TEST_F(CapsExecTest, SecurebitKeepCapsIsNotPreservedAcrossExecve) {
unsigned int securebits;
ASSERT_TRUE(RunPrintSecurebits([]() { SAFE_SYSCALL(prctl(PR_SET_SECUREBITS, SECBIT_KEEP_CAPS)); },
&securebits));
EXPECT_NE(securebits & SECBIT_KEEP_CAPS, static_cast<unsigned int>(SECBIT_KEEP_CAPS));
}
TEST_F(CapsExecTest, SecurebitFlagsArePreservedAcrossExecve) {
unsigned int securebits;
constexpr unsigned int kSecurebitInheritableFlags =
SECBIT_NO_SETUID_FIXUP | SECBIT_NO_SETUID_FIXUP_LOCKED | SECBIT_NOROOT |
SECBIT_NOROOT_LOCKED | SECBIT_NO_CAP_AMBIENT_RAISE | SECBIT_NO_CAP_AMBIENT_RAISE_LOCKED;
ASSERT_TRUE(RunPrintSecurebits(
[]() { SAFE_SYSCALL(prctl(PR_SET_SECUREBITS, kSecurebitInheritableFlags)); }, &securebits));
EXPECT_EQ(securebits & kSecurebitInheritableFlags, kSecurebitInheritableFlags);
}
TEST_F(CapsExecTest, NonRootExecutingRegularBinaryClearsPermittedAndEffectiveCaps) {
// If a non-root user executes a regular binary, without file capabilities,
// it should clear its permitted and effective capabilities.
std::vector<capability_t> capabilities;
ASSERT_TRUE(RunPrintCapabilities(
[]() {
// We want to keep capabilities after switching uids.
SAFE_SYSCALL(prctl(PR_SET_SECUREBITS, SECBIT_KEEP_CAPS));
// After switching to a non-root uid, with the secbits
// all our effective capabilities are cleaned, but the permitted are
// still there.
SAFE_SYSCALL(setresuid(kUser1Uid, kUser1Uid, kUser1Gid));
// CAP_AUDIT_READ will be effective and permitted.
test_helper::SetCapabilityEffective(CAP_AUDIT_READ);
// CAP_AUDIT_WRITE will be effective, permitted, and inheritable.
test_helper::SetCapabilityEffective(CAP_AUDIT_WRITE);
test_helper::SetCapabilityInheritable(CAP_AUDIT_WRITE);
},
capabilities));
for (const auto &capability : capabilities) {
EXPECT_EQ(capability.effective, 0);
EXPECT_EQ(capability.permitted, 0);
}
}
TEST_F(CapsExecTest, RootExecutingRegularBinaryGetsAllCapabilitiesBack) {
// A program with euid root will get all capabilities back when it executes a
// regular binary.
std::vector<capability_t> capabilities;
ASSERT_TRUE(RunPrintCapabilities(
[&]() {
// This test doesn't care about CAP_SYS_ADMIN, it only cares about
// having the root uid.
SAFE_SYSCALL(setresuid(kRootUid, kRootUid, kRootUid));
// We expect to have all bounding capabilities set.
for (int cap = 0; cap <= cap_last_cap_; cap++) {
ASSERT_TRUE(test_helper::HasCapabilityBounding(cap));
}
// Disable one: this should not be enabled after execve.
test_helper::UnsetCapabilityBounding(CAP_AUDIT_READ);
test_helper::DropAllCapabilities();
test_helper::DropAllAmbientCapabilities();
},
capabilities));
for (int cap = 0; cap <= cap_last_cap_; cap++) {
if (cap == CAP_AUDIT_READ) {
continue;
}
EXPECT_EQ(capabilities[cap].effective, 1);
EXPECT_EQ(capabilities[cap].permitted, 1);
EXPECT_EQ(capabilities[cap].inheritable, 0);
EXPECT_EQ(capabilities[cap].bounding, 1);
EXPECT_EQ(capabilities[cap].ambient, 0);
}
// The one we removed from the bounding set was not added.
EXPECT_EQ(capabilities[CAP_AUDIT_READ].effective, 0);
EXPECT_EQ(capabilities[CAP_AUDIT_READ].permitted, 0);
EXPECT_EQ(capabilities[CAP_AUDIT_READ].inheritable, 0);
EXPECT_EQ(capabilities[CAP_AUDIT_READ].bounding, 0);
EXPECT_EQ(capabilities[CAP_AUDIT_READ].ambient, 0);
}
TEST_F(CapsExecTest, ChangingFromRootUidDropsAllCapabilities) {
test_helper::ForkHelper helper;
ASSERT_EQ(geteuid(), kRootUid);
helper.RunInForkedProcess([&]() {
// Changing from a root uid to a non-root uid drops all capabilities.
SAFE_SYSCALL(setresuid(kUser1Uid, kUser1Uid, kUser1Uid));
for (int cap = 0; cap <= cap_last_cap_; cap++) {
EXPECT_TRUE(test_helper::HasCapabilityBounding(cap));
EXPECT_FALSE(test_helper::HasCapabilityAmbient(cap));
EXPECT_FALSE(test_helper::HasCapabilityEffective(cap));
EXPECT_FALSE(test_helper::HasCapabilityPermitted(cap));
EXPECT_FALSE(test_helper::HasCapabilityInheritable(cap));
}
});
}
TEST_F(CapsExecTest, ChangingFromRootUidWithKeepCapsDropsAllEffectiveCapabilities) {
test_helper::ForkHelper helper;
ASSERT_EQ(geteuid(), kRootUid);
helper.RunInForkedProcess([&]() {
SAFE_SYSCALL(prctl(PR_SET_SECUREBITS, SECBIT_KEEP_CAPS));
// With SECBIT_KEEP_CAPS, changing from a root euid to a non-root euid drops all effective
// capabilities.
SAFE_SYSCALL(setresuid(kUser1Uid, kUser1Uid, kUser1Uid));
for (int cap = 0; cap <= cap_last_cap_; cap++) {
EXPECT_TRUE(test_helper::HasCapabilityBounding(cap));
EXPECT_FALSE(test_helper::HasCapabilityAmbient(cap));
EXPECT_FALSE(test_helper::HasCapabilityEffective(cap));
EXPECT_TRUE(test_helper::HasCapabilityPermitted(cap));
EXPECT_FALSE(test_helper::HasCapabilityInheritable(cap));
}
});
}
TEST_F(CapsExecTest, ChangingFromRootUidWithNoFixupSetuidKeepsCapabilities) {
test_helper::ForkHelper helper;
ASSERT_EQ(geteuid(), kRootUid);
helper.RunInForkedProcess([&]() {
SAFE_SYSCALL(prctl(PR_SET_SECUREBITS, SECBIT_NO_SETUID_FIXUP));
// With SECBIT_NO_SETUID_FIXUP, capabilities don't change when changing uids.
SAFE_SYSCALL(setresuid(kUser1Uid, kUser1Uid, kUser1Uid));
for (int cap = 0; cap <= cap_last_cap_; cap++) {
EXPECT_TRUE(test_helper::HasCapabilityBounding(cap));
EXPECT_FALSE(test_helper::HasCapabilityAmbient(cap));
EXPECT_TRUE(test_helper::HasCapabilityEffective(cap));
EXPECT_TRUE(test_helper::HasCapabilityPermitted(cap));
EXPECT_FALSE(test_helper::HasCapabilityInheritable(cap));
}
});
}
TEST_F(CapsExecTest, RegularUserExecutingSUIDRootGetsAllCapabilities) {
/*
When a process with nonzero UIDs execves a set-user-ID root program
that does not have capabilities attached, the calculation of the permitted
and effective capabilities is as follows:
P'(permitted) = P(inheritable) | P(bounding)
P'(effective) = P'(permitted)
*/
std::vector<capability_t> capabilities;
SAFE_SYSCALL(chown(print_helper_.c_str(), kRootUid, kRootGid));
SAFE_SYSCALL(chmod(print_helper_.c_str(), S_ISUID | S_IXOTH | S_IRWXU));
ASSERT_TRUE(RunPrintCapabilities(
[&]() {
SAFE_SYSCALL(setresgid(kUser1Gid, kUser1Gid, kUser1Gid));
SAFE_SYSCALL(setgroups(0, nullptr));
// Will not drop permitted capabilities.
SAFE_SYSCALL(prctl(PR_SET_SECUREBITS, SECBIT_KEEP_CAPS));
SAFE_SYSCALL(setresuid(kUser1Uid, kUser1Uid, kUser1Uid));
test_helper::SetCapabilityEffective(CAP_SETPCAP);
// Leave only CAP_SETPCAP in effective capabilities.
for (int cap = 0; cap < cap_last_cap_; cap++) {
if (cap == CAP_SETPCAP) {
continue;
}
test_helper::UnsetCapabilityEffective(cap);
test_helper::UnsetCapabilityPermitted(cap);
test_helper::UnsetCapabilityInheritable(cap);
test_helper::UnsetCapabilityAmbient(cap);
}
// CAP_SYSLOG will be in the bounding set but not in inheritable.
ASSERT_TRUE(test_helper::HasCapabilityBounding(CAP_SYSLOG));
ASSERT_FALSE(test_helper::HasCapabilityInheritable(CAP_SYSLOG));
// CAP_AUDIT_READ will not be in bounding nor in inheritable.
test_helper::UnsetCapabilityBounding(CAP_AUDIT_READ);
// CAP_AUDIT_CONTROL will be both inheritable and bounding.
test_helper::SetCapabilityInheritable(CAP_AUDIT_CONTROL);
// CAP_AUDIT_WRITE will be inheritable but not in bounding.
test_helper::SetCapabilityInheritable(CAP_AUDIT_WRITE);
test_helper::UnsetCapabilityBounding(CAP_AUDIT_WRITE);
test_helper::UnsetCapabilityEffective(CAP_SETPCAP);
test_helper::UnsetCapabilityPermitted(CAP_SETPCAP);
test_helper::UnsetCapabilityInheritable(CAP_SETPCAP);
test_helper::UnsetCapabilityAmbient(CAP_SETPCAP);
},
capabilities));
EXPECT_EQ(capabilities[CAP_SYSLOG].effective, 1);
EXPECT_EQ(capabilities[CAP_SYSLOG].permitted, 1);
EXPECT_EQ(capabilities[CAP_SYSLOG].inheritable, 0);
EXPECT_EQ(capabilities[CAP_SYSLOG].bounding, 1);
EXPECT_EQ(capabilities[CAP_SYSLOG].ambient, 0);
EXPECT_EQ(capabilities[CAP_AUDIT_READ].effective, 0);
EXPECT_EQ(capabilities[CAP_AUDIT_READ].permitted, 0);
EXPECT_EQ(capabilities[CAP_AUDIT_READ].inheritable, 0);
EXPECT_EQ(capabilities[CAP_AUDIT_READ].bounding, 0);
EXPECT_EQ(capabilities[CAP_AUDIT_READ].ambient, 0);
EXPECT_EQ(capabilities[CAP_AUDIT_WRITE].effective, 1);
EXPECT_EQ(capabilities[CAP_AUDIT_WRITE].permitted, 1);
EXPECT_EQ(capabilities[CAP_AUDIT_WRITE].inheritable, 1);
EXPECT_EQ(capabilities[CAP_AUDIT_WRITE].bounding, 0);
EXPECT_EQ(capabilities[CAP_AUDIT_WRITE].ambient, 0);
EXPECT_EQ(capabilities[CAP_AUDIT_CONTROL].effective, 1);
EXPECT_EQ(capabilities[CAP_AUDIT_CONTROL].permitted, 1);
EXPECT_EQ(capabilities[CAP_AUDIT_CONTROL].inheritable, 1);
EXPECT_EQ(capabilities[CAP_AUDIT_CONTROL].bounding, 1);
EXPECT_EQ(capabilities[CAP_AUDIT_CONTROL].ambient, 0);
}
void SetUpCapabilities(capability_t cap) {
test_helper::SetCapabilityEffective(CAP_SETPCAP);
int cap_num = cap.cap_num;
test_helper::SetCapabilityEffective(cap_num);
test_helper::SetCapabilityPermitted(cap_num);
test_helper::SetCapabilityInheritable(cap_num);
test_helper::SetCapabilityAmbient(cap_num);
if (cap.effective == 0) {
test_helper::UnsetCapabilityEffective(cap_num);
}
if (cap.permitted == 0) {
test_helper::UnsetCapabilityPermitted(cap_num);
}
if (cap.inheritable == 0) {
test_helper::UnsetCapabilityInheritable(cap_num);
}
if (cap.ambient == 0) {
test_helper::UnsetCapabilityAmbient(cap_num);
}
if (cap.bounding == 0) {
test_helper::UnsetCapabilityBounding(cap_num);
}
EXPECT_EQ(test_helper::HasCapabilityEffective(cap_num), cap.effective);
EXPECT_EQ(test_helper::HasCapabilityPermitted(cap_num), cap.permitted);
EXPECT_EQ(test_helper::HasCapabilityInheritable(cap_num), cap.inheritable);
EXPECT_EQ(test_helper::HasCapabilityAmbient(cap_num), cap.ambient);
EXPECT_EQ(test_helper::HasCapabilityBounding(cap_num), cap.bounding);
}
TEST(CapsTest, PermittedCapabilitiesCannotBeRegained) {
if (!test_helper::HasSysAdmin()) {
GTEST_SKIP() << "Not running with sysadmin capabilities. skipping.";
}
std::vector<capability_t> caps;
// Capabilities combinations that don't have the permitted bit.
caps.push_back({CAP_AUDIT_READ, 0, 0, 0, 0, 0});
caps.push_back({CAP_AUDIT_READ, 0, 0, 0, 1, 0});
caps.push_back({CAP_AUDIT_READ, 0, 0, 1, 0, 0});
caps.push_back({CAP_AUDIT_READ, 0, 0, 1, 1, 0});
test_helper::ForkHelper helper;
for (const auto &cap : caps) {
helper.RunInForkedProcess([cap]() {
SetUpCapabilities(cap);
EXPECT_NONFATAL_FAILURE(test_helper::SetCapabilityPermitted(cap.cap_num),
"Operation not permitted");
});
EXPECT_TRUE(helper.WaitForChildren());
}
}
TEST(CapsTest, PermittedCapabilitiesCannotBeDroppedIfTheyAreEffective) {
if (!test_helper::HasSysAdmin()) {
GTEST_SKIP() << "Not running with sysadmin capabilities. skipping.";
}
std::vector<capability_t> caps;
// Capabilities combinations that have effective and permitted bits.
caps.push_back({CAP_AUDIT_READ, 1, 1, 0, 0, 0});
caps.push_back({CAP_AUDIT_READ, 1, 1, 1, 0, 0});
caps.push_back({CAP_AUDIT_READ, 1, 1, 0, 1, 0});
caps.push_back({CAP_AUDIT_READ, 1, 1, 1, 1, 0});
caps.push_back({CAP_AUDIT_READ, 1, 1, 1, 1, 1});
test_helper::ForkHelper helper;
for (const auto &cap : caps) {
helper.RunInForkedProcess([cap]() {
SetUpCapabilities(cap);
EXPECT_NONFATAL_FAILURE(test_helper::UnsetCapabilityPermitted(cap.cap_num),
"Operation not permitted");
});
EXPECT_TRUE(helper.WaitForChildren());
}
}
TEST(CapsTest, CannotSetEffectiveCapsIfNotPermitted) {
// Tests that you cannot set a capability as effective if it's not permitted.
if (!test_helper::HasSysAdmin()) {
GTEST_SKIP() << "Not running with sysadmin capabilities. skipping.";
}
std::vector<capability_t> test_cases;
test_cases.push_back({CAP_AUDIT_READ, 0, 0, 0, 0, 0});
test_cases.push_back({CAP_AUDIT_READ, 0, 0, 0, 1, 0});
test_cases.push_back({CAP_AUDIT_READ, 0, 0, 1, 0, 0});
test_cases.push_back({CAP_AUDIT_READ, 0, 0, 1, 1, 0});
test_helper::ForkHelper helper;
for (const auto &test_case : test_cases) {
helper.RunInForkedProcess([test_case]() {
SetUpCapabilities(test_case);
EXPECT_NONFATAL_FAILURE(test_helper::SetCapabilityEffective(CAP_AUDIT_READ), "SYS_capset");
EXPECT_FALSE(test_helper::HasCapabilityEffective(CAP_AUDIT_READ));
});
EXPECT_TRUE(helper.WaitForChildren());
}
}
TEST(CapsTest, AmbientCapabilitiesRequirePermittedAndInheritable) {
// Tests that ambient capabilities will be unset if inheritable or permitted
// are unset.
if (!test_helper::HasSysAdmin()) {
GTEST_SKIP() << "Not running with sysadmin capabilities. skipping.";
}
struct test_case_t {
capability_t caps;
const char *failure;
};
std::vector<test_case_t> test_cases;
test_cases.push_back({{CAP_AUDIT_READ, 0, 0, 0, 0, 1}, "HasCapabilityAmbient"});
test_cases.push_back({{CAP_AUDIT_READ, 0, 0, 0, 1, 1}, "HasCapabilityAmbient"});
test_cases.push_back({{CAP_AUDIT_READ, 0, 0, 1, 0, 1}, "HasCapabilityAmbient"});
test_cases.push_back({{CAP_AUDIT_READ, 0, 0, 1, 1, 1}, "HasCapabilityAmbient"});
test_cases.push_back({{CAP_AUDIT_READ, 0, 1, 0, 0, 1}, "HasCapabilityAmbient"});
test_cases.push_back({{CAP_AUDIT_READ, 0, 1, 0, 1, 1}, "HasCapabilityAmbient"});
test_cases.push_back({{CAP_AUDIT_READ, 1, 1, 0, 0, 1}, "HasCapabilityAmbient"});
test_cases.push_back({{CAP_AUDIT_READ, 1, 1, 0, 1, 1}, "HasCapabilityAmbient"});
test_helper::ForkHelper helper;
for (const auto &test_case : test_cases) {
SCOPED_TRACE(fxl::StringPrintf("(Eff Per Inh Bnd Amb) (%d %d %d %d %d)",
test_case.caps.effective, test_case.caps.permitted,
test_case.caps.inheritable, test_case.caps.bounding,
test_case.caps.ambient));
helper.RunInForkedProcess([test_case]() {
EXPECT_NONFATAL_FAILURE(SetUpCapabilities(test_case.caps), test_case.failure);
});
EXPECT_TRUE(helper.WaitForChildren());
}
}
TEST_F(CapsExecTest, NonRootUserExecutingNonSUIDProgram) {
std::vector<capability_t> starting_caps;
std::vector<capability_t> expected_caps;
// Effective Permitted Inheritable Bounding Ambient
// Ef Pe In Bn Am
starting_caps.push_back({CAP_AUDIT_READ, 0, 0, 0, 0, 0});
expected_caps.push_back({CAP_AUDIT_READ, 0, 0, 0, 0, 0});
// NOT POSSIBLE 0, 0, 0, 0, 1
starting_caps.push_back({CAP_AUDIT_READ, 0, 0, 0, 1, 0});
expected_caps.push_back({CAP_AUDIT_READ, 0, 0, 0, 1, 0});
// NOT POSSIBLE 0, 0, 0, 1, 1
starting_caps.push_back({CAP_AUDIT_READ, 0, 0, 1, 0, 0});
expected_caps.push_back({CAP_AUDIT_READ, 0, 0, 1, 0, 0});
// NOT POSSIBLE 0, 0, 1, 0, 1
starting_caps.push_back({CAP_AUDIT_READ, 0, 0, 1, 1, 0});
expected_caps.push_back({CAP_AUDIT_READ, 0, 0, 1, 1, 0});
// NOT POSSIBLE 0, 0, 1, 1, 1
starting_caps.push_back({CAP_AUDIT_READ, 0, 1, 0, 0, 0});
expected_caps.push_back({CAP_AUDIT_READ, 0, 0, 0, 0, 0});
// NOT POSSIBLE 0, 1, 0, 0, 1
starting_caps.push_back({CAP_AUDIT_READ, 0, 1, 0, 1, 0});
expected_caps.push_back({CAP_AUDIT_READ, 0, 0, 0, 1, 0});
// NOT POSSIBLE 0, 1, 0, 1, 1
starting_caps.push_back({CAP_AUDIT_READ, 0, 1, 1, 0, 0});
expected_caps.push_back({CAP_AUDIT_READ, 0, 0, 1, 0, 0});
starting_caps.push_back({CAP_AUDIT_READ, 0, 1, 1, 0, 1});
expected_caps.push_back({CAP_AUDIT_READ, 1, 1, 1, 0, 1});
starting_caps.push_back({CAP_AUDIT_READ, 0, 1, 1, 1, 0});
expected_caps.push_back({CAP_AUDIT_READ, 0, 0, 1, 1, 0});
starting_caps.push_back({CAP_AUDIT_READ, 0, 1, 1, 1, 1});
expected_caps.push_back({CAP_AUDIT_READ, 1, 1, 1, 1, 1});
// NOT POSSIBLE 1, 0, 0, 0, 0
// NOT POSSIBLE 1, 0, 0, 0, 1
// NOT POSSIBLE 1, 0, 0, 1, 0
// NOT POSSIBLE 1, 0, 0, 1, 1
// NOT POSSIBLE 1, 0, 1, 0, 0
// NOT POSSIBLE 1, 0, 1, 0, 1
// NOT POSSIBLE 1, 0, 1, 1, 0
// NOT POSSIBLE 1, 0, 1, 1, 1
starting_caps.push_back({CAP_AUDIT_READ, 1, 1, 0, 0, 0});
expected_caps.push_back({CAP_AUDIT_READ, 0, 0, 0, 0, 0});
// NOT POSSIBLE 1, 1, 0, 0, 1
starting_caps.push_back({CAP_AUDIT_READ, 1, 1, 0, 1, 0});
expected_caps.push_back({CAP_AUDIT_READ, 0, 0, 0, 1, 0});
// NOT POSSIBLE 1, 1, 0, 1, 1
starting_caps.push_back({CAP_AUDIT_READ, 1, 1, 1, 0, 0});
expected_caps.push_back({CAP_AUDIT_READ, 0, 0, 1, 0, 0});
starting_caps.push_back({CAP_AUDIT_READ, 1, 1, 1, 0, 1});
expected_caps.push_back({CAP_AUDIT_READ, 1, 1, 1, 0, 1});
starting_caps.push_back({CAP_AUDIT_READ, 1, 1, 1, 1, 0});
expected_caps.push_back({CAP_AUDIT_READ, 0, 0, 1, 1, 0});
starting_caps.push_back({CAP_AUDIT_READ, 1, 1, 1, 1, 1});
expected_caps.push_back({CAP_AUDIT_READ, 1, 1, 1, 1, 1});
for (size_t test_case = 0; test_case < starting_caps.size(); test_case++) {
SCOPED_TRACE(
fxl::StringPrintf("(Eff Per Inh Bnd Amb) (%d %d %d %d %d) -> (%d %d %d %d %d)",
starting_caps[test_case].effective, starting_caps[test_case].permitted,
starting_caps[test_case].inheritable, starting_caps[test_case].bounding,
starting_caps[test_case].ambient, expected_caps[test_case].effective,
expected_caps[test_case].permitted, expected_caps[test_case].inheritable,
expected_caps[test_case].bounding, expected_caps[test_case].ambient));
std::vector<capability_t> capabilities;
ASSERT_TRUE(RunPrintCapabilities(
[&]() {
SAFE_SYSCALL(setresgid(kUser1Gid, kUser1Gid, kUser1Gid));
SAFE_SYSCALL(setgroups(0, nullptr));
// Will not drop permitted capabilities.
SAFE_SYSCALL(prctl(PR_SET_SECUREBITS, SECBIT_KEEP_CAPS));
SAFE_SYSCALL(setresuid(kUser1Uid, kUser1Uid, kUser1Uid));
SetUpCapabilities(starting_caps[test_case]);
// Drop all other capabilities.
for (int cap = 0; cap <= cap_last_cap_; cap++) {
if (cap == starting_caps[test_case].cap_num)
continue;
test_helper::UnsetCapabilityEffective(cap);
test_helper::UnsetCapabilityPermitted(cap);
test_helper::UnsetCapabilityInheritable(cap);
test_helper::UnsetCapabilityAmbient(cap);
}
},
capabilities));
int cap_num = starting_caps[test_case].cap_num;
EXPECT_EQ(capabilities[cap_num].effective, expected_caps[test_case].effective);
EXPECT_EQ(capabilities[cap_num].permitted, expected_caps[test_case].permitted);
EXPECT_EQ(capabilities[cap_num].inheritable, expected_caps[test_case].inheritable);
EXPECT_EQ(capabilities[cap_num].bounding, expected_caps[test_case].bounding);
EXPECT_EQ(capabilities[cap_num].ambient, expected_caps[test_case].ambient);
}
}