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/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 55 namespace fs = utils::fs; 56 namespace passwd = utils::passwd; 57 namespace process = utils::process; 58 namespace signals = utils::signals; 59 60 using utils::optional; 61 62 63 /// Magic exit code to denote an error while preparing the subprocess. 64 const int process::exit_isolation_failure = 124; 65 66 67 namespace { 68 69 70 static 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. 78 static void 79 fail(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. 95 static void 96 do_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. 109 static void 110 prepare_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. 136 void 137 process::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. 186 void 187 process::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