1 // Copyright 2014 The Kyua Authors. 2 // All rights reserved. 3 // 4 // Redistribution and use in source and binary forms, with or without 5 // modification, are permitted provided that the following conditions are 6 // met: 7 // 8 // * Redistributions of source code must retain the above copyright 9 // notice, this list of conditions and the following disclaimer. 10 // * Redistributions in binary form must reproduce the above copyright 11 // notice, this list of conditions and the following disclaimer in the 12 // documentation and/or other materials provided with the distribution. 13 // * Neither the name of Google Inc. nor the names of its contributors 14 // may be used to endorse or promote products derived from this software 15 // without specific prior written permission. 16 // 17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 29 #include "utils/process/isolation.hpp" 30 31 extern "C" { 32 #include <sys/types.h> 33 #include <sys/resource.h> 34 #include <sys/stat.h> 35 36 #include <unistd.h> 37 } 38 39 #include <cerrno> 40 #include <cstdlib> 41 #include <fstream> 42 #include <iostream> 43 44 #include <atf-c++.hpp> 45 46 #include "utils/defs.hpp" 47 #include "utils/env.hpp" 48 #include "utils/format/macros.hpp" 49 #include "utils/fs/operations.hpp" 50 #include "utils/fs/path.hpp" 51 #include "utils/optional.ipp" 52 #include "utils/passwd.hpp" 53 #include "utils/process/child.ipp" 54 #include "utils/process/status.hpp" 55 #include "utils/sanity.hpp" 56 #include "utils/test_utils.ipp" 57 58 namespace fs = utils::fs; 59 namespace passwd = utils::passwd; 60 namespace process = utils::process; 61 62 using utils::none; 63 using utils::optional; 64 65 66 namespace { 67 68 69 /// Runs the given hook in a subprocess. 70 /// 71 /// \param hook The code to run in the subprocess. 72 /// 73 /// \return The status of the subprocess for further validation. 74 /// 75 /// \post The subprocess.stdout and subprocess.stderr files, created in the 76 /// current directory, contain the output of the subprocess. 77 template< typename Hook > 78 static process::status 79 fork_and_run(Hook hook) 80 { 81 std::auto_ptr< process::child > child = process::child::fork_files( 82 hook, fs::path("subprocess.stdout"), fs::path("subprocess.stderr")); 83 const process::status status = child->wait(); 84 85 atf::utils::cat_file("subprocess.stdout", "isolated child stdout: "); 86 atf::utils::cat_file("subprocess.stderr", "isolated child stderr: "); 87 88 return status; 89 } 90 91 92 /// Subprocess that validates the cleanliness of the environment. 93 /// 94 /// \post Exits with success if the environment is clean; failure otherwise. 95 static void 96 check_clean_environment(void) 97 { 98 fs::mkdir(fs::path("some-directory"), 0755); 99 process::isolate_child(none, fs::path("some-directory")); 100 101 bool failed = false; 102 103 const char* empty[] = { "LANG", "LC_ALL", "LC_COLLATE", "LC_CTYPE", 104 "LC_MESSAGES", "LC_MONETARY", "LC_NUMERIC", 105 "LC_TIME", NULL }; 106 const char** iter; 107 for (iter = empty; *iter != NULL; ++iter) { 108 if (utils::getenv(*iter)) { 109 failed = true; 110 std::cout << F("%s was not unset\n") % *iter; 111 } 112 } 113 114 if (utils::getenv_with_default("HOME", "") != "some-directory") { 115 failed = true; 116 std::cout << "HOME was not set to the work directory\n"; 117 } 118 119 if (utils::getenv_with_default("TMPDIR", "") != "some-directory") { 120 failed = true; 121 std::cout << "TMPDIR was not set to the work directory\n"; 122 } 123 124 if (utils::getenv_with_default("TZ", "") != "UTC") { 125 failed = true; 126 std::cout << "TZ was not set to UTC\n"; 127 } 128 129 if (utils::getenv_with_default("LEAVE_ME_ALONE", "") != "kill-some-day") { 130 failed = true; 131 std::cout << "LEAVE_ME_ALONE was modified while it should not have " 132 "been\n"; 133 } 134 135 std::exit(failed ? EXIT_FAILURE : EXIT_SUCCESS); 136 } 137 138 139 /// Subprocess that checks if user privileges are dropped. 140 class check_drop_privileges { 141 /// The user to drop the privileges to. 142 const passwd::user _unprivileged_user; 143 144 public: 145 /// Constructor. 146 /// 147 /// \param unprivileged_user The user to drop the privileges to. 148 check_drop_privileges(const passwd::user& unprivileged_user) : 149 _unprivileged_user(unprivileged_user) 150 { 151 } 152 153 /// Body of the subprocess. 154 /// 155 /// \post Exits with success if the process has dropped privileges as 156 /// expected. 157 void 158 operator()(void) const 159 { 160 fs::mkdir(fs::path("subdir"), 0755); 161 process::isolate_child(utils::make_optional(_unprivileged_user), 162 fs::path("subdir")); 163 164 if (::getuid() == 0) { 165 std::cout << "UID is still 0\n"; 166 std::exit(EXIT_FAILURE); 167 } 168 169 if (::getgid() == 0) { 170 std::cout << "GID is still 0\n"; 171 std::exit(EXIT_FAILURE); 172 } 173 174 ::gid_t groups[1]; 175 if (::getgroups(1, groups) == -1) { 176 // Should only fail if we get more than one group notifying about 177 // not enough space in the groups variable to store the whole 178 // result. 179 INV(errno == EINVAL); 180 std::exit(EXIT_FAILURE); 181 } 182 if (groups[0] == 0) { 183 std::cout << "Primary group is still 0\n"; 184 std::exit(EXIT_FAILURE); 185 } 186 187 std::ofstream output("file.txt"); 188 if (!output) { 189 std::cout << "Cannot write to isolated directory; owner not " 190 "changed?\n"; 191 std::exit(EXIT_FAILURE); 192 } 193 194 std::exit(EXIT_SUCCESS); 195 } 196 }; 197 198 199 /// Subprocess that dumps core to validate core dumping abilities. 200 static void 201 check_enable_core_dumps(void) 202 { 203 process::isolate_child(none, fs::path(".")); 204 std::abort(); 205 } 206 207 208 /// Subprocess that checks if the work directory is entered. 209 class check_enter_work_directory { 210 /// Directory to enter. May be releative. 211 const fs::path _directory; 212 213 public: 214 /// Constructor. 215 /// 216 /// \param directory Directory to enter. 217 check_enter_work_directory(const fs::path& directory) : 218 _directory(directory) 219 { 220 } 221 222 /// Body of the subprocess. 223 /// 224 /// \post Exits with success if the process has entered the given work 225 /// directory; false otherwise. 226 void 227 operator()(void) const 228 { 229 const fs::path exp_subdir = fs::current_path() / _directory; 230 process::isolate_child(none, _directory); 231 std::exit(fs::current_path() == exp_subdir ? 232 EXIT_SUCCESS : EXIT_FAILURE); 233 } 234 }; 235 236 237 /// Subprocess that validates that it owns a session. 238 /// 239 /// \post Exits with success if the process lives in its own session; 240 /// failure otherwise. 241 static void 242 check_new_session(void) 243 { 244 process::isolate_child(none, fs::path(".")); 245 std::exit(::getsid(::getpid()) == ::getpid() ? EXIT_SUCCESS : EXIT_FAILURE); 246 } 247 248 249 /// Subprocess that validates the disconnection from any terminal. 250 /// 251 /// \post Exits with success if the environment is clean; failure otherwise. 252 static void 253 check_no_terminal(void) 254 { 255 process::isolate_child(none, fs::path(".")); 256 257 const char* const args[] = { 258 "/bin/sh", 259 "-i", 260 "-c", 261 "echo success", 262 NULL 263 }; 264 ::execv("/bin/sh", UTILS_UNCONST(char*, args)); 265 std::abort(); 266 } 267 268 269 /// Subprocess that validates that it has become the leader of a process group. 270 /// 271 /// \post Exits with success if the process lives in its own process group; 272 /// failure otherwise. 273 static void 274 check_process_group(void) 275 { 276 process::isolate_child(none, fs::path(".")); 277 std::exit(::getpgid(::getpid()) == ::getpid() ? 278 EXIT_SUCCESS : EXIT_FAILURE); 279 } 280 281 282 /// Subprocess that validates that the umask has been reset. 283 /// 284 /// \post Exits with success if the umask matches the expected value; failure 285 /// otherwise. 286 static void 287 check_umask(void) 288 { 289 process::isolate_child(none, fs::path(".")); 290 std::exit(::umask(0) == 0022 ? EXIT_SUCCESS : EXIT_FAILURE); 291 } 292 293 294 } // anonymous namespace 295 296 297 ATF_TEST_CASE_WITHOUT_HEAD(isolate_child__clean_environment); 298 ATF_TEST_CASE_BODY(isolate_child__clean_environment) 299 { 300 utils::setenv("HOME", "/non-existent/directory"); 301 utils::setenv("TMPDIR", "/non-existent/directory"); 302 utils::setenv("LANG", "C"); 303 utils::setenv("LC_ALL", "C"); 304 utils::setenv("LC_COLLATE", "C"); 305 utils::setenv("LC_CTYPE", "C"); 306 utils::setenv("LC_MESSAGES", "C"); 307 utils::setenv("LC_MONETARY", "C"); 308 utils::setenv("LC_NUMERIC", "C"); 309 utils::setenv("LC_TIME", "C"); 310 utils::setenv("LEAVE_ME_ALONE", "kill-some-day"); 311 utils::setenv("TZ", "EST+5"); 312 313 const process::status status = fork_and_run(check_clean_environment); 314 ATF_REQUIRE(status.exited()); 315 ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus()); 316 } 317 318 319 ATF_TEST_CASE(isolate_child__other_user_when_unprivileged); 320 ATF_TEST_CASE_HEAD(isolate_child__other_user_when_unprivileged) 321 { 322 set_md_var("require.user", "unprivileged"); 323 } 324 ATF_TEST_CASE_BODY(isolate_child__other_user_when_unprivileged) 325 { 326 const passwd::user user = passwd::current_user(); 327 328 passwd::user other_user = user; 329 other_user.uid += 1; 330 other_user.gid += 1; 331 process::isolate_child(utils::make_optional(other_user), fs::path(".")); 332 333 ATF_REQUIRE_EQ(user.uid, ::getuid()); 334 ATF_REQUIRE_EQ(user.gid, ::getgid()); 335 } 336 337 338 ATF_TEST_CASE(isolate_child__drop_privileges); 339 ATF_TEST_CASE_HEAD(isolate_child__drop_privileges) 340 { 341 set_md_var("require.config", "unprivileged-user"); 342 set_md_var("require.user", "root"); 343 } 344 ATF_TEST_CASE_BODY(isolate_child__drop_privileges) 345 { 346 const passwd::user unprivileged_user = passwd::find_user_by_name( 347 get_config_var("unprivileged-user")); 348 349 const process::status status = fork_and_run(check_drop_privileges( 350 unprivileged_user)); 351 ATF_REQUIRE(status.exited()); 352 ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus()); 353 } 354 355 356 ATF_TEST_CASE(isolate_child__drop_privileges_fail_uid); 357 ATF_TEST_CASE_HEAD(isolate_child__drop_privileges_fail_uid) 358 { 359 set_md_var("require.user", "unprivileged"); 360 } 361 ATF_TEST_CASE_BODY(isolate_child__drop_privileges_fail_uid) 362 { 363 // Fake the current user as root so that we bypass the protections in 364 // isolate_child that prevent us from attempting a user switch when we are 365 // not root. We do this so we can trigger the setuid failure. 366 passwd::user root = passwd::user("root", 0, 0); 367 ATF_REQUIRE(root.is_root()); 368 passwd::set_current_user_for_testing(root); 369 370 passwd::user unprivileged_user = passwd::current_user(); 371 unprivileged_user.uid += 1; 372 373 const process::status status = fork_and_run(check_drop_privileges( 374 unprivileged_user)); 375 ATF_REQUIRE(status.exited()); 376 ATF_REQUIRE_EQ(process::exit_isolation_failure, status.exitstatus()); 377 ATF_REQUIRE(atf::utils::grep_file("(chown|setuid).*failed", 378 "subprocess.stderr")); 379 } 380 381 382 ATF_TEST_CASE(isolate_child__drop_privileges_fail_gid); 383 ATF_TEST_CASE_HEAD(isolate_child__drop_privileges_fail_gid) 384 { 385 set_md_var("require.user", "unprivileged"); 386 } 387 ATF_TEST_CASE_BODY(isolate_child__drop_privileges_fail_gid) 388 { 389 // Fake the current user as root so that we bypass the protections in 390 // isolate_child that prevent us from attempting a user switch when we are 391 // not root. We do this so we can trigger the setgid failure. 392 passwd::user root = passwd::user("root", 0, 0); 393 ATF_REQUIRE(root.is_root()); 394 passwd::set_current_user_for_testing(root); 395 396 passwd::user unprivileged_user = passwd::current_user(); 397 unprivileged_user.gid += 1; 398 399 const process::status status = fork_and_run(check_drop_privileges( 400 unprivileged_user)); 401 ATF_REQUIRE(status.exited()); 402 ATF_REQUIRE_EQ(process::exit_isolation_failure, status.exitstatus()); 403 ATF_REQUIRE(atf::utils::grep_file("(chown|setgid).*failed", 404 "subprocess.stderr")); 405 } 406 407 408 ATF_TEST_CASE_WITHOUT_HEAD(isolate_child__enable_core_dumps); 409 ATF_TEST_CASE_BODY(isolate_child__enable_core_dumps) 410 { 411 utils::require_run_coredump_tests(this); 412 413 struct ::rlimit rl; 414 if (::getrlimit(RLIMIT_CORE, &rl) == -1) 415 fail("Failed to query the core size limit"); 416 if (rl.rlim_cur == 0 || rl.rlim_max == 0) 417 skip("Maximum core size is zero; cannot run test"); 418 rl.rlim_cur = 0; 419 if (::setrlimit(RLIMIT_CORE, &rl) == -1) 420 fail("Failed to lower the core size limit"); 421 422 const process::status status = fork_and_run(check_enable_core_dumps); 423 ATF_REQUIRE(status.signaled()); 424 ATF_REQUIRE(status.coredump()); 425 } 426 427 428 ATF_TEST_CASE_WITHOUT_HEAD(isolate_child__enter_work_directory); 429 ATF_TEST_CASE_BODY(isolate_child__enter_work_directory) 430 { 431 const fs::path directory("some/sub/directory"); 432 fs::mkdir_p(directory, 0755); 433 const process::status status = fork_and_run( 434 check_enter_work_directory(directory)); 435 ATF_REQUIRE(status.exited()); 436 ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus()); 437 } 438 439 440 ATF_TEST_CASE_WITHOUT_HEAD(isolate_child__enter_work_directory_failure); 441 ATF_TEST_CASE_BODY(isolate_child__enter_work_directory_failure) 442 { 443 const fs::path directory("some/sub/directory"); 444 const process::status status = fork_and_run( 445 check_enter_work_directory(directory)); 446 ATF_REQUIRE(status.exited()); 447 ATF_REQUIRE_EQ(process::exit_isolation_failure, status.exitstatus()); 448 ATF_REQUIRE(atf::utils::grep_file("chdir\\(some/sub/directory\\) failed", 449 "subprocess.stderr")); 450 } 451 452 453 ATF_TEST_CASE_WITHOUT_HEAD(isolate_child__new_session); 454 ATF_TEST_CASE_BODY(isolate_child__new_session) 455 { 456 const process::status status = fork_and_run(check_new_session); 457 ATF_REQUIRE(status.exited()); 458 ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus()); 459 } 460 461 462 ATF_TEST_CASE_WITHOUT_HEAD(isolate_child__no_terminal); 463 ATF_TEST_CASE_BODY(isolate_child__no_terminal) 464 { 465 const process::status status = fork_and_run(check_no_terminal); 466 ATF_REQUIRE(status.exited()); 467 ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus()); 468 } 469 470 471 ATF_TEST_CASE_WITHOUT_HEAD(isolate_child__process_group); 472 ATF_TEST_CASE_BODY(isolate_child__process_group) 473 { 474 const process::status status = fork_and_run(check_process_group); 475 ATF_REQUIRE(status.exited()); 476 ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus()); 477 } 478 479 480 ATF_TEST_CASE_WITHOUT_HEAD(isolate_child__reset_umask); 481 ATF_TEST_CASE_BODY(isolate_child__reset_umask) 482 { 483 const process::status status = fork_and_run(check_umask); 484 ATF_REQUIRE(status.exited()); 485 ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus()); 486 } 487 488 489 /// Executes isolate_path() and compares the on-disk changes to expected values. 490 /// 491 /// \param unprivileged_user The user to pass to isolate_path; may be none. 492 /// \param exp_uid Expected UID or none to expect the old value. 493 /// \param exp_gid Expected GID or none to expect the old value. 494 static void 495 do_isolate_path_test(const optional< passwd::user >& unprivileged_user, 496 const optional< uid_t >& exp_uid, 497 const optional< gid_t >& exp_gid) 498 { 499 const fs::path dir("dir"); 500 fs::mkdir(dir, 0755); 501 struct ::stat old_sb; 502 ATF_REQUIRE(::stat(dir.c_str(), &old_sb) != -1); 503 504 process::isolate_path(unprivileged_user, dir); 505 506 struct ::stat new_sb; 507 ATF_REQUIRE(::stat(dir.c_str(), &new_sb) != -1); 508 509 if (exp_uid) 510 ATF_REQUIRE_EQ(exp_uid.get(), new_sb.st_uid); 511 else 512 ATF_REQUIRE_EQ(old_sb.st_uid, new_sb.st_uid); 513 514 if (exp_gid) 515 ATF_REQUIRE_EQ(exp_gid.get(), new_sb.st_gid); 516 else 517 ATF_REQUIRE_EQ(old_sb.st_gid, new_sb.st_gid); 518 } 519 520 521 ATF_TEST_CASE_WITHOUT_HEAD(isolate_path__no_user); 522 ATF_TEST_CASE_BODY(isolate_path__no_user) 523 { 524 do_isolate_path_test(none, none, none); 525 } 526 527 528 ATF_TEST_CASE_WITHOUT_HEAD(isolate_path__same_user); 529 ATF_TEST_CASE_BODY(isolate_path__same_user) 530 { 531 do_isolate_path_test(utils::make_optional(passwd::current_user()), 532 none, none); 533 } 534 535 536 ATF_TEST_CASE(isolate_path__other_user_when_unprivileged); 537 ATF_TEST_CASE_HEAD(isolate_path__other_user_when_unprivileged) 538 { 539 set_md_var("require.user", "unprivileged"); 540 } 541 ATF_TEST_CASE_BODY(isolate_path__other_user_when_unprivileged) 542 { 543 passwd::user user = passwd::current_user(); 544 user.uid += 1; 545 user.gid += 1; 546 547 do_isolate_path_test(utils::make_optional(user), none, none); 548 } 549 550 551 ATF_TEST_CASE(isolate_path__drop_privileges); 552 ATF_TEST_CASE_HEAD(isolate_path__drop_privileges) 553 { 554 set_md_var("require.config", "unprivileged-user"); 555 set_md_var("require.user", "root"); 556 } 557 ATF_TEST_CASE_BODY(isolate_path__drop_privileges) 558 { 559 const passwd::user unprivileged_user = passwd::find_user_by_name( 560 get_config_var("unprivileged-user")); 561 do_isolate_path_test(utils::make_optional(unprivileged_user), 562 utils::make_optional(unprivileged_user.uid), 563 utils::make_optional(unprivileged_user.gid)); 564 } 565 566 567 ATF_TEST_CASE(isolate_path__drop_privileges_only_uid); 568 ATF_TEST_CASE_HEAD(isolate_path__drop_privileges_only_uid) 569 { 570 set_md_var("require.config", "unprivileged-user"); 571 set_md_var("require.user", "root"); 572 } 573 ATF_TEST_CASE_BODY(isolate_path__drop_privileges_only_uid) 574 { 575 passwd::user unprivileged_user = passwd::find_user_by_name( 576 get_config_var("unprivileged-user")); 577 unprivileged_user.gid = ::getgid(); 578 do_isolate_path_test(utils::make_optional(unprivileged_user), 579 utils::make_optional(unprivileged_user.uid), 580 none); 581 } 582 583 584 ATF_TEST_CASE(isolate_path__drop_privileges_only_gid); 585 ATF_TEST_CASE_HEAD(isolate_path__drop_privileges_only_gid) 586 { 587 set_md_var("require.config", "unprivileged-user"); 588 set_md_var("require.user", "root"); 589 } 590 ATF_TEST_CASE_BODY(isolate_path__drop_privileges_only_gid) 591 { 592 passwd::user unprivileged_user = passwd::find_user_by_name( 593 get_config_var("unprivileged-user")); 594 unprivileged_user.uid = ::getuid(); 595 do_isolate_path_test(utils::make_optional(unprivileged_user), 596 none, 597 utils::make_optional(unprivileged_user.gid)); 598 } 599 600 601 ATF_INIT_TEST_CASES(tcs) 602 { 603 ATF_ADD_TEST_CASE(tcs, isolate_child__clean_environment); 604 ATF_ADD_TEST_CASE(tcs, isolate_child__other_user_when_unprivileged); 605 ATF_ADD_TEST_CASE(tcs, isolate_child__drop_privileges); 606 ATF_ADD_TEST_CASE(tcs, isolate_child__drop_privileges_fail_uid); 607 ATF_ADD_TEST_CASE(tcs, isolate_child__drop_privileges_fail_gid); 608 ATF_ADD_TEST_CASE(tcs, isolate_child__enable_core_dumps); 609 ATF_ADD_TEST_CASE(tcs, isolate_child__enter_work_directory); 610 ATF_ADD_TEST_CASE(tcs, isolate_child__enter_work_directory_failure); 611 ATF_ADD_TEST_CASE(tcs, isolate_child__new_session); 612 ATF_ADD_TEST_CASE(tcs, isolate_child__no_terminal); 613 ATF_ADD_TEST_CASE(tcs, isolate_child__process_group); 614 ATF_ADD_TEST_CASE(tcs, isolate_child__reset_umask); 615 616 ATF_ADD_TEST_CASE(tcs, isolate_path__no_user); 617 ATF_ADD_TEST_CASE(tcs, isolate_path__same_user); 618 ATF_ADD_TEST_CASE(tcs, isolate_path__other_user_when_unprivileged); 619 ATF_ADD_TEST_CASE(tcs, isolate_path__drop_privileges); 620 ATF_ADD_TEST_CASE(tcs, isolate_path__drop_privileges_only_uid); 621 ATF_ADD_TEST_CASE(tcs, isolate_path__drop_privileges_only_gid); 622 } 623