code-intelligence.com/cifuzz@v0.40.0/third-party/minijail/minijail0_cli_unittest.cc (about)

     1  /* Copyright 2018 The Chromium OS Authors. All rights reserved.
     2   * Use of this source code is governed by a BSD-style license that can be
     3   * found in the LICENSE file.
     4   *
     5   * Test the minijail0 CLI using gtest.
     6   *
     7   * Note: We don't verify that the minijail struct was set correctly from these
     8   * flags as only libminijail.c knows that definition.  If we wanted to improve
     9   * this test, we'd have to pull that struct into a common (internal) header.
    10   */
    11  
    12  #include <stdio.h>
    13  #include <stdlib.h>
    14  
    15  #include <gtest/gtest.h>
    16  
    17  #include "libminijail.h"
    18  #include "minijail0_cli.h"
    19  
    20  namespace {
    21  
    22  constexpr char kValidUser[] = "nobody";
    23  constexpr char kValidUid[] = "100";
    24  constexpr char kValidGroup[] = "users";
    25  constexpr char kValidGid[] = "100";
    26  
    27  class CliTest : public ::testing::Test {
    28   protected:
    29    virtual void SetUp() {
    30      // Most tests do not care about this logic.  For the few that do, make
    31      // them opt into it so they can validate specifically.
    32      elftype_ = ELFDYNAMIC;
    33    }
    34    virtual void TearDown() {}
    35  
    36    // We use a vector of strings rather than const char * pointers because we
    37    // need the backing memory to be writable.  The CLI might mutate the strings
    38    // as it parses things (which is normally permissible with argv).
    39    int parse_args_(const std::vector<std::string>& argv,
    40                    int* exit_immediately,
    41                    ElfType* elftype) {
    42      // Make sure we reset the getopts state when scanning a new argv.  Setting
    43      // this to 0 is a GNU extension, but AOSP/BSD also checks this (as an alias
    44      // to their "optreset").
    45      optind = 0;
    46  
    47      // We create & destroy this for every parse_args call because some API
    48      // calls can dupe memory which confuses LSAN.  https://crbug.com/844615
    49      struct minijail *j = minijail_new();
    50  
    51      std::vector<const char *> pargv;
    52      pargv.push_back("minijail0");
    53      for (const std::string& arg : argv)
    54        pargv.push_back(arg.c_str());
    55  
    56      // We grab stdout from parse_args itself as it might dump things we don't
    57      // usually care about like help output.
    58      testing::internal::CaptureStdout();
    59  
    60      const char* preload_path = PRELOADPATH;
    61      int ret =
    62          parse_args(j, pargv.size(), const_cast<char* const*>(pargv.data()),
    63                     exit_immediately, elftype, &preload_path);
    64      testing::internal::GetCapturedStdout();
    65  
    66      minijail_destroy(j);
    67  
    68      return ret;
    69    }
    70  
    71    int parse_args_(const std::vector<std::string>& argv) {
    72      return parse_args_(argv, &exit_immediately_, &elftype_);
    73    }
    74  
    75    ElfType elftype_;
    76    int exit_immediately_;
    77  };
    78  
    79  }  // namespace
    80  
    81  // Should exit non-zero when there's no arguments.
    82  TEST_F(CliTest, no_args) {
    83    std::vector<std::string> argv = {};
    84    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
    85  }
    86  
    87  // Should exit zero when we asked for help.
    88  TEST_F(CliTest, help) {
    89    std::vector<std::string> argv = {"-h"};
    90    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(0), "");
    91  
    92    argv = {"--help"};
    93    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(0), "");
    94  
    95    argv = {"-H"};
    96    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(0), "");
    97  }
    98  
    99  // Just a simple program to run.
   100  TEST_F(CliTest, valid_program) {
   101    std::vector<std::string> argv = {"/bin/sh"};
   102    ASSERT_TRUE(parse_args_(argv));
   103  }
   104  
   105  // Valid calls to the change user option.
   106  TEST_F(CliTest, valid_set_user) {
   107    std::vector<std::string> argv = {"-u", "", "/bin/sh"};
   108  
   109    argv[1] = kValidUser;
   110    ASSERT_TRUE(parse_args_(argv));
   111  
   112    argv[1] = kValidUid;
   113    ASSERT_TRUE(parse_args_(argv));
   114  }
   115  
   116  // Invalid calls to the change user option.
   117  TEST_F(CliTest, invalid_set_user) {
   118    std::vector<std::string> argv = {"-u", "", "/bin/sh"};
   119    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
   120  
   121    argv[1] = "j;lX:J*Pj;oijfs;jdlkjC;j";
   122    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
   123  
   124    argv[1] = "1000x";
   125    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
   126  
   127    // Supplying -u more than once is bad.
   128    argv = {"-u", kValidUser, "-u", kValidUid, "/bin/sh"};
   129    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1),
   130                "-u provided multiple times");
   131  }
   132  
   133  // Valid calls to the change group option.
   134  TEST_F(CliTest, valid_set_group) {
   135    std::vector<std::string> argv = {"-g", "", "/bin/sh"};
   136  
   137    argv[1] = kValidGroup;
   138    ASSERT_TRUE(parse_args_(argv));
   139  
   140    argv[1] = kValidGid;
   141    ASSERT_TRUE(parse_args_(argv));
   142  }
   143  
   144  // Invalid calls to the change group option.
   145  TEST_F(CliTest, invalid_set_group) {
   146    std::vector<std::string> argv = {"-g", "", "/bin/sh"};
   147    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
   148  
   149    argv[1] = "j;lX:J*Pj;oijfs;jdlkjC;j";
   150    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
   151  
   152    argv[1] = "1000x";
   153    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
   154  
   155    // Supplying -g more than once is bad.
   156    argv = {"-g", kValidGroup, "-g", kValidGid, "/bin/sh"};
   157    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1),
   158                "-g provided multiple times");
   159  }
   160  
   161  // Valid calls to the add-suppl-group option.
   162  TEST_F(CliTest, valid_add_supp_group) {
   163    std::vector<std::string> argv = {"--add-suppl-group", "", "/bin/sh"};
   164  
   165    argv[1] = kValidGroup;
   166    ASSERT_TRUE(parse_args_(argv));
   167  
   168    argv[1] = kValidGid;
   169    ASSERT_TRUE(parse_args_(argv));
   170  
   171    std::vector<std::string> argv2 = {"--add-suppl-group", "",
   172                                      "--add-suppl-group", "", "/bin/sh"};
   173    argv[1] = kValidGroup;
   174    argv[2] = kValidGid;
   175    ASSERT_TRUE(parse_args_(argv));
   176  }
   177  
   178  // Invalid calls to the add-suppl-group option.
   179  TEST_F(CliTest, invalid_add_supp_group) {
   180    std::vector<std::string> argv = {"--add-suppl-group", "", "/bin/sh"};
   181  
   182    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
   183  
   184    argv[1] = "j;lX:J*Pj;oijfs;jdlkjC;j";
   185    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
   186  
   187    argv[1] = "1000x";
   188    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
   189  }
   190  
   191  // Valid calls to the skip securebits option.
   192  TEST_F(CliTest, valid_skip_securebits) {
   193    // An empty string is the same as 0.
   194    std::vector<std::string> argv = {"-B", "", "/bin/sh"};
   195    ASSERT_TRUE(parse_args_(argv));
   196  
   197    argv[1] = "0xAB";
   198    ASSERT_TRUE(parse_args_(argv));
   199  
   200    argv[1] = "1234";
   201    ASSERT_TRUE(parse_args_(argv));
   202  }
   203  
   204  // Invalid calls to the skip securebits option.
   205  TEST_F(CliTest, invalid_skip_securebits) {
   206    std::vector<std::string> argv = {"-B", "", "/bin/sh"};
   207  
   208    argv[1] = "xja";
   209    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
   210  }
   211  
   212  // Valid calls to the caps option.
   213  TEST_F(CliTest, valid_caps) {
   214    // An empty string is the same as 0.
   215    std::vector<std::string> argv = {"-c", "", "/bin/sh"};
   216    ASSERT_TRUE(parse_args_(argv));
   217  
   218    argv[1] = "0xAB";
   219    ASSERT_TRUE(parse_args_(argv));
   220  
   221    argv[1] = "1234";
   222    ASSERT_TRUE(parse_args_(argv));
   223  }
   224  
   225  // Invalid calls to the caps option.
   226  TEST_F(CliTest, invalid_caps) {
   227    std::vector<std::string> argv = {"-c", "", "/bin/sh"};
   228  
   229    argv[1] = "xja";
   230    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
   231  }
   232  
   233  // Valid calls to the logging option.
   234  TEST_F(CliTest, valid_logging) {
   235    std::vector<std::string> argv = {"--logging", "", "/bin/sh"};
   236  
   237    // This should list all valid logging targets.
   238    const std::vector<std::string> profiles = {
   239      "stderr",
   240      "syslog",
   241    };
   242  
   243    for (const auto& profile : profiles) {
   244      argv[1] = profile;
   245      ASSERT_TRUE(parse_args_(argv));
   246    }
   247  }
   248  
   249  // Invalid calls to the logging option.
   250  TEST_F(CliTest, invalid_logging) {
   251    std::vector<std::string> argv = {"--logging", "", "/bin/sh"};
   252    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
   253  
   254    argv[1] = "stdout";
   255    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
   256  }
   257  
   258  // Valid calls to the rlimit option.
   259  TEST_F(CliTest, valid_rlimit) {
   260    std::vector<std::string> argv = {"-R", "", "/bin/sh"};
   261  
   262    argv[1] = "0,1,2";
   263    ASSERT_TRUE(parse_args_(argv));
   264  
   265    argv[1] = "0,0x100,4";
   266    ASSERT_TRUE(parse_args_(argv));
   267  
   268    argv[1] = "1,1,unlimited";
   269    ASSERT_TRUE(parse_args_(argv));
   270  
   271    argv[1] = "2,unlimited,2";
   272    ASSERT_TRUE(parse_args_(argv));
   273  
   274    argv[1] = "RLIMIT_AS,unlimited,unlimited";
   275    ASSERT_TRUE(parse_args_(argv));
   276  }
   277  
   278  // Invalid calls to the rlimit option.
   279  TEST_F(CliTest, invalid_rlimit) {
   280    std::vector<std::string> argv = {"-R", "", "/bin/sh"};
   281    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
   282  
   283    // Missing cur & max.
   284    argv[1] = "0";
   285    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
   286  
   287    // Missing max.
   288    argv[1] = "0,0";
   289    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
   290  
   291    // Too many options.
   292    argv[1] = "0,0,0,0";
   293    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
   294  
   295    // Non-numeric limits
   296    argv[1] = "0,0,invalid-limit";
   297    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
   298  
   299    // Invalid number.
   300    argv[1] = "0,0,0j";
   301    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
   302  
   303    // Invalid hex number.
   304    argv[1] = "0,0x1jf,0";
   305    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
   306  
   307    // Invalid rlimit constant.
   308    argv[1] = "RLIMIT_GOGOOGOG,0,0";
   309    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
   310  }
   311  
   312  // Valid calls to the profile option.
   313  TEST_F(CliTest, valid_profile) {
   314    std::vector<std::string> argv = {"--profile", "", "/bin/sh"};
   315  
   316    // This should list all valid profiles.
   317    const std::vector<std::string> profiles = {
   318      "minimalistic-mountns",
   319      "minimalistic-mountns-nodev",
   320    };
   321  
   322    for (const auto& profile : profiles) {
   323      argv[1] = profile;
   324      ASSERT_TRUE(parse_args_(argv));
   325    }
   326  }
   327  
   328  // Invalid calls to the profile option.
   329  TEST_F(CliTest, invalid_profile) {
   330    std::vector<std::string> argv = {"--profile", "", "/bin/sh"};
   331    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
   332  
   333    argv[1] = "random-unknown-profile";
   334    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
   335  }
   336  
   337  // Valid calls to the chroot option.
   338  TEST_F(CliTest, valid_chroot) {
   339    std::vector<std::string> argv = {"-C", "/", "/bin/sh"};
   340    ASSERT_TRUE(parse_args_(argv));
   341  }
   342  
   343  // Valid calls to the pivot root option.
   344  TEST_F(CliTest, valid_pivot_root) {
   345    std::vector<std::string> argv = {"-P", "/", "/bin/sh"};
   346    ASSERT_TRUE(parse_args_(argv));
   347  }
   348  
   349  // We cannot handle multiple options with chroot/profile/pivot root.
   350  TEST_F(CliTest, conflicting_roots) {
   351    std::vector<std::string> argv;
   352  
   353    // Chroot & pivot root.
   354    argv = {"-C", "/", "-P", "/", "/bin/sh"};
   355    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
   356  
   357    // Chroot & minimalistic-mountns profile.
   358    argv = {"-C", "/", "--profile", "minimalistic-mountns", "/bin/sh"};
   359    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
   360  
   361    // Pivot root & minimalistic-mountns profile.
   362    argv = {"-P", "/", "--profile", "minimalistic-mountns", "/bin/sh"};
   363    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
   364  }
   365  
   366  // Valid calls to the uidmap option.
   367  TEST_F(CliTest, valid_uidmap) {
   368    std::vector<std::string> argv = {"-m", "/bin/sh"};
   369    // Use a default map (no option from user).
   370    ASSERT_TRUE(parse_args_(argv));
   371  
   372    // Use a single map.
   373    argv = {"-m0 0 1", "/bin/sh"};
   374    ASSERT_TRUE(parse_args_(argv));
   375  
   376    // Multiple maps.
   377    argv = {"-m0 0 1,100 100 1", "/bin/sh"};
   378    ASSERT_TRUE(parse_args_(argv));
   379  }
   380  
   381  // Valid calls to the gidmap option.
   382  TEST_F(CliTest, valid_gidmap) {
   383    std::vector<std::string> argv = {"-M", "/bin/sh"};
   384    // Use a default map (no option from user).
   385    ASSERT_TRUE(parse_args_(argv));
   386  
   387    // Use a single map.
   388    argv = {"-M0 0 1", "/bin/sh"};
   389    ASSERT_TRUE(parse_args_(argv));
   390  
   391    // Multiple maps.
   392    argv = {"-M0 0 1,100 100 1", "/bin/sh"};
   393    ASSERT_TRUE(parse_args_(argv));
   394  }
   395  
   396  // Invalid calls to the uidmap/gidmap options.
   397  // Note: Can't really test these as all validation is delayed/left to the
   398  // runtime kernel.  Minijail will simply write verbatim what the user gave
   399  // it to the corresponding /proc/.../[ug]id_map.
   400  
   401  // Valid calls to the binding option.
   402  TEST_F(CliTest, valid_binding) {
   403    std::vector<std::string> argv = {"-v", "-b", "", "/bin/sh"};
   404  
   405    // Dest & writable are optional.
   406    argv[1] = "/";
   407    ASSERT_TRUE(parse_args_(argv));
   408  
   409    // Writable is optional.
   410    argv[1] = "/,/";
   411    ASSERT_TRUE(parse_args_(argv));
   412  
   413    // Writable is an integer.
   414    argv[1] = "/,/,0";
   415    ASSERT_TRUE(parse_args_(argv));
   416    argv[1] = "/,/,1";
   417    ASSERT_TRUE(parse_args_(argv));
   418  
   419    // Dest is optional.
   420    argv[1] = "/,,0";
   421    ASSERT_TRUE(parse_args_(argv));
   422  }
   423  
   424  // Invalid calls to the binding option.
   425  TEST_F(CliTest, invalid_binding) {
   426    std::vector<std::string> argv = {"-v", "-b", "", "/bin/sh"};
   427  
   428    // Missing source.
   429    argv[2] = "";
   430    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
   431  
   432    // Too many args.
   433    argv[2] = "/,/,0,what";
   434    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
   435  
   436    // Missing mount namespace/etc...
   437    argv = {"-b", "/", "/bin/sh"};
   438    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
   439  
   440    // Bad value for <writable>.
   441    argv = {"-b", "/,,writable", "/bin/sh"};
   442    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
   443  }
   444  
   445  // Valid calls to the mount option.
   446  TEST_F(CliTest, valid_mount) {
   447    std::vector<std::string> argv = {"-v", "-k", "", "/bin/sh"};
   448  
   449    // Flags & data are optional.
   450    argv[2] = "none,/,none";
   451    ASSERT_TRUE(parse_args_(argv));
   452  
   453    // Data is optional.
   454    argv[2] = "none,/,none,0xe";
   455    ASSERT_TRUE(parse_args_(argv));
   456  
   457    // Flags are optional.
   458    argv[2] = "none,/,none,,mode=755";
   459    ASSERT_TRUE(parse_args_(argv));
   460  
   461    // Multiple data options to the kernel.
   462    argv[2] = "none,/,none,0xe,mode=755,uid=0,gid=10";
   463    ASSERT_TRUE(parse_args_(argv));
   464  
   465    // Single MS constant.
   466    argv[2] = "none,/,none,MS_NODEV,mode=755";
   467    ASSERT_TRUE(parse_args_(argv));
   468  
   469    // Multiple MS constants.
   470    argv[2] = "none,/,none,MS_NODEV|MS_NOEXEC,mode=755";
   471    ASSERT_TRUE(parse_args_(argv));
   472  
   473    // Mixed constant & number.
   474    argv[2] = "none,/,none,MS_NODEV|0xf,mode=755";
   475    ASSERT_TRUE(parse_args_(argv));
   476  }
   477  
   478  // Invalid calls to the mount option.
   479  TEST_F(CliTest, invalid_mount) {
   480    std::vector<std::string> argv = {"-v", "-k", "", "/bin/sh"};
   481  
   482    // Missing source.
   483    argv[2] = "";
   484    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
   485  
   486    // Missing dest.
   487    argv[2] = "none";
   488    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
   489  
   490    // Missing type.
   491    argv[2] = "none,/";
   492    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
   493  
   494    // Unknown MS constant.
   495    argv[2] = "none,/,none,MS_WHOOPS";
   496    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
   497  }
   498  
   499  // Valid calls to the remount mode option.
   500  TEST_F(CliTest, valid_remount_mode) {
   501    std::vector<std::string> argv = {"-v", "", "/bin/sh"};
   502  
   503    // Mode is optional.
   504    argv[1] = "-K";
   505    ASSERT_TRUE(parse_args_(argv));
   506  
   507    // This should list all valid modes.
   508    const std::vector<std::string> modes = {
   509      "shared",
   510      "private",
   511      "slave",
   512      "unbindable",
   513    };
   514  
   515    for (const auto& mode : modes) {
   516      argv[1] = "-K" + mode;
   517      ASSERT_TRUE(parse_args_(argv));
   518    }
   519  }
   520  
   521  // Invalid calls to the remount mode option.
   522  TEST_F(CliTest, invalid_remount_mode) {
   523    std::vector<std::string> argv = {"-v", "", "/bin/sh"};
   524  
   525    // Unknown mode.
   526    argv[1] = "-Kfoo";
   527    ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
   528  }