diff options
author | Mike Frysinger <vapier@google.com> | 2018-01-26 19:59:24 +0000 |
---|---|---|
committer | android-build-merger <android-build-merger@google.com> | 2018-01-26 19:59:24 +0000 |
commit | 25600c806806e9acd26a06efca4954a83f20642c (patch) | |
tree | 1504f8fddcf0094192ff9f0523173599d020b4db | |
parent | 94e16e22f3772e39968d7bc19e69390323b37ec2 (diff) | |
parent | 0d14394e89239728aee9182242bb54f09b29f633 (diff) | |
download | minijail-25600c806806e9acd26a06efca4954a83f20642c.tar.gz |
add unittests for the minijail0 cli am: 4d2a81e578 am: 79d80def4b
am: 0d14394e89
Change-Id: Iad6f8cf663a5fddc9fc42132f6ebed06251e4142
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Android.bp | 32 | ||||
-rw-r--r-- | Makefile | 11 | ||||
-rw-r--r-- | minijail0_cli.c | 2 | ||||
-rw-r--r-- | minijail0_cli_unittest.cc | 401 |
5 files changed, 447 insertions, 0 deletions
@@ -10,6 +10,7 @@ # Executables when compiling in-tree. /libminijail_unittest /minijail0 +/minijail0_cli_unittest /syscall_filter_unittest /system_unittest /util_unittest @@ -264,6 +264,38 @@ cc_test { }, } +// Utility functionality unit tests using gtest. +// +// For a device, run with: +// adb shell /data/nativetest/minijail0_cli_unittest_gtest/minijail0_cli_unittest_gtest +// +// For host, run with: +// out/host/linux-x86/nativetest(64)/minijail0_cli_unittest_gtest/minijail0_cli_unittest_gtest +// ========================================================= +cc_test { + name: "minijail0_cli_unittest_gtest", + defaults: ["libminijail_flags"], + host_supported: true, + + cflags: [ + "-DPRELOADPATH=\"/invalid\"", + ], + srcs: libminijailSrcFiles + [ + "elfparse.c", + "minijail0_cli.c", + "minijail0_cli_unittest.cc", + ] + unittestSrcFiles, + + static_libs: ["libminijail_generated"], + shared_libs: minijailCommonLibraries, + + target: { + android: { + test_suites: ["device-tests"], + }, + }, +} + // libminijail_test executable for brillo_Minijail test. // ========================================================= cc_test { @@ -54,6 +54,7 @@ all: CC_BINARY(minijail0) CC_LIBRARY(libminijail.so) \ parse_seccomp_policy: CXX_BINARY(parse_seccomp_policy) tests: TEST(CXX_BINARY(libminijail_unittest)) \ + TEST(CXX_BINARY(minijail0_cli_unittest)) \ TEST(CXX_BINARY(syscall_filter_unittest)) \ TEST(CXX_BINARY(system_unittest)) \ TEST(CXX_BINARY(util_unittest)) \ @@ -86,6 +87,16 @@ CC_LIBRARY(libminijailpreload.so): libminijailpreload.o $(CORE_OBJECT_FILES) clean: CLEAN(libminijailpreload.so) +CXX_BINARY(minijail0_cli_unittest): CXXFLAGS += $(GTEST_CXXFLAGS) +CXX_BINARY(minijail0_cli_unittest): LDLIBS += -lcap $(GTEST_LIBS) +ifeq ($(USE_SYSTEM_GTEST),no) +CXX_BINARY(minijail0_cli_unittest): $(GTEST_LIBS) +endif +CXX_BINARY(minijail0_cli_unittest): minijail0_cli_unittest.o \ + $(CORE_OBJECT_FILES) minijail0_cli.o elfparse.o testrunner.o +clean: CLEAN(minijail0_cli_unittest) + + CXX_BINARY(syscall_filter_unittest): CXXFLAGS += -Wno-write-strings \ $(GTEST_CXXFLAGS) CXX_BINARY(syscall_filter_unittest): LDLIBS += -lcap $(GTEST_LIBS) diff --git a/minijail0_cli.c b/minijail0_cli.c index 0d605ce..dc83c8b 100644 --- a/minijail0_cli.c +++ b/minijail0_cli.c @@ -272,6 +272,8 @@ static void use_pivot_root(struct minijail *j, const char *path, static void use_profile(struct minijail *j, const char *profile, int *pivot_root, int chroot, size_t *tmp_size) { + /* Note: New profiles should be added in minijail0_cli_unittest.cc. */ + if (!strcmp(profile, "minimalistic-mountns")) { minijail_namespace_vfs(j); if (minijail_bind(j, "/", "/", 0)) { diff --git a/minijail0_cli_unittest.cc b/minijail0_cli_unittest.cc new file mode 100644 index 0000000..856243a --- /dev/null +++ b/minijail0_cli_unittest.cc @@ -0,0 +1,401 @@ +/* Copyright 2018 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Test the minijail0 CLI using gtest. + * + * Note: We don't verify that the minijail struct was set correctly from these + * flags as only libminijail.c knows that definition. If we wanted to improve + * this test, we'd have to pull that struct into a common (internal) header. + */ + +#include <stdio.h> +#include <stdlib.h> + +#include <gtest/gtest.h> + +#include "libminijail.h" +#include "minijail0_cli.h" + +namespace { + +constexpr char kValidUser[] = "nobody"; +constexpr char kValidUid[] = "100"; +constexpr char kValidGroup[] = "users"; +constexpr char kValidGid[] = "100"; + +class CliTest : public ::testing::Test { + protected: + virtual void SetUp() { + j_ = minijail_new(); + + // Most tests do not care about this logic. For the few that do, make + // them opt into it so they can validate specifically. + elftype_ = ELFDYNAMIC; + } + virtual void TearDown() { + minijail_destroy(j_); + } + + // We use a vector of strings rather than const char * pointers because we + // need the backing memory to be writable. The CLI might mutate the strings + // as it parses things (which is normally permissible with argv). + int parse_args_(const std::vector<std::string>& argv, int *exit_immediately, + ElfType *elftype) { + // Make sure we reset the getopts state when scanning a new argv. + optind = 1; + + std::vector<const char *> pargv; + pargv.push_back("minijail0"); + for (const std::string& arg : argv) + pargv.push_back(arg.c_str()); + + // We grab stdout from parse_args itself as it might dump things we don't + // usually care about like help output. + testing::internal::CaptureStdout(); + int ret = parse_args(j_, pargv.size(), + const_cast<char* const*>(pargv.data()), + exit_immediately, elftype); + testing::internal::GetCapturedStdout(); + + return ret; + } + + int parse_args_(const std::vector<std::string>& argv) { + return parse_args_(argv, &exit_immediately_, &elftype_); + } + + struct minijail *j_; + ElfType elftype_; + int exit_immediately_; +}; + +} // namespace + +// Should exit non-zero when there's no arguments. +TEST_F(CliTest, no_args) { + std::vector<std::string> argv = {}; + ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), ""); +} + +// Should exit zero when we asked for help. +TEST_F(CliTest, help) { + std::vector<std::string> argv = {"-h"}; + ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(0), ""); + + argv = {"--help"}; + ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(0), ""); + + argv = {"-H"}; + ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(0), ""); +} + +// Just a simple program to run. +TEST_F(CliTest, valid_program) { + std::vector<std::string> argv = {"/bin/sh"}; + ASSERT_TRUE(parse_args_(argv)); +} + +// Valid calls to the change user option. +TEST_F(CliTest, valid_set_user) { + std::vector<std::string> argv = {"-u", "", "/bin/sh"}; + + argv[1] = kValidUser; + ASSERT_TRUE(parse_args_(argv)); + + argv[1] = kValidUid; + ASSERT_TRUE(parse_args_(argv)); +} + +// Invalid calls to the change user option. +TEST_F(CliTest, invalid_set_user) { + std::vector<std::string> argv = {"-u", "", "/bin/sh"}; + ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), ""); + + argv[1] = "j;lX:J*Pj;oijfs;jdlkjC;j"; + ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), ""); + + argv[1] = "1000x"; + ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), ""); +} + +// Valid calls to the change group option. +TEST_F(CliTest, valid_set_group) { + std::vector<std::string> argv = {"-g", "", "/bin/sh"}; + + argv[1] = kValidGroup; + ASSERT_TRUE(parse_args_(argv)); + + argv[1] = kValidGid; + ASSERT_TRUE(parse_args_(argv)); +} + +// Invalid calls to the change group option. +TEST_F(CliTest, invalid_set_group) { + std::vector<std::string> argv = {"-g", "", "/bin/sh"}; + ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), ""); + + argv[1] = "j;lX:J*Pj;oijfs;jdlkjC;j"; + ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), ""); + + argv[1] = "1000x"; + ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), ""); +} + +// Valid calls to the skip securebits option. +TEST_F(CliTest, valid_skip_securebits) { + // An empty string is the same as 0. + std::vector<std::string> argv = {"-B", "", "/bin/sh"}; + ASSERT_TRUE(parse_args_(argv)); + + argv[1] = "0xAB"; + ASSERT_TRUE(parse_args_(argv)); + + argv[1] = "1234"; + ASSERT_TRUE(parse_args_(argv)); +} + +// Invalid calls to the skip securebits option. +TEST_F(CliTest, invalid_skip_securebits) { + std::vector<std::string> argv = {"-B", "", "/bin/sh"}; + + argv[1] = "xja"; + ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), ""); +} + +// Valid calls to the caps option. +TEST_F(CliTest, valid_caps) { + // An empty string is the same as 0. + std::vector<std::string> argv = {"-c", "", "/bin/sh"}; + ASSERT_TRUE(parse_args_(argv)); + + argv[1] = "0xAB"; + ASSERT_TRUE(parse_args_(argv)); + + argv[1] = "1234"; + ASSERT_TRUE(parse_args_(argv)); +} + +// Invalid calls to the caps option. +TEST_F(CliTest, invalid_caps) { + std::vector<std::string> argv = {"-c", "", "/bin/sh"}; + + argv[1] = "xja"; + ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), ""); +} + +// Valid calls to the logging option. +TEST_F(CliTest, valid_logging) { + std::vector<std::string> argv = {"--logging", "", "/bin/sh"}; + + // This should list all valid logging targets. + const std::vector<std::string> profiles = { + "stderr", + "syslog", + }; + + for (const auto profile : profiles) { + argv[1] = profile; + ASSERT_TRUE(parse_args_(argv)); + } +} + +// Invalid calls to the logging option. +TEST_F(CliTest, invalid_logging) { + std::vector<std::string> argv = {"--logging", "", "/bin/sh"}; + ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), ""); + + argv[1] = "stdout"; + ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), ""); +} + +// Valid calls to the rlimit option. +TEST_F(CliTest, valid_rlimit) { + std::vector<std::string> argv = {"-R", "", "/bin/sh"}; + + argv[1] = "0,1,2"; + ASSERT_TRUE(parse_args_(argv)); +} + +// Invalid calls to the rlimit option. +TEST_F(CliTest, invalid_rlimit) { + std::vector<std::string> argv = {"-R", "", "/bin/sh"}; + ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), ""); + + // Missing cur & max. + argv[1] = "0"; + ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), ""); + + // Missing max. + argv[1] = "0,0"; + ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), ""); + + // Too many options. + argv[1] = "0,0,0,0"; + ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), ""); + + // TODO: We probably should reject non-numbers, but the current CLI ignores + // them and converts them to zeros. Oops. +} + +// Valid calls to the profile option. +TEST_F(CliTest, valid_profile) { + std::vector<std::string> argv = {"--profile", "", "/bin/sh"}; + + // This should list all valid profiles. + const std::vector<std::string> profiles = { + "minimalistic-mountns", + }; + + for (const auto profile : profiles) { + argv[1] = profile; + ASSERT_TRUE(parse_args_(argv)); + } +} + +// Invalid calls to the profile option. +TEST_F(CliTest, invalid_profile) { + std::vector<std::string> argv = {"--profile", "", "/bin/sh"}; + ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), ""); + + argv[1] = "random-unknown-profile"; + ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), ""); +} + +// Valid calls to the chroot option. +TEST_F(CliTest, valid_chroot) { + std::vector<std::string> argv = {"-C", "/", "/bin/sh"}; + ASSERT_TRUE(parse_args_(argv)); +} + +// Valid calls to the pivot root option. +TEST_F(CliTest, valid_pivot_root) { + std::vector<std::string> argv = {"-P", "/", "/bin/sh"}; + ASSERT_TRUE(parse_args_(argv)); +} + +// We cannot handle multiple options with chroot/profile/pivot root. +TEST_F(CliTest, conflicting_roots) { + std::vector<std::string> argv; + + // Chroot & pivot root. + argv = {"-C", "/", "-P", "/", "/bin/sh"}; + ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), ""); + + // Chroot & minimalistic-mountns profile. + argv = {"-C", "/", "--profile", "minimalistic-mountns", "/bin/sh"}; + ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), ""); + + // Pivot root & minimalistic-mountns profile. + argv = {"-P", "/", "--profile", "minimalistic-mountns", "/bin/sh"}; + ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), ""); +} + +// Valid calls to the uidmap option. +TEST_F(CliTest, valid_uidmap) { + std::vector<std::string> argv = {"-m", "/bin/sh"}; + // Use a default map (no option from user). + ASSERT_TRUE(parse_args_(argv)); + + // Use a single map. + argv = {"-m0 0 1", "/bin/sh"}; + ASSERT_TRUE(parse_args_(argv)); + + // Multiple maps. + argv = {"-m0 0 1,100 100 1", "/bin/sh"}; + ASSERT_TRUE(parse_args_(argv)); +} + +// Valid calls to the gidmap option. +TEST_F(CliTest, valid_gidmap) { + std::vector<std::string> argv = {"-M", "/bin/sh"}; + // Use a default map (no option from user). + ASSERT_TRUE(parse_args_(argv)); + + // Use a single map. + argv = {"-M0 0 1", "/bin/sh"}; + ASSERT_TRUE(parse_args_(argv)); + + // Multiple maps. + argv = {"-M0 0 1,100 100 1", "/bin/sh"}; + ASSERT_TRUE(parse_args_(argv)); +} + +// Invalid calls to the uidmap/gidmap options. +// Note: Can't really test these as all validation is delayed/left to the +// runtime kernel. Minijail will simply write verbatim what the user gave +// it to the corresponding /proc/.../[ug]id_map. + +// Valid calls to the binding option. +TEST_F(CliTest, valid_binding) { + std::vector<std::string> argv = {"-v", "-b", "", "/bin/sh"}; + + // Dest & writable are optional. + argv[1] = "/"; + ASSERT_TRUE(parse_args_(argv)); + + // Writable is optional. + argv[1] = "/,/"; + ASSERT_TRUE(parse_args_(argv)); + + // Writable is an integer. + argv[1] = "/,/,0"; + ASSERT_TRUE(parse_args_(argv)); + argv[1] = "/,/,1"; + ASSERT_TRUE(parse_args_(argv)); + + // Dest is optional. + argv[1] = "/,,0"; + ASSERT_TRUE(parse_args_(argv)); +} + +// Invalid calls to the binding option. +TEST_F(CliTest, invalid_binding) { + std::vector<std::string> argv = {"-v", "-b", "", "/bin/sh"}; + + // Missing source. + argv[2] = ""; + ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), ""); + + // Too many args. + argv[2] = "/,/,0,what"; + ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), ""); + + // Missing mount namespace/etc... + argv = {"-b", "/", "/bin/sh"}; + ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), ""); +} + +// Valid calls to the mount option. +TEST_F(CliTest, valid_mount) { + std::vector<std::string> argv = {"-v", "-k", "", "/bin/sh"}; + + // Flags & data are optional. + argv[2] = "none,/,none"; + ASSERT_TRUE(parse_args_(argv)); + + // Data is optional. + argv[2] = "none,/,none,0xe"; + ASSERT_TRUE(parse_args_(argv)); + + // Flags are optional. + argv[2] = "none,/,none,,mode=755"; + ASSERT_TRUE(parse_args_(argv)); +} + +// Invalid calls to the mount option. +TEST_F(CliTest, invalid_mount) { + std::vector<std::string> argv = {"-v", "-k", "", "/bin/sh"}; + + // Missing source. + argv[2] = ""; + ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), ""); + + // Missing dest. + argv[2] = "none"; + ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), ""); + + // Missing type. + argv[2] = "none,/"; + ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), ""); +} |