/* MIT License * * Copyright (c) The c-ares project and its contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * SPDX-License-Identifier: MIT */ #include "ares-test.h" #ifdef HAVE_CONTAINER #include #include #include #include #include #include #include #include #include namespace ares { namespace test { namespace { struct ContainerInfo { ContainerFilesystem* fs_; std::string hostname_; std::string domainname_; VoidToIntFn fn_; }; int EnterContainer(void *data) { ContainerInfo *container = (ContainerInfo*)data; if (verbose) { std::cerr << "Running function in container {chroot='" << container->fs_->root() << "', hostname='" << container->hostname_ << "', domainname='" << container->domainname_ << "'}" << std::endl; } // Ensure we are apparently root before continuing. int count = 10; while (getuid() != 0 && count > 0) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); count--; } if (getuid() != 0) { std::cerr << "Child in user namespace has uid " << getuid() << std::endl; return -1; } if (!container->fs_->mountpt().empty()) { // We want to bind mount this inside the specified directory. std::string innerdir = container->fs_->root() + container->fs_->mountpt(); if (verbose) std::cerr << " mount --bind " << container->fs_->mountpt() << " " << innerdir << std::endl; int rc = mount(container->fs_->mountpt().c_str(), innerdir.c_str(), "none", MS_BIND, 0); if (rc != 0) { std::cerr << "Warning: failed to bind mount " << container->fs_->mountpt() << " at " << innerdir << ", errno=" << errno << std::endl; } } // Move into the specified directory. if (chdir(container->fs_->root().c_str()) != 0) { std::cerr << "Failed to chdir('" << container->fs_->root() << "'), errno=" << errno << std::endl; return -1; } // And make it the new root directory; char buffer[PATH_MAX + 1]; if (getcwd(buffer, PATH_MAX) == NULL) { std::cerr << "failed to retrieve cwd, errno=" << errno << std::endl; return -1; } buffer[PATH_MAX] = '\0'; if (chroot(buffer) != 0) { std::cerr << "chroot('" << buffer << "') failed, errno=" << errno << std::endl; return -1; } // Set host/domainnames if specified if (!container->hostname_.empty()) { if (sethostname(container->hostname_.c_str(), container->hostname_.size()) != 0) { std::cerr << "Failed to sethostname('" << container->hostname_ << "'), errno=" << errno << std::endl; return -1; } } if (!container->domainname_.empty()) { if (setdomainname(container->domainname_.c_str(), container->domainname_.size()) != 0) { std::cerr << "Failed to setdomainname('" << container->domainname_ << "'), errno=" << errno << std::endl; return -1; } } return container->fn_(); } } // namespace // Run a function while: // - chroot()ed into a particular directory // - having a specified hostname/domainname int RunInContainer(ContainerFilesystem* fs, const std::string& hostname, const std::string& domainname, VoidToIntFn fn) { const int stack_size = 1024 * 1024; std::vector stack(stack_size, 0); ContainerInfo container = {fs, hostname, domainname, fn}; // Start a child process in a new user and UTS namespace pid_t child = clone(EnterContainer, stack.data() + stack_size, CLONE_VM|CLONE_NEWNS|CLONE_NEWUSER|CLONE_NEWUTS|SIGCHLD, (void *)&container); if (child < 0) { std::cerr << "Failed to clone(), errno=" << errno << std::endl; return -1; } // Build the UID map that makes us look like root inside the namespace. std::stringstream mapfiless; mapfiless << "/proc/" << child << "/uid_map"; std::string mapfile = mapfiless.str(); int fd = open(mapfile.c_str(), O_CREAT|O_WRONLY|O_TRUNC, 0644); if (fd < 0) { std::cerr << "Failed to create '" << mapfile << "'" << std::endl; return -1; } std::stringstream contentss; contentss << "0 " << getuid() << " 1" << std::endl; std::string content = contentss.str(); ssize_t rc = write(fd, content.c_str(), content.size()); if (rc != (ssize_t)content.size()) { std::cerr << "Failed to write uid map to '" << mapfile << "'" << std::endl; } close(fd); // Wait for the child process and retrieve its status. int status; waitpid(child, &status, 0); if (rc <= 0) { std::cerr << "Failed to waitpid(" << child << ")" << std::endl; return -1; } if (!WIFEXITED(status)) { std::cerr << "Child " << child << " did not exit normally" << std::endl; return -1; } return status; } ContainerFilesystem::ContainerFilesystem(NameContentList files, const std::string& mountpt) { rootdir_ = TempNam(nullptr, "ares-chroot"); mkdir(rootdir_.c_str(), 0755); dirs_.push_front(rootdir_); for (const auto& nc : files) { std::string fullpath = rootdir_ + nc.first; size_t idx = fullpath.rfind('/'); std::string dir; if (idx != SIZE_MAX) { dir = fullpath.substr(0, idx); } else { dir = fullpath; } EnsureDirExists(dir); files_.push_back(std::unique_ptr( new TransientFile(fullpath, nc.second))); } if (!mountpt.empty()) { char buffer[PATH_MAX + 1]; if (realpath(mountpt.c_str(), buffer)) { mountpt_ = buffer; std::string fullpath = rootdir_ + mountpt_; EnsureDirExists(fullpath); } } } ContainerFilesystem::~ContainerFilesystem() { files_.clear(); for (const std::string& dir : dirs_) { rmdir(dir.c_str()); } } void ContainerFilesystem::EnsureDirExists(const std::string& dir) { if (std::find(dirs_.begin(), dirs_.end(), dir) != dirs_.end()) { return; } size_t idx = dir.rfind('/'); if (idx != SIZE_MAX) { std::string prevdir = dir.substr(0, idx); EnsureDirExists(prevdir); } // Ensure this directory is in the list before its ancestors. mkdir(dir.c_str(), 0755); dirs_.push_front(dir); } } // namespace test } // namespace ares #endif