Path: blob/main/contrib/kyua/utils/process/isolation.cpp
48180 views
// Copyright 2014 The Kyua Authors.1// All rights reserved.2//3// Redistribution and use in source and binary forms, with or without4// modification, are permitted provided that the following conditions are5// met:6//7// * Redistributions of source code must retain the above copyright8// notice, this list of conditions and the following disclaimer.9// * Redistributions in binary form must reproduce the above copyright10// notice, this list of conditions and the following disclaimer in the11// documentation and/or other materials provided with the distribution.12// * Neither the name of Google Inc. nor the names of its contributors13// may be used to endorse or promote products derived from this software14// without specific prior written permission.15//16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS17// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT18// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR19// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT20// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,21// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT22// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,23// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY24// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT25// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.2728#include "utils/process/isolation.hpp"2930extern "C" {31#include <sys/stat.h>3233#include <grp.h>34#include <signal.h>35#include <unistd.h>36}3738#include <cerrno>39#include <cstdlib>40#include <cstring>41#include <iostream>4243#include "utils/defs.hpp"44#include "utils/format/macros.hpp"45#include "utils/fs/path.hpp"46#include "utils/env.hpp"47#include "utils/logging/macros.hpp"48#include "utils/optional.ipp"49#include "utils/passwd.hpp"50#include "utils/sanity.hpp"51#include "utils/signals/misc.hpp"52#include "utils/stacktrace.hpp"5354namespace fs = utils::fs;55namespace passwd = utils::passwd;56namespace process = utils::process;57namespace signals = utils::signals;5859using utils::optional;606162/// Magic exit code to denote an error while preparing the subprocess.63const int process::exit_isolation_failure = 124;646566namespace {676869static void fail(const std::string&, const int) UTILS_NORETURN;707172/// Fails the process with an errno-based error message.73///74/// \param message The message to print. The errno-based string will be75/// appended to this, just like in perror(3).76/// \param original_errno The error code to format.77static void78fail(const std::string& message, const int original_errno)79{80std::cerr << message << ": " << std::strerror(original_errno) << '\n';81std::exit(process::exit_isolation_failure);82}838485/// Changes the owner of a path.86///87/// This function is intended to be called from a subprocess getting ready to88/// invoke an external binary. Therefore, if there is any error during the89/// setup, the new process is terminated with an error code.90///91/// \param file The path to the file or directory to affect.92/// \param uid The UID to set on the path.93/// \param gid The GID to set on the path.94static void95do_chown(const fs::path& file, const uid_t uid, const gid_t gid)96{97if (::chown(file.c_str(), uid, gid) == -1)98fail(F("chown(%s, %s, %s) failed; UID is %s and GID is %s")99% file % uid % gid % ::getuid() % ::getgid(), errno);100}101102103/// Resets the environment of the process to a known state.104///105/// \param work_directory Path to the work directory being used.106///107/// \throw std::runtime_error If there is a problem setting up the environment.108static void109prepare_environment(const fs::path& work_directory)110{111const char* to_unset[] = { "LANG", "LC_ALL", "LC_COLLATE", "LC_CTYPE",112"LC_MESSAGES", "LC_MONETARY", "LC_NUMERIC",113"LC_TIME", NULL };114const char** iter;115for (iter = to_unset; *iter != NULL; ++iter) {116utils::unsetenv(*iter);117}118119utils::setenv("HOME", work_directory.str());120utils::setenv("TMPDIR", work_directory.str());121utils::setenv("TZ", "UTC");122}123124125} // anonymous namespace126127128/// Cleans up the container process to run a new child.129///130/// If there is any error during the setup, the new process is terminated131/// with an error code.132///133/// \param unprivileged_user Unprivileged user to run the test case as.134/// \param work_directory Path to the test case-specific work directory.135void136process::isolate_child(const optional< passwd::user >& unprivileged_user,137const fs::path& work_directory)138{139isolate_path(unprivileged_user, work_directory);140if (::chdir(work_directory.c_str()) == -1)141fail(F("chdir(%s) failed") % work_directory, errno);142143utils::unlimit_core_size();144if (!signals::reset_all()) {145LW("Failed to reset one or more signals to their default behavior");146}147prepare_environment(work_directory);148(void)::umask(0022);149150if (unprivileged_user && passwd::current_user().is_root()) {151const passwd::user& user = unprivileged_user.get();152153if (user.gid != ::getgid()) {154if (::setgid(user.gid) == -1)155fail(F("setgid(%s) failed; UID is %s and GID is %s")156% user.gid % ::getuid() % ::getgid(), errno);157if (::getuid() == 0) {158::gid_t groups[1];159groups[0] = user.gid;160if (::setgroups(1, groups) == -1)161fail(F("setgroups(1, [%s]) failed; UID is %s and GID is %s")162% user.gid % ::getuid() % ::getgid(), errno);163}164}165if (user.uid != ::getuid()) {166if (::setuid(user.uid) == -1)167fail(F("setuid(%s) failed; UID is %s and GID is %s")168% user.uid % ::getuid() % ::getgid(), errno);169}170}171}172173174/// Sets up a path to be writable by a child isolated with isolate_child.175///176/// If there is any error during the setup, the new process is terminated177/// with an error code.178///179/// The caller should use this to prepare any directory or file that the child180/// should be able to write to *before* invoking isolate_child(). Note that181/// isolate_child() will use isolate_path() on the work directory though.182///183/// \param unprivileged_user Unprivileged user to run the test case as.184/// \param file Path to the file to modify.185void186process::isolate_path(const optional< passwd::user >& unprivileged_user,187const fs::path& file)188{189if (!unprivileged_user || !passwd::current_user().is_root())190return;191const passwd::user& user = unprivileged_user.get();192193const bool change_group = user.gid != ::getgid();194const bool change_user = user.uid != ::getuid();195196if (!change_user && !change_group) {197// Keep same permissions.198} else if (change_user && change_group) {199do_chown(file, user.uid, user.gid);200} else if (!change_user && change_group) {201do_chown(file, ::getuid(), user.gid);202} else {203INV(change_user && !change_group);204do_chown(file, user.uid, ::getgid());205}206}207208209