aboutsummaryrefslogtreecommitdiff
path: root/libminijail_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'libminijail_unittest.cc')
-rw-r--r--libminijail_unittest.cc457
1 files changed, 444 insertions, 13 deletions
diff --git a/libminijail_unittest.cc b/libminijail_unittest.cc
index 868b7d7..7ffbde5 100644
--- a/libminijail_unittest.cc
+++ b/libminijail_unittest.cc
@@ -1,4 +1,4 @@
-/* Copyright 2016 The Chromium OS Authors. All rights reserved.
+/* Copyright 2016 The ChromiumOS Authors
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*
@@ -23,9 +23,11 @@
#include <set>
#include <string>
+#include "landlock_util.h"
#include "libminijail-private.h"
#include "libminijail.h"
#include "scoped_minijail.h"
+#include "unittest_util.h"
#include "util.h"
namespace {
@@ -101,6 +103,21 @@ std::map<std::string, std::string> GetNamespaces(
return namespaces;
}
+void set_preload_path(minijail *j) {
+#if defined(__ANDROID__)
+ // libminijailpreload.so isn't available in android, so skip trying to load
+ // it. Even without the preload, all the test cases either pass or are skipped
+ // for other reasons.
+ return;
+#endif
+ // We need to get the absolute path because entering a new mntns will
+ // implicitly chdir(/) for us.
+ char *preload_path = realpath(kPreloadPath, nullptr);
+ ASSERT_NE(preload_path, nullptr);
+ minijail_set_preload_path(j, preload_path);
+ free(preload_path);
+}
+
} // namespace
/* Silence unused variable warnings. */
@@ -569,7 +586,7 @@ TEST(Test, minijail_run_env_pid_pipes) {
GTEST_SKIP();
ScopedMinijail j(minijail_new());
- minijail_set_preload_path(j.get(), kPreloadPath);
+ set_preload_path(j.get());
char *argv[4];
argv[0] = const_cast<char*>(kCatPath);
@@ -632,7 +649,7 @@ TEST(Test, minijail_run_fd_env_pid_pipes) {
GTEST_SKIP();
ScopedMinijail j(minijail_new());
- minijail_set_preload_path(j.get(), kPreloadPath);
+ set_preload_path(j.get());
char *argv[4];
argv[0] = const_cast<char*>(kShellPath);
@@ -722,7 +739,7 @@ TEST(Test, minijail_run_env_pid_pipes_with_local_preload) {
ASSERT_EQ(setenv("TEST_PARENT", "test", 1 /*overwrite*/), 0);
// Use the preload library from this test build.
- ASSERT_EQ(0, minijail_set_preload_path(j.get(), "./libminijailpreload.so"));
+ set_preload_path(j.get());
int child_stderr;
mj_run_ret =
@@ -938,7 +955,7 @@ TEST(Test, test_minijail_preserve_fd) {
status = read(read_pipe[0], buf, 8);
EXPECT_EQ(status, (int)teststr_len);
buf[teststr_len] = 0;
- EXPECT_EQ(strcmp(buf, teststr), 0);
+ EXPECT_STREQ(buf, teststr);
status = minijail_wait(j);
EXPECT_EQ(status, 0);
@@ -1006,11 +1023,68 @@ TEST(Test, test_minijail_reset_signal_handlers) {
minijail_destroy(j);
}
+// Test that bind mounting onto a non-existing location works.
+TEST(Test, test_bind_mount_nonexistent_dest) {
+ TemporaryDir dir;
+ ASSERT_TRUE(dir.is_valid());
+
+ // minijail_bind() expects absolute paths, but TemporaryDir::path can return
+ // relative paths on Linux.
+ std::string path = dir.path;
+ if (!is_android()) {
+ std::string cwd(getcwd(NULL, 0));
+ path = cwd + "/" + path;
+ }
+
+ std::string path_src = path + "/src";
+ std::string path_dest = path + "/dest";
+
+ EXPECT_EQ(mkdir(path_src.c_str(), 0700), 0);
+
+ ScopedMinijail j(minijail_new());
+ int bind_res = minijail_bind(j.get(), path_src.c_str(), path_dest.c_str(),
+ 0 /*writable*/);
+ EXPECT_EQ(bind_res, 0);
+}
+
+// Test that bind mounting with a symlink behaves according to build-time
+// configuration.
+TEST(Test, test_bind_mount_symlink) {
+ TemporaryDir dir;
+ ASSERT_TRUE(dir.is_valid());
+
+ // minijail_bind() expects absolute paths, but TemporaryDir::path can return
+ // relative paths on Linux.
+ std::string path = dir.path;
+ if (!is_android()) {
+ std::string cwd(getcwd(NULL, 0));
+ path = cwd + "/" + path;
+ }
+
+ std::string path_src = path + "/src";
+ std::string path_dest = path + "/dest";
+ std::string path_sym = path + "/symlink";
+
+ EXPECT_EQ(mkdir(path_src.c_str(), 0700), 0);
+ EXPECT_EQ(mkdir(path_dest.c_str(), 0700), 0);
+ EXPECT_EQ(symlink(path_src.c_str(), path_sym.c_str()), 0);
+
+ ScopedMinijail j(minijail_new());
+ int bind_res = minijail_bind(j.get(), path_sym.c_str(), path_dest.c_str(),
+ 0 /*writable*/);
+ if (block_symlinks_in_bindmount_paths()) {
+ EXPECT_NE(bind_res, 0);
+ } else {
+ EXPECT_EQ(bind_res, 0);
+ }
+ EXPECT_EQ(unlink(path_sym.c_str()), 0);
+}
+
namespace {
// Tests that require userns access.
// Android unit tests don't currently support entering user namespaces as
-// unprivileged users due to having an older kernel. Chrome OS unit tests
+// unprivileged users due to having an older kernel. ChromeOS unit tests
// don't support it either due to being in a chroot environment (see man 2
// clone for more information about failure modes with the CLONE_NEWUSER flag).
class NamespaceTest : public ::testing::Test {
@@ -1119,7 +1193,7 @@ TEST_F(NamespaceTest, test_namespaces) {
{minijail_run_pid_pipes, minijail_run_pid_pipes_no_preload}) {
for (const auto& test_function : test_functions) {
ScopedMinijail j(minijail_new());
- minijail_set_preload_path(j.get(), kPreloadPath);
+ set_preload_path(j.get());
// Enter all the namespaces we can.
minijail_namespace_cgroups(j.get());
@@ -1155,7 +1229,7 @@ TEST_F(NamespaceTest, test_namespaces) {
ssize_t read_ret = read(child_stdout, buf, 8);
EXPECT_EQ(read_ret, static_cast<ssize_t>(teststr_len));
buf[teststr_len] = 0;
- EXPECT_EQ(strcmp(buf, teststr), 0);
+ EXPECT_STREQ(buf, teststr);
// Grab the set of namespaces in every container process. They must not
// match the ones in the init namespace, and they must all match each
@@ -1214,11 +1288,7 @@ TEST_F(NamespaceTest, test_enter_ns) {
// Finally enter those namespaces.
j = minijail_new();
- // We need to get the absolute path because entering a new mntns will
- // implicitly chdir(/) for us.
- char *path = realpath(kPreloadPath, nullptr);
- ASSERT_NE(nullptr, path);
- minijail_set_preload_path(j, path);
+ set_preload_path(j);
minijail_namespace_net(j);
minijail_namespace_vfs(j);
@@ -1396,6 +1466,367 @@ TEST_F(NamespaceTest, test_remount_one_shared) {
minijail_destroy(j);
}
+// Test that using minijail_mount() for bind mounts works.
+TEST_F(NamespaceTest, test_remount_ro_using_mount) {
+ int status;
+ char uidmap[kBufferSize], gidmap[kBufferSize];
+ constexpr uid_t kTargetUid = 1000; // Any non-zero value will do.
+ constexpr gid_t kTargetGid = 1000;
+
+ if (!userns_supported_)
+ GTEST_SKIP();
+
+ struct minijail *j = minijail_new();
+
+ minijail_namespace_pids(j);
+ minijail_namespace_vfs(j);
+ minijail_mount_tmp(j);
+ minijail_run_as_init(j);
+
+ // Perform userns mapping.
+ minijail_namespace_user(j);
+ snprintf(uidmap, sizeof(uidmap), "%d %d 1", kTargetUid, getuid());
+ snprintf(gidmap, sizeof(gidmap), "%d %d 1", kTargetGid, getgid());
+ minijail_change_uid(j, kTargetUid);
+ minijail_change_gid(j, kTargetGid);
+ minijail_uidmap(j, uidmap);
+ minijail_gidmap(j, gidmap);
+ minijail_namespace_user_disable_setgroups(j);
+
+ // Perform a RO remount using minijail_mount().
+ minijail_mount(j, "none", "/", "none", MS_REMOUNT | MS_BIND | MS_RDONLY);
+
+ char *argv[] = {"/bin/true", nullptr};
+ minijail_run_no_preload(j, argv[0], argv);
+
+ status = minijail_wait(j);
+ EXPECT_EQ(status, 0);
+
+ minijail_destroy(j);
+}
+
+namespace {
+
+// Tests that require Landlock support.
+//
+// These subclass NamespaceTest because they also require userns access.
+// TODO(akhna): ideally, Landlock unit tests should be able to run w/o
+// namespace_pids or namespace_user.
+class LandlockTest : public NamespaceTest {
+ protected:
+ static void SetUpTestCase() {
+ run_landlock_tests_ = LandlockSupported() && UsernsSupported();
+ }
+
+ // Whether Landlock tests should be run.
+ static bool run_landlock_tests_;
+
+ static bool LandlockSupported() {
+ // Check the Landlock version w/o creating a ruleset file descriptor.
+ int landlock_version = landlock_create_ruleset(
+ NULL, 0, LANDLOCK_CREATE_RULESET_VERSION);
+ if (landlock_version <= 0) {
+ const int err = errno;
+ warn("Skipping Landlock tests");
+ switch (err) {
+ case ENOSYS:
+ warn("Landlock not supported by the current kernel.");
+ break;
+ case EOPNOTSUPP:
+ warn("Landlock is currently disabled.");
+ break;
+ }
+ return false;
+ }
+ return true;
+ }
+
+ // Sets up a minijail to make Landlock syscalls and child processes.
+ void SetupLandlockTestingNamespaces(struct minijail *j) {
+ minijail_namespace_pids(j);
+ minijail_namespace_user(j);
+ }
+};
+
+bool LandlockTest::run_landlock_tests_;
+
+// Constants used in Landlock tests.
+constexpr char kBinPath[] = "/bin";
+constexpr char kEtcPath[] = "/etc";
+constexpr char kLibPath[] = "/lib";
+constexpr char kLib64Path[] = "/lib64";
+constexpr char kTmpPath[] = "/tmp";
+constexpr char kLsPath[] = "/bin/ls";
+constexpr char kTestSymlinkScript[] = R"(
+ unlink /tmp/test-sym-link-1;
+ ln -s /bin /tmp/test-sym-link-1
+ )";
+
+} // namespace
+
+TEST_F(LandlockTest, test_rule_rx_allow) {
+ int mj_run_ret;
+ int status;
+ char *argv[3];
+ if (!run_landlock_tests_)
+ GTEST_SKIP();
+ ScopedMinijail j(minijail_new());
+ SetupLandlockTestingNamespaces(j.get());
+ minijail_add_fs_restriction_rx(j.get(), kBinPath);
+ minijail_add_fs_restriction_rx(j.get(), kEtcPath);
+ minijail_add_fs_restriction_rx(j.get(), kLibPath);
+ minijail_add_fs_restriction_rx(j.get(), kLib64Path);
+
+ argv[0] = const_cast<char*>(kLsPath);
+ argv[1] = const_cast<char*>(kCatPath);
+ argv[2] = NULL;
+
+ mj_run_ret = minijail_run_no_preload(j.get(), argv[0], argv);
+ EXPECT_EQ(mj_run_ret, 0);
+ status = minijail_wait(j.get());
+ EXPECT_EQ(status, 0);
+}
+
+TEST_F(LandlockTest, test_rule_rx_deny) {
+ int mj_run_ret;
+ int status;
+ char *argv[3];
+ if (!run_landlock_tests_)
+ GTEST_SKIP();
+ ScopedMinijail j(minijail_new());
+ SetupLandlockTestingNamespaces(j.get());
+ // Add irrelevant Landlock rule.
+ minijail_add_fs_restriction_rx(j.get(), "/var");
+
+ argv[0] = const_cast<char*>(kLsPath);
+ argv[1] = const_cast<char*>(kCatPath);
+ argv[2] = NULL;
+
+ mj_run_ret = minijail_run_no_preload(j.get(), argv[0], argv);
+ EXPECT_EQ(mj_run_ret, 0);
+ status = minijail_wait(j.get());
+ EXPECT_NE(status, 0);
+}
+
+TEST_F(LandlockTest, test_rule_ro_allow) {
+ int mj_run_ret;
+ int status;
+ char *argv[3];
+ if (!run_landlock_tests_)
+ GTEST_SKIP();
+ ScopedMinijail j(minijail_new());
+ SetupLandlockTestingNamespaces(j.get());
+ minijail_add_fs_restriction_rx(j.get(), kBinPath);
+ minijail_add_fs_restriction_rx(j.get(), kEtcPath);
+ minijail_add_fs_restriction_rx(j.get(), kLibPath);
+ minijail_add_fs_restriction_rx(j.get(), kLib64Path);
+ // Add RO rule.
+ minijail_add_fs_restriction_ro(j.get(), "/var");
+
+ argv[0] = const_cast<char*>(kLsPath);
+ argv[1] = "/var";
+ argv[2] = NULL;
+
+ mj_run_ret = minijail_run_no_preload(j.get(), argv[0], argv);
+ EXPECT_EQ(mj_run_ret, 0);
+ status = minijail_wait(j.get());
+ EXPECT_EQ(status, 0);
+}
+
+TEST_F(LandlockTest, test_rule_ro_deny) {
+ int mj_run_ret;
+ int status;
+ char *argv[3];
+ if (!run_landlock_tests_)
+ GTEST_SKIP();
+ ScopedMinijail j(minijail_new());
+ SetupLandlockTestingNamespaces(j.get());
+ minijail_add_fs_restriction_rx(j.get(), kBinPath);
+ minijail_add_fs_restriction_rx(j.get(), kEtcPath);
+ minijail_add_fs_restriction_rx(j.get(), kLibPath);
+ minijail_add_fs_restriction_rx(j.get(), kLib64Path);
+ // No RO rule for /var, because we want the cmd to fail.
+
+ argv[0] = const_cast<char*>(kLsPath);
+ argv[1] = "/var";
+ argv[2] = NULL;
+
+ mj_run_ret = minijail_run_no_preload(j.get(), argv[0], argv);
+ EXPECT_EQ(mj_run_ret, 0);
+ status = minijail_wait(j.get());
+ EXPECT_NE(status, 0);
+}
+
+TEST_F(LandlockTest, test_rule_rw_allow) {
+ int mj_run_ret;
+ int status;
+ char *argv[4];
+ if (!run_landlock_tests_)
+ GTEST_SKIP();
+ ScopedMinijail j(minijail_new());
+ SetupLandlockTestingNamespaces(j.get());
+ minijail_add_fs_restriction_rx(j.get(), kBinPath);
+ minijail_add_fs_restriction_rx(j.get(), kEtcPath);
+ minijail_add_fs_restriction_rx(j.get(), kLibPath);
+ minijail_add_fs_restriction_rx(j.get(), kLib64Path);
+ // Add RW Landlock rule.
+ minijail_add_fs_restriction_rw(j.get(), kTmpPath);
+
+ argv[0] = const_cast<char*>(kShellPath);
+ argv[1] = "-c";
+ argv[2] = "exec echo 'bar' > /tmp/baz";
+ argv[3] = NULL;
+
+ mj_run_ret = minijail_run_no_preload(j.get(), argv[0], argv);
+ EXPECT_EQ(mj_run_ret, 0);
+ status = minijail_wait(j.get());
+ EXPECT_EQ(status, 0);
+}
+
+TEST_F(LandlockTest, test_rule_rw_deny) {
+ int mj_run_ret;
+ int status;
+ char *argv[4];
+ if (!run_landlock_tests_)
+ GTEST_SKIP();
+ ScopedMinijail j(minijail_new());
+ SetupLandlockTestingNamespaces(j.get());
+ minijail_add_fs_restriction_rx(j.get(), kBinPath);
+ minijail_add_fs_restriction_rx(j.get(), kEtcPath);
+ minijail_add_fs_restriction_rx(j.get(), kLibPath);
+ minijail_add_fs_restriction_rx(j.get(), kLib64Path);
+ // No RW rule, because we want the cmd to fail.
+
+ argv[0] = const_cast<char*>(kShellPath);
+ argv[1] = "-c";
+ argv[2] = "exec echo 'bar' > /tmp/baz";
+ argv[3] = NULL;
+
+ mj_run_ret = minijail_run_no_preload(j.get(), argv[0], argv);
+ EXPECT_EQ(mj_run_ret, 0);
+ status = minijail_wait(j.get());
+ EXPECT_NE(status, 0);
+}
+
+TEST_F(LandlockTest, test_rule_allow_symlinks_advanced_rw) {
+ int mj_run_ret;
+ int status;
+ if (!run_landlock_tests_)
+ GTEST_SKIP();
+ ScopedMinijail j(minijail_new());
+ SetupLandlockTestingNamespaces(j.get());
+ minijail_add_fs_restriction_rx(j.get(), kBinPath);
+ minijail_add_fs_restriction_rx(j.get(), kEtcPath);
+ minijail_add_fs_restriction_rx(j.get(), kLibPath);
+ minijail_add_fs_restriction_rx(j.get(), kLib64Path);
+ minijail_add_fs_restriction_advanced_rw(j.get(), kTmpPath);
+
+ char* const argv[] = {"sh", "-c", const_cast<char*>(kTestSymlinkScript),
+ nullptr};
+
+ mj_run_ret = minijail_run_no_preload(j.get(), kShellPath, argv);
+ EXPECT_EQ(mj_run_ret, 0);
+ status = minijail_wait(j.get());
+ EXPECT_EQ(status, 0);
+}
+
+TEST_F(LandlockTest, test_rule_deny_symlinks_basic_rw) {
+ int mj_run_ret;
+ int status;
+ if (!run_landlock_tests_)
+ GTEST_SKIP();
+ ScopedMinijail j(minijail_new());
+ SetupLandlockTestingNamespaces(j.get());
+ minijail_add_fs_restriction_rx(j.get(), kBinPath);
+ minijail_add_fs_restriction_rx(j.get(), kEtcPath);
+ minijail_add_fs_restriction_rx(j.get(), kLibPath);
+ minijail_add_fs_restriction_rx(j.get(), kLib64Path);
+ minijail_add_fs_restriction_rw(j.get(), kTmpPath);
+
+ char* const argv[] = {"sh", "-c", const_cast<char*>(kTestSymlinkScript),
+ nullptr};
+
+ mj_run_ret = minijail_run_no_preload(j.get(), kShellPath, argv);
+ EXPECT_EQ(mj_run_ret, 0);
+ status = minijail_wait(j.get());
+ EXPECT_NE(status, 0);
+}
+
+TEST_F(LandlockTest, test_rule_rx_cannot_write) {
+ int mj_run_ret;
+ int status;
+ char *argv[4];
+ if (!run_landlock_tests_)
+ GTEST_SKIP();
+ ScopedMinijail j(minijail_new());
+ SetupLandlockTestingNamespaces(j.get());
+ minijail_add_fs_restriction_rx(j.get(), kBinPath);
+ minijail_add_fs_restriction_rx(j.get(), kEtcPath);
+ minijail_add_fs_restriction_rx(j.get(), kLibPath);
+ minijail_add_fs_restriction_rx(j.get(), kLib64Path);
+ minijail_add_fs_restriction_rx(j.get(), kTmpPath);
+
+ argv[0] = const_cast<char*>(kShellPath);
+ argv[1] = "-c";
+ argv[2] = "exec echo 'bar' > /tmp/baz";
+ argv[3] = NULL;
+
+ mj_run_ret = minijail_run_no_preload(j.get(), argv[0], argv);
+ EXPECT_EQ(mj_run_ret, 0);
+ status = minijail_wait(j.get());
+ EXPECT_NE(status, 0);
+}
+
+TEST_F(LandlockTest, test_rule_ro_cannot_wx) {
+ int mj_run_ret;
+ int status;
+ char *argv[4];
+ if (!run_landlock_tests_)
+ GTEST_SKIP();
+ ScopedMinijail j(minijail_new());
+ SetupLandlockTestingNamespaces(j.get());
+ minijail_add_fs_restriction_ro(j.get(), kBinPath);
+ minijail_add_fs_restriction_ro(j.get(), kEtcPath);
+ minijail_add_fs_restriction_ro(j.get(), kLibPath);
+ minijail_add_fs_restriction_ro(j.get(), kLib64Path);
+ minijail_add_fs_restriction_ro(j.get(), kTmpPath);
+
+ argv[0] = const_cast<char*>(kShellPath);
+ argv[1] = "-c";
+ argv[2] = "exec echo 'bar' > /tmp/baz";
+ argv[3] = NULL;
+
+ mj_run_ret = minijail_run_no_preload(j.get(), argv[0], argv);
+ EXPECT_EQ(mj_run_ret, 0);
+ status = minijail_wait(j.get());
+ EXPECT_NE(status, 0);
+}
+
+TEST_F(LandlockTest, test_rule_rw_cannot_exec) {
+ int mj_run_ret;
+ int status;
+ char *argv[4];
+ if (!run_landlock_tests_)
+ GTEST_SKIP();
+ ScopedMinijail j(minijail_new());
+ SetupLandlockTestingNamespaces(j.get());
+ minijail_add_fs_restriction_rw(j.get(), kBinPath);
+ minijail_add_fs_restriction_rw(j.get(), kEtcPath);
+ minijail_add_fs_restriction_rw(j.get(), kLibPath);
+ minijail_add_fs_restriction_rw(j.get(), kLib64Path);
+ minijail_add_fs_restriction_rw(j.get(), kTmpPath);
+
+ argv[0] = const_cast<char*>(kShellPath);
+ argv[1] = "-c";
+ argv[2] = "exec echo 'bar' > /tmp/baz";
+ argv[3] = NULL;
+
+ mj_run_ret = minijail_run_no_preload(j.get(), argv[0], argv);
+ EXPECT_EQ(mj_run_ret, 0);
+ status = minijail_wait(j.get());
+ EXPECT_NE(status, 0);
+}
+
void TestCreateSession(bool create_session) {
int status;
int pipe_fds[2];