mirror of https://github.com/c-ares/c-ares.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
229 lines
7.2 KiB
229 lines
7.2 KiB
/* 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 <sys/mount.h> |
|
#include <sys/types.h> |
|
#include <sys/stat.h> |
|
#include <fcntl.h> |
|
|
|
#include <iostream> |
|
#include <functional> |
|
#include <string> |
|
#include <sstream> |
|
#include <vector> |
|
|
|
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<byte> 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<TransientFile>( |
|
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
|
|
|