#include "config.h"
#include <poll.h>
#include <sched.h>
#include <pwd.h>
#include <grp.h>
#include <ctype.h>
#include <sys/mount.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/eventfd.h>
#include <sys/fsuid.h>
#include <sys/signalfd.h>
#include <sys/capability.h>
#include <sys/prctl.h>
#include <linux/sched.h>
#include <linux/seccomp.h>
#include <linux/filter.h>
#include "utils.h"
#include "network.h"
#include "bind-mount.h"
#ifndef CLONE_NEWCGROUP
#define CLONE_NEWCGROUP 0x02000000
#endif
#define MAX_TMPFS_BYTES ((size_t) (SIZE_MAX >> 1))
static uid_t real_uid;
static gid_t real_gid;
static uid_t overflow_uid;
static gid_t overflow_gid;
static const char *argv0;
static const char *host_tty_dev;
static const char *opt_exec_label = NULL;
static const char *opt_file_label = NULL;
static bool opt_as_pid_1;
static const char *opt_argv0 = NULL;
static const char *opt_chdir_path = NULL;
static bool opt_assert_userns_disabled = false;
static bool opt_disable_userns = false;
static bool opt_unshare_user = false;
static bool opt_unshare_user_try = false;
static bool opt_unshare_pid = false;
static bool opt_unshare_ipc = false;
static bool opt_unshare_net = false;
static bool opt_unshare_uts = false;
static bool opt_unshare_cgroup = false;
static bool opt_unshare_cgroup_try = false;
static bool opt_needs_devpts = false;
static bool opt_new_session = false;
static bool opt_die_with_parent = false;
static uid_t opt_sandbox_uid = -1;
static gid_t opt_sandbox_gid = -1;
static int opt_sync_fd = -1;
static int opt_block_fd = -1;
static int opt_userns_block_fd = -1;
static int opt_info_fd = -1;
static int opt_json_status_fd = -1;
static int opt_seccomp_fd = -1;
static const char *opt_sandbox_hostname = NULL;
static char *opt_args_data = NULL;
static int opt_userns_fd = -1;
static int opt_userns2_fd = -1;
static int opt_pidns_fd = -1;
static int opt_tmp_overlay_count = 0;
static int next_perms = -1;
static size_t next_size_arg = 0;
static int next_overlay_src_count = 0;
static bool opt_not_a_security_boundary = false;
#define CAP_TO_MASK_0(x) (1L << ((x) & 31))
#define CAP_TO_MASK_1(x) CAP_TO_MASK_0(x - 32)
typedef struct _NsInfo NsInfo;
struct _NsInfo {
const char *name;
bool *do_unshare;
ino_t id;
};
static NsInfo ns_infos[] = {
{"cgroup", &opt_unshare_cgroup, 0},
{"ipc", &opt_unshare_ipc, 0},
{"mnt", NULL, 0},
{"net", &opt_unshare_net, 0},
{"pid", &opt_unshare_pid, 0},
{"uts", &opt_unshare_uts, 0},
{NULL, NULL, 0}
};
typedef enum {
SETUP_BIND_MOUNT,
SETUP_RO_BIND_MOUNT,
SETUP_DEV_BIND_MOUNT,
SETUP_OVERLAY_MOUNT,
SETUP_TMP_OVERLAY_MOUNT,
SETUP_RO_OVERLAY_MOUNT,
SETUP_OVERLAY_SRC,
SETUP_MOUNT_PROC,
SETUP_MOUNT_DEV,
SETUP_MOUNT_TMPFS,
SETUP_MOUNT_MQUEUE,
SETUP_MAKE_DIR,
SETUP_MAKE_FILE,
SETUP_MAKE_BIND_FILE,
SETUP_MAKE_RO_BIND_FILE,
SETUP_MAKE_SYMLINK,
SETUP_REMOUNT_RO_NO_RECURSIVE,
SETUP_SET_HOSTNAME,
SETUP_CHMOD,
} SetupOpType;
typedef enum {
NO_CREATE_DEST = (1 << 0),
ALLOW_NOTEXIST = (1 << 1),
} SetupOpFlag;
typedef struct _SetupOp SetupOp;
typedef enum {
NODE_TYPE_NONE,
NODE_TYPE_DIR,
NODE_TYPE_FILE,
NODE_TYPE_EXISTING,
} NodeType;
struct _SetupOp
{
SetupOpType type;
const char *source;
const char *dest;
int fd;
SetupOpFlag flags;
int perms;
size_t size;
SetupOp *next;
};
typedef struct _LockFile LockFile;
struct _LockFile
{
const char *path;
int fd;
LockFile *next;
};
#define DEFINE_LINKED_LIST(Type, name) \
static Type *name ## s = NULL; \
static Type *last_ ## name = NULL; \
\
static inline Type * \
_ ## name ## _append_new (void) \
{ \
Type *self = xcalloc (1, sizeof (Type)); \
\
if (last_ ## name != NULL) \
last_ ## name ->next = self; \
else \
name ## s = self; \
\
last_ ## name = self; \
return self; \
}
DEFINE_LINKED_LIST (SetupOp, op)
static SetupOp *
setup_op_new (SetupOpType type)
{
SetupOp *op = _op_append_new ();
op->type = type;
op->fd = -1;
op->flags = 0;
return op;
}
DEFINE_LINKED_LIST (LockFile, lock_file)
static LockFile *
lock_file_new (const char *path)
{
LockFile *lock = _lock_file_append_new ();
lock->path = path;
return lock;
}
typedef struct _SeccompProgram SeccompProgram;
struct _SeccompProgram
{
struct sock_fprog program;
SeccompProgram *next;
};
DEFINE_LINKED_LIST (SeccompProgram, seccomp_program)
static SeccompProgram *
seccomp_program_new (int *fd)
{
SeccompProgram *self = _seccomp_program_append_new ();
cleanup_free char *data = NULL;
size_t len;
data = load_file_data (*fd, &len);
if (data == NULL)
die_with_error ("Can't read seccomp data");
close (*fd);
*fd = -1;
if (len % 8 != 0)
die ("Invalid seccomp data, must be multiple of 8");
self->program.len = len / 8;
self->program.filter = (struct sock_filter *) steal_pointer (&data);
return self;
}
static void
seccomp_programs_apply (void)
{
SeccompProgram *program;
for (program = seccomp_programs; program != NULL; program = program->next)
{
if (prctl (PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &program->program) != 0)
{
if (errno == EINVAL)
die ("Unable to set up system call filtering as requested: "
"prctl(PR_SET_SECCOMP) reported EINVAL. "
"(Hint: this requires a kernel configured with "
"CONFIG_SECCOMP and CONFIG_SECCOMP_FILTER.)");
die_with_error ("prctl(PR_SET_SECCOMP)");
}
}
}
static void
usage (int ecode, FILE *out)
{
fprintf (out, "usage: %s [OPTIONS...] [--] COMMAND [ARGS...]\n\n", argv0 ? argv0 : "bwrap");
fprintf (out,
" --help Print this help\n"
" --version Print version\n"
" --args FD Parse NUL-separated args from FD\n"
" --argv0 VALUE Set argv[0] to the value VALUE before running the program\n"
" --level-prefix Prepend e.g. <3> to diagnostic messages\n"
" --unshare-all Unshare every namespace we support by default\n"
" --share-net Retain the network namespace (can only combine with --unshare-all)\n"
" --unshare-user Create new user namespace (may be automatically implied if not root)\n"
" --unshare-user-try Create new user namespace if possible else continue by skipping it\n"
" --unshare-ipc Create new ipc namespace\n"
" --unshare-pid Create new pid namespace\n"
" --unshare-net Create new network namespace\n"
" --unshare-uts Create new uts namespace\n"
" --unshare-cgroup Create new cgroup namespace\n"
" --unshare-cgroup-try Create new cgroup namespace if possible else continue by skipping it\n"
" --userns FD Use this user namespace (cannot combine with --unshare-user)\n"
" --userns2 FD After setup switch to this user namespace, only useful with --userns\n"
" --disable-userns Disable further use of user namespaces inside sandbox\n"
" --assert-userns-disabled Fail unless further use of user namespace inside sandbox is disabled\n"
" --pidns FD Use this pid namespace (as parent namespace if using --unshare-pid)\n"
" --uid UID Custom uid in the sandbox (requires --unshare-user or --userns)\n"
" --gid GID Custom gid in the sandbox (requires --unshare-user or --userns)\n"
" --hostname NAME Custom hostname in the sandbox (requires --unshare-uts)\n"
" --chdir DIR Change directory to DIR\n"
" --clearenv Unset all environment variables\n"
" --setenv VAR VALUE Set an environment variable\n"
" --unsetenv VAR Unset an environment variable\n"
" --lock-file DEST Take a lock on DEST while sandbox is running\n"
" --sync-fd FD Keep this fd open while sandbox is running\n"
" --bind SRC DEST Bind mount the host path SRC on DEST\n"
" --bind-try SRC DEST Equal to --bind but ignores non-existent SRC\n"
" --dev-bind SRC DEST Bind mount the host path SRC on DEST, allowing device access\n"
" --dev-bind-try SRC DEST Equal to --dev-bind but ignores non-existent SRC\n"
" --ro-bind SRC DEST Bind mount the host path SRC readonly on DEST\n"
" --ro-bind-try SRC DEST Equal to --ro-bind but ignores non-existent SRC\n"
" --bind-fd FD DEST Bind open directory or path fd on DEST\n"
" --ro-bind-fd FD DEST Bind open directory or path fd read-only on DEST\n"
" --remount-ro DEST Remount DEST as readonly; does not recursively remount\n"
" --overlay-src SRC Read files from SRC in the following overlay\n"
" --overlay RWSRC WORKDIR DEST Mount overlayfs on DEST, with RWSRC as the host path for writes and\n"
" WORKDIR an empty directory on the same filesystem as RWSRC\n"
" --tmp-overlay DEST Mount overlayfs on DEST, with writes going to an invisible tmpfs\n"
" --ro-overlay DEST Mount overlayfs read-only on DEST\n"
" --exec-label LABEL Exec label for the sandbox\n"
" --file-label LABEL File label for temporary sandbox content\n"
" --proc DEST Mount new procfs on DEST\n"
" --dev DEST Mount new dev on DEST\n"
" --tmpfs DEST Mount new tmpfs on DEST\n"
" --mqueue DEST Mount new mqueue on DEST\n"
" --dir DEST Create dir at DEST\n"
" --file FD DEST Copy from FD to destination DEST\n"
" --bind-data FD DEST Copy from FD to file which is bind-mounted on DEST\n"
" --ro-bind-data FD DEST Copy from FD to file which is readonly bind-mounted on DEST\n"
" --symlink SRC DEST Create symlink at DEST with target SRC\n"
" --seccomp FD Load and use seccomp rules from FD (not repeatable)\n"
" --add-seccomp-fd FD Load and use seccomp rules from FD (repeatable)\n"
" --block-fd FD Block on FD until some data to read is available\n"
" --userns-block-fd FD Block on FD until the user namespace is ready\n"
" --info-fd FD Write information about the running container to FD\n"
" --json-status-fd FD Write container status to FD as multiple JSON documents\n"
" --new-session Create a new terminal session\n"
" --die-with-parent Kills with SIGKILL child process (COMMAND) when bwrap or bwrap's parent dies.\n"
" --as-pid-1 Do not install a reaper process with PID=1\n"
" --cap-add CAP Add cap CAP when running as privileged user\n"
" --cap-drop CAP Drop cap CAP when running as privileged user\n"
" --perms OCTAL Set permissions of next argument (--bind-data, --file, etc.)\n"
" --size BYTES Set size of next argument (only for --tmpfs)\n"
" --chmod OCTAL PATH Change permissions of PATH (must already exist)\n"
" --not-a-security-boundary Do not fail hard when some sandbox setup steps fail;\n"
" use only when the sandbox is not a security boundary\n"
);
exit (ecode);
}
static void
handle_die_with_parent (void)
{
if (opt_die_with_parent && prctl (PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0) != 0)
die_with_error ("prctl");
}
static void
block_sigchild (void)
{
sigset_t mask;
int status;
sigemptyset (&mask);
sigaddset (&mask, SIGCHLD);
if (sigprocmask (SIG_BLOCK, &mask, NULL) == -1)
die_with_error ("sigprocmask");
while (waitpid (-1, &status, WNOHANG) > 0)
;
}
static void
unblock_sigchild (void)
{
sigset_t mask;
sigemptyset (&mask);
sigaddset (&mask, SIGCHLD);
if (sigprocmask (SIG_UNBLOCK, &mask, NULL) == -1)
die_with_error ("sigprocmask");
}
static int
close_extra_fds (void *data, int fd)
{
int *extra_fds = (int *) data;
int i;
for (i = 0; extra_fds[i] != -1; i++)
if (fd == extra_fds[i])
return 0;
if (fd <= 2)
return 0;
close (fd);
return 0;
}
static int
propagate_exit_status (int status)
{
if (WIFEXITED (status))
return WEXITSTATUS (status);
if (WIFSIGNALED (status))
return 128 + WTERMSIG (status);
return 255;
}
static void
dump_info (int fd, const char *output, bool exit_on_error)
{
size_t len = strlen (output);
if (write_to_fd (fd, output, len))
{
if (exit_on_error)
die_with_error ("Write to info_fd");
}
}
static void
report_child_exit_status (int exitc, int setup_finished_fd)
{
ssize_t s;
char data[2];
cleanup_free char *output = NULL;
if (opt_json_status_fd == -1 || setup_finished_fd == -1)
return;
s = TEMP_FAILURE_RETRY (read (setup_finished_fd, data, sizeof data));
if (s == -1 && errno != EAGAIN)
die_with_error ("read eventfd");
if (s != 1) return;
output = xasprintf ("{ \"exit-code\": %i }\n", exitc);
dump_info (opt_json_status_fd, output, false);
close (opt_json_status_fd);
opt_json_status_fd = -1;
close (setup_finished_fd);
}
static int
monitor_child (int event_fd, pid_t child_pid, int setup_finished_fd)
{
int res;
uint64_t val;
ssize_t s;
int signal_fd;
sigset_t mask;
struct pollfd fds[2];
int num_fds;
struct signalfd_siginfo fdsi;
int dont_close[] = {-1, -1, -1, -1};
unsigned int j = 0;
int exitc;
pid_t died_pid;
int died_status;
if (event_fd != -1)
dont_close[j++] = event_fd;
if (opt_json_status_fd != -1)
dont_close[j++] = opt_json_status_fd;
if (setup_finished_fd != -1)
dont_close[j++] = setup_finished_fd;
assert (j < sizeof(dont_close)/sizeof(*dont_close));
fdwalk (close_extra_fds, dont_close);
sigemptyset (&mask);
sigaddset (&mask, SIGCHLD);
signal_fd = signalfd (-1, &mask, SFD_CLOEXEC | SFD_NONBLOCK);
if (signal_fd == -1)
die_with_error ("Can't create signalfd");
num_fds = 1;
fds[0].fd = signal_fd;
fds[0].events = POLLIN;
if (event_fd != -1)
{
fds[1].fd = event_fd;
fds[1].events = POLLIN;
num_fds++;
}
while (1)
{
fds[0].revents = fds[1].revents = 0;
res = poll (fds, num_fds, -1);
if (res == -1 && errno != EINTR)
die_with_error ("poll");
if (event_fd != -1)
{
s = read (event_fd, &val, 8);
if (s == -1 && errno != EINTR && errno != EAGAIN)
die_with_error ("read eventfd");
else if (s == 8)
{
exitc = (int) val - 1;
report_child_exit_status (exitc, setup_finished_fd);
return exitc;
}
}
s = read (signal_fd, &fdsi, sizeof (struct signalfd_siginfo));
if (s == -1 && errno != EINTR && errno != EAGAIN)
die_with_error ("read signalfd");
while ((died_pid = waitpid (-1, &died_status, WNOHANG)) > 0)
{
if (died_pid == child_pid)
{
exitc = propagate_exit_status (died_status);
report_child_exit_status (exitc, setup_finished_fd);
return exitc;
}
}
}
die ("Should not be reached");
return 0;
}
static int
do_init (int event_fd, pid_t initial_pid)
{
int initial_exit_status = 1;
LockFile *lock;
for (lock = lock_files; lock != NULL; lock = lock->next)
{
int fd = TEMP_FAILURE_RETRY (open (lock->path, O_RDONLY | O_NOCTTY | O_CLOEXEC));
if (fd == -1)
die_with_error ("Unable to open lock file %s", lock->path);
struct flock l = {
.l_type = F_RDLCK,
.l_whence = SEEK_SET,
.l_start = 0,
.l_len = 0
};
if (TEMP_FAILURE_RETRY (fcntl (fd, F_SETLK, &l)) < 0)
die_with_error ("Unable to lock file %s", lock->path);
lock->fd = fd;
}
handle_die_with_parent ();
seccomp_programs_apply ();
while (true)
{
pid_t child;
int status;
child = TEMP_FAILURE_RETRY (wait (&status));
if (child == initial_pid)
{
initial_exit_status = propagate_exit_status (status);
if(event_fd != -1)
{
uint64_t val;
int res UNUSED;
val = initial_exit_status + 1;
res = TEMP_FAILURE_RETRY (write (event_fd, &val, 8));
}
}
if (child == -1 && errno != EINTR)
{
if (errno != ECHILD)
die_with_error ("init wait()");
break;
}
}
for (lock = lock_files; lock != NULL; lock = lock->next)
{
if (lock->fd >= 0)
{
close (lock->fd);
lock->fd = -1;
}
}
return initial_exit_status;
}
#define CAP_TO_MASK_0(x) (1L << ((x) & 31))
#define CAP_TO_MASK_1(x) CAP_TO_MASK_0(x - 32)
static bool opt_cap_add_or_drop_used;
static uint32_t requested_caps[2] = {0, 0};
#define REQUIRED_CAPS_0 (CAP_TO_MASK_0 (CAP_SYS_ADMIN) | CAP_TO_MASK_0 (CAP_SYS_CHROOT) | CAP_TO_MASK_0 (CAP_NET_ADMIN) | CAP_TO_MASK_0 (CAP_SETUID) | CAP_TO_MASK_0 (CAP_SETGID) | CAP_TO_MASK_0 (CAP_SYS_PTRACE))
#define REQUIRED_CAPS_1 0
static void
drop_all_caps (bool keep_requested_caps)
{
struct __user_cap_header_struct hdr = { _LINUX_CAPABILITY_VERSION_3, 0 };
struct __user_cap_data_struct data[2] = { { 0 } };
if (keep_requested_caps)
{
if (!opt_cap_add_or_drop_used && real_uid == 0)
return;
data[0].effective = requested_caps[0];
data[0].permitted = requested_caps[0];
data[0].inheritable = requested_caps[0];
data[1].effective = requested_caps[1];
data[1].permitted = requested_caps[1];
data[1].inheritable = requested_caps[1];
}
if (capset (&hdr, data) < 0)
{
if (errno == EPERM && real_uid == 0)
return;
else
die_with_error ("capset failed");
}
}
static bool
has_caps (void)
{
struct __user_cap_header_struct hdr = { _LINUX_CAPABILITY_VERSION_3, 0 };
struct __user_cap_data_struct data[2] = { { 0 } };
if (capget (&hdr, data) < 0)
die_with_error ("capget failed");
return data[0].permitted != 0 || data[1].permitted != 0;
}
static void
prctl_caps (uint32_t *caps, bool do_cap_bounding, bool do_set_ambient)
{
unsigned long cap;
for (cap = 0; cap <= CAP_LAST_CAP; cap++)
{
bool keep = false;
if (cap < 32)
{
if (CAP_TO_MASK_0 (cap) & caps[0])
keep = true;
}
else
{
if (CAP_TO_MASK_1 (cap) & caps[1])
keep = true;
}
if (keep && do_set_ambient)
{
#ifdef PR_CAP_AMBIENT
int res = prctl (PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, cap, 0, 0);
if (res == -1 && !(errno == EINVAL || errno == EPERM))
die_with_error ("Adding ambient capability %ld", cap);
#else
#endif
}
if (!keep && do_cap_bounding)
{
int res = prctl (PR_CAPBSET_DROP, cap, 0, 0, 0);
if (res == -1 && !(errno == EINVAL || errno == EPERM))
die_with_error ("Dropping capability %ld from bounds", cap);
}
}
}
static void
drop_cap_bounding_set (bool drop_all)
{
if (!drop_all)
prctl_caps (requested_caps, true, false);
else
{
uint32_t no_caps[2] = {0, 0};
prctl_caps (no_caps, true, false);
}
}
static void
set_ambient_capabilities (void)
{
prctl_caps (requested_caps, false, true);
}
static void
acquire_privs (void)
{
uid_t euid;
euid = geteuid ();
if (real_uid != euid)
{
die ("setuid use of bubblewrap is not supported");
}
else if (real_uid != 0 && has_caps ())
{
die ("Unexpected capabilities but not setuid, old file caps config?");
}
else if (real_uid == 0)
{
struct __user_cap_header_struct hdr = { _LINUX_CAPABILITY_VERSION_3, 0 };
struct __user_cap_data_struct data[2] = { { 0 } };
if (capget (&hdr, data) < 0)
die_with_error ("capget (for uid == 0) failed");
requested_caps[0] = data[0].effective;
requested_caps[1] = data[1].effective;
}
}
static void
switch_to_user_with_privs (void)
{
if (opt_unshare_user || opt_userns_fd != -1)
drop_cap_bounding_set (false);
if (opt_userns_fd != -1)
{
if (opt_sandbox_uid != real_uid && setuid (opt_sandbox_uid) < 0)
die_with_error ("unable to switch to uid %d", opt_sandbox_uid);
if (opt_sandbox_gid != real_gid && setgid (opt_sandbox_gid) < 0)
die_with_error ("unable to switch to gid %d", opt_sandbox_gid);
}
}
static void
drop_privs (bool keep_requested_caps)
{
drop_all_caps (keep_requested_caps);
if (prctl (PR_SET_DUMPABLE, 1, 0, 0, 0) != 0)
die_with_error ("can't set dumpable");
}
static int
openat_in_root (const char *root, const char *path, int flags)
{
cleanup_fd int root_fd = TEMP_FAILURE_RETRY (open (root, O_PATH | O_NOCTTY | O_CLOEXEC));
return safe_openat (root_fd, root, path, flags, 0);
}
static void
write_uid_gid_map (uid_t sandbox_uid,
uid_t parent_uid,
uid_t sandbox_gid,
uid_t parent_gid,
pid_t pid,
bool deny_groups,
bool map_root)
{
cleanup_free char *uid_map = NULL;
cleanup_free char *gid_map = NULL;
cleanup_free char *dir = NULL;
cleanup_fd int dir_fd = -1;
if (pid == -1)
dir = xstrdup ("self");
else
dir = xasprintf ("%d", pid);
dir_fd = openat (proc_fd, dir, O_PATH);
if (dir_fd < 0)
die_with_error ("open /proc/%s failed", dir);
if (map_root && parent_uid != 0 && sandbox_uid != 0)
uid_map = xasprintf ("0 %d 1\n"
"%d %d 1\n", overflow_uid, sandbox_uid, parent_uid);
else
uid_map = xasprintf ("%d %d 1\n", sandbox_uid, parent_uid);
if (map_root && parent_gid != 0 && sandbox_gid != 0)
gid_map = xasprintf ("0 %d 1\n"
"%d %d 1\n", overflow_gid, sandbox_gid, parent_gid);
else
gid_map = xasprintf ("%d %d 1\n", sandbox_gid, parent_gid);
if (write_file_at (dir_fd, "uid_map", uid_map) != 0)
die_with_error ("setting up uid map");
if (deny_groups &&
write_file_at (dir_fd, "setgroups", "deny\n") != 0)
{
if (errno != ENOENT)
die_with_error ("error writing to setgroups");
}
if (write_file_at (dir_fd, "gid_map", gid_map) != 0)
die_with_error ("setting up gid map");
}
static void
setup_op_bind_mount_fd (bind_option_t options,
int src_fd,
const char *src_display,
int dest_fd,
const char *dest_display)
{
bind_mount_result bind_result;
char *failing_path = NULL;
bind_result = bind_mount_fd (src_fd, dest_fd, BIND_RECURSIVE | options, &failing_path);
if (bind_result != BIND_MOUNT_SUCCESS)
die_with_bind_result (bind_result, errno, failing_path,
"Can't bind mount %s on %s", src_display, dest_display);
assert (failing_path == NULL);
}
static void
setup_op_bind_mount (bind_option_t options,
const char *src,
int dest_fd,
const char *dest_display)
{
cleanup_fd int src_fd = openat_in_root ("/oldroot", src, O_PATH);
if (src_fd < 0)
die_with_error("Can't open source %s", src);
setup_op_bind_mount_fd (options, src_fd, src, dest_fd, dest_display);
}
static void
setup_op_tmpfs_mount (uint32_t perms,
size_t size,
int dest_fd,
const char *dest_display)
{
cleanup_free char *mode = NULL;
if (size > MAX_TMPFS_BYTES)
die_with_error ("Specified tmpfs size too large (%zu > %zu)", size, MAX_TMPFS_BYTES);
if (size != 0)
mode = xasprintf ("mode=%#o,size=%zu", perms, size);
else
mode = xasprintf ("mode=%#o", perms);
cleanup_free char *dest_path = fd_to_proc_path (dest_fd);
cleanup_free char *opt = label_mount (mode, opt_file_label);
if (mount ("tmpfs", dest_path, "tmpfs", MS_NOSUID | MS_NODEV, opt) != 0)
die_with_mount_error ("Can't mount tmpfs on %s", dest_display);
}
static int
ensure_node_at (int parent_fd, const char *name, NodeType node_type, int perms,
bool return_writable)
{
assert (node_type != NODE_TYPE_NONE);
if (node_type == NODE_TYPE_DIR)
{
if (mkdirat (parent_fd, name, perms >= 0 ? perms : 0755) < 0 && errno != EEXIST)
return -1;
cleanup_fd int fd = TEMP_FAILURE_RETRY (
openat (parent_fd, name, O_PATH | O_NOFOLLOW | O_CLOEXEC));
return steal_fd (&fd);
}
else
{
if (node_type == NODE_TYPE_FILE)
{
cleanup_fd int fd = TEMP_FAILURE_RETRY (
openat (parent_fd, name,
O_WRONLY | O_CREAT | (return_writable ? O_TRUNC : O_EXCL) | O_NOFOLLOW | O_NOCTTY | O_CLOEXEC,
perms >= 0 ? perms : 0444));
if (return_writable)
{
if (fd < 0)
return -1;
return steal_fd (&fd);
}
if (fd < 0 && errno != EEXIST)
return -1;
}
return TEMP_FAILURE_RETRY (openat (parent_fd, name,
O_PATH | O_NOFOLLOW | O_CLOEXEC));
}
}
static int
ensure_newroot_parents_at (const char *path,
mode_t mode,
char **last_component)
{
cleanup_free char *fn = NULL;
char *p;
if (path == NULL)
{
errno = EINVAL;
return -1;
}
fn = xstrdup (path);
p = fn;
cleanup_fd int parent_fd = openat_in_root ("/newroot", "/", O_DIRECTORY);
while (*p == '/')
p++;
*last_component = NULL;
while (*p)
{
char *start_component = p;
while (*p && *p != '/')
p++;
char *after_component = p;
while (*p == '/')
p++;
cleanup_free char *component = xstrndup (start_component, after_component - start_component);
bool is_last = (*p == '\0');
if (is_last)
{
*last_component = steal_pointer (&component);
break;
}
if (mkdirat (parent_fd, component, mode) < 0 && errno != EEXIST)
return -1;
char saved = *after_component;
*after_component = 0;
cleanup_fd int new_parent_fd = openat_in_root ("/newroot", fn, O_DIRECTORY);
*after_component = saved;
if (new_parent_fd < 0)
return -1;
close (parent_fd);
parent_fd = steal_fd(&new_parent_fd);
}
if (*last_component == NULL)
*last_component = xstrdup(".");
return steal_fd(&parent_fd);
}
static bool
op_type_is_mount (SetupOpType type)
{
switch (type) {
case SETUP_BIND_MOUNT:
case SETUP_RO_BIND_MOUNT:
case SETUP_DEV_BIND_MOUNT:
case SETUP_OVERLAY_MOUNT:
case SETUP_TMP_OVERLAY_MOUNT:
case SETUP_RO_OVERLAY_MOUNT:
case SETUP_OVERLAY_SRC:
case SETUP_MOUNT_PROC:
case SETUP_MOUNT_DEV:
case SETUP_MOUNT_TMPFS:
case SETUP_MOUNT_MQUEUE:
case SETUP_REMOUNT_RO_NO_RECURSIVE:
case SETUP_MAKE_BIND_FILE:
case SETUP_MAKE_RO_BIND_FILE:
return true;
case SETUP_MAKE_DIR:
case SETUP_MAKE_FILE:
case SETUP_MAKE_SYMLINK:
case SETUP_SET_HOSTNAME:
case SETUP_CHMOD:
default:
return false;
}
}
static int
reopen_newroot_fd (int dest_fd, const char *dest_path)
{
close (dest_fd);
dest_fd = openat_in_root ("/newroot", dest_path, O_PATH | O_NOFOLLOW);
if (dest_fd < 0)
die_with_error ("Can't reopen %s", dest_path);
return dest_fd;
}
static void
setup_newroot (bool unshare_pid)
{
SetupOp *op;
int tmp_overlay_idx = 0;
for (op = ops; op != NULL; op = op->next)
{
cleanup_fd int source_fd = -1;
cleanup_fd int dest_fd = -1;
cleanup_fd int parent_fd = -1;
cleanup_free char *name = NULL;
int source_mode = 0;
int dest_mode = 0;
unsigned int i;
if (op->source &&
op->type != SETUP_MAKE_SYMLINK)
{
if (has_path_prefix (op->source, "/proc"))
{
cleanup_free char *proc_oldroot_path = get_oldroot_path (op->source);
source_fd = TEMP_FAILURE_RETRY (
open (proc_oldroot_path, O_PATH | O_CLOEXEC));
}
else
source_fd = openat_in_root ("/oldroot", op->source, O_PATH);
if (source_fd < 0)
{
if (op->flags & ALLOW_NOTEXIST && errno == ENOENT)
continue;
die_with_error("Can't open source %s", op->source);
}
source_mode = get_file_mode (source_fd);
if (source_mode < 0)
{
if (op->flags & ALLOW_NOTEXIST && errno == ENOENT)
continue;
die_with_error("Can't get type of source %s", op->source);
}
}
if (op->dest &&
(op->flags & NO_CREATE_DEST) == 0)
{
unsigned parent_mode = 0755;
NodeType dest_type = NODE_TYPE_NONE;
switch (op->type)
{
default:
case SETUP_MAKE_SYMLINK:
case SETUP_SET_HOSTNAME:
dest_type = NODE_TYPE_NONE;
break;
case SETUP_REMOUNT_RO_NO_RECURSIVE:
case SETUP_CHMOD:
dest_type = NODE_TYPE_EXISTING;
break;
case SETUP_MAKE_FILE:
case SETUP_MAKE_BIND_FILE:
case SETUP_MAKE_RO_BIND_FILE:
dest_type = NODE_TYPE_FILE;
break;
case SETUP_BIND_MOUNT:
case SETUP_RO_BIND_MOUNT:
case SETUP_DEV_BIND_MOUNT:
dest_type = (source_mode == S_IFDIR) ? NODE_TYPE_DIR : NODE_TYPE_FILE;
break;
case SETUP_OVERLAY_MOUNT:
case SETUP_RO_OVERLAY_MOUNT:
case SETUP_TMP_OVERLAY_MOUNT:
case SETUP_OVERLAY_SRC:
case SETUP_MOUNT_PROC:
case SETUP_MOUNT_DEV:
case SETUP_MOUNT_TMPFS:
case SETUP_MOUNT_MQUEUE:
case SETUP_MAKE_DIR:
dest_type = NODE_TYPE_DIR;
break;
}
if (op->perms >= 0 &&
(op->perms & 0070) == 0)
parent_mode &= ~0050U;
if (op->perms >= 0 &&
(op->perms & 0007) == 0)
parent_mode &= ~0005U;
parent_fd = ensure_newroot_parents_at (op->dest, parent_mode, &name);
if (parent_fd < 0)
die_with_error ("Can't mkdir parents for %s", op->dest);
if (dest_type != NODE_TYPE_NONE)
{
bool writable_fd = (op->type == SETUP_MAKE_FILE);
int node_perms = (op->type == SETUP_MAKE_DIR || op->type == SETUP_MAKE_FILE) ? op->perms : -1;
dest_fd = ensure_node_at (parent_fd, name, dest_type, node_perms, writable_fd);
if (dest_fd < 0)
die_with_error ("Can't create file %s", op->dest);
dest_mode = get_file_mode (dest_fd);
if (dest_mode < 0)
die_with_error("Can't get type of dest %s", op->dest);
if (op_type_is_mount (op->type) && dest_mode == S_IFLNK)
die ("Can't mount on symlink destination %s", op->dest);
if (dest_type == NODE_TYPE_DIR && dest_mode != S_IFDIR)
die ("Destination is not a directory %s", op->dest);
if (dest_type == NODE_TYPE_FILE && dest_mode == S_IFDIR)
die ("Destination is not a file %s", op->dest);
}
}
switch (op->type)
{
case SETUP_RO_BIND_MOUNT:
case SETUP_DEV_BIND_MOUNT:
case SETUP_BIND_MOUNT:
{
bind_option_t bind_flags = 0;
if (opt_not_a_security_boundary)
bind_flags |= BIND_FAIL_OPEN;
if (op->type == SETUP_RO_BIND_MOUNT)
bind_flags |= BIND_READONLY;
if (op->type == SETUP_DEV_BIND_MOUNT)
bind_flags |= BIND_DEVICES;
setup_op_bind_mount_fd (bind_flags, source_fd, op->source, dest_fd, op->dest);
if (op->fd >= 0)
{
struct stat fd_st, mount_st;
cleanup_fd int mounted_fd = openat_in_root ("/newroot", op->dest, O_PATH);
if (mounted_fd < 0)
die_with_error ("Can't reopen %s", op->dest);
if (fstat (op->fd, &fd_st) != 0)
die_with_error ("Can't stat fd %d", op->fd);
if (fstat (mounted_fd, &mount_st) != 0)
die_with_error ("Can't stat mount at %s", op->dest);
if (fd_st.st_ino != mount_st.st_ino ||
fd_st.st_dev != mount_st.st_dev)
die_with_error ("Race condition binding dirfd");
close (op->fd);
op->fd = -1;
}
break;
}
case SETUP_OVERLAY_MOUNT:
case SETUP_RO_OVERLAY_MOUNT:
case SETUP_TMP_OVERLAY_MOUNT:
{
StringBuilder sb = {0};
bool multi_src = false;
cleanup_fdset FdSet fds = {0};
cleanup_free char *dest_path = fd_to_proc_path (dest_fd);
if (op->source != NULL)
{
cleanup_free char *upper_path = fdset_add_to_proc_path (&fds, steal_fd (&source_fd));
strappendf (&sb, "upperdir=%s,", upper_path);
op = op->next;
int work_fd = openat_in_root ("/oldroot", op->source, O_PATH);
if (work_fd < 0)
die_with_error ("Can't open overlay workdir %s", op->source);
cleanup_free char *work_path = fdset_add_to_proc_path (&fds, work_fd);
strappendf (&sb, "workdir=%s,", work_path);
}
else if (op->type == SETUP_TMP_OVERLAY_MOUNT)
strappendf (&sb, "upperdir=/tmp-overlay-upper-%1$d,workdir=/tmp-overlay-work-%1$d,",
tmp_overlay_idx++);
strappend (&sb, "lowerdir=");
while (op->next != NULL && op->next->type == SETUP_OVERLAY_SRC)
{
op = op->next;
int lower_fd = openat_in_root ("/oldroot", op->source, O_PATH);
if (lower_fd < 0)
die_with_error ("Can't open overlay source %s", op->source);
cleanup_free char *lower_path = fdset_add_to_proc_path (&fds, lower_fd);
if (multi_src)
strappend (&sb, ":");
strappend (&sb, lower_path);
multi_src = true;
}
strappend (&sb, ",userxattr");
if (mount ("overlay", dest_path, "overlay", MS_MGC_VAL | MS_NOSUID | MS_NODEV, sb.str) != 0)
{
if (errno == ELOOP)
die ("Can't make overlay mount on %s with options %s: "
"Overlay directories may not overlap",
op->dest, sb.str);
die_with_mount_error ("Can't make overlay mount on %s with options %s",
op->dest, sb.str);
}
free (sb.str);
}
break;
case SETUP_REMOUNT_RO_NO_RECURSIVE:
{
char *failing_path = NULL;
bind_mount_result bind_result;
bind_result = bind_mount_fd (-1, dest_fd, BIND_READONLY, &failing_path);
if (bind_result != BIND_MOUNT_SUCCESS)
die_with_bind_result (bind_result, errno, failing_path,
"Can't remount readonly on %s", op->dest);
assert (failing_path == NULL);
}
break;
case SETUP_MOUNT_PROC:
{
cleanup_free char *dest_path = fd_to_proc_path (dest_fd);
if (unshare_pid || opt_pidns_fd != -1)
{
if (mount ("proc", dest_path, "proc", MS_NOSUID | MS_NOEXEC | MS_NODEV, NULL) != 0)
die_with_mount_error ("Can't mount proc on %s", op->dest);
}
else
{
setup_op_bind_mount (0, "/proc", dest_fd, op->dest);
}
dest_fd = reopen_newroot_fd (dest_fd, op->dest);
static const char * const cover_proc_dirs[] = { "sys", "sysrq-trigger", "irq", "bus" };
for (i = 0; i < N_ELEMENTS (cover_proc_dirs); i++)
{
if (faccessat (dest_fd, cover_proc_dirs[i], W_OK, 0) < 0)
{
if (errno == EACCES || errno == ENOENT || errno == EROFS)
continue;
die_with_error ("Can't access %s/%s", op->dest, cover_proc_dirs[i]);
}
cleanup_fd int subdir_fd = TEMP_FAILURE_RETRY (
openat (dest_fd, cover_proc_dirs[i], O_PATH | O_NOFOLLOW | O_CLOEXEC));
if (subdir_fd < 0)
die_with_error ("Can't open %s/%s", op->dest, cover_proc_dirs[i]);
cleanup_free char *subdir_path = fd_to_proc_path (subdir_fd);
setup_op_bind_mount_fd (BIND_READONLY,
subdir_fd, cover_proc_dirs[i],
subdir_fd, cover_proc_dirs[i]);
}
break;
}
case SETUP_MOUNT_DEV:
{
setup_op_tmpfs_mount (0755, 0, dest_fd, op->dest);
dest_fd = reopen_newroot_fd (dest_fd, op->dest);
static const char *const devnodes[] = { "null", "zero", "full", "random", "urandom", "tty" };
for (i = 0; i < N_ELEMENTS (devnodes); i++)
{
cleanup_free char *node_src = strconcat ("/dev/", devnodes[i]);
cleanup_fd int node_fd = -1;
node_fd = ensure_node_at (dest_fd, devnodes[i], NODE_TYPE_FILE, 0444, false);
if (node_fd < 0)
die_with_error ("Can't create file %s/%s", op->dest, devnodes[i]);
setup_op_bind_mount (BIND_DEVICES, node_src, node_fd, devnodes[i]);
}
static const char *const stdionodes[] = { "stdin", "stdout", "stderr" };
for (i = 0; i < N_ELEMENTS (stdionodes); i++)
{
cleanup_free char *target = xasprintf ("/proc/self/fd/%d", i);
if (symlinkat (target, dest_fd, stdionodes[i]) < 0)
die_with_error ("Can't create symlink %s/%s", op->dest, stdionodes[i]);
}
if (symlinkat ("/proc/self/fd", dest_fd, "fd") < 0)
die_with_error ("Can't create symlink %s/fd", op->dest);
if (symlinkat ("/proc/kcore", dest_fd, "core") < 0)
die_with_error ("Can't create symlink %s/core", op->dest);
if (mkdirat (dest_fd, "shm", 0755) == -1)
die_with_error ("Can't create %s/shm", op->dest);
if (mkdirat (dest_fd, "pts", 0755) == -1)
die_with_error ("Can't create %s/pts", op->dest);
{
cleanup_fd int pts_fd = TEMP_FAILURE_RETRY (
openat (dest_fd, "pts", O_PATH | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC));
if (pts_fd < 0)
die_with_error ("Can't open %s/pts", op->dest);
cleanup_free char *pts_path = fd_to_proc_path (pts_fd);
if (mount ("devpts", pts_path, "devpts", MS_NOSUID | MS_NOEXEC,
"newinstance,ptmxmode=0666,mode=620") != 0)
die_with_mount_error ("Can't mount devpts on %s/pts", op->dest);
}
if (symlinkat ("pts/ptmx", dest_fd, "ptmx") != 0)
die_with_error ("Can't make symlink at %s/ptmx", op->dest);
if (host_tty_dev != NULL && *host_tty_dev != 0)
{
cleanup_fd int console_fd = ensure_node_at (dest_fd, "console", NODE_TYPE_FILE, 0444, false);
if (console_fd < 0)
die_with_error ("creating %s/console", op->dest);
setup_op_bind_mount (BIND_DEVICES, host_tty_dev, console_fd, "console");
}
break;
}
case SETUP_MOUNT_TMPFS:
assert (op->perms >= 0);
assert (op->perms <= 07777);
setup_op_tmpfs_mount (op->perms, op->size, dest_fd, op->dest);
break;
case SETUP_MOUNT_MQUEUE:
{
cleanup_free char *dest_path = fd_to_proc_path (dest_fd);
if (mount ("mqueue", dest_path, "mqueue", 0, NULL) != 0)
die_with_mount_error ("Can't mount mqueue on %s", op->dest);
break;
}
case SETUP_MAKE_DIR:
assert (op->perms >= 0);
assert (op->perms <= 07777);
break;
case SETUP_CHMOD:
{
{
dest_fd = openat_in_root ("/newroot", op->dest, O_PATH | O_NOFOLLOW);
if (dest_fd < 0)
die_with_error ("Can't open %s", op->dest);
}
cleanup_free char *dest_path = fd_to_proc_path (dest_fd);
assert (op->perms >= 0);
assert (op->perms <= 07777);
if (chmod (dest_path, op->perms) != 0)
die_with_error ("Can't chmod %#o %s", op->perms, op->dest);
break;
}
case SETUP_MAKE_FILE:
assert (op->perms >= 0);
assert (op->perms <= 07777);
if (copy_file_data (op->fd, dest_fd) != 0)
die_with_error ("Can't write data to file %s", op->dest);
close (op->fd);
op->fd = -1;
break;
case SETUP_MAKE_BIND_FILE:
case SETUP_MAKE_RO_BIND_FILE:
{
cleanup_fd int tmp_fd = -1;
char tempfile[] = "/bindfileXXXXXX";
assert (op->perms >= 0);
assert (op->perms <= 07777);
tmp_fd = mkstemp (tempfile);
if (tmp_fd == -1)
die_with_error ("Can't create tmpfile for %s", op->dest);
if (fchmod (tmp_fd, op->perms) != 0)
die_with_error ("Can't set mode %#o on file to be used for %s",
op->perms, op->dest);
if (copy_file_data (op->fd, tmp_fd) != 0)
die_with_error ("Can't write data to file %s", op->dest);
close (op->fd);
op->fd = -1;
setup_op_bind_mount_fd ((op->type == SETUP_MAKE_RO_BIND_FILE ? BIND_READONLY : 0),
tmp_fd, "tmpfile", dest_fd, op->dest);
unlink (tempfile);
}
break;
case SETUP_MAKE_SYMLINK:
assert (op->source != NULL);
if (symlinkat (op->source, parent_fd, name) != 0)
{
if (errno == EEXIST)
{
char buf[PATH_MAX + 1];
ssize_t len = readlinkat (parent_fd, name, buf, sizeof (buf) - 1);
if (len < 0)
{
if (errno == EINVAL)
die ("Can't make symlink at %s: destination exists and is not a symlink", op->dest);
else
die_with_error ("Can't make symlink at %s: destination exists, and cannot read symlink target", op->dest);
}
buf[len] = '\0';
if (strcmp (buf, op->source) == 0)
break;
die ("Can't make symlink at %s: existing destination is %s", op->dest, buf);
}
die_with_error ("Can't make symlink at %s", op->dest);
}
break;
case SETUP_SET_HOSTNAME:
assert (op->dest != NULL);
if (op->dest == NULL)
die ("Hostname argument is NULL");
if (sethostname (op->dest, strlen(op->dest)) != 0)
die_with_error ("Can't set hostname to %s", op->dest);
break;
case SETUP_OVERLAY_SRC:
default:
die ("Unexpected type %d", op->type);
}
}
}
static void
close_ops_fd (void)
{
SetupOp *op;
for (op = ops; op != NULL; op = op->next)
{
if (op->fd != -1)
{
(void) close (op->fd);
op->fd = -1;
}
}
}
static void
resolve_symlinks_in_ops (void)
{
SetupOp *op;
for (op = ops; op != NULL; op = op->next)
{
const char *old_source;
switch (op->type)
{
case SETUP_RO_BIND_MOUNT:
case SETUP_DEV_BIND_MOUNT:
case SETUP_BIND_MOUNT:
case SETUP_OVERLAY_SRC:
case SETUP_OVERLAY_MOUNT:
old_source = op->source;
op->source = realpath (old_source, NULL);
if (op->source == NULL)
{
if (op->flags & ALLOW_NOTEXIST && errno == ENOENT)
op->source = old_source;
else
die_with_error("Can't find source path %s", old_source);
}
break;
case SETUP_RO_OVERLAY_MOUNT:
case SETUP_TMP_OVERLAY_MOUNT:
case SETUP_MOUNT_PROC:
case SETUP_MOUNT_DEV:
case SETUP_MOUNT_TMPFS:
case SETUP_MOUNT_MQUEUE:
case SETUP_MAKE_DIR:
case SETUP_MAKE_FILE:
case SETUP_MAKE_BIND_FILE:
case SETUP_MAKE_RO_BIND_FILE:
case SETUP_MAKE_SYMLINK:
case SETUP_REMOUNT_RO_NO_RECURSIVE:
case SETUP_SET_HOSTNAME:
case SETUP_CHMOD:
default:
break;
}
}
}
static void __attribute__ ((noreturn))
print_version_and_exit (void)
{
printf ("%s\n", PACKAGE_STRING);
exit (0);
}
static int
is_modifier_option (const char *option)
{
return strcmp (option, "--perms") == 0
|| strcmp(option, "--size") == 0;
}
static void
warn_only_last_option (const char *name)
{
warn ("Only the last %s option will take effect", name);
}
static void
make_setup_overlay_src_ops (const char *const *const argv)
{
int i;
SetupOp *op;
for (i = 1; i <= next_overlay_src_count; i++)
{
op = setup_op_new (SETUP_OVERLAY_SRC);
op->source = argv[1 - 2 * i];
}
next_overlay_src_count = 0;
}
static void
parse_args_recurse (int *argcp,
const char ***argvp,
bool in_file,
int *total_parsed_argc_p)
{
SetupOp *op;
int argc = *argcp;
const char **argv = *argvp;
static const int32_t MAX_ARGS = 9000;
if (*total_parsed_argc_p > MAX_ARGS)
die ("Exceeded maximum number of arguments %u", MAX_ARGS);
while (argc > 0)
{
const char *arg = argv[0];
if (strcmp (arg, "--help") == 0)
{
usage (EXIT_SUCCESS, stdout);
}
else if (strcmp (arg, "--version") == 0)
{
print_version_and_exit ();
}
else if (strcmp (arg, "--args") == 0)
{
int the_fd;
char *endptr;
const char *p, *data_end;
size_t data_len;
cleanup_free const char **data_argv = NULL;
const char **data_argv_copy;
int data_argc;
int i;
if (in_file)
die ("--args not supported in arguments file");
if (argc < 2)
die ("--args takes an argument");
the_fd = strtol (argv[1], &endptr, 10);
if (argv[1][0] == 0 || endptr[0] != 0 || the_fd < 0)
die ("Invalid fd: %s", argv[1]);
opt_args_data = load_file_data (the_fd, &data_len);
if (opt_args_data == NULL)
die_with_error ("Can't read --args data");
(void) close (the_fd);
data_end = opt_args_data + data_len;
data_argc = 0;
p = opt_args_data;
while (p != NULL && p < data_end)
{
data_argc++;
(*total_parsed_argc_p)++;
if (*total_parsed_argc_p > MAX_ARGS)
die ("Exceeded maximum number of arguments %u", MAX_ARGS);
p = memchr (p, 0, data_end - p);
if (p != NULL)
p++;
}
data_argv = xcalloc (data_argc + 1, sizeof (char *));
i = 0;
p = opt_args_data;
while (p != NULL && p < data_end)
{
data_argv[i++] = p;
p = memchr (p, 0, data_end - p);
if (p != NULL)
p++;
}
data_argv_copy = data_argv;
parse_args_recurse (&data_argc, &data_argv_copy, true, total_parsed_argc_p);
argv += 1;
argc -= 1;
}
else if (strcmp (arg, "--argv0") == 0)
{
if (argc < 2)
die ("--argv0 takes one argument");
if (opt_argv0 != NULL)
die ("--argv0 used multiple times");
opt_argv0 = argv[1];
argv++;
argc--;
}
else if (strcmp (arg, "--level-prefix") == 0)
{
bwrap_level_prefix = true;
}
else if (strcmp (arg, "--unshare-all") == 0)
{
opt_unshare_user_try = opt_unshare_ipc = opt_unshare_pid =
opt_unshare_uts = opt_unshare_cgroup_try =
opt_unshare_net = true;
}
else if (strcmp (arg, "--unshare-user") == 0)
{
opt_unshare_user = true;
}
else if (strcmp (arg, "--unshare-user-try") == 0)
{
opt_unshare_user_try = true;
}
else if (strcmp (arg, "--unshare-ipc") == 0)
{
opt_unshare_ipc = true;
}
else if (strcmp (arg, "--unshare-pid") == 0)
{
opt_unshare_pid = true;
}
else if (strcmp (arg, "--unshare-net") == 0)
{
opt_unshare_net = true;
}
else if (strcmp (arg, "--unshare-uts") == 0)
{
opt_unshare_uts = true;
}
else if (strcmp (arg, "--unshare-cgroup") == 0)
{
opt_unshare_cgroup = true;
}
else if (strcmp (arg, "--unshare-cgroup-try") == 0)
{
opt_unshare_cgroup_try = true;
}
else if (strcmp (arg, "--share-net") == 0)
{
opt_unshare_net = false;
}
else if (strcmp (arg, "--chdir") == 0)
{
if (argc < 2)
die ("--chdir takes one argument");
if (opt_chdir_path != NULL)
warn_only_last_option ("--chdir");
opt_chdir_path = argv[1];
argv++;
argc--;
}
else if (strcmp (arg, "--disable-userns") == 0)
{
opt_disable_userns = true;
}
else if (strcmp (arg, "--assert-userns-disabled") == 0)
{
opt_assert_userns_disabled = true;
}
else if (strcmp (arg, "--remount-ro") == 0)
{
if (argc < 2)
die ("--remount-ro takes one argument");
op = setup_op_new (SETUP_REMOUNT_RO_NO_RECURSIVE);
op->dest = argv[1];
argv++;
argc--;
}
else if (strcmp(arg, "--bind") == 0 ||
strcmp(arg, "--bind-try") == 0)
{
if (argc < 3)
die ("%s takes two arguments", arg);
op = setup_op_new (SETUP_BIND_MOUNT);
op->source = argv[1];
op->dest = argv[2];
if (strcmp(arg, "--bind-try") == 0)
op->flags = ALLOW_NOTEXIST;
argv += 2;
argc -= 2;
}
else if (strcmp(arg, "--ro-bind") == 0 ||
strcmp(arg, "--ro-bind-try") == 0)
{
if (argc < 3)
die ("%s takes two arguments", arg);
op = setup_op_new (SETUP_RO_BIND_MOUNT);
op->source = argv[1];
op->dest = argv[2];
if (strcmp(arg, "--ro-bind-try") == 0)
op->flags = ALLOW_NOTEXIST;
argv += 2;
argc -= 2;
}
else if (strcmp (arg, "--dev-bind") == 0 ||
strcmp (arg, "--dev-bind-try") == 0)
{
if (argc < 3)
die ("%s takes two arguments", arg);
op = setup_op_new (SETUP_DEV_BIND_MOUNT);
op->source = argv[1];
op->dest = argv[2];
if (strcmp(arg, "--dev-bind-try") == 0)
op->flags = ALLOW_NOTEXIST;
argv += 2;
argc -= 2;
}
else if (strcmp (arg, "--bind-fd") == 0 ||
strcmp (arg, "--ro-bind-fd") == 0)
{
int src_fd;
char *endptr;
if (argc < 3)
die ("--bind-fd takes two arguments");
src_fd = strtol (argv[1], &endptr, 10);
if (argv[1][0] == 0 || endptr[0] != 0 || src_fd < 0)
die ("Invalid fd: %s", argv[1]);
if (strcmp(arg, "--ro-bind-fd") == 0)
op = setup_op_new (SETUP_RO_BIND_MOUNT);
else
op = setup_op_new (SETUP_BIND_MOUNT);
op->source = xasprintf ("/proc/self/fd/%d", src_fd);
op->fd = src_fd;
op->dest = argv[2];
argv += 2;
argc -= 2;
}
else if (strcmp (arg, "--overlay-src") == 0)
{
next_overlay_src_count++;
argv += 1;
argc -= 1;
}
else if (strcmp (arg, "--overlay") == 0)
{
SetupOp *workdir_op;
if (argc < 4)
die ("--overlay takes three arguments");
if (next_overlay_src_count < 1)
die ("--overlay requires at least one --overlay-src");
op = setup_op_new (SETUP_OVERLAY_MOUNT);
op->source = argv[1];
workdir_op = setup_op_new (SETUP_OVERLAY_SRC);
workdir_op->source = argv[2];
op->dest = argv[3];
make_setup_overlay_src_ops (argv);
argv += 3;
argc -= 3;
}
else if (strcmp (arg, "--tmp-overlay") == 0)
{
if (argc < 2)
die ("--tmp-overlay takes an argument");
if (next_overlay_src_count < 1)
die ("--tmp-overlay requires at least one --overlay-src");
op = setup_op_new (SETUP_TMP_OVERLAY_MOUNT);
op->dest = argv[1];
make_setup_overlay_src_ops (argv);
opt_tmp_overlay_count++;
argv += 1;
argc -= 1;
}
else if (strcmp (arg, "--ro-overlay") == 0)
{
if (argc < 2)
die ("--ro-overlay takes an argument");
if (next_overlay_src_count < 2)
die ("--ro-overlay requires at least two --overlay-src");
op = setup_op_new (SETUP_RO_OVERLAY_MOUNT);
op->dest = argv[1];
make_setup_overlay_src_ops (argv);
argv += 1;
argc -= 1;
}
else if (strcmp (arg, "--proc") == 0)
{
if (argc < 2)
die ("--proc takes an argument");
op = setup_op_new (SETUP_MOUNT_PROC);
op->dest = argv[1];
argv += 1;
argc -= 1;
}
else if (strcmp (arg, "--exec-label") == 0)
{
if (argc < 2)
die ("--exec-label takes an argument");
if (opt_exec_label != NULL)
warn_only_last_option ("--exec-label");
opt_exec_label = argv[1];
die_unless_label_valid (opt_exec_label);
argv += 1;
argc -= 1;
}
else if (strcmp (arg, "--file-label") == 0)
{
if (argc < 2)
die ("--file-label takes an argument");
if (opt_file_label != NULL)
warn_only_last_option ("--file-label");
opt_file_label = argv[1];
die_unless_label_valid (opt_file_label);
if (label_create_file (opt_file_label))
die_with_error ("--file-label setup failed");
argv += 1;
argc -= 1;
}
else if (strcmp (arg, "--dev") == 0)
{
if (argc < 2)
die ("--dev takes an argument");
op = setup_op_new (SETUP_MOUNT_DEV);
op->dest = argv[1];
opt_needs_devpts = true;
argv += 1;
argc -= 1;
}
else if (strcmp (arg, "--tmpfs") == 0)
{
if (argc < 2)
die ("--tmpfs takes an argument");
op = setup_op_new (SETUP_MOUNT_TMPFS);
op->dest = argv[1];
if (next_perms >= 0)
op->perms = next_perms;
else
op->perms = 0755;
next_perms = -1;
op->size = next_size_arg;
next_size_arg = 0;
argv += 1;
argc -= 1;
}
else if (strcmp (arg, "--mqueue") == 0)
{
if (argc < 2)
die ("--mqueue takes an argument");
op = setup_op_new (SETUP_MOUNT_MQUEUE);
op->dest = argv[1];
argv += 1;
argc -= 1;
}
else if (strcmp (arg, "--dir") == 0)
{
if (argc < 2)
die ("--dir takes an argument");
op = setup_op_new (SETUP_MAKE_DIR);
op->dest = argv[1];
if (next_perms >= 0)
op->perms = next_perms;
else
op->perms = 0755;
next_perms = -1;
argv += 1;
argc -= 1;
}
else if (strcmp (arg, "--file") == 0)
{
int file_fd;
char *endptr;
if (argc < 3)
die ("--file takes two arguments");
file_fd = strtol (argv[1], &endptr, 10);
if (argv[1][0] == 0 || endptr[0] != 0 || file_fd < 0)
die ("Invalid fd: %s", argv[1]);
op = setup_op_new (SETUP_MAKE_FILE);
op->fd = file_fd;
op->dest = argv[2];
if (next_perms >= 0)
op->perms = next_perms;
else
op->perms = 0666;
next_perms = -1;
argv += 2;
argc -= 2;
}
else if (strcmp (arg, "--bind-data") == 0)
{
int file_fd;
char *endptr;
if (argc < 3)
die ("--bind-data takes two arguments");
file_fd = strtol (argv[1], &endptr, 10);
if (argv[1][0] == 0 || endptr[0] != 0 || file_fd < 0)
die ("Invalid fd: %s", argv[1]);
op = setup_op_new (SETUP_MAKE_BIND_FILE);
op->fd = file_fd;
op->dest = argv[2];
if (next_perms >= 0)
op->perms = next_perms;
else
op->perms = 0600;
next_perms = -1;
argv += 2;
argc -= 2;
}
else if (strcmp (arg, "--ro-bind-data") == 0)
{
int file_fd;
char *endptr;
if (argc < 3)
die ("--ro-bind-data takes two arguments");
file_fd = strtol (argv[1], &endptr, 10);
if (argv[1][0] == 0 || endptr[0] != 0 || file_fd < 0)
die ("Invalid fd: %s", argv[1]);
op = setup_op_new (SETUP_MAKE_RO_BIND_FILE);
op->fd = file_fd;
op->dest = argv[2];
if (next_perms >= 0)
op->perms = next_perms;
else
op->perms = 0600;
next_perms = -1;
argv += 2;
argc -= 2;
}
else if (strcmp (arg, "--symlink") == 0)
{
if (argc < 3)
die ("--symlink takes two arguments");
op = setup_op_new (SETUP_MAKE_SYMLINK);
op->source = argv[1];
op->dest = argv[2];
argv += 2;
argc -= 2;
}
else if (strcmp (arg, "--lock-file") == 0)
{
if (argc < 2)
die ("--lock-file takes an argument");
(void) lock_file_new (argv[1]);
argv += 1;
argc -= 1;
}
else if (strcmp (arg, "--sync-fd") == 0)
{
int the_fd;
char *endptr;
if (argc < 2)
die ("--sync-fd takes an argument");
if (opt_sync_fd != -1)
warn_only_last_option ("--sync-fd");
the_fd = strtol (argv[1], &endptr, 10);
if (argv[1][0] == 0 || endptr[0] != 0 || the_fd < 0)
die ("Invalid fd: %s", argv[1]);
opt_sync_fd = the_fd;
argv += 1;
argc -= 1;
}
else if (strcmp (arg, "--block-fd") == 0)
{
int the_fd;
char *endptr;
if (argc < 2)
die ("--block-fd takes an argument");
if (opt_block_fd != -1)
warn_only_last_option ("--block-fd");
the_fd = strtol (argv[1], &endptr, 10);
if (argv[1][0] == 0 || endptr[0] != 0 || the_fd < 0)
die ("Invalid fd: %s", argv[1]);
opt_block_fd = the_fd;
argv += 1;
argc -= 1;
}
else if (strcmp (arg, "--userns-block-fd") == 0)
{
int the_fd;
char *endptr;
if (argc < 2)
die ("--userns-block-fd takes an argument");
if (opt_userns_block_fd != -1)
warn_only_last_option ("--userns-block-fd");
the_fd = strtol (argv[1], &endptr, 10);
if (argv[1][0] == 0 || endptr[0] != 0 || the_fd < 0)
die ("Invalid fd: %s", argv[1]);
opt_userns_block_fd = the_fd;
argv += 1;
argc -= 1;
}
else if (strcmp (arg, "--info-fd") == 0)
{
int the_fd;
char *endptr;
if (argc < 2)
die ("--info-fd takes an argument");
if (opt_info_fd != -1)
warn_only_last_option ("--info-fd");
the_fd = strtol (argv[1], &endptr, 10);
if (argv[1][0] == 0 || endptr[0] != 0 || the_fd < 0)
die ("Invalid fd: %s", argv[1]);
opt_info_fd = the_fd;
argv += 1;
argc -= 1;
}
else if (strcmp (arg, "--json-status-fd") == 0)
{
int the_fd;
char *endptr;
if (argc < 2)
die ("--json-status-fd takes an argument");
if (opt_json_status_fd != -1)
warn_only_last_option ("--json-status-fd");
the_fd = strtol (argv[1], &endptr, 10);
if (argv[1][0] == 0 || endptr[0] != 0 || the_fd < 0)
die ("Invalid fd: %s", argv[1]);
opt_json_status_fd = the_fd;
argv += 1;
argc -= 1;
}
else if (strcmp (arg, "--seccomp") == 0)
{
int the_fd;
char *endptr;
if (argc < 2)
die ("--seccomp takes an argument");
if (seccomp_programs != NULL)
die ("--seccomp cannot be combined with --add-seccomp-fd");
if (opt_seccomp_fd != -1)
warn_only_last_option ("--seccomp");
the_fd = strtol (argv[1], &endptr, 10);
if (argv[1][0] == 0 || endptr[0] != 0 || the_fd < 0)
die ("Invalid fd: %s", argv[1]);
opt_seccomp_fd = the_fd;
argv += 1;
argc -= 1;
}
else if (strcmp (arg, "--add-seccomp-fd") == 0)
{
int the_fd;
char *endptr;
if (argc < 2)
die ("--add-seccomp-fd takes an argument");
if (opt_seccomp_fd != -1)
die ("--add-seccomp-fd cannot be combined with --seccomp");
the_fd = strtol (argv[1], &endptr, 10);
if (argv[1][0] == 0 || endptr[0] != 0 || the_fd < 0)
die ("Invalid fd: %s", argv[1]);
seccomp_program_new (&the_fd);
argv += 1;
argc -= 1;
}
else if (strcmp (arg, "--userns") == 0)
{
int the_fd;
char *endptr;
if (argc < 2)
die ("--userns takes an argument");
if (opt_userns_fd != -1)
warn_only_last_option ("--userns");
the_fd = strtol (argv[1], &endptr, 10);
if (argv[1][0] == 0 || endptr[0] != 0 || the_fd < 0)
die ("Invalid fd: %s", argv[1]);
opt_userns_fd = the_fd;
argv += 1;
argc -= 1;
}
else if (strcmp (arg, "--userns2") == 0)
{
int the_fd;
char *endptr;
if (argc < 2)
die ("--userns2 takes an argument");
if (opt_userns2_fd != -1)
warn_only_last_option ("--userns2");
the_fd = strtol (argv[1], &endptr, 10);
if (argv[1][0] == 0 || endptr[0] != 0 || the_fd < 0)
die ("Invalid fd: %s", argv[1]);
opt_userns2_fd = the_fd;
argv += 1;
argc -= 1;
}
else if (strcmp (arg, "--pidns") == 0)
{
int the_fd;
char *endptr;
if (argc < 2)
die ("--pidns takes an argument");
if (opt_pidns_fd != -1)
warn_only_last_option ("--pidns");
the_fd = strtol (argv[1], &endptr, 10);
if (argv[1][0] == 0 || endptr[0] != 0 || the_fd < 0)
die ("Invalid fd: %s", argv[1]);
opt_pidns_fd = the_fd;
argv += 1;
argc -= 1;
}
else if (strcmp (arg, "--clearenv") == 0)
{
xclearenv ();
}
else if (strcmp (arg, "--setenv") == 0)
{
if (argc < 3)
die ("--setenv takes two arguments");
xsetenv (argv[1], argv[2], 1);
argv += 2;
argc -= 2;
}
else if (strcmp (arg, "--unsetenv") == 0)
{
if (argc < 2)
die ("--unsetenv takes an argument");
xunsetenv (argv[1]);
argv += 1;
argc -= 1;
}
else if (strcmp (arg, "--uid") == 0)
{
int the_uid;
char *endptr;
if (argc < 2)
die ("--uid takes an argument");
if (opt_sandbox_uid != (uid_t)-1)
warn_only_last_option ("--uid");
the_uid = strtol (argv[1], &endptr, 10);
if (argv[1][0] == 0 || endptr[0] != 0 || the_uid < 0)
die ("Invalid uid: %s", argv[1]);
opt_sandbox_uid = the_uid;
argv += 1;
argc -= 1;
}
else if (strcmp (arg, "--gid") == 0)
{
int the_gid;
char *endptr;
if (argc < 2)
die ("--gid takes an argument");
if (opt_sandbox_gid != (gid_t)-1)
warn_only_last_option ("--gid");
the_gid = strtol (argv[1], &endptr, 10);
if (argv[1][0] == 0 || endptr[0] != 0 || the_gid < 0)
die ("Invalid gid: %s", argv[1]);
opt_sandbox_gid = the_gid;
argv += 1;
argc -= 1;
}
else if (strcmp (arg, "--hostname") == 0)
{
if (argc < 2)
die ("--hostname takes an argument");
if (opt_sandbox_hostname != NULL)
warn_only_last_option ("--hostname");
op = setup_op_new (SETUP_SET_HOSTNAME);
op->dest = argv[1];
op->flags = NO_CREATE_DEST;
opt_sandbox_hostname = argv[1];
argv += 1;
argc -= 1;
}
else if (strcmp (arg, "--new-session") == 0)
{
opt_new_session = true;
}
else if (strcmp (arg, "--die-with-parent") == 0)
{
opt_die_with_parent = true;
}
else if (strcmp (arg, "--as-pid-1") == 0)
{
opt_as_pid_1 = true;
}
else if (strcmp (arg, "--cap-add") == 0)
{
cap_value_t cap;
if (argc < 2)
die ("--cap-add takes an argument");
opt_cap_add_or_drop_used = true;
if (strcasecmp (argv[1], "ALL") == 0)
{
requested_caps[0] = requested_caps[1] = 0xFFFFFFFF;
}
else
{
if (cap_from_name (argv[1], &cap) < 0)
die ("unknown cap: %s", argv[1]);
if (cap < 32)
requested_caps[0] |= CAP_TO_MASK_0 (cap);
else
requested_caps[1] |= CAP_TO_MASK_1 (cap - 32);
}
argv += 1;
argc -= 1;
}
else if (strcmp (arg, "--cap-drop") == 0)
{
cap_value_t cap;
if (argc < 2)
die ("--cap-drop takes an argument");
opt_cap_add_or_drop_used = true;
if (strcasecmp (argv[1], "ALL") == 0)
{
requested_caps[0] = requested_caps[1] = 0;
}
else
{
if (cap_from_name (argv[1], &cap) < 0)
die ("unknown cap: %s", argv[1]);
if (cap < 32)
requested_caps[0] &= ~CAP_TO_MASK_0 (cap);
else
requested_caps[1] &= ~CAP_TO_MASK_1 (cap - 32);
}
argv += 1;
argc -= 1;
}
else if (strcmp (arg, "--perms") == 0)
{
unsigned long perms;
char *endptr = NULL;
if (argc < 2)
die ("--perms takes an argument");
if (next_perms != -1)
die ("--perms given twice for the same action");
perms = strtoul (argv[1], &endptr, 8);
if (argv[1][0] == '\0'
|| endptr == NULL
|| *endptr != '\0'
|| perms > 07777)
die ("--perms takes an octal argument <= 07777");
next_perms = (int) perms;
argv += 1;
argc -= 1;
}
else if (strcmp (arg, "--size") == 0)
{
unsigned long long size;
char *endptr = NULL;
if (argc < 2)
die ("--size takes an argument");
if (next_size_arg != 0)
die ("--size given twice for the same action");
errno = 0;
size = strtoull (argv[1], &endptr, 0);
if (errno != 0
|| !isdigit(argv[1][0])
|| endptr == NULL
|| *endptr != '\0'
|| size == 0)
die ("--size takes a non-zero number of bytes");
if (size > MAX_TMPFS_BYTES)
die ("--size (for tmpfs) is limited to %zu", MAX_TMPFS_BYTES);
next_size_arg = (size_t) size;
argv += 1;
argc -= 1;
}
else if (strcmp (arg, "--chmod") == 0)
{
unsigned long perms;
char *endptr = NULL;
if (argc < 3)
die ("--chmod takes two arguments");
perms = strtoul (argv[1], &endptr, 8);
if (argv[1][0] == '\0'
|| endptr == NULL
|| *endptr != '\0'
|| perms > 07777)
die ("--chmod takes an octal argument <= 07777");
op = setup_op_new (SETUP_CHMOD);
op->flags = NO_CREATE_DEST;
op->perms = (int) perms;
op->dest = argv[2];
argv += 2;
argc -= 2;
}
else if (strcmp (arg, "--not-a-security-boundary") == 0)
{
opt_not_a_security_boundary = true;
}
else if (has_prefix (arg, "--debug-opt="))
{
const char *val = arg + strlen ("--debug-opt=");
if (strcmp (val, "force-openat-fallback") == 0)
opt_force_openat_fallback = true;
else
die ("Unknown --debug-opt value: %s", val);
}
else if (strcmp (arg, "--") == 0)
{
argv += 1;
argc -= 1;
break;
}
else if (*arg == '-')
{
die ("Unknown option %s", arg);
}
else
{
break;
}
if (!is_modifier_option(arg) && next_perms >= 0)
die ("--perms must be followed by an option that creates a file");
if (!is_modifier_option(arg) && next_size_arg != 0)
die ("--size must be followed by --tmpfs");
if (strcmp (arg, "--overlay-src") != 0 && next_overlay_src_count > 0)
die ("--overlay-src must be followed by another --overlay-src or one of --overlay, --tmp-overlay, or --ro-overlay");
argv++;
argc--;
}
*argcp = argc;
*argvp = argv;
}
static void
parse_args (int *argcp,
const char ***argvp)
{
int total_parsed_argc = *argcp;
parse_args_recurse (argcp, argvp, false, &total_parsed_argc);
if (next_overlay_src_count > 0)
die ("--overlay-src must be followed by another --overlay-src or one of --overlay, --tmp-overlay, or --ro-overlay");
}
static void
read_overflowids (void)
{
cleanup_free char *uid_data = NULL;
cleanup_free char *gid_data = NULL;
uid_data = load_file_at (AT_FDCWD, "/proc/sys/kernel/overflowuid");
if (uid_data == NULL)
die_with_error ("Can't read /proc/sys/kernel/overflowuid");
overflow_uid = strtol (uid_data, NULL, 10);
if (overflow_uid == 0)
die ("Can't parse /proc/sys/kernel/overflowuid");
gid_data = load_file_at (AT_FDCWD, "/proc/sys/kernel/overflowgid");
if (gid_data == NULL)
die_with_error ("Can't read /proc/sys/kernel/overflowgid");
overflow_gid = strtol (gid_data, NULL, 10);
if (overflow_gid == 0)
die ("Can't parse /proc/sys/kernel/overflowgid");
}
static void
namespace_ids_read (pid_t pid)
{
cleanup_free char *dir = NULL;
cleanup_fd int ns_fd = -1;
NsInfo *info;
dir = xasprintf ("%d/ns", pid);
ns_fd = TEMP_FAILURE_RETRY (openat (proc_fd, dir, O_PATH));
if (ns_fd < 0)
die_with_error ("open /proc/%s/ns failed", dir);
for (info = ns_infos; info->name; info++)
{
bool *do_unshare = info->do_unshare;
struct stat st;
int r;
if (do_unshare && *do_unshare == false)
continue;
r = fstatat (ns_fd, info->name, &st, 0);
if (r != 0)
continue;
info->id = st.st_ino;
}
}
static void
namespace_ids_write (int fd,
bool in_json)
{
NsInfo *info;
for (info = ns_infos; info->name; info++)
{
cleanup_free char *output = NULL;
const char *indent;
uintmax_t nsid;
nsid = (uintmax_t) info->id;
if (nsid == 0)
continue;
indent = in_json ? " " : "\n ";
output = xasprintf (",%s\"%s-namespace\": %ju",
indent, info->name, nsid);
dump_info (fd, output, true);
}
}
int
main (int argc,
char **argv)
{
mode_t old_umask;
const char *base_path = NULL;
int clone_flags;
char *old_cwd = NULL;
pid_t pid;
int event_fd = -1;
int child_wait_fd = -1;
int setup_finished_pipe[] = {-1, -1};
const char *new_cwd;
uid_t ns_uid;
gid_t ns_gid;
struct stat sbuf;
uint64_t val;
int res UNUSED;
cleanup_free char *args_data UNUSED = NULL;
int intermediate_pids_sockets[2] = {-1, -1};
const char *exec_path = NULL;
int i;
struct sigaction sa = {};
if (argc == 2 && (strcmp (argv[1], "--version") == 0))
print_version_and_exit ();
sigemptyset (&sa.sa_mask);
sa.sa_handler = SIG_DFL;
sigaction (SIGCHLD, &sa, NULL);
real_uid = getuid ();
real_gid = getgid ();
acquire_privs ();
if (prctl (PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0)
die_with_error ("prctl(PR_SET_NO_NEW_PRIVS) failed");
read_overflowids ();
argv0 = argv[0];
if (isatty (1))
host_tty_dev = ttyname (1);
argv++;
argc--;
if (argc <= 0)
usage (EXIT_FAILURE, stderr);
parse_args (&argc, (const char ***) &argv);
args_data = opt_args_data;
opt_args_data = NULL;
if (opt_userns_block_fd != -1 && !opt_unshare_user)
die ("--userns-block-fd requires --unshare-user");
if (opt_userns_block_fd != -1 && opt_info_fd == -1)
die ("--userns-block-fd requires --info-fd");
if (opt_userns_fd != -1 && opt_unshare_user)
die ("--userns is not compatible with --unshare-user");
if (opt_userns_fd != -1 && opt_unshare_user_try)
die ("--userns is not compatible with --unshare-user-try");
if (opt_disable_userns && !opt_unshare_user)
die ("--disable-userns requires --unshare-user");
if (opt_disable_userns && opt_userns_block_fd != -1)
die ("--disable-userns is not compatible with --userns-block-fd");
if (getuid () != 0 && opt_userns_fd == -1)
opt_unshare_user = true;
if (opt_unshare_user_try &&
stat ("/proc/self/ns/user", &sbuf) == 0)
{
bool disabled = false;
if (stat ("/sys/module/user_namespace/parameters/enable", &sbuf) == 0)
{
cleanup_free char *enable = NULL;
enable = load_file_at (AT_FDCWD, "/sys/module/user_namespace/parameters/enable");
if (enable != NULL && enable[0] == 'N')
disabled = true;
}
if (stat ("/proc/sys/user/max_user_namespaces", &sbuf) == 0)
{
cleanup_free char *max_user_ns = NULL;
max_user_ns = load_file_at (AT_FDCWD, "/proc/sys/user/max_user_namespaces");
if (max_user_ns != NULL && strcmp(max_user_ns, "0\n") == 0)
disabled = true;
}
if (!disabled)
opt_unshare_user = true;
}
if (argc <= 0)
usage (EXIT_FAILURE, stderr);
debug ("Creating root mount point");
if (opt_sandbox_uid == (uid_t)-1)
opt_sandbox_uid = real_uid;
if (opt_sandbox_gid == (gid_t)-1)
opt_sandbox_gid = real_gid;
if (!opt_unshare_user && opt_userns_fd == -1 && opt_sandbox_uid != real_uid)
die ("Specifying --uid requires --unshare-user or --userns");
if (!opt_unshare_user && opt_userns_fd == -1 && opt_sandbox_gid != real_gid)
die ("Specifying --gid requires --unshare-user or --userns");
if (!opt_unshare_uts && opt_sandbox_hostname != NULL)
die ("Specifying --hostname requires --unshare-uts");
if (opt_as_pid_1 && !opt_unshare_pid)
die ("Specifying --as-pid-1 requires --unshare-pid");
if (opt_as_pid_1 && lock_files != NULL)
die ("Specifying --as-pid-1 and --lock-file is not permitted");
proc_fd = TEMP_FAILURE_RETRY (open ("/proc", O_PATH));
if (proc_fd == -1)
die_with_error ("Can't open /proc");
base_path = "/tmp";
debug ("creating new namespace");
if (opt_unshare_pid && !opt_as_pid_1)
{
event_fd = eventfd (0, EFD_CLOEXEC | EFD_NONBLOCK);
if (event_fd == -1)
die_with_error ("eventfd()");
}
block_sigchild ();
clone_flags = SIGCHLD | CLONE_NEWNS;
if (opt_unshare_user)
clone_flags |= CLONE_NEWUSER;
if (opt_unshare_pid && opt_pidns_fd == -1)
clone_flags |= CLONE_NEWPID;
if (opt_unshare_net)
clone_flags |= CLONE_NEWNET;
if (opt_unshare_ipc)
clone_flags |= CLONE_NEWIPC;
if (opt_unshare_uts)
clone_flags |= CLONE_NEWUTS;
if (opt_unshare_cgroup)
{
if (stat ("/proc/self/ns/cgroup", &sbuf))
{
if (errno == ENOENT)
die ("Cannot create new cgroup namespace because the kernel does not support it");
else
die_with_error ("stat on /proc/self/ns/cgroup failed");
}
clone_flags |= CLONE_NEWCGROUP;
}
if (opt_unshare_cgroup_try)
{
opt_unshare_cgroup = !stat ("/proc/self/ns/cgroup", &sbuf);
if (opt_unshare_cgroup)
clone_flags |= CLONE_NEWCGROUP;
}
child_wait_fd = eventfd (0, EFD_CLOEXEC);
if (child_wait_fd == -1)
die_with_error ("eventfd()");
if (opt_json_status_fd != -1)
{
int ret;
ret = pipe2 (setup_finished_pipe, O_CLOEXEC);
if (ret == -1)
die_with_error ("pipe2()");
}
if (opt_userns_fd != -1 && setns (opt_userns_fd, CLONE_NEWUSER) != 0)
{
if (errno == EINVAL)
die ("Joining the specified user namespace failed, it might not be a descendant of the current user namespace.");
die_with_error ("Joining specified user namespace failed");
}
if (opt_pidns_fd != -1)
{
prctl (PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0);
create_pid_socketpair (intermediate_pids_sockets);
}
pid = raw_clone (clone_flags, NULL);
if (pid == -1)
{
if (opt_unshare_user)
{
if (errno == EINVAL)
die ("Creating new namespace failed, likely because the kernel does not support user namespaces.");
else if (errno == EPERM)
die ("No permissions to create a new namespace, likely because the kernel does not allow non-privileged user namespaces. On e.g. debian this can be enabled with 'sysctl kernel.unprivileged_userns_clone=1'.");
}
if (errno == ENOSPC)
die ("Creating new namespace failed: nesting depth or /proc/sys/user/max_*_namespaces exceeded (ENOSPC)");
die_with_error ("Creating new namespace failed");
}
ns_uid = opt_sandbox_uid;
ns_gid = opt_sandbox_gid;
if (pid != 0)
{
if (intermediate_pids_sockets[0] != -1)
{
close (intermediate_pids_sockets[1]);
pid = read_pid_from_socket (intermediate_pids_sockets[0]);
close (intermediate_pids_sockets[0]);
}
namespace_ids_read (pid);
if (opt_userns2_fd != -1 && setns (opt_userns2_fd, CLONE_NEWUSER) != 0)
die_with_error ("Setting userns2 failed");
drop_privs (false);
handle_die_with_parent ();
if (opt_info_fd != -1)
{
cleanup_free char *output = xasprintf ("{\n \"child-pid\": %i", pid);
dump_info (opt_info_fd, output, true);
namespace_ids_write (opt_info_fd, false);
dump_info (opt_info_fd, "\n}\n", true);
close (opt_info_fd);
}
if (opt_json_status_fd != -1)
{
cleanup_free char *output = xasprintf ("{ \"child-pid\": %i", pid);
dump_info (opt_json_status_fd, output, true);
namespace_ids_write (opt_json_status_fd, true);
dump_info (opt_json_status_fd, " }\n", true);
}
if (opt_userns_block_fd != -1)
{
char b[1];
(void) TEMP_FAILURE_RETRY (read (opt_userns_block_fd, b, 1));
close (opt_userns_block_fd);
}
val = 1;
res = TEMP_FAILURE_RETRY (write (child_wait_fd, &val, 8));
close (child_wait_fd);
return monitor_child (event_fd, pid, setup_finished_pipe[0]);
}
if (opt_pidns_fd != -1)
{
if (setns (opt_pidns_fd, CLONE_NEWPID) != 0)
die_with_error ("Setting pidns failed");
fork_intermediate_child ();
if (opt_unshare_pid)
{
if (unshare (CLONE_NEWPID))
die_with_error ("unshare pid ns");
fork_intermediate_child ();
}
close (intermediate_pids_sockets[0]);
send_pid_on_socket (intermediate_pids_sockets[1]);
close (intermediate_pids_sockets[1]);
}
if (opt_info_fd != -1)
close (opt_info_fd);
if (opt_json_status_fd != -1)
close (opt_json_status_fd);
res = read (child_wait_fd, &val, 8);
close (child_wait_fd);
switch_to_user_with_privs ();
if (opt_unshare_net)
loopback_setup ();
ns_uid = opt_sandbox_uid;
ns_gid = opt_sandbox_gid;
if (opt_unshare_user && opt_userns_block_fd == -1)
{
if (opt_needs_devpts)
{
ns_uid = 0;
ns_gid = 0;
}
write_uid_gid_map (ns_uid, real_uid,
ns_gid, real_gid,
-1, true, false);
}
old_umask = umask (0);
resolve_symlinks_in_ops ();
if (mount (NULL, "/", NULL, MS_SILENT | MS_SLAVE | MS_REC, NULL) < 0)
die_with_mount_error ("Failed to make / slave");
if (mount ("tmpfs", base_path, "tmpfs", MS_NODEV | MS_NOSUID, NULL) != 0)
die_with_mount_error ("Failed to mount tmpfs");
old_cwd = get_current_dir_name ();
if (chdir (base_path) != 0)
die_with_error ("chdir base_path");
if (mkdir ("newroot", 0755))
die_with_error ("Creating newroot failed");
if (mount ("newroot", "newroot", NULL, MS_SILENT | MS_MGC_VAL | MS_BIND | MS_REC, NULL) < 0)
die_with_mount_error ("setting up newroot bind");
if (mkdir ("oldroot", 0755))
die_with_error ("Creating oldroot failed");
for (i = 0; i < opt_tmp_overlay_count; i++)
{
char *dirname;
dirname = xasprintf ("tmp-overlay-upper-%d", i);
if (mkdir (dirname, 0755))
die_with_error ("Creating --tmp-overlay upperdir failed");
free (dirname);
dirname = xasprintf ("tmp-overlay-work-%d", i);
if (mkdir (dirname, 0755))
die_with_error ("Creating --tmp-overlay workdir failed");
free (dirname);
}
if (pivot_root (base_path, "oldroot"))
die_with_error ("pivot_root");
if (chdir ("/") != 0)
die_with_error ("chdir / (base path)");
if (mkdir ("proc", 0755))
die_with_error ("Creating proc failed");
if (mount ("oldroot/proc", "proc", NULL, MS_SILENT | MS_BIND | MS_REC, NULL) != 0)
die_with_mount_error ("mounting proc");
setup_newroot (opt_unshare_pid);
close_ops_fd ();
if (mount ("oldroot", "oldroot", NULL, MS_SILENT | MS_REC | MS_PRIVATE, NULL) != 0)
die_with_mount_error ("Failed to make old root rprivate");
if (umount2 ("oldroot", MNT_DETACH))
die_with_error ("unmount old root");
{ cleanup_fd int oldrootfd = TEMP_FAILURE_RETRY (open ("/", O_DIRECTORY | O_RDONLY));
if (oldrootfd < 0)
die_with_error ("can't open /");
if (chdir ("/newroot") != 0)
die_with_error ("chdir /newroot");
if (pivot_root (".", ".") != 0)
die_with_error ("pivot_root(/newroot)");
if (fchdir (oldrootfd) < 0)
die_with_error ("fchdir to oldroot");
if (umount2 (".", MNT_DETACH) < 0)
die_with_error ("umount old root");
if (chdir ("/") != 0)
die_with_error ("chdir /");
}
if (opt_userns2_fd != -1 && setns (opt_userns2_fd, CLONE_NEWUSER) != 0)
die_with_error ("Setting userns2 failed");
if (opt_unshare_user && opt_userns_block_fd == -1 &&
(ns_uid != opt_sandbox_uid || ns_gid != opt_sandbox_gid ||
opt_disable_userns))
{
if (opt_disable_userns)
{
cleanup_fd int sysctl_fd = -1;
sysctl_fd = TEMP_FAILURE_RETRY (openat (proc_fd, "sys/user/max_user_namespaces", O_WRONLY));
if (sysctl_fd < 0)
die_with_error ("cannot open /proc/sys/user/max_user_namespaces");
if (write_to_fd (sysctl_fd, "1", 1) < 0)
die_with_error ("sysctl user.max_user_namespaces = 1");
}
if (unshare (CLONE_NEWUSER))
die_with_error ("unshare user ns");
drop_cap_bounding_set (false);
write_uid_gid_map (opt_sandbox_uid, ns_uid,
opt_sandbox_gid, ns_gid,
-1, false, false);
}
if (opt_disable_userns || opt_assert_userns_disabled)
{
res = unshare (CLONE_NEWUSER);
if (res == 0)
die ("creation of new user namespaces was not disabled as requested");
}
drop_privs (true);
if (opt_block_fd != -1)
{
char b[1];
(void) TEMP_FAILURE_RETRY (read (opt_block_fd, b, 1));
close (opt_block_fd);
}
if (opt_seccomp_fd != -1)
{
assert (seccomp_programs == NULL);
seccomp_program_new (&opt_seccomp_fd);
}
umask (old_umask);
new_cwd = "/";
if (opt_chdir_path)
{
if (chdir (opt_chdir_path))
die_with_error ("Can't chdir to %s", opt_chdir_path);
new_cwd = opt_chdir_path;
}
else if (chdir (old_cwd) == 0)
{
new_cwd = old_cwd;
}
else
{
const char *home = getenv ("HOME");
if (home != NULL &&
chdir (home) == 0)
new_cwd = home;
}
xsetenv ("PWD", new_cwd, 1);
free (old_cwd);
if (opt_new_session &&
setsid () == (pid_t) -1)
die_with_error ("setsid");
if (label_exec (opt_exec_label) == -1)
die_with_error ("label_exec %s", argv[0]);
debug ("forking for child");
if (!opt_as_pid_1 && (opt_unshare_pid || lock_files != NULL || opt_sync_fd != -1))
{
pid = fork ();
if (pid == -1)
die_with_error ("Can't fork for pid 1");
if (pid != 0)
{
drop_all_caps (false);
{
int dont_close[3];
int j = 0;
if (event_fd != -1)
dont_close[j++] = event_fd;
if (opt_sync_fd != -1)
dont_close[j++] = opt_sync_fd;
dont_close[j++] = -1;
fdwalk (close_extra_fds, dont_close);
}
return do_init (event_fd, pid);
}
}
debug ("launch executable %s", argv[0]);
if (proc_fd != -1)
close (proc_fd);
if (!opt_as_pid_1)
{
if (opt_sync_fd != -1)
close (opt_sync_fd);
}
unblock_sigchild ();
handle_die_with_parent ();
set_ambient_capabilities ();
seccomp_programs_apply ();
if (setup_finished_pipe[1] != -1)
{
char data = 0;
res = write_to_fd (setup_finished_pipe[1], &data, 1);
}
exec_path = argv[0];
if (opt_argv0 != NULL)
argv[0] = (char *) opt_argv0;
if (execvp (exec_path, argv) == -1)
{
if (setup_finished_pipe[1] != -1)
{
int saved_errno = errno;
char data = 0;
res = write_to_fd (setup_finished_pipe[1], &data, 1);
errno = saved_errno;
}
die_with_error ("execvp %s", exec_path);
}
return 0;
}