1 /* MIT License
2 *
3 * Copyright (c) The c-ares project and its contributors
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to deal
7 * in the Software without restriction, including without limitation the rights
8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 * copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice (including the next
13 * paragraph) shall be included in all copies or substantial portions of the
14 * Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 * SOFTWARE.
23 *
24 * SPDX-License-Identifier: MIT
25 */
26 #include "ares-test.h"
27
28 #ifdef HAVE_CONTAINER
29
30 #include <sys/mount.h>
31 #include <sys/types.h>
32 #include <sys/stat.h>
33 #include <fcntl.h>
34
35 #include <iostream>
36 #include <functional>
37 #include <string>
38 #include <sstream>
39 #include <vector>
40
41 namespace ares {
42 namespace test {
43
44 namespace {
45
46 struct ContainerInfo {
47 ContainerFilesystem* fs_;
48 std::string hostname_;
49 std::string domainname_;
50 VoidToIntFn fn_;
51 };
52
EnterContainer(void * data)53 int EnterContainer(void *data) {
54 ContainerInfo *container = (ContainerInfo*)data;
55
56 if (verbose) {
57 std::cerr << "Running function in container {chroot='"
58 << container->fs_->root() << "', hostname='" << container->hostname_
59 << "', domainname='" << container->domainname_ << "'}"
60 << std::endl;
61 }
62
63 // Ensure we are apparently root before continuing.
64 int count = 10;
65 while (getuid() != 0 && count > 0) {
66 std::this_thread::sleep_for(std::chrono::milliseconds(100));
67 count--;
68 }
69 if (getuid() != 0) {
70 std::cerr << "Child in user namespace has uid " << getuid() << std::endl;
71 return -1;
72 }
73 if (!container->fs_->mountpt().empty()) {
74 // We want to bind mount this inside the specified directory.
75 std::string innerdir = container->fs_->root() + container->fs_->mountpt();
76 if (verbose) std::cerr << " mount --bind " << container->fs_->mountpt()
77 << " " << innerdir << std::endl;
78 int rc = mount(container->fs_->mountpt().c_str(), innerdir.c_str(),
79 "none", MS_BIND, 0);
80 if (rc != 0) {
81 std::cerr << "Warning: failed to bind mount " << container->fs_->mountpt() << " at "
82 << innerdir << ", errno=" << errno << std::endl;
83 }
84 }
85
86 // Move into the specified directory.
87 if (chdir(container->fs_->root().c_str()) != 0) {
88 std::cerr << "Failed to chdir('" << container->fs_->root()
89 << "'), errno=" << errno << std::endl;
90 return -1;
91 }
92 // And make it the new root directory;
93 char buffer[PATH_MAX + 1];
94 if (getcwd(buffer, PATH_MAX) == NULL) {
95 std::cerr << "failed to retrieve cwd, errno=" << errno << std::endl;
96 return -1;
97 }
98 buffer[PATH_MAX] = '\0';
99 if (chroot(buffer) != 0) {
100 std::cerr << "chroot('" << buffer << "') failed, errno=" << errno << std::endl;
101 return -1;
102 }
103
104 // Set host/domainnames if specified
105 if (!container->hostname_.empty()) {
106 if (sethostname(container->hostname_.c_str(),
107 container->hostname_.size()) != 0) {
108 std::cerr << "Failed to sethostname('" << container->hostname_
109 << "'), errno=" << errno << std::endl;
110 return -1;
111 }
112 }
113 if (!container->domainname_.empty()) {
114 if (setdomainname(container->domainname_.c_str(),
115 container->domainname_.size()) != 0) {
116 std::cerr << "Failed to setdomainname('" << container->domainname_
117 << "'), errno=" << errno << std::endl;
118 return -1;
119 }
120 }
121
122 return container->fn_();
123 }
124
125 } // namespace
126
127 // Run a function while:
128 // - chroot()ed into a particular directory
129 // - having a specified hostname/domainname
130
RunInContainer(ContainerFilesystem * fs,const std::string & hostname,const std::string & domainname,VoidToIntFn fn)131 int RunInContainer(ContainerFilesystem* fs, const std::string& hostname,
132 const std::string& domainname, VoidToIntFn fn) {
133 const int stack_size = 1024 * 1024;
134 std::vector<byte> stack(stack_size, 0);
135 ContainerInfo container = {fs, hostname, domainname, fn};
136
137 // Start a child process in a new user and UTS namespace
138 pid_t child = clone(EnterContainer, stack.data() + stack_size,
139 CLONE_VM|CLONE_NEWNS|CLONE_NEWUSER|CLONE_NEWUTS|SIGCHLD,
140 (void *)&container);
141 if (child < 0) {
142 std::cerr << "Failed to clone(), errno=" << errno << std::endl;
143 return -1;
144 }
145
146 // Build the UID map that makes us look like root inside the namespace.
147 std::stringstream mapfiless;
148 mapfiless << "/proc/" << child << "/uid_map";
149 std::string mapfile = mapfiless.str();
150 int fd = open(mapfile.c_str(), O_CREAT|O_WRONLY|O_TRUNC, 0644);
151 if (fd < 0) {
152 std::cerr << "Failed to create '" << mapfile << "'" << std::endl;
153 return -1;
154 }
155 std::stringstream contentss;
156 contentss << "0 " << getuid() << " 1" << std::endl;
157 std::string content = contentss.str();
158 ssize_t rc = write(fd, content.c_str(), content.size());
159 if (rc != (ssize_t)content.size()) {
160 std::cerr << "Failed to write uid map to '" << mapfile << "'" << std::endl;
161 }
162 close(fd);
163
164 // Wait for the child process and retrieve its status.
165 int status;
166 waitpid(child, &status, 0);
167 if (rc <= 0) {
168 std::cerr << "Failed to waitpid(" << child << ")" << std::endl;
169 return -1;
170 }
171 if (!WIFEXITED(status)) {
172 std::cerr << "Child " << child << " did not exit normally" << std::endl;
173 return -1;
174 }
175 return status;
176 }
177
ContainerFilesystem(NameContentList files,const std::string & mountpt)178 ContainerFilesystem::ContainerFilesystem(NameContentList files, const std::string& mountpt) {
179 rootdir_ = TempNam(nullptr, "ares-chroot");
180 mkdir(rootdir_.c_str(), 0755);
181 dirs_.push_front(rootdir_);
182 for (const auto& nc : files) {
183 std::string fullpath = rootdir_ + nc.first;
184 size_t idx = fullpath.rfind('/');
185 std::string dir;
186 if (idx != SIZE_MAX) {
187 dir = fullpath.substr(0, idx);
188 } else {
189 dir = fullpath;
190 }
191 EnsureDirExists(dir);
192 files_.push_back(std::unique_ptr<TransientFile>(
193 new TransientFile(fullpath, nc.second)));
194 }
195 if (!mountpt.empty()) {
196 char buffer[PATH_MAX + 1];
197 if (realpath(mountpt.c_str(), buffer)) {
198 mountpt_ = buffer;
199 std::string fullpath = rootdir_ + mountpt_;
200 EnsureDirExists(fullpath);
201 }
202 }
203 }
204
~ContainerFilesystem()205 ContainerFilesystem::~ContainerFilesystem() {
206 files_.clear();
207 for (const std::string& dir : dirs_) {
208 rmdir(dir.c_str());
209 }
210 }
211
EnsureDirExists(const std::string & dir)212 void ContainerFilesystem::EnsureDirExists(const std::string& dir) {
213 if (std::find(dirs_.begin(), dirs_.end(), dir) != dirs_.end()) {
214 return;
215 }
216 size_t idx = dir.rfind('/');
217 if (idx != SIZE_MAX) {
218 std::string prevdir = dir.substr(0, idx);
219 EnsureDirExists(prevdir);
220 }
221 // Ensure this directory is in the list before its ancestors.
222 mkdir(dir.c_str(), 0755);
223 dirs_.push_front(dir);
224 }
225
226 } // namespace test
227 } // namespace ares
228
229 #endif
230