ares-test-ns.cc 5.9 KB

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