1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
|
#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) {
usleep(100000);
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
|