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 31extern "C" { 32#include <sys/stat.h> 33 34#include <grp.h> 35#include <signal.h> 36#include <unistd.h> 37} 38 39#include <cerrno> 40#include <cstdlib> 41#include <cstring> 42#include <iostream> 43 44#include "utils/defs.hpp" 45#include "utils/format/macros.hpp" 46#include "utils/fs/path.hpp" 47#include "utils/env.hpp" 48#include "utils/logging/macros.hpp" 49#include "utils/optional.ipp" 50#include "utils/passwd.hpp" 51#include "utils/sanity.hpp" 52#include "utils/signals/misc.hpp" 53#include "utils/stacktrace.hpp" 54 55namespace fs = utils::fs; 56namespace passwd = utils::passwd; 57namespace process = utils::process; 58namespace signals = utils::signals; 59 60using utils::optional; 61 62 63/// Magic exit code to denote an error while preparing the subprocess. 64const int process::exit_isolation_failure = 124; 65 66 67namespace { 68 69 70static void fail(const std::string&, const int) UTILS_NORETURN; 71 72 73/// Fails the process with an errno-based error message. 74/// 75/// \param message The message to print. The errno-based string will be 76/// appended to this, just like in perror(3). 77/// \param original_errno The error code to format. 78static void 79fail(const std::string& message, const int original_errno) 80{ 81 std::cerr << message << ": " << std::strerror(original_errno) << '\n'; 82 std::exit(process::exit_isolation_failure); 83} 84 85 86/// Changes the owner of a path. 87/// 88/// This function is intended to be called from a subprocess getting ready to 89/// invoke an external binary. Therefore, if there is any error during the 90/// setup, the new process is terminated with an error code. 91/// 92/// \param file The path to the file or directory to affect. 93/// \param uid The UID to set on the path. 94/// \param gid The GID to set on the path. 95static void 96do_chown(const fs::path& file, const uid_t uid, const gid_t gid) 97{ 98 if (::chown(file.c_str(), uid, gid) == -1) 99 fail(F("chown(%s, %s, %s) failed; UID is %s and GID is %s") 100 % file % uid % gid % ::getuid() % ::getgid(), errno); 101} 102 103 104/// Resets the environment of the process to a known state. 105/// 106/// \param work_directory Path to the work directory being used. 107/// 108/// \throw std::runtime_error If there is a problem setting up the environment. 109static void 110prepare_environment(const fs::path& work_directory) 111{ 112 const char* to_unset[] = { "LANG", "LC_ALL", "LC_COLLATE", "LC_CTYPE", 113 "LC_MESSAGES", "LC_MONETARY", "LC_NUMERIC", 114 "LC_TIME", NULL }; 115 const char** iter; 116 for (iter = to_unset; *iter != NULL; ++iter) { 117 utils::unsetenv(*iter); 118 } 119 120 utils::setenv("HOME", work_directory.str()); 121 utils::setenv("TMPDIR", work_directory.str()); 122 utils::setenv("TZ", "UTC"); 123} 124 125 126} // anonymous namespace 127 128 129/// Cleans up the container process to run a new child. 130/// 131/// If there is any error during the setup, the new process is terminated 132/// with an error code. 133/// 134/// \param unprivileged_user Unprivileged user to run the test case as. 135/// \param work_directory Path to the test case-specific work directory. 136void 137process::isolate_child(const optional< passwd::user >& unprivileged_user, 138 const fs::path& work_directory) 139{ 140 isolate_path(unprivileged_user, work_directory); 141 if (::chdir(work_directory.c_str()) == -1) 142 fail(F("chdir(%s) failed") % work_directory, errno); 143 144 utils::unlimit_core_size(); 145 if (!signals::reset_all()) { 146 LW("Failed to reset one or more signals to their default behavior"); 147 } 148 prepare_environment(work_directory); 149 (void)::umask(0022); 150 151 if (unprivileged_user && passwd::current_user().is_root()) { 152 const passwd::user& user = unprivileged_user.get(); 153 154 if (user.gid != ::getgid()) { 155 if (::setgid(user.gid) == -1) 156 fail(F("setgid(%s) failed; UID is %s and GID is %s") 157 % user.gid % ::getuid() % ::getgid(), errno); 158 if (::getuid() == 0) { 159 ::gid_t groups[1]; 160 groups[0] = user.gid; 161 if (::setgroups(1, groups) == -1) 162 fail(F("setgroups(1, [%s]) failed; UID is %s and GID is %s") 163 % user.gid % ::getuid() % ::getgid(), errno); 164 } 165 } 166 if (user.uid != ::getuid()) { 167 if (::setuid(user.uid) == -1) 168 fail(F("setuid(%s) failed; UID is %s and GID is %s") 169 % user.uid % ::getuid() % ::getgid(), errno); 170 } 171 } 172} 173 174 175/// Sets up a path to be writable by a child isolated with isolate_child. 176/// 177/// If there is any error during the setup, the new process is terminated 178/// with an error code. 179/// 180/// The caller should use this to prepare any directory or file that the child 181/// should be able to write to *before* invoking isolate_child(). Note that 182/// isolate_child() will use isolate_path() on the work directory though. 183/// 184/// \param unprivileged_user Unprivileged user to run the test case as. 185/// \param file Path to the file to modify. 186void 187process::isolate_path(const optional< passwd::user >& unprivileged_user, 188 const fs::path& file) 189{ 190 if (!unprivileged_user || !passwd::current_user().is_root()) 191 return; 192 const passwd::user& user = unprivileged_user.get(); 193 194 const bool change_group = user.gid != ::getgid(); 195 const bool change_user = user.uid != ::getuid(); 196 197 if (!change_user && !change_group) { 198 // Keep same permissions. 199 } else if (change_user && change_group) { 200 do_chown(file, user.uid, user.gid); 201 } else if (!change_user && change_group) { 202 do_chown(file, ::getuid(), user.gid); 203 } else { 204 INV(change_user && !change_group); 205 do_chown(file, user.uid, ::getgid()); 206 } 207} 208