diff options
author | Evgeny Zinoviev <me@ch1p.io> | 2020-12-07 23:47:39 +0300 |
---|---|---|
committer | Evgeny Zinoviev <me@ch1p.io> | 2020-12-07 23:47:55 +0300 |
commit | a5511a0b0f74cc15c807bcee39fd9b4bc44beae2 (patch) | |
tree | 44595ba638d1123244c1f02a2aee183c5686454f | |
parent | 2551cab43facf32a5a2b3768ae3034cd10095619 (diff) |
implement a way for programs in the mount namespace (with glibc binaries) to launch processes in the original namespace (with musl binaries)
-rw-r--r-- | .gitignore | 6 | ||||
-rw-r--r-- | Makefile | 26 | ||||
-rw-r--r-- | config.h | 13 | ||||
-rw-r--r-- | macros.h | 24 | ||||
-rw-r--r-- | testclient.c | 44 | ||||
-rw-r--r-- | testserver.c | 96 | ||||
-rw-r--r-- | utils.c | 154 | ||||
-rw-r--r-- | utils.h | 35 | ||||
-rw-r--r-- | voidnsrun.c | 501 | ||||
-rw-r--r-- | voidnsundo.c | 154 |
10 files changed, 876 insertions, 177 deletions
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 @@ -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 <stdio.h> +#include <string.h> + +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 <stdio.h> +#include <errno.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/stat.h> +#include <string.h> +#include <unistd.h> +#include <assert.h> +#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 <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include <signal.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/prctl.h> +#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 @@ -0,0 +1,154 @@ +#include <string.h> +#include <stdbool.h> +#include <stdlib.h> +#include <errno.h> +#include <fcntl.h> +#include <assert.h> +#include <unistd.h> +#include <sys/socket.h> +#include <sys/stat.h> +#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 @@ -0,0 +1,35 @@ +#ifndef VOIDNSRUN_UTILS_H +#define VOIDNSRUN_UTILS_H + +#include <stdbool.h> +#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 <stdio.h> #include <stdlib.h> #include <string.h> +#include <fcntl.h> #include <unistd.h> #include <errno.h> #include <sched.h> #include <stdbool.h> +#include <dirent.h> +#include <signal.h> #include <sys/mount.h> #include <sys/stat.h> +#include <sys/types.h> +#include <sys/prctl.h> +#include <sys/socket.h> +#include <sys/un.h> #include <linux/limits.h> -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 <path>: Container path. When this option is not present,\n" + " " CONTAINER_DIR_VAR " environment variable is used.\n" + " -m <path>: Add bind mount. You can add up to %d paths.\n" + " -u <path>: Add undo utility bind mount. You can add up to %d paths.\n" + " -U <path>: 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 <path>: add bind mount\n" - " -r <path>: 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 <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <libgen.h> +#include <stdbool.h> +#include <getopt.h> +#include <dirent.h> +#include <errno.h> +#include <unistd.h> +#include <sched.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <linux/limits.h> + +#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 |