ares-test-ns.cc (7382B)
1 /* MIT License 2 * 3 * Copyright (c) The c-ares project and its contributors 4 * 5 * Permission is hereby granted, free of charge, to any person obtaining a copy 6 * of this software and associated documentation files (the "Software"), to deal 7 * in the Software without restriction, including without limitation the rights 8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 * copies of the Software, and to permit persons to whom the Software is 10 * furnished to do so, subject to the following conditions: 11 * 12 * The above copyright notice and this permission notice (including the next 13 * paragraph) shall be included in all copies or substantial portions of the 14 * Software. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 * SOFTWARE. 23 * 24 * SPDX-License-Identifier: MIT 25 */ 26 #include "ares-test.h" 27 28 #ifdef HAVE_CONTAINER 29 30 #include <sys/mount.h> 31 #include <sys/types.h> 32 #include <sys/stat.h> 33 #include <fcntl.h> 34 35 #include <iostream> 36 #include <functional> 37 #include <string> 38 #include <sstream> 39 #include <vector> 40 41 namespace ares { 42 namespace test { 43 44 namespace { 45 46 struct ContainerInfo { 47 ContainerFilesystem* fs_; 48 std::string hostname_; 49 std::string domainname_; 50 VoidToIntFn fn_; 51 }; 52 53 int EnterContainer(void *data) { 54 ContainerInfo *container = (ContainerInfo*)data; 55 56 if (verbose) { 57 std::cerr << "Running function in container {chroot='" 58 << container->fs_->root() << "', hostname='" << container->hostname_ 59 << "', domainname='" << container->domainname_ << "'}" 60 << std::endl; 61 } 62 63 // Ensure we are apparently root before continuing. 64 int count = 10; 65 while (getuid() != 0 && count > 0) { 66 std::this_thread::sleep_for(std::chrono::milliseconds(100)); 67 count--; 68 } 69 if (getuid() != 0) { 70 std::cerr << "Child in user namespace has uid " << getuid() << std::endl; 71 return -1; 72 } 73 if (!container->fs_->mountpt().empty()) { 74 // We want to bind mount this inside the specified directory. 75 std::string innerdir = container->fs_->root() + container->fs_->mountpt(); 76 if (verbose) std::cerr << " mount --bind " << container->fs_->mountpt() 77 << " " << innerdir << std::endl; 78 int rc = mount(container->fs_->mountpt().c_str(), innerdir.c_str(), 79 "none", MS_BIND, 0); 80 if (rc != 0) { 81 std::cerr << "Warning: failed to bind mount " << container->fs_->mountpt() << " at " 82 << innerdir << ", errno=" << errno << std::endl; 83 } 84 } 85 86 // Move into the specified directory. 87 if (chdir(container->fs_->root().c_str()) != 0) { 88 std::cerr << "Failed to chdir('" << container->fs_->root() 89 << "'), errno=" << errno << std::endl; 90 return -1; 91 } 92 // And make it the new root directory; 93 char buffer[PATH_MAX + 1]; 94 if (getcwd(buffer, PATH_MAX) == NULL) { 95 std::cerr << "failed to retrieve cwd, errno=" << errno << std::endl; 96 return -1; 97 } 98 buffer[PATH_MAX] = '\0'; 99 if (chroot(buffer) != 0) { 100 std::cerr << "chroot('" << buffer << "') failed, errno=" << errno << std::endl; 101 return -1; 102 } 103 104 // Set host/domainnames if specified 105 if (!container->hostname_.empty()) { 106 if (sethostname(container->hostname_.c_str(), 107 container->hostname_.size()) != 0) { 108 std::cerr << "Failed to sethostname('" << container->hostname_ 109 << "'), errno=" << errno << std::endl; 110 return -1; 111 } 112 } 113 if (!container->domainname_.empty()) { 114 if (setdomainname(container->domainname_.c_str(), 115 container->domainname_.size()) != 0) { 116 std::cerr << "Failed to setdomainname('" << container->domainname_ 117 << "'), errno=" << errno << std::endl; 118 return -1; 119 } 120 } 121 122 return container->fn_(); 123 } 124 125 } // namespace 126 127 // Run a function while: 128 // - chroot()ed into a particular directory 129 // - having a specified hostname/domainname 130 131 int RunInContainer(ContainerFilesystem* fs, const std::string& hostname, 132 const std::string& domainname, VoidToIntFn fn) { 133 const int stack_size = 1024 * 1024; 134 std::vector<byte> stack(stack_size, 0); 135 ContainerInfo container = {fs, hostname, domainname, fn}; 136 137 // Start a child process in a new user and UTS namespace 138 pid_t child = clone(EnterContainer, stack.data() + stack_size, 139 CLONE_VM|CLONE_NEWNS|CLONE_NEWUSER|CLONE_NEWUTS|SIGCHLD, 140 (void *)&container); 141 if (child < 0) { 142 std::cerr << "Failed to clone(), errno=" << errno << std::endl; 143 return -1; 144 } 145 146 // Build the UID map that makes us look like root inside the namespace. 147 std::stringstream mapfiless; 148 mapfiless << "/proc/" << child << "/uid_map"; 149 std::string mapfile = mapfiless.str(); 150 int fd = open(mapfile.c_str(), O_CREAT|O_WRONLY|O_TRUNC, 0644); 151 if (fd < 0) { 152 std::cerr << "Failed to create '" << mapfile << "'" << std::endl; 153 return -1; 154 } 155 std::stringstream contentss; 156 contentss << "0 " << getuid() << " 1" << std::endl; 157 std::string content = contentss.str(); 158 ssize_t rc = write(fd, content.c_str(), content.size()); 159 if (rc != (ssize_t)content.size()) { 160 std::cerr << "Failed to write uid map to '" << mapfile << "'" << std::endl; 161 } 162 close(fd); 163 164 // Wait for the child process and retrieve its status. 165 int status; 166 waitpid(child, &status, 0); 167 if (rc <= 0) { 168 std::cerr << "Failed to waitpid(" << child << ")" << std::endl; 169 return -1; 170 } 171 if (!WIFEXITED(status)) { 172 std::cerr << "Child " << child << " did not exit normally" << std::endl; 173 return -1; 174 } 175 return status; 176 } 177 178 ContainerFilesystem::ContainerFilesystem(NameContentList files, const std::string& mountpt) { 179 rootdir_ = TempNam(nullptr, "ares-chroot"); 180 mkdir(rootdir_.c_str(), 0755); 181 dirs_.push_front(rootdir_); 182 for (const auto& nc : files) { 183 std::string fullpath = rootdir_ + nc.first; 184 size_t idx = fullpath.rfind('/'); 185 std::string dir; 186 if (idx != SIZE_MAX) { 187 dir = fullpath.substr(0, idx); 188 } else { 189 dir = fullpath; 190 } 191 EnsureDirExists(dir); 192 files_.push_back(std::unique_ptr<TransientFile>( 193 new TransientFile(fullpath, nc.second))); 194 } 195 if (!mountpt.empty()) { 196 char buffer[PATH_MAX + 1]; 197 if (realpath(mountpt.c_str(), buffer)) { 198 mountpt_ = buffer; 199 std::string fullpath = rootdir_ + mountpt_; 200 EnsureDirExists(fullpath); 201 } 202 } 203 } 204 205 ContainerFilesystem::~ContainerFilesystem() { 206 files_.clear(); 207 for (const std::string& dir : dirs_) { 208 rmdir(dir.c_str()); 209 } 210 } 211 212 void ContainerFilesystem::EnsureDirExists(const std::string& dir) { 213 if (std::find(dirs_.begin(), dirs_.end(), dir) != dirs_.end()) { 214 return; 215 } 216 size_t idx = dir.rfind('/'); 217 if (idx != SIZE_MAX) { 218 std::string prevdir = dir.substr(0, idx); 219 EnsureDirExists(prevdir); 220 } 221 // Ensure this directory is in the list before its ancestors. 222 mkdir(dir.c_str(), 0755); 223 dirs_.push_front(dir); 224 } 225 226 } // namespace test 227 } // namespace ares 228 229 #endif