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 }