summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEvgeny Zinoviev <me@ch1p.io>2020-12-07 23:47:39 +0300
committerEvgeny Zinoviev <me@ch1p.io>2020-12-07 23:47:55 +0300
commita5511a0b0f74cc15c807bcee39fd9b4bc44beae2 (patch)
tree44595ba638d1123244c1f02a2aee183c5686454f
parent2551cab43facf32a5a2b3768ae3034cd10095619 (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--.gitignore6
-rw-r--r--Makefile26
-rw-r--r--config.h13
-rw-r--r--macros.h24
-rw-r--r--testclient.c44
-rw-r--r--testserver.c96
-rw-r--r--utils.c154
-rw-r--r--utils.h35
-rw-r--r--voidnsrun.c501
-rw-r--r--voidnsundo.c154
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
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 <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
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 <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
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 <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