Migrate namespaces related tests out of policybuilder_test

PiperOrigin-RevId: 514325688
Change-Id: I9c581d14da3ac9fe5c3c0b43e156d8ad8d90c73f
This commit is contained in:
Wiktor Garbacz 2023-03-06 00:27:06 -08:00 committed by Copybara-Service
parent 64b52ff3b5
commit 526401166e
8 changed files with 207 additions and 256 deletions

View File

@ -663,9 +663,9 @@ cc_test(
name = "namespace_test",
srcs = ["namespace_test.cc"],
copts = sapi_platform_copts(),
data = [
"//sandboxed_api/sandbox2/testcases:hostname",
"//sandboxed_api/sandbox2/testcases:namespace",
data = ["//sandboxed_api/sandbox2/testcases:namespace"],
tags = [
"requires-net:external",
],
deps = [
":namespace",
@ -1009,17 +1009,8 @@ cc_test(
name = "policybuilder_test",
srcs = ["policybuilder_test.cc"],
copts = sapi_platform_copts(),
tags = [
"no_qemu_user_mode",
"requires-net:external",
],
deps = [
":comms",
":policybuilder",
":sandbox2",
":testonly_allow_all_syscalls",
"//sandboxed_api:config",
"//sandboxed_api:testing",
"//sandboxed_api/sandbox2/util:bpf_helper",
"//sandboxed_api/util:status_matchers",
"@com_google_absl//absl/log",

View File

@ -800,7 +800,6 @@ if(BUILD_TESTING AND SAPI_BUILD_TESTING)
OUTPUT_NAME namespace_test
)
add_dependencies(sandbox2_namespace_test
sandbox2::testcase_hostname
sandbox2::testcase_namespace
)
target_link_libraries(sandbox2_namespace_test PRIVATE
@ -1109,11 +1108,11 @@ if(BUILD_TESTING AND SAPI_BUILD_TESTING)
)
target_link_libraries(sandbox2_policybuilder_test
PRIVATE absl::strings
sandbox2::allow_all_syscalls
absl::log
absl::status
absl::statusor
sandbox2::bpf_helper
sandbox2::comms
sandbox2::policybuilder
sandbox2::sandbox2
sapi::testing
sapi::status_matchers
sapi::test_main

View File

@ -21,7 +21,9 @@
#include <initializer_list>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
@ -47,28 +49,48 @@ using ::sapi::CreateDefaultPermissiveTestPolicy;
using ::sapi::CreateNamedTempFile;
using ::sapi::GetTestSourcePath;
using ::sapi::GetTestTempPath;
using ::testing::Contains;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::Gt;
using ::testing::HasSubstr;
using ::testing::IsEmpty;
using ::testing::Ne;
using ::testing::SizeIs;
using ::testing::StartsWith;
using ::testing::StrEq; // sapi::google3-only(broken matchers)
std::string GetTestcaseBinPath(absl::string_view bin_name) {
return GetTestSourcePath(absl::StrCat("sandbox2/testcases/", bin_name));
}
int RunSandboxeeWithArgsAndPolicy(const std::string& bin_path,
std::initializer_list<std::string> args,
std::unique_ptr<Policy> policy = nullptr) {
std::vector<std::string> RunSandboxeeWithArgsAndPolicy(
const std::string& bin_path, std::initializer_list<std::string> args,
std::unique_ptr<Policy> policy = nullptr) {
if (!policy) {
policy = CreateDefaultPermissiveTestPolicy(bin_path).BuildOrDie();
}
Sandbox2 sandbox(std::make_unique<Executor>(bin_path, args),
std::move(policy));
Result result = sandbox.Run();
EXPECT_THAT(result.final_status(), Eq(Result::OK));
return result.reason_code();
}
CHECK(sandbox.RunAsync());
Comms* comms = sandbox.comms();
uint64_t num;
constexpr absl::string_view kHostnameTestBinary = "sandbox2/testcases/hostname";
std::vector<std::string> entries;
if (comms->RecvUint64(&num)) {
entries.reserve(num);
for (int i = 0; i < num; ++i) {
std::string entry;
CHECK(comms->RecvString(&entry));
entries.push_back(std::move(entry));
}
}
Result result = sandbox.AwaitResult();
EXPECT_THAT(result.final_status(), Eq(Result::OK));
EXPECT_THAT(result.reason_code(), Eq(0));
return entries;
}
TEST(NamespaceTest, FileNamespaceWorks) {
// Mount /binary_path RO and check that it exists and is readable.
@ -78,9 +100,9 @@ TEST(NamespaceTest, FileNamespaceWorks) {
SAPI_ASSERT_OK_AND_ASSIGN(auto policy, CreateDefaultPermissiveTestPolicy(path)
.AddFileAt(path, "/binary_path")
.TryBuild());
int reason_code = RunSandboxeeWithArgsAndPolicy(
std::vector<std::string> result = RunSandboxeeWithArgsAndPolicy(
path, {path, "0", "/binary_path", "/etc/passwd"}, std::move(policy));
EXPECT_THAT(reason_code, Eq(2));
EXPECT_THAT(result, ElementsAre("/binary_path"));
}
TEST(NamespaceTest, ReadOnlyIsRespected) {
@ -95,9 +117,9 @@ TEST(NamespaceTest, ReadOnlyIsRespected) {
.AddFileAt(name, "/temp_file")
.TryBuild());
// Check that it is readable
int reason_code = RunSandboxeeWithArgsAndPolicy(
std::vector<std::string> result = RunSandboxeeWithArgsAndPolicy(
path, {path, "0", "/temp_file"}, std::move(policy));
EXPECT_THAT(reason_code, Eq(0));
EXPECT_THAT(result, ElementsAre("/temp_file"));
}
{
SAPI_ASSERT_OK_AND_ASSIGN(auto policy,
@ -105,9 +127,9 @@ TEST(NamespaceTest, ReadOnlyIsRespected) {
.AddFileAt(name, "/temp_file")
.TryBuild());
// Now check that it is not writeable
int reason_code = RunSandboxeeWithArgsAndPolicy(
std::vector<std::string> result = RunSandboxeeWithArgsAndPolicy(
path, {path, "1", "/temp_file"}, std::move(policy));
EXPECT_THAT(reason_code, Eq(1));
EXPECT_THAT(result, IsEmpty());
}
}
@ -116,19 +138,20 @@ TEST(NamespaceTest, UserNamespaceWorks) {
// Check that getpid() returns 2 (which is the case inside pid NS).
{
int reason_code = RunSandboxeeWithArgsAndPolicy(path, {path, "2"});
EXPECT_THAT(reason_code, Eq(0));
std::vector<std::string> result =
RunSandboxeeWithArgsAndPolicy(path, {path, "2"});
EXPECT_THAT(result, ElementsAre("2"));
}
// Validate that getpid() does not return 2 when outside of a pid NS.
{
int reason_code = RunSandboxeeWithArgsAndPolicy(
std::vector<std::string> result = RunSandboxeeWithArgsAndPolicy(
path, {path, "2"},
PolicyBuilder()
.DisableNamespaces()
.DefaultAction(AllowAllSyscalls()) // Do not restrict syscalls
.BuildOrDie());
EXPECT_THAT(reason_code, Ne(0));
EXPECT_THAT(result, ElementsAre(Ne("2")));
}
}
@ -137,20 +160,21 @@ TEST(NamespaceTest, UserNamespaceIDMapWritten) {
// started.
const std::string path = GetTestcaseBinPath("namespace");
{
int reason_code =
std::vector<std::string> result =
RunSandboxeeWithArgsAndPolicy(path, {path, "3", "1000", "1000"});
EXPECT_THAT(reason_code, Eq(0));
EXPECT_THAT(result, ElementsAre("1000", "1000"));
}
// Check that the uid/gid is the same when not using namespaces.
{
int reason_code = RunSandboxeeWithArgsAndPolicy(
path, {path, "3", absl::StrCat(getuid()), absl::StrCat(getgid())},
std::vector<std::string> result = RunSandboxeeWithArgsAndPolicy(
path, {path, "3"},
PolicyBuilder()
.DisableNamespaces()
.DefaultAction(AllowAllSyscalls()) // Do not restrict syscalls
.BuildOrDie());
EXPECT_THAT(reason_code, Eq(0));
EXPECT_THAT(result,
ElementsAre(absl::StrCat(getuid()), absl::StrCat(getgid())));
}
}
@ -162,9 +186,9 @@ TEST(NamespaceTest, RootReadOnly) {
auto policy, CreateDefaultPermissiveTestPolicy(path)
.AddTmpfs("/tmp", /*size=*/4ULL << 20 /* 4 MiB */)
.TryBuild());
int reason_code = RunSandboxeeWithArgsAndPolicy(
std::vector<std::string> result = RunSandboxeeWithArgsAndPolicy(
path, {path, "4", "/tmp/testfile", "/testfile"}, std::move(policy));
EXPECT_THAT(reason_code, Eq(2));
EXPECT_THAT(result, ElementsAre("/tmp/testfile"));
}
TEST(NamespaceTest, RootWritable) {
@ -173,36 +197,58 @@ TEST(NamespaceTest, RootWritable) {
SAPI_ASSERT_OK_AND_ASSIGN(
auto policy,
CreateDefaultPermissiveTestPolicy(path).SetRootWritable().TryBuild());
int reason_code = RunSandboxeeWithArgsAndPolicy(
std::vector<std::string> result = RunSandboxeeWithArgsAndPolicy(
path, {path, "4", "/testfile"}, std::move(policy));
EXPECT_THAT(reason_code, Eq(0));
EXPECT_THAT(result, ElementsAre("/testfile"));
}
TEST(HostnameTest, None) {
const std::string path = GetTestcaseBinPath("hostname");
int reason_code = RunSandboxeeWithArgsAndPolicy(
path, {path, "sandbox2"},
TEST(NamespaceTest, HostnameNone) {
const std::string path = GetTestcaseBinPath("namespace");
std::vector<std::string> result = RunSandboxeeWithArgsAndPolicy(
path, {path, "7"},
PolicyBuilder()
.DisableNamespaces()
.DefaultAction(AllowAllSyscalls()) // Do not restrict syscalls
.BuildOrDie());
EXPECT_THAT(reason_code, Eq(1));
EXPECT_THAT(result, ElementsAre(Ne("sandbox2")));
}
TEST(HostnameTest, Default) {
const std::string path = GetTestcaseBinPath("hostname");
int reason_code = RunSandboxeeWithArgsAndPolicy(path, {path, "sandbox2"});
EXPECT_THAT(reason_code, Eq(0));
TEST(NamespaceTest, HostnameDefault) {
const std::string path = GetTestcaseBinPath("namespace");
std::vector<std::string> result =
RunSandboxeeWithArgsAndPolicy(path, {path, "7"});
EXPECT_THAT(result, ElementsAre("sandbox2"));
}
TEST(HostnameTest, Configured) {
const std::string path = GetTestcaseBinPath("hostname");
TEST(NamespaceTest, HostnameConfigured) {
const std::string path = GetTestcaseBinPath("namespace");
SAPI_ASSERT_OK_AND_ASSIGN(auto policy, CreateDefaultPermissiveTestPolicy(path)
.SetHostname("configured")
.TryBuild());
int reason_code = RunSandboxeeWithArgsAndPolicy(path, {path, "configured"},
std::move(policy));
EXPECT_THAT(reason_code, Eq(0));
std::vector<std::string> result =
RunSandboxeeWithArgsAndPolicy(path, {path, "7"}, std::move(policy));
EXPECT_THAT(result, ElementsAre("configured"));
}
TEST(NamespaceTest, TestInterfacesNoNetwork) {
const std::string path = GetTestcaseBinPath("namespace");
std::vector<std::string> result =
RunSandboxeeWithArgsAndPolicy(path, {path, "5"});
// Only loopback network interface 'lo'.
EXPECT_THAT(result, ElementsAre("lo"));
}
TEST(NamespaceTest, TestInterfacesWithNetwork) {
const std::string path = GetTestcaseBinPath("namespace");
SAPI_ASSERT_OK_AND_ASSIGN(auto policy, CreateDefaultPermissiveTestPolicy(path)
.AllowUnrestrictedNetworking()
.TryBuild());
std::vector<std::string> result =
RunSandboxeeWithArgsAndPolicy(path, {path, "5"}, std::move(policy));
// Loopback network interface 'lo' and more.
EXPECT_THAT(result, Contains("lo"));
EXPECT_THAT(result, SizeIs(Gt(1)));
}
} // namespace

View File

@ -26,18 +26,7 @@
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_split.h"
#include "sandboxed_api/config.h"
#include "sandboxed_api/sandbox2/allow_all_syscalls.h"
#include "sandboxed_api/sandbox2/comms.h"
#include "sandboxed_api/sandbox2/executor.h"
#include "sandboxed_api/sandbox2/ipc.h"
#include "sandboxed_api/sandbox2/result.h"
#include "sandboxed_api/sandbox2/sandbox2.h"
#include "sandboxed_api/sandbox2/util/bpf_helper.h"
#include "sandboxed_api/testing.h"
#include "sandboxed_api/util/status_matchers.h"
namespace sandbox2 {
@ -59,24 +48,14 @@ class PolicyBuilderPeer {
namespace {
using ::testing::AllOf;
using ::testing::AnyOf;
using ::testing::Eq;
using ::testing::Gt;
using ::testing::HasSubstr;
using ::testing::Lt;
using ::testing::StartsWith;
using ::testing::StrEq;
using ::sapi::IsOk;
using ::sapi::StatusIs;
class PolicyBuilderTest : public testing::Test {
protected:
static std::string Run(const std::vector<std::string>& args,
bool network = false);
};
TEST_F(PolicyBuilderTest, Testpolicy_size) {
TEST(PolicyBuilderTest, Testpolicy_size) {
ssize_t last_size = 0;
PolicyBuilder builder;
PolicyBuilderPeer builder_peer{&builder};
@ -131,7 +110,7 @@ TEST_F(PolicyBuilderTest, Testpolicy_size) {
// clang-format on
}
TEST_F(PolicyBuilderTest, TestValidateAbsolutePath) {
TEST(PolicyBuilderTest, TestValidateAbsolutePath) {
for (auto const& bad_path : {
"..",
"a",
@ -155,52 +134,14 @@ TEST_F(PolicyBuilderTest, TestValidateAbsolutePath) {
}
}
std::string PolicyBuilderTest::Run(const std::vector<std::string>& args,
bool network) {
PolicyBuilder builder;
// Don't restrict the syscalls at all.
builder.DefaultAction(AllowAllSyscalls());
if constexpr (sapi::host_os::IsAndroid()) {
builder.DisableNamespaces();
} else {
builder.AddLibrariesForBinary(args[0]);
}
if (network) {
builder.AllowUnrestrictedNetworking();
}
auto executor = std::make_unique<sandbox2::Executor>(args[0], args);
int fd1 = executor->ipc()->ReceiveFd(STDOUT_FILENO);
sandbox2::Sandbox2 s2(std::move(executor), builder.BuildOrDie());
s2.RunAsync();
char buf[4096];
std::string output;
while (true) {
int nbytes;
PCHECK((nbytes = read(fd1, buf, sizeof(buf))) >= 0);
if (nbytes == 0) break;
output += std::string(buf, nbytes);
}
auto result = s2.AwaitResult();
EXPECT_EQ(result.final_status(), sandbox2::Result::OK);
return output;
}
TEST_F(PolicyBuilderTest, TestCanOnlyBuildOnce) {
TEST(PolicyBuilderTest, TestCanOnlyBuildOnce) {
PolicyBuilder b;
ASSERT_THAT(b.TryBuild(), IsOk());
EXPECT_THAT(b.TryBuild(), StatusIs(absl::StatusCode::kFailedPrecondition,
"Can only build policy once."));
}
TEST_F(PolicyBuilderTest, TestIsCopyable) {
TEST(PolicyBuilderTest, TestIsCopyable) {
PolicyBuilder builder;
builder.AllowSyscall(__NR_getpid);
@ -212,40 +153,5 @@ TEST_F(PolicyBuilderTest, TestIsCopyable) {
EXPECT_THAT(builder.TryBuild(), IsOk());
EXPECT_THAT(copy.TryBuild(), IsOk());
}
TEST_F(PolicyBuilderTest, TestEcho) {
ASSERT_THAT(Run({"/bin/echo", "HELLO"}), StrEq("HELLO\n"));
}
TEST_F(PolicyBuilderTest, TestInterfacesNoNetwork) {
SKIP_ANDROID;
auto lines = absl::StrSplit(Run({"/sbin/ip", "addr", "show", "up"}), '\n');
int count = 0;
for (auto const& line : lines) {
if (!line.empty() && !absl::StartsWith(line, " ")) {
count += 1;
}
}
// Only loopback network interface 'lo'.
EXPECT_THAT(count, Eq(1));
}
TEST_F(PolicyBuilderTest, TestInterfacesNetwork) {
SKIP_ANDROID;
auto lines =
absl::StrSplit(Run({"/sbin/ip", "addr", "show", "up"}, true), '\n');
int count = 0;
for (auto const& line : lines) {
if (!line.empty() && !absl::StartsWith(line, " ")) {
count += 1;
}
}
// Loopback network interface 'lo' and more.
EXPECT_THAT(count, Gt(1));
}
} // namespace
} // namespace sandbox2

View File

@ -213,14 +213,6 @@ cc_binary(
features = ["fully_static_link"],
)
cc_binary(
name = "hostname",
testonly = True,
srcs = ["hostname.cc"],
copts = sapi_platform_copts(),
features = ["fully_static_link"],
)
cc_binary(
name = "limits",
testonly = True,
@ -235,4 +227,11 @@ cc_binary(
srcs = ["namespace.cc"],
copts = sapi_platform_copts(),
features = ["fully_static_link"],
deps = [
"//sandboxed_api/sandbox2:comms",
"//sandboxed_api/util:file_base",
"//sandboxed_api/util:fileops",
"@com_google_absl//absl/container:flat_hash_set",
"@com_google_absl//absl/log:check",
],
)

View File

@ -261,19 +261,6 @@ target_link_libraries(sandbox2_testcase_tsync PRIVATE
sapi::base
)
# sandboxed_api/sandbox2/testcases:hostname
add_executable(sandbox2_testcase_hostname
hostname.cc
)
add_executable(sandbox2::testcase_hostname ALIAS sandbox2_testcase_hostname)
set_target_properties(sandbox2_testcase_hostname PROPERTIES
OUTPUT_NAME hostname
)
target_link_libraries(sandbox2_testcase_hostname PRIVATE
-static
sapi::base
)
# sandboxed_api/sandbox2/testcases:limits
add_executable(sandbox2_testcase_limits
limits.cc
@ -297,5 +284,10 @@ set_target_properties(sandbox2_testcase_namespace PROPERTIES
)
target_link_libraries(sandbox2_testcase_namespace PRIVATE
-static
absl::check
absl::flat_hash_set
sandbox2::comms
sapi::base
sapi::file_base
sapi::fileops
)

View File

@ -1,44 +0,0 @@
// 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
//
// https://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.
// A binary to test network namespace hostname.
// Usage: ./hostname <expected hostname>
// Success only if the hostname is as expected.
#include <unistd.h>
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <cstring>
int main(int argc, char* argv[]) {
if (argc < 2) {
printf("argc < 2\n");
return EXIT_FAILURE;
}
char hostname[1024];
if (gethostname(hostname, sizeof(hostname)) == -1) {
printf("gethostname: error %d\n", errno);
return EXIT_FAILURE;
}
if (strcmp(hostname, argv[1]) != 0) {
printf("gethostname: got %s, want %s\n", hostname, argv[1]);
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

View File

@ -29,11 +29,48 @@
// Create provided files, return 0 on OK.
// Returns the index of the first non-creatable file on failure.
#include <fcntl.h>
#include <ifaddrs.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <cstdlib>
#include <string>
#include <vector>
#include "absl/container/flat_hash_set.h"
#include "absl/log/check.h"
#include "sandboxed_api/sandbox2/comms.h"
#include "sandboxed_api/util/fileops.h"
#include "sandboxed_api/util/path.h"
namespace {
using sapi::file::JoinPath;
using sapi::file_util::fileops::ListDirectoryEntries;
bool IsDirectory(const std::string& path) {
struct stat statbuf;
PCHECK(stat(path.c_str(), &statbuf) == 0);
return statbuf.st_mode & S_IFDIR;
}
void ListDirectoriesRecursively(const std::string& path,
std::vector<std::string>& files) {
std::string error;
std::vector<std::string> entries;
CHECK(ListDirectoryEntries(path, &entries, &error)) << error;
for (const std::string& entry : entries) {
std::string new_path = JoinPath(path, entry);
if (IsDirectory(new_path)) {
ListDirectoriesRecursively(new_path, files);
} else {
files.push_back(new_path);
}
}
}
} // namespace
int main(int argc, char* argv[]) {
if (argc < 2) {
@ -41,53 +78,78 @@ int main(int argc, char* argv[]) {
}
int mode = atoi(argv[1]); // NOLINT(runtime/deprecated_fn)
std::vector<std::string> result;
sandbox2::Comms comms(sandbox2::Comms::kDefaultConnection);
switch (mode) {
case 0: {
case 0:
// Make sure file exist
for (int i = 2; i < argc; i++) {
if (access(argv[i], R_OK)) {
return i - 1;
}
}
} break;
case 1: {
for (int i = 2; i < argc; i++) {
if (access(argv[i], W_OK)) {
return i - 1;
}
}
} break;
case 2: {
if (getpid() != 2) {
return -1;
}
} break;
case 3: {
if (argc != 4) {
return 1;
}
if (getuid() != atoi(argv[2]) // NOLINT(runtime/deprecated_fn)
|| getgid() != atoi(argv[3])) { // NOLINT(runtime/deprecated_fn)
return -1;
}
} break;
case 4:
for (int i = 2; i < argc; ++i) {
if (open(argv[i], O_CREAT | O_WRONLY, 0644) == -1) {
return i - 1;
if (access(argv[i], R_OK) == 0) {
result.push_back(argv[i]);
}
}
break;
case 1:
for (int i = 2; i < argc; i++) {
if (access(argv[i], W_OK) == 0) {
result.push_back(argv[i]);
}
}
break;
case 2:
result.push_back(absl::StrCat(getpid()));
break;
case 3:
result.push_back(absl::StrCat(getuid()));
result.push_back(absl::StrCat(getgid()));
break;
case 4:
for (int i = 2; i < argc; ++i) {
if (open(argv[i], O_CREAT | O_WRONLY, 0644) != -1) {
result.push_back(argv[i]);
}
}
break;
case 5: {
absl::flat_hash_set<std::string> ifnames;
struct ifaddrs* addrs;
if (getifaddrs(&addrs)) {
return -1;
}
for (struct ifaddrs* cur = addrs; cur; cur = cur->ifa_next) {
ifnames.insert(cur->ifa_name);
}
result.insert(result.end(), ifnames.begin(), ifnames.end());
freeifaddrs(addrs);
break;
}
case 6:
ListDirectoriesRecursively(argv[2], result);
break;
case 7: {
char hostname[1000];
if (gethostname(hostname, sizeof(hostname)) == -1) {
return -1;
}
result.push_back(hostname);
break;
}
default:
return 1;
}
CHECK(comms.SendUint64(result.size()));
for (const std::string& entry : result) {
CHECK(comms.SendString(entry));
}
return 0;
}