Created
October 22, 2017 02:14
-
-
Save lhchavez/c942fb342b398b52687a1d9267998df1 to your computer and use it in GitHub Desktop.
Create namespaces and bind their nsfs FDs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <dirent.h> | |
#include <errno.h> | |
#include <fcntl.h> | |
#include <getopt.h> | |
#include <signal.h> | |
#include <stdarg.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <sys/mount.h> | |
#include <sys/stat.h> | |
#include <sys/types.h> | |
#include <sys/wait.h> | |
#include <syscall.h> | |
#include <unistd.h> | |
#include <stack> | |
#include <string> | |
#include <utility> | |
#include <vector> | |
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ | |
TypeName(const TypeName&) = delete; \ | |
TypeName& operator=(const TypeName&) = delete | |
namespace { | |
std::string StringPrintf(const char* format, ...) { | |
va_list args; | |
va_start(args, format); | |
char* dst = nullptr; | |
int written = vasprintf(&dst, format, args); | |
if (written == -1) { | |
fprintf(stderr, "Unable to allocate buffer: %m\n"); | |
return std::string(); | |
} | |
va_end(args); | |
std::string retval(dst, written); | |
free(dst); | |
return retval; | |
} | |
bool CleanupDirectory(const std::string& dirname) { | |
DIR* dir = opendir(dirname.c_str()); | |
if (!dir) { | |
fprintf(stderr, "Unable to open directory %s\n", dirname.c_str()); | |
return false; | |
} | |
bool result = false; | |
while (true) { | |
errno = 0; | |
struct dirent* entry = readdir(dir); | |
if (!entry) { | |
if (errno != 0) { | |
fprintf(stderr, "Unable to read directory %s\n", dirname.c_str()); | |
result = false; | |
} | |
break; | |
} | |
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) | |
continue; | |
std::string path = dirname + "/" + entry->d_name; | |
if (umount2(path.c_str(), MNT_DETACH) == -1) { | |
fprintf(stderr, "Unable to unmount %s: %m\n", path.c_str()); | |
// Not considered an error unless we cannot unlink. | |
} | |
if (unlink(path.c_str()) == -1) { | |
fprintf(stderr, "Unable to unlink %s: %m\n", path.c_str()); | |
result = false; | |
} | |
} | |
closedir(dir); | |
return result; | |
} | |
class ScopedFD { | |
public: | |
static constexpr int kInvalidFd = -1; | |
explicit ScopedFD(int fd = kInvalidFd) : fd_(fd) {} | |
~ScopedFD() { reset(); } | |
bool is_valid() const { return fd_ != kInvalidFd; } | |
int get() const { return fd_; } | |
void reset(int fd = kInvalidFd) { | |
if (is_valid()) | |
close(fd_); | |
fd_ = fd; | |
} | |
private: | |
int fd_; | |
DISALLOW_COPY_AND_ASSIGN(ScopedFD); | |
}; | |
class ScopedDir { | |
public: | |
ScopedDir() = default; | |
~ScopedDir() { reset(); } | |
bool Create(const std::string& path) { | |
if (mkdir(path.c_str(), 0755) == -1) { | |
fprintf(stderr, "Unable to create directory %s: %m\n", path.c_str()); | |
return false; | |
} | |
path_ = path; | |
return true; | |
} | |
const std::string& path() const { return path_; } | |
void reset() { | |
if (path_.empty()) | |
return; | |
CleanupDirectory(path_); | |
path_ = std::string(); | |
} | |
void release() { path_ = std::string(); } | |
private: | |
std::string path_; | |
DISALLOW_COPY_AND_ASSIGN(ScopedDir); | |
}; | |
class ScopedMount { | |
public: | |
ScopedMount() = default; | |
~ScopedMount() { reset(); } | |
bool Mount(const std::string& source, | |
const std::string& target, | |
const std::string& fstype, | |
int flags, | |
const std::string& data) { | |
reset(); | |
if (mount(source.c_str(), target.c_str(), fstype.c_str(), flags, | |
data.c_str()) == -1) { | |
fprintf(stderr, "Unable to mount %s: %m\n", target.c_str()); | |
return false; | |
} | |
destination_ = target; | |
return true; | |
} | |
void reset() { | |
if (destination_.empty()) | |
return; | |
if (umount2(destination_.c_str(), MNT_DETACH)) | |
fprintf(stderr, "Unable to umount %s: %m\n", destination_.c_str()); | |
destination_ = std::string(); | |
} | |
void release() { destination_ = std::string(); } | |
private: | |
std::string destination_; | |
DISALLOW_COPY_AND_ASSIGN(ScopedMount); | |
}; | |
bool Touch(const std::string& path) { | |
ScopedFD fd(creat(path.c_str(), 0644)); | |
if (!fd.is_valid()) { | |
fprintf(stderr, "Unable to create %s: %m\n", path.c_str()); | |
return false; | |
} | |
return true; | |
} | |
class WaitablePipe { | |
public: | |
WaitablePipe() = default; | |
~WaitablePipe() = default; | |
bool Create() { | |
int raw_fds[2]; | |
if (pipe2(raw_fds, O_CLOEXEC) == -1) { | |
return false; | |
} | |
pipe_fd_[0].reset(raw_fds[0]); | |
pipe_fd_[1].reset(raw_fds[1]); | |
return true; | |
} | |
void Wait() { | |
char data; | |
pipe_fd_[1].reset(); | |
TEMP_FAILURE_RETRY(read(pipe_fd_[0].get(), &data, sizeof(data))); | |
pipe_fd_[0].reset(); | |
} | |
void Signal() { | |
pipe_fd_[0].reset(); | |
pipe_fd_[1].reset(); | |
} | |
private: | |
ScopedFD pipe_fd_[2]; | |
DISALLOW_COPY_AND_ASSIGN(WaitablePipe); | |
}; | |
bool DirEntries(const std::string& dirname, | |
std::vector<std::string>* entries_out) { | |
DIR* dir = opendir(dirname.c_str()); | |
if (!dir) { | |
fprintf(stderr, "Unable to open directory %s\n", dirname.c_str()); | |
return false; | |
} | |
while (true) { | |
errno = 0; | |
struct dirent* entry = readdir(dir); | |
if (!entry) { | |
if (errno != 0) { | |
fprintf(stderr, "Unable to read directory %s\n", dirname.c_str()); | |
return false; | |
} | |
break; | |
} | |
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) | |
continue; | |
entries_out->push_back(entry->d_name); | |
} | |
closedir(dir); | |
return true; | |
} | |
bool MountNamespaceFds(pid_t pid, const std::string& path) { | |
std::string ns_path = StringPrintf("/proc/%d/ns", pid); | |
std::vector<std::string> ns_names; | |
if (!DirEntries(ns_path, &ns_names)) | |
return false; | |
for (const auto& ns_name : ns_names) { | |
std::string src_path = | |
StringPrintf("%s/%s", ns_path.c_str(), ns_name.c_str()); | |
std::string target_path = | |
StringPrintf("%s/%s", path.c_str(), ns_name.c_str()); | |
if (!Touch(target_path)) | |
return false; | |
if (mount(src_path.c_str(), target_path.c_str(), nullptr, MS_BIND, | |
nullptr) == -1) { | |
fprintf(stderr, "Unable to bind-mount %s: %m\n", src_path.c_str()); | |
return false; | |
} | |
} | |
return true; | |
} | |
void PrintUsage(char* program_name) { | |
fprintf(stderr, "Usage: %s [options] <directory>\n", program_name); | |
fprintf(stderr, "\n"); | |
fprintf(stderr, | |
"Create namespaces and bind their nsfs FDs in <directory>.\n"); | |
fprintf(stderr, "\n"); | |
fprintf(stderr, "Options:\n"); | |
fprintf(stderr, " -m, --mount create a mount namespace\n"); | |
fprintf(stderr, " -u, --uts create a UTS namespace (hostname etc)\n"); | |
fprintf(stderr, " -i, --ipc create a System V IPC namespace\n"); | |
fprintf(stderr, " -n, --net create a network namespace\n"); | |
fprintf(stderr, " -p, --pid create a pid namespace\n"); | |
fprintf(stderr, " -U, --user create a user namespace\n"); | |
fprintf(stderr, "\n"); | |
fprintf(stderr, " -h, --help display this help and exit\n"); | |
exit(EXIT_FAILURE); | |
} | |
bool ParseArgs(int argc, | |
char* argv[], | |
int* flags_out, | |
std::string* target_out) { | |
static struct option long_options[] = { | |
{"mount", no_argument, nullptr, 'm'}, {"uts", no_argument, nullptr, 'u'}, | |
{"ipc", no_argument, nullptr, 'i'}, {"net", no_argument, nullptr, 'n'}, | |
{"pid", no_argument, nullptr, 'p'}, {"user", no_argument, nullptr, 'U'}, | |
{"help", no_argument, nullptr, 'h'}, {nullptr, 0, nullptr, 0}}; | |
int opt; | |
*flags_out = 0; | |
while ((opt = getopt_long(argc, argv, "muinpUh", long_options, nullptr)) != | |
-1) { | |
switch (opt) { | |
case 'm': | |
*flags_out |= CLONE_NEWNS; | |
break; | |
case 'u': | |
*flags_out |= CLONE_NEWUTS; | |
break; | |
case 'i': | |
*flags_out |= CLONE_NEWIPC; | |
break; | |
case 'n': | |
*flags_out |= CLONE_NEWNET; | |
break; | |
case 'p': | |
*flags_out |= CLONE_NEWPID; | |
break; | |
case 'U': | |
*flags_out |= CLONE_NEWUSER; | |
break; | |
case 'h': | |
default: | |
PrintUsage(argv[0]); | |
break; | |
} | |
} | |
if (!*flags_out || optind >= argc) | |
PrintUsage(argv[0]); | |
*target_out = std::string(argv[optind]); | |
return true; | |
} | |
} // namespace | |
int main(int argc, char* argv[]) { | |
int mount_flags; | |
std::string target_path; | |
if (!ParseArgs(argc, argv, &mount_flags, &target_path)) | |
return EXIT_FAILURE; | |
ScopedDir target; | |
if (!target.Create(target_path)) | |
return EXIT_FAILURE; | |
ScopedMount tmpfs; | |
if (!tmpfs.Mount("tmpfs", target.path(), "tmpfs", 0, "")) | |
return EXIT_FAILURE; | |
if (mount(nullptr, target.path().c_str(), nullptr, MS_PRIVATE, nullptr) == | |
-1) { | |
fprintf(stderr, "Unable to remount %s as private: %m\n", | |
target.path().c_str()); | |
return EXIT_FAILURE; | |
} | |
WaitablePipe parent_pipe; | |
if (!parent_pipe.Create()) { | |
fprintf(stderr, "Unable to create a pipe: %m\n"); | |
return EXIT_FAILURE; | |
} | |
pid_t pid = syscall(SYS_clone, mount_flags | SIGCHLD, nullptr); | |
if (pid == -1) { | |
fprintf(stderr, "Unable to create a new process: %m\n"); | |
return EXIT_FAILURE; | |
} | |
if (pid == 0) { | |
parent_pipe.Wait(); | |
_exit(EXIT_SUCCESS); | |
} | |
int retval = EXIT_SUCCESS; | |
if (MountNamespaceFds(pid, target.path())) { | |
target.release(); | |
tmpfs.release(); | |
} else { | |
retval = EXIT_FAILURE; | |
} | |
parent_pipe.Signal(); | |
while (TEMP_FAILURE_RETRY(wait(nullptr)) != -1 || errno != ECHILD) | |
; | |
return retval; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment