|
|
|
/* 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();
|
|
|
|
int rc = write(fd, content.c_str(), content.size());
|
|
|
|
if (rc != (int)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;
|
|
|
|
int idx = fullpath.rfind('/');
|
|
|
|
std::string dir = fullpath.substr(0, idx);
|
|
|
|
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 != std::string::npos) {
|
|
|
|
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
|