1 #include "ares-test.h"
2
3 #ifdef HAVE_CONTAINER
4
5 #include <sys/mount.h>
6 #include <sys/types.h>
7 #include <sys/stat.h>
8 #include <fcntl.h>
9
10 #include <iostream>
11 #include <functional>
12 #include <string>
13 #include <sstream>
14 #include <vector>
15
16 namespace ares {
17 namespace test {
18
19 namespace {
20
21 struct ContainerInfo {
22 ContainerFilesystem* fs_;
23 std::string hostname_;
24 std::string domainname_;
25 VoidToIntFn fn_;
26 };
27
EnterContainer(void * data)28 int EnterContainer(void *data) {
29 ContainerInfo *container = (ContainerInfo*)data;
30
31 if (verbose) {
32 std::cerr << "Running function in container {chroot='"
33 << container->fs_->root() << "', hostname='" << container->hostname_
34 << "', domainname='" << container->domainname_ << "'}"
35 << std::endl;
36 }
37
38 // Ensure we are apparently root before continuing.
39 int count = 10;
40 while (getuid() != 0 && count > 0) {
41 usleep(100000);
42 count--;
43 }
44 if (getuid() != 0) {
45 std::cerr << "Child in user namespace has uid " << getuid() << std::endl;
46 return -1;
47 }
48 if (!container->fs_->mountpt().empty()) {
49 // We want to bind mount this inside the specified directory.
50 std::string innerdir = container->fs_->root() + container->fs_->mountpt();
51 if (verbose) std::cerr << " mount --bind " << container->fs_->mountpt()
52 << " " << innerdir << std::endl;
53 int rc = mount(container->fs_->mountpt().c_str(), innerdir.c_str(),
54 "none", MS_BIND, 0);
55 if (rc != 0) {
56 std::cerr << "Warning: failed to bind mount " << container->fs_->mountpt() << " at "
57 << innerdir << ", errno=" << errno << std::endl;
58 }
59 }
60
61 // Move into the specified directory.
62 if (chdir(container->fs_->root().c_str()) != 0) {
63 std::cerr << "Failed to chdir('" << container->fs_->root()
64 << "'), errno=" << errno << std::endl;
65 return -1;
66 }
67 // And make it the new root directory;
68 char buffer[PATH_MAX + 1];
69 if (getcwd(buffer, PATH_MAX) == NULL) {
70 std::cerr << "failed to retrieve cwd, errno=" << errno << std::endl;
71 return -1;
72 }
73 buffer[PATH_MAX] = '\0';
74 if (chroot(buffer) != 0) {
75 std::cerr << "chroot('" << buffer << "') failed, errno=" << errno << std::endl;
76 return -1;
77 }
78
79 // Set host/domainnames if specified
80 if (!container->hostname_.empty()) {
81 if (sethostname(container->hostname_.c_str(),
82 container->hostname_.size()) != 0) {
83 std::cerr << "Failed to sethostname('" << container->hostname_
84 << "'), errno=" << errno << std::endl;
85 return -1;
86 }
87 }
88 if (!container->domainname_.empty()) {
89 if (setdomainname(container->domainname_.c_str(),
90 container->domainname_.size()) != 0) {
91 std::cerr << "Failed to setdomainname('" << container->domainname_
92 << "'), errno=" << errno << std::endl;
93 return -1;
94 }
95 }
96
97 return container->fn_();
98 }
99
100 } // namespace
101
102 // Run a function while:
103 // - chroot()ed into a particular directory
104 // - having a specified hostname/domainname
105
RunInContainer(ContainerFilesystem * fs,const std::string & hostname,const std::string & domainname,VoidToIntFn fn)106 int RunInContainer(ContainerFilesystem* fs, const std::string& hostname,
107 const std::string& domainname, VoidToIntFn fn) {
108 const int stack_size = 1024 * 1024;
109 std::vector<byte> stack(stack_size, 0);
110 ContainerInfo container = {fs, hostname, domainname, fn};
111
112 // Start a child process in a new user and UTS namespace
113 pid_t child = clone(EnterContainer, stack.data() + stack_size,
114 CLONE_VM|CLONE_NEWNS|CLONE_NEWUSER|CLONE_NEWUTS|SIGCHLD,
115 (void *)&container);
116 if (child < 0) {
117 std::cerr << "Failed to clone(), errno=" << errno << std::endl;
118 return -1;
119 }
120
121 // Build the UID map that makes us look like root inside the namespace.
122 std::stringstream mapfiless;
123 mapfiless << "/proc/" << child << "/uid_map";
124 std::string mapfile = mapfiless.str();
125 int fd = open(mapfile.c_str(), O_CREAT|O_WRONLY|O_TRUNC, 0644);
126 if (fd < 0) {
127 std::cerr << "Failed to create '" << mapfile << "'" << std::endl;
128 return -1;
129 }
130 std::stringstream contentss;
131 contentss << "0 " << getuid() << " 1" << std::endl;
132 std::string content = contentss.str();
133 int rc = write(fd, content.c_str(), content.size());
134 if (rc != (int)content.size()) {
135 std::cerr << "Failed to write uid map to '" << mapfile << "'" << std::endl;
136 }
137 close(fd);
138
139 // Wait for the child process and retrieve its status.
140 int status;
141 waitpid(child, &status, 0);
142 if (rc <= 0) {
143 std::cerr << "Failed to waitpid(" << child << ")" << std::endl;
144 return -1;
145 }
146 if (!WIFEXITED(status)) {
147 std::cerr << "Child " << child << " did not exit normally" << std::endl;
148 return -1;
149 }
150 return status;
151 }
152
ContainerFilesystem(NameContentList files,const std::string & mountpt)153 ContainerFilesystem::ContainerFilesystem(NameContentList files, const std::string& mountpt) {
154 rootdir_ = TempNam(nullptr, "ares-chroot");
155 mkdir(rootdir_.c_str(), 0755);
156 dirs_.push_front(rootdir_);
157 for (const auto& nc : files) {
158 std::string fullpath = rootdir_ + nc.first;
159 int idx = fullpath.rfind('/');
160 std::string dir = fullpath.substr(0, idx);
161 EnsureDirExists(dir);
162 files_.push_back(std::unique_ptr<TransientFile>(
163 new TransientFile(fullpath, nc.second)));
164 }
165 if (!mountpt.empty()) {
166 char buffer[PATH_MAX + 1];
167 if (realpath(mountpt.c_str(), buffer)) {
168 mountpt_ = buffer;
169 std::string fullpath = rootdir_ + mountpt_;
170 EnsureDirExists(fullpath);
171 }
172 }
173 }
174
~ContainerFilesystem()175 ContainerFilesystem::~ContainerFilesystem() {
176 files_.clear();
177 for (const std::string& dir : dirs_) {
178 rmdir(dir.c_str());
179 }
180 }
181
EnsureDirExists(const std::string & dir)182 void ContainerFilesystem::EnsureDirExists(const std::string& dir) {
183 if (std::find(dirs_.begin(), dirs_.end(), dir) != dirs_.end()) {
184 return;
185 }
186 size_t idx = dir.rfind('/');
187 if (idx != std::string::npos) {
188 std::string prevdir = dir.substr(0, idx);
189 EnsureDirExists(prevdir);
190 }
191 // Ensure this directory is in the list before its ancestors.
192 mkdir(dir.c_str(), 0755);
193 dirs_.push_front(dir);
194 }
195
196 } // namespace test
197 } // namespace ares
198
199 #endif
200