• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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