diff options
Diffstat (limited to 'libminijail_unittest.cc')
-rw-r--r-- | libminijail_unittest.cc | 457 |
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]; |