From a5511a0b0f74cc15c807bcee39fd9b4bc44beae2 Mon Sep 17 00:00:00 2001 From: Evgeny Zinoviev Date: Mon, 7 Dec 2020 23:47:39 +0300 Subject: implement a way for programs in the mount namespace (with glibc binaries) to launch processes in the original namespace (with musl binaries) --- .gitignore | 6 + Makefile | 26 ++-- config.h | 13 ++ macros.h | 24 +++ testclient.c | 44 ++++++ testserver.c | 96 ++++++++++++ utils.c | 154 ++++++++++++++++++ utils.h | 35 +++++ voidnsrun.c | 501 +++++++++++++++++++++++++++++++++++++++-------------------- voidnsundo.c | 154 ++++++++++++++++++ 10 files changed, 876 insertions(+), 177 deletions(-) create mode 100644 .gitignore create mode 100644 config.h create mode 100644 macros.h create mode 100644 testclient.c create mode 100644 testserver.c create mode 100644 utils.c create mode 100644 utils.h create mode 100644 voidnsundo.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c0651ec --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.o +.idea +voidnsrun +voidnsundo +testclient +testserver diff --git a/Makefile b/Makefile index 050d4ef..54945cb 100644 --- a/Makefile +++ b/Makefile @@ -1,27 +1,35 @@ CC := gcc -PROGRAM = voidnsrun CFLAGS = -O2 -std=c99 -Wall -W LDFLAGS = INSTALL = /usr/bin/env install PREFIX = /usr/local -OBJS = voidnsrun.o +all: voidnsrun voidnsundo -all: $(PROGRAM) +test: testserver testclient -$(PROGRAM): $(OBJS) +voidnsrun: voidnsrun.o utils.o $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) -install: $(PROGRAM) - $(INSTALL) $(PROGRAM) $(PREFIX)/bin - chmod u+s $(PREFIX)/bin/${PROGRAM} +voidnsundo: voidnsundo.o utils.o + $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) + +testserver: testserver.o utils.o + $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) + +testclient: testclient.o utils.o + $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) + +install: voidnsrun voidnsundo + $(INSTALL) voidnsrun voidnsundo $(PREFIX)/bin + chmod u+s $(PREFIX)/bin/voidnsrun $(PREFIX)/bin/voidnsundo clean: - rm -f $(OBJS) $(PROGRAM) + rm -f *.o voidnsrun voidnsundo testserver testclient %.o: %.c $(CC) $(CFLAGS) -c $^ -I. -o $@ -.PHONY: all install clean distclean +.PHONY: all install clean distclean \ No newline at end of file diff --git a/config.h b/config.h new file mode 100644 index 0000000..60f7fd4 --- /dev/null +++ b/config.h @@ -0,0 +1,13 @@ +#ifndef VOIDNSRUN_CONFIG_H +#define VOIDNSRUN_CONFIG_H + +#define PROG_VERSION "1.2" +#define USER_LISTS_MAX 50 +#define CONTAINER_DIR_VAR "VOIDNSRUN_DIR" +#define UNDO_BIN_VAR "VOIDNSUNDO_BIN" +#define SOCK_DIR_VAR "VOIDNSRUN_SOCK_DIR" +#define SOCK_DIR_DEFAULT "/run/voidnsrun" +#define SOCK_NAME "/sock" +#define VOIDNSUNDO_NAME "voidnsundo" + +#endif //VOIDNSRUN_CONFIG_H diff --git a/macros.h b/macros.h new file mode 100644 index 0000000..973886c --- /dev/null +++ b/macros.h @@ -0,0 +1,24 @@ +#ifndef VOIDNSRUN_MACROS_H +#define VOIDNSRUN_MACROS_H + +#include +#include + +extern bool g_verbose; + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) +#define UNUSED(x) (void)(x) +#define ERROR(f_, ...) fprintf(stderr, (f_), ##__VA_ARGS__) +#define DEBUG(f_, ...) if (g_verbose) { \ + fprintf(stderr, "debug: "); \ + fprintf(stderr, (f_), ##__VA_ARGS__); \ + } + +#define ERROR_EXIT(f_, ...) { \ + fprintf(stderr, (f_), ##__VA_ARGS__); \ + goto end; \ + } + +#define SOCK_DIR_PATH_MAX (108 - strlen(SOCK_NAME) - 1) + +#endif //VOIDNSRUN_MACROS_H diff --git a/testclient.c b/testclient.c new file mode 100644 index 0000000..19db1c1 --- /dev/null +++ b/testclient.c @@ -0,0 +1,44 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "utils.h" + +#define ERROR_EXIT(f_, ...) { \ + fprintf(stderr, (f_), ##__VA_ARGS__); \ + return 1; \ + } + +int main() +{ + struct sockaddr_un addr = {0}; + int sock; + + // Create and connect a unix domain socket + sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock == -1) + ERROR_EXIT("socket: %s\n", strerror(errno)); + + addr.sun_family = AF_UNIX; + strcpy(&addr.sun_path[1], "/tmp/voidnsrun-test.sock"); + + if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == -1) + ERROR_EXIT("connect: %s\n", strerror(errno)); + + int fd = recv_fd(sock); + close(sock); + + assert(fd != 0); + + struct stat st; + if (fstat(fd, &st) == -1) + ERROR_EXIT("stat: %s\n", strerror(errno)); + + printf("st_ino: %lu\n", st.st_ino); + + return 0; +} \ No newline at end of file diff --git a/testserver.c b/testserver.c new file mode 100644 index 0000000..091d580 --- /dev/null +++ b/testserver.c @@ -0,0 +1,96 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "utils.h" + +#define ERROR(f_, ...) fprintf(stderr, (f_), ##__VA_ARGS__) +#define UNUSED(x) (void)(x) +#define ERROR_EXIT(f_, ...) { \ + fprintf(stderr, (f_), ##__VA_ARGS__); \ + return 1; \ + } + +volatile sig_atomic_t term_caught = 0; +void onterm(int sig) +{ + printf("sigterm caught\n"); + UNUSED(sig); + term_caught = 1; +} + +int main() +{ + int result; + int sock_fd, sock_conn; + int nsfd; + + /* Get current namespace's file descriptor. */ + nsfd = open("/proc/self/ns/mnt", O_RDONLY); + if (nsfd == -1) + ERROR_EXIT("error: failed to acquire mount namespace's fd.%s\n", + strerror(errno)); + + /* Fork. */ + pid_t ppid_before_fork = getpid(); + pid_t pid = fork(); + if (pid == -1) + ERROR_EXIT("fork: %s\n", strerror(errno)); + + if (pid == 0) { + /* Catch SIGTERM. */ + struct sigaction sa = {0}; + sa.sa_handler = onterm; + sigaction(SIGTERM, &sa, NULL); + + /* Ignore SIGINT. */ + signal(SIGINT, SIG_IGN); + + /* Set the child to die when parent thread dies. */ + int r = prctl(PR_SET_PDEATHSIG, SIGTERM); + if (r == -1) + ERROR_EXIT("prctl: %s\n", strerror(errno)); + if (getppid() != ppid_before_fork) + ERROR_EXIT("error: parent has died already.\n"); + + /* Create unix socket. */ + sock_fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock_fd == -1) + ERROR_EXIT("socket: %s.\n", strerror(errno)); + + struct sockaddr_un sock_addr = {0}; + sock_addr.sun_family = AF_UNIX; + strcpy(&sock_addr.sun_path[1], "/tmp/voidnsrun-test.sock"); + + if (bind(sock_fd, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) == -1) + ERROR_EXIT("bind: %s\n", strerror(errno)); + + listen(sock_fd, 1); + + while (!term_caught) { + sock_conn = accept(sock_fd, NULL, 0); + if (sock_conn == -1) { + ERROR("accept: %s\n", strerror(errno)); + continue; + } + printf("accepted\n"); + send_fd(sock_conn, nsfd); + } + printf("exiting\n"); + } else { + /* This is parent. Launch a program. */ + char *argv[2] = {"/bin/sh", NULL}; + + result = execvp(argv[0], (char *const *)argv); + if (result == -1) + ERROR_EXIT("execvp: %s\n", strerror(errno)); + } + + return 0; +} \ No newline at end of file diff --git a/utils.c b/utils.c new file mode 100644 index 0000000..233a6e5 --- /dev/null +++ b/utils.c @@ -0,0 +1,154 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "macros.h" +#include "utils.h" + +bool isdir(const char *s) +{ + struct stat st; + int result = stat(s, &st); + if (result == -1) + ERROR("stat(%s): %s\n", s, strerror(errno)); + return result == 0 && S_ISDIR(st.st_mode); +} + +bool isexe(const char *s) +{ + struct stat st; + int result = stat(s, &st); + if (result == -1) + ERROR("stat(%s): %s\n", s, strerror(errno)); + return result == 0 && !S_ISDIR(st.st_mode) && st.st_mode & S_IXUSR; +} + +bool exists(const char *s) +{ + struct stat st; + return stat(s, &st) == 0; +} + +bool mkfile(const char *s) +{ + int fd; + if ((fd = creat(s, 0700)) == -1) + return false; + close(fd); + return true; +} + +int send_fd(int sock, int fd) +{ + struct msghdr msg = {0}; + struct iovec iov[1]; + struct cmsghdr *cmsg = NULL; + char ctrl_buf[CMSG_SPACE(sizeof(int))]; + char data[1]; + + memset(ctrl_buf, 0, CMSG_SPACE(sizeof(int))); + + data[0] = ' '; + iov[0].iov_base = data; + iov[0].iov_len = sizeof(data); + + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_iov = iov; + msg.msg_iovlen = 1; + msg.msg_controllen = CMSG_SPACE(sizeof(int)); + msg.msg_control = ctrl_buf; + + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + + *((int *)CMSG_DATA(cmsg)) = fd; + + return sendmsg(sock, &msg, 0); +} + +int recv_fd(int sock) +{ + struct msghdr msg = {0}; + struct iovec iov[1]; + struct cmsghdr *cmsg = NULL; + char ctrl_buf[CMSG_SPACE(sizeof(int))]; + char data[1]; + + memset(ctrl_buf, 0, CMSG_SPACE(sizeof(int))); + + iov[0].iov_base = data; + iov[0].iov_len = sizeof(data); + + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_control = ctrl_buf; + msg.msg_controllen = CMSG_SPACE(sizeof(int)); + msg.msg_iov = iov; + msg.msg_iovlen = 1; + + recvmsg(sock, &msg, 0); + + cmsg = CMSG_FIRSTHDR(&msg); + + return *((int *) CMSG_DATA(cmsg)); +} + +bool isxbpscommand(const char *s) +{ + const char *commands[] = { + "/xbps-install", + "/xbps-remove", + "/xbps-reconfigure" + }; + for (size_t i = 0; i < ARRAY_SIZE(commands); i++) { + const char *command = commands[i]; + if (!strcmp(s, command+1)) + return true; + char *slash = strrchr(s, '/'); + if (slash && !strcmp(slash, command)) + return true; + } + return false; +} + +bool strarray_append(struct strarray *a, char *s) +{ + if (a->end == a->size - 1) + return false; + else + a->list[a->end++] = s; + return true; +} + +bool intarray_append(struct intarray *a, int i) +{ + if (a->end == a->size - 1) + return false; + else + a->list[a->end++] = i; + return true; +} + +void strarray_alloc(struct strarray *a, size_t size) +{ + a->end = 0; + a->size = size; + a->list = malloc(sizeof(char *) * size); + assert(a->list != NULL); +} + +void intarray_alloc(struct intarray *i, size_t size) +{ + i->end = 0; + i->size = size; + i->list = malloc(sizeof(int) * size); + assert(i->list != NULL); +} \ No newline at end of file diff --git a/utils.h b/utils.h new file mode 100644 index 0000000..cc9f610 --- /dev/null +++ b/utils.h @@ -0,0 +1,35 @@ +#ifndef VOIDNSRUN_UTILS_H +#define VOIDNSRUN_UTILS_H + +#include +#include "config.h" + +struct strarray { + size_t end; + size_t size; + char **list; +}; + +struct intarray { + size_t end; + size_t size; + int *list; +}; + +bool isdir(const char *s); +bool isexe(const char *s); +bool exists(const char *s); +bool mkfile(const char *s); + +int send_fd(int sock, int fd); +int recv_fd(int sock); + +bool isxbpscommand(const char *s); + +void strarray_alloc(struct strarray *a, size_t size); +bool strarray_append(struct strarray *a, char *s); + +void intarray_alloc(struct intarray *i, size_t size); +bool intarray_append(struct intarray *a, int i); + +#endif //VOIDNSRUN_UTILS_H diff --git a/voidnsrun.c b/voidnsrun.c index b1a34b3..ba48139 100644 --- a/voidnsrun.c +++ b/voidnsrun.c @@ -1,197 +1,362 @@ #define _GNU_SOURCE + #include #include #include +#include #include #include #include #include +#include +#include #include #include +#include +#include +#include +#include #include -const char *var_name = "VOIDNSRUN_DIR"; -const char *prog_version = "1.0"; - -#define USERMOUNTS_MAX 8 +#include "config.h" +#include "utils.h" +#include "macros.h" -#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) -#define ERROR(f_, ...) fprintf(stderr, (f_), ##__VA_ARGS__) +volatile sig_atomic_t term_caught = 0; +bool g_verbose = false; -bool isdir(const char *s) +void usage(const char *progname) { - struct stat st; - int result = stat(s, &st); - if (result == -1) - ERROR("stat: %s\n", strerror(errno)); - return result == 0 && S_ISDIR(st.st_mode); + printf("Usage: %s [OPTIONS] PROGRAM [ARGS]\n", progname); + printf("\n" + "Options:\n" + " -r : Container path. When this option is not present,\n" + " " CONTAINER_DIR_VAR " environment variable is used.\n" + " -m : Add bind mount. You can add up to %d paths.\n" + " -u : Add undo utility bind mount. You can add up to %d paths.\n" + " -U : Undo program path. When this option is not present,\n" + " " UNDO_BIN_VAR " environment variable is used.\n" + " -i: Don't treat missing source or target for an added mount\n" + " as an error.\n" + " -s: Socket directory path. When this option is not present,\n" + " " SOCK_DIR_VAR " environment variable is used. If both are\n" + " missing, defaults to " SOCK_DIR_DEFAULT ".\n" + " -V: Verbose output.\n" + " -h: Print this help.\n" + " -v: Print version.\n", + USER_LISTS_MAX, USER_LISTS_MAX); } -void usage(const char *progname) +size_t mount_dirs(const char *source_prefix, size_t source_prefix_len, struct strarray *targets) { - printf("Usage: %s [OPTIONS] PROGRAM [ARGS]\n", progname); - printf("\n" - "Options:\n" - " -m : add bind mount\n" - " -r : altroot path. If this option is not present,\n" - " %s environment variable is used.\n" - " -h: print this help\n" - " -v: print version\n", - var_name); + char buf[PATH_MAX]; + int successful = 0; + for (size_t i = 0; i < targets->end; i++) { + /* Check if it's safe to proceed. */ + if (source_prefix_len + strlen(targets->list[i]) >= PATH_MAX) { + ERROR("error: path %s%s is too large.\n", source_prefix, targets->list[i]); + continue; + } + + strcpy(buf, source_prefix); + strcat(buf, targets->list[i]); + if (!isdir(buf)) { + ERROR("error: source mount dir %s does not exists.\n", buf); + continue; + } + + if (!isdir(targets->list[i])) { + ERROR("error: mount point %s does not exists.\n", targets->list[i]); + continue; + } + + if (mount(buf, targets->list[i], NULL, MS_BIND|MS_REC, NULL) == -1) + ERROR("mount: failed to mount %s: %s\n", targets->list[i], strerror(errno)); + else + successful++; + } + return successful; } -bool mountlist( - const char *dirptr, - size_t dirlen, - const char **mountpoints, - size_t len, - bool ignore_missing) +size_t mount_undo(const char *source, const struct strarray *targets, struct intarray *created) { - char buf[PATH_MAX]; - for (size_t i = 0; i < len; i++) { - /* Check if it's safe to proceed. */ - if (dirlen + strlen(mountpoints[i]) >= PATH_MAX) { - ERROR("error: path %s%s is too large.\n", dirptr, mountpoints[i]); - return false; - } - - strcpy(buf, dirptr); - strcat(buf, mountpoints[i]); - if (!isdir(buf)) { - if (ignore_missing) - continue; - ERROR("error: source mount dir %s does not exists.\n", buf); - return false; - } - if (!isdir(mountpoints[i])) { - ERROR("error: mountpoint %s does not exists.\n", mountpoints[i]); - return false; - } - if (mount(buf, mountpoints[i], NULL, MS_BIND|MS_REC, NULL) == -1) { - ERROR("mount: failed to mount %s: %s\n", - mountpoints[i], strerror(errno)); - return false; - } - } - return true; + int successful = 0; + for (size_t i = 0; i < targets->end; i++) { + if (!exists(targets->list[i])) { + if (mkfile(targets->list[i])) + intarray_append(created, i); + else + continue; + } + + DEBUG("%s: source=%s, target=%s\n", __func__, source, targets->list[i]); + if (mount(source, targets->list[i], NULL, MS_BIND, NULL) == -1) + ERROR("mount: failed to mount %s to %s: %s", + source, targets->list[i], strerror(errno)); + else + successful++; + } + return successful; } -bool isxbpscommand(const char *s) +void onterm(int sig) { - const char *commands[] = { - "/xbps-install", - "/xbps-remove", - "/xbps-reconfigure" - }; - for (size_t i = 0; i < ARRAY_SIZE(commands); i++) { - const char *command = commands[i]; - if (!strcmp(s, command+1)) - return true; - char *slash = strrchr(s, '/'); - if (slash && !strcmp(slash, command)) - return true; - } - return false; + UNUSED(sig); + term_caught = 1; } int main(int argc, char **argv) { - if (argc < 2) { - usage(argv[0]); - return 0; - } - - int result; - char *dir = NULL; - size_t dirlen; - const char *usermounts[USERMOUNTS_MAX] = {0}; - int usermounts_num = 0; - int c; - while ((c = getopt(argc, argv, "vhm:r:")) != -1) { - switch (c) { - case 'v': - printf("%s\n", prog_version); - return 0; - case 'h': - usage(argv[0]); - return 0; - case 'r': - dir = optarg; - break; - case 'm': - if (usermounts_num < USERMOUNTS_MAX) { - usermounts[usermounts_num++] = optarg; - break; - } else { - ERROR("error: only up to %d user mounts allowed.\n", - USERMOUNTS_MAX); - return 1; - } - case '?': - return 1; - } - } - - /* Get alternative root dir. */ - if (!dir) - dir = getenv(var_name); - if (!dir) { - ERROR("error: environment variable %s not found.\n", var_name); - return 1; - } - - /* Validate it. */ - if (!isdir(dir)) { - ERROR("error: %s is not a directory.\n", dir); - return 1; - } - - dirlen = strlen(dir); - - /* Do the unshare magic. */ - result = unshare(CLONE_NEWNS); - if (result == -1) { - ERROR("unshare: %s\n", strerror(errno)); - return 1; - } - - /* Mount stuff from altroot to our private namespace. */ - const char *mountpoints[3] = {"/usr", NULL, NULL}; - if (isxbpscommand(argv[optind])) { - mountpoints[1] = "/var"; - mountpoints[2] = "/etc"; - } else { - mountpoints[1] = "/var/db/xbps"; - mountpoints[2] = "/etc/xbps.d"; - } - if (!mountlist(dir, dirlen, mountpoints, ARRAY_SIZE(mountpoints), true)) - return 1; - if (usermounts_num > 0 && - !mountlist(dir, dirlen, usermounts, usermounts_num, false)) - return 1; - - /* Drop root. */ - uid_t uid = getuid(); - gid_t gid = getgid(); - - result = setreuid(uid, uid); - if (result == -1) { - ERROR("setreuid: %s\n", strerror(errno)); - return 1; - } - - result = setregid(gid, gid); - if (result == -1) { - ERROR("setregid: %s\n", strerror(errno)); - return 1; - } - - /* Launch program. */ - result = execvp(argv[optind], (char *const *)argv+optind); - if (result == -1) { - ERROR("execvp(%s): %s\n", argv[optind], strerror(errno)); - return 1; - } - - return 0; + if (argc < 2) { + usage(argv[0]); + return 0; + } + + int nsfd = -1; + char *dir = NULL; + char *undo_bin = NULL; + char *sock_dir = NULL; + int sock_fd = -1, sock_conn = -1; + size_t dirlen; + int c; + int exit_code = 1; + DIR *dirptr = NULL; + bool ignore_missing = false; + bool forked = false; + pid_t pid = 0; + char cwd[PATH_MAX]; + + struct strarray user_mounts; + strarray_alloc(&user_mounts, USER_LISTS_MAX); + + struct strarray undo_mounts; + strarray_alloc(&undo_mounts, USER_LISTS_MAX); + + struct intarray tounlink; + intarray_alloc(&tounlink, USER_LISTS_MAX); + + while ((c = getopt(argc, argv, "vhm:r:u:U:is:V")) != -1) { + switch (c) { + case 'v': + printf("%s\n", PROG_VERSION); + return 0; + case 'h': + usage(argv[0]); + return 0; + case 'i': + ignore_missing = true; + break; + case 'r': + dir = optarg; + break; + case 'U': + undo_bin = optarg; + break; + case 's': + sock_dir = optarg; + break; + case 'V': + g_verbose = true; + break; + case 'm': + if (!strarray_append(&user_mounts, optarg)) + ERROR_EXIT("error: only up to %lu user mounts allowed.\n", + user_mounts.size); + break; + case 'u': + if (!strarray_append(&undo_mounts, optarg)) + ERROR_EXIT("error: only up to %lu user mounts allowed.\n", + undo_mounts.size); + break; + case '?': + return 1; + } + } + + if (!argv[optind]) { + usage(argv[0]); + return 1; + } + + /* Get container path. */ + if (!dir) + dir = getenv(CONTAINER_DIR_VAR); + if (!dir) + ERROR_EXIT("error: environment variable %s not found.\n", + CONTAINER_DIR_VAR); + + /* Validate it. */ + if (!isdir(dir)) + ERROR_EXIT("error: %s is not a directory.\n", dir); + + dirlen = strlen(dir); + + DEBUG("dir=%s\n", dir); + + /* Get undo binary path, if needed. */ + if (undo_mounts.end > 0) { + if (!undo_bin) + undo_bin = getenv(UNDO_BIN_VAR); + if (!undo_bin) { + ERROR_EXIT("error: environment variable %s not found.\n", + UNDO_BIN_VAR); + } else if (strlen(undo_bin) > PATH_MAX) + ERROR_EXIT("error: undo binary path is too long.\n"); + + /* Validate it. */ + if (!isexe(undo_bin)) + ERROR_EXIT("error: %s is not an executable.\n", undo_bin); + + DEBUG("undo_bin=%s\n", undo_bin); + } + + /* Get current namespace's file descriptor. */ + nsfd = open("/proc/self/ns/mnt", O_RDONLY); + if (nsfd == -1) + ERROR_EXIT("error: failed to acquire mount namespace's fd.%s\n", + strerror(errno)); + + /* Check socket directory. */ + if (!sock_dir) + sock_dir = getenv(SOCK_DIR_VAR); + if (!sock_dir) + sock_dir = SOCK_DIR_DEFAULT; + if (strlen(sock_dir) > SOCK_DIR_PATH_MAX) + ERROR_EXIT("error: socket directory path is too long.\n"); + + if (access(sock_dir, F_OK) == -1) { + if (mkdir(sock_dir, 0700) == -1) + ERROR_EXIT("error: failed to create %s directory.\n", sock_dir); + } else { + if ((dirptr = opendir(sock_dir)) == NULL) + ERROR_EXIT("error: %s is not a directory.\n", sock_dir); + } + DEBUG("sock_dir=%s\n", sock_dir); + + /* Get current working directory. */ + getcwd(cwd, PATH_MAX); + DEBUG("cwd=%s\n", cwd); + + /* Do the unshare magic. */ + if (unshare(CLONE_NEWNS) == -1) + ERROR_EXIT("unshare: %s\n", strerror(errno)); + + /* Mount stuff from the container to the namespace. */ + /* First, mount what user asked us to mount. */ + if (mount_dirs(dir, dirlen, &user_mounts) < user_mounts.end && !ignore_missing) + ERROR_EXIT("error: some mounts failed.\n"); + + /* Then necessary stuff. */ + struct strarray default_mounts; + strarray_alloc(&default_mounts, 3); + strarray_append(&default_mounts, "/usr"); + if (isxbpscommand(argv[optind])) { + strarray_append(&default_mounts, "/var"); + strarray_append(&default_mounts, "/etc"); + } + if (mount_dirs(dir, dirlen, &default_mounts) < default_mounts.end) + ERROR_EXIT("error: some necessary mounts failed.\n"); + + /* Bind mount undo binary. */ + if (mount_undo(undo_bin, &undo_mounts, &tounlink) < undo_mounts.end + && !ignore_missing) + ERROR_EXIT("error: some undo mounts failed.\n"); + + /* Mount sock_dir as tmpfs. It will only be visible in this namespace. */ + if (mount("tmpfs", sock_dir, "tmpfs", 0, NULL) == -1) + ERROR_EXIT("mount: error mounting tmpfs in %s.\n", sock_dir); + + /* Fork. */ + pid_t ppid_before_fork = getpid(); + pid = fork(); + if (pid == -1) + ERROR_EXIT("fork: %s\n", strerror(errno)); + + forked = true; + + if (pid == 0) { + /* Catch SIGTERM. */ + struct sigaction sa = {0}; + sa.sa_handler = onterm; + sigaction(SIGTERM, &sa, NULL); + + /* Ignore SIGINT. */ + signal(SIGINT, SIG_IGN); + + /* Set the child to get SIGTERM when parent thread dies. */ + int r = prctl(PR_SET_PDEATHSIG, SIGTERM); + if (r == -1) + ERROR_EXIT("prctl: %s\n", strerror(errno)); + if (getppid() != ppid_before_fork) + ERROR_EXIT("error: parent has died already.\n"); + + /* Create unix socket. */ + sock_fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock_fd == -1) + ERROR_EXIT("socket: %s.\n", strerror(errno)); + + struct sockaddr_un sock_addr = {0}; + sock_addr.sun_family = AF_UNIX; + strcpy(sock_addr.sun_path, sock_dir); + strcat(sock_addr.sun_path, SOCK_NAME); + + if (bind(sock_fd, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) == -1) + ERROR_EXIT("bind: %s\n", strerror(errno)); + + listen(sock_fd, 1); + + while (!term_caught) { + sock_conn = accept(sock_fd, NULL, 0); + if (sock_conn == -1) + continue; + send_fd(sock_conn, nsfd); + } + } else { + /* Parent process. Dropping root rights. */ + uid_t uid = getuid(); + gid_t gid = getgid(); + + if (setreuid(uid, uid) == -1) + ERROR_EXIT("setreuid: %s\n", strerror(errno)); + + if (setregid(gid, gid) == -1) + ERROR_EXIT("setregid: %s\n", strerror(errno)); + + /* Restore working directory. */ + if (chdir(cwd) == -1) + DEBUG("chdir: %s\n", strerror(errno)); + + /* Launching program. */ + if (execvp(argv[optind], (char *const *)argv+optind) == -1) + ERROR_EXIT("execvp(%s): %s\n", argv[optind], strerror(errno)); + } + + exit_code = 0; + +end: + if (nsfd != -1) + close(nsfd); + + if (sock_fd != -1) + close(sock_fd); + + if (sock_conn != -1) + close(sock_conn); + + if (dirptr != NULL) + closedir(dirptr); + + if (tounlink.end > 0 && (!forked || pid == 0)) { + for (size_t i = 0; i < tounlink.end; i++) { + char *path = undo_mounts.list[tounlink.list[i]]; + if (umount(path) == -1) + DEBUG("umount(%s): %s\n", path, strerror(errno)); + if (unlink(path) == -1) + ERROR("unlink(%s): %s\n", path, strerror(errno)); + else + DEBUG("unlink(%s)\n", path); + } + } + + return exit_code; } diff --git a/voidnsundo.c b/voidnsundo.c new file mode 100644 index 0000000..fac27aa --- /dev/null +++ b/voidnsundo.c @@ -0,0 +1,154 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "utils.h" +#include "macros.h" + +bool g_verbose = false; + +void usage(const char *progname) +{ + printf("Usage: %s [OPTIONS] PROGRAM [ARGS]\n", progname); + printf("\n" + "Options:\n" + " -s: Socket directory path. When this option is not present,\n" + " " SOCK_DIR_VAR " environment variable is used. If both are\n" + " missing, defaults to " SOCK_DIR_DEFAULT ".\n" + " -V: Verbose output.\n" + " -h: Print this help.\n" + " -v: Print version.\n"); +} + +int main(int argc, char **argv) +{ + bool binded = strcmp(basename(argv[0]), VOIDNSUNDO_NAME) != 0; + int c; + char *sock_dir = NULL; + int sock_fd = -1; + int exit_code = 1; + char realpath_buf[PATH_MAX]; + char cwd[PATH_MAX]; + if (!binded) { + if (argc < 2) { + usage(argv[0]); + return 0; + } + + while ((c = getopt(argc, argv, "vhs:V")) != -1) { + switch (c) { + case 'v': + printf("%s\n", PROG_VERSION); + return 0; + case 'h': + usage(argv[0]); + return 0; + case 's': + sock_dir = optarg; + break; + case 'V': + g_verbose = true; + break; + case '?': + return 1; + } + } + + if (!argv[optind]) { + usage(argv[0]); + return 1; + } + } else { + int bytes = readlink("/proc/self/exe", realpath_buf, PATH_MAX); + realpath_buf[bytes] = '\0'; + /* DEBUG("/proc/self/exe points to %s\n", realpath_buf); */ + } + + /* Check socket directory. */ + DIR *dirptr = NULL; + if (!sock_dir) + sock_dir = getenv(SOCK_DIR_VAR); + if (!sock_dir) + sock_dir = SOCK_DIR_DEFAULT; + if (strlen(sock_dir) > SOCK_DIR_PATH_MAX) + ERROR_EXIT("error: socket directory path is too long.\n"); + if (!isdir(sock_dir)) + ERROR_EXIT("error: %s is not a directory.\n", sock_dir); + if (access(sock_dir, F_OK) == -1) { + ERROR_EXIT("error: failed to access socket directory: %s.\n", + strerror(errno)); + } else { + if ((dirptr = opendir(sock_dir)) == NULL) + ERROR_EXIT("error: %s is not a directory.\n", sock_dir); + } + DEBUG("sock_dir=%s\n", sock_dir); + + /* Get current working directory. */ + getcwd(cwd, PATH_MAX); + DEBUG("cwd=%s\n", cwd); + + /* Get namespace's fd. */ + sock_fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock_fd == -1) + ERROR_EXIT("socket: %s.\n", strerror(errno)); + + struct sockaddr_un sock_addr = {0}; + sock_addr.sun_family = AF_UNIX; + strcpy(sock_addr.sun_path, sock_dir); + strcat(sock_addr.sun_path, SOCK_NAME); + + if (connect(sock_fd, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) == -1) + ERROR_EXIT("connect: %s\n", strerror(errno)); + + int nsfd = recv_fd(sock_fd); + if (!nsfd) + ERROR_EXIT("error: failed to get nsfd.\n"); + + if (setns(nsfd, CLONE_NEWNS) == -1) + ERROR_EXIT("setns: %s.\n", strerror(errno)); + + /* Drop root. */ + uid_t uid = getuid(); + gid_t gid = getgid(); + + if (setreuid(uid, uid) == -1) + ERROR_EXIT("setreuid: %s\n", strerror(errno)); + + if (setregid(gid, gid) == -1) + ERROR_EXIT("setregid: %s\n", strerror(errno)); + + /* Restore working directory. */ + if (chdir(cwd) == -1) + DEBUG("chdir: %s\n", strerror(errno)); + + /* Launch program. */ + int argind = binded ? 0 : optind; + if (binded) + argv[0] = realpath_buf; + if (execvp(argv[argind], (char *const *)argv+argind) == -1) + ERROR_EXIT("execvp(%s): %s\n", argv[argind], strerror(errno)); + + exit_code = 0; + +end: + if (dirptr != NULL) + closedir(dirptr); + + if (sock_fd != -1) + close(sock_fd); + + return exit_code; +} \ No newline at end of file -- cgit v1.2.3