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