implement a way for programs in the mount namespace (with glibc binaries) to launch processes in the original namespace (with musl binaries)
This commit is contained in:
parent
2551cab43f
commit
a5511a0b0f
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
*.o
|
||||
.idea
|
||||
voidnsrun
|
||||
voidnsundo
|
||||
testclient
|
||||
testserver
|
26
Makefile
26
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
|
13
config.h
Normal file
13
config.h
Normal file
@ -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
|
24
macros.h
Normal file
24
macros.h
Normal file
@ -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
|
44
testclient.c
Normal file
44
testclient.c
Normal file
@ -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;
|
||||
}
|
96
testserver.c
Normal file
96
testserver.c
Normal file
@ -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;
|
||||
}
|
154
utils.c
Normal file
154
utils.c
Normal 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);
|
||||
}
|
35
utils.h
Normal file
35
utils.h
Normal 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
|
483
voidnsrun.c
483
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";
|
||||
#include "config.h"
|
||||
#include "utils.h"
|
||||
#include "macros.h"
|
||||
|
||||
#define USERMOUNTS_MAX 8
|
||||
|
||||
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
|
||||
#define ERROR(f_, ...) fprintf(stderr, (f_), ##__VA_ARGS__)
|
||||
|
||||
bool isdir(const char *s)
|
||||
{
|
||||
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);
|
||||
}
|
||||
volatile sig_atomic_t term_caught = 0;
|
||||
bool g_verbose = false;
|
||||
|
||||
void usage(const char *progname)
|
||||
{
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
bool mountlist(
|
||||
const char *dirptr,
|
||||
size_t dirlen,
|
||||
const char **mountpoints,
|
||||
size_t len,
|
||||
bool ignore_missing)
|
||||
size_t mount_dirs(const char *source_prefix, size_t source_prefix_len, struct strarray *targets)
|
||||
{
|
||||
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;
|
||||
}
|
||||
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, 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;
|
||||
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 isxbpscommand(const char *s)
|
||||
size_t mount_undo(const char *source, const struct strarray *targets, struct intarray *created)
|
||||
{
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
void onterm(int sig)
|
||||
{
|
||||
UNUSED(sig);
|
||||
term_caught = 1;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc < 2) {
|
||||
usage(argv[0]);
|
||||
return 0;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
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];
|
||||
|
||||
/* Get alternative root dir. */
|
||||
if (!dir)
|
||||
dir = getenv(var_name);
|
||||
if (!dir) {
|
||||
ERROR("error: environment variable %s not found.\n", var_name);
|
||||
return 1;
|
||||
}
|
||||
struct strarray user_mounts;
|
||||
strarray_alloc(&user_mounts, USER_LISTS_MAX);
|
||||
|
||||
/* Validate it. */
|
||||
if (!isdir(dir)) {
|
||||
ERROR("error: %s is not a directory.\n", dir);
|
||||
return 1;
|
||||
}
|
||||
struct strarray undo_mounts;
|
||||
strarray_alloc(&undo_mounts, USER_LISTS_MAX);
|
||||
|
||||
dirlen = strlen(dir);
|
||||
struct intarray tounlink;
|
||||
intarray_alloc(&tounlink, USER_LISTS_MAX);
|
||||
|
||||
/* Do the unshare magic. */
|
||||
result = unshare(CLONE_NEWNS);
|
||||
if (result == -1) {
|
||||
ERROR("unshare: %s\n", strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/* 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;
|
||||
if (!argv[optind]) {
|
||||
usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Drop root. */
|
||||
uid_t uid = getuid();
|
||||
gid_t gid = getgid();
|
||||
/* Get container path. */
|
||||
if (!dir)
|
||||
dir = getenv(CONTAINER_DIR_VAR);
|
||||
if (!dir)
|
||||
ERROR_EXIT("error: environment variable %s not found.\n",
|
||||
CONTAINER_DIR_VAR);
|
||||
|
||||
result = setreuid(uid, uid);
|
||||
if (result == -1) {
|
||||
ERROR("setreuid: %s\n", strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
/* Validate it. */
|
||||
if (!isdir(dir))
|
||||
ERROR_EXIT("error: %s is not a directory.\n", dir);
|
||||
|
||||
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;
|
||||
}
|
||||
dirlen = strlen(dir);
|
||||
|
||||
return 0;
|
||||
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;
|
||||
}
|
||||
|
154
voidnsundo.c
Normal file
154
voidnsundo.c
Normal file
@ -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;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user