#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;
#ifdef ENABLE_SUPPORT_SETUID
static bool is_privileged;
#else
#define is_privileged 0
#endif
static const char *argv0;
static const char *host_tty_dev;
static int proc_fd = -1;
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;
#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;
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;
};
enum {
PRIV_SEP_OP_DONE,
PRIV_SEP_OP_BIND_MOUNT,
PRIV_SEP_OP_OVERLAY_MOUNT,
PRIV_SEP_OP_PROC_MOUNT,
PRIV_SEP_OP_TMPFS_MOUNT,
PRIV_SEP_OP_DEVPTS_MOUNT,
PRIV_SEP_OP_MQUEUE_MOUNT,
PRIV_SEP_OP_REMOUNT_RO_NO_RECURSIVE,
PRIV_SEP_OP_SET_HOSTNAME,
};
typedef struct
{
uint32_t op;
uint32_t flags;
uint32_t perms;
size_t size_arg;
uint32_t arg1_offset;
uint32_t arg2_offset;
} PrivSepOp;
#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 setuid)\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"
);
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 (proc_fd, 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_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
set_required_caps (void)
{
struct __user_cap_header_struct hdr = { _LINUX_CAPABILITY_VERSION_3, 0 };
struct __user_cap_data_struct data[2] = { { 0 } };
data[0].effective = REQUIRED_CAPS_0;
data[0].permitted = REQUIRED_CAPS_0;
data[0].inheritable = 0;
data[1].effective = REQUIRED_CAPS_1;
data[1].permitted = REQUIRED_CAPS_1;
data[1].inheritable = 0;
if (capset (&hdr, data) < 0)
die_with_error ("capset failed");
}
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)
{
assert (!is_privileged);
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 && !is_privileged)
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)
{
if (is_privileged)
return;
prctl_caps (requested_caps, false, true);
}
static void
acquire_privs (void)
{
uid_t euid;
euid = geteuid ();
if (real_uid != euid)
{
#ifdef ENABLE_SUPPORT_SETUID
uid_t new_fsuid;
if (euid != 0)
die ("Unexpected setuid user %d, should be 0", euid);
is_privileged = true;
if (setfsuid (real_uid) < 0)
die_with_error ("Unable to set fsuid");
new_fsuid = setfsuid (-1);
if (new_fsuid != real_uid)
die_with_error ("Unable to set fsuid (was %d)", (int)new_fsuid);
drop_cap_bounding_set (true);
set_required_caps ();
#else
die ("setuid use of bubblewrap is not supported in this build");
#endif
}
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);
}
if (!is_privileged)
return;
if (prctl (PR_SET_KEEPCAPS, 1, 0, 0, 0) < 0)
die_with_error ("prctl(PR_SET_KEEPCAPS) failed");
if (setuid (opt_sandbox_uid) < 0)
die_with_error ("unable to drop root uid");
set_required_caps ();
}
static void
drop_privs (bool keep_requested_caps,
bool already_changed_uid,
bool set_dumpable)
{
assert (!keep_requested_caps || !is_privileged);
if (is_privileged && !already_changed_uid &&
setuid (opt_sandbox_uid) < 0)
die_with_error ("unable to drop root uid");
drop_all_caps (keep_requested_caps);
if (set_dumpable)
{
if (prctl (PR_SET_DUMPABLE, 1, 0, 0, 0) != 0)
die_with_error ("can't set dumpable");
}
}
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;
uid_t old_fsuid = (uid_t)-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 (is_privileged)
old_fsuid = setfsuid (0);
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");
if (is_privileged)
{
setfsuid (old_fsuid);
if ((uid_t) setfsuid (-1) != real_uid)
die ("Unable to re-set fsuid");
}
}
static void
privileged_op (int privileged_op_socket,
uint32_t op,
uint32_t flags,
uint32_t perms,
size_t size_arg,
const char *arg1,
const char *arg2)
{
bind_mount_result bind_result;
char *failing_path = NULL;
if (privileged_op_socket != -1)
{
uint32_t buffer[2048];
PrivSepOp *op_buffer = (PrivSepOp *) buffer;
size_t buffer_size = sizeof (PrivSepOp);
uint32_t arg1_offset = 0, arg2_offset = 0;
if (arg1 != NULL)
{
arg1_offset = buffer_size;
buffer_size += strlen (arg1) + 1;
}
if (arg2 != NULL)
{
arg2_offset = buffer_size;
buffer_size += strlen (arg2) + 1;
}
if (buffer_size >= sizeof (buffer))
die ("privilege separation operation to large");
op_buffer->op = op;
op_buffer->flags = flags;
op_buffer->perms = perms;
op_buffer->size_arg = size_arg;
op_buffer->arg1_offset = arg1_offset;
op_buffer->arg2_offset = arg2_offset;
if (arg1 != NULL)
strcpy ((char *) buffer + arg1_offset, arg1);
if (arg2 != NULL)
strcpy ((char *) buffer + arg2_offset, arg2);
if (TEMP_FAILURE_RETRY (write (privileged_op_socket, buffer, buffer_size)) != (ssize_t)buffer_size)
die ("Can't write to privileged_op_socket");
if (TEMP_FAILURE_RETRY (read (privileged_op_socket, buffer, 1)) != 1)
die ("Can't read from privileged_op_socket");
return;
}
switch (op)
{
case PRIV_SEP_OP_DONE:
break;
case PRIV_SEP_OP_REMOUNT_RO_NO_RECURSIVE:
bind_result = bind_mount (proc_fd, NULL, arg2, 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", arg2);
assert (failing_path == NULL);
break;
case PRIV_SEP_OP_BIND_MOUNT:
bind_result = bind_mount (proc_fd, arg1, arg2, BIND_RECURSIVE | flags, &failing_path);
if (bind_result != BIND_MOUNT_SUCCESS)
die_with_bind_result (bind_result, errno, failing_path,
"Can't bind mount %s on %s", arg1, arg2);
assert (failing_path == NULL);
break;
case PRIV_SEP_OP_PROC_MOUNT:
if (mount ("proc", arg1, "proc", MS_NOSUID | MS_NOEXEC | MS_NODEV, NULL) != 0)
die_with_mount_error ("Can't mount proc on %s", arg1);
break;
case PRIV_SEP_OP_TMPFS_MOUNT:
{
cleanup_free char *mode = NULL;
if (size_arg > MAX_TMPFS_BYTES)
die_with_error ("Specified tmpfs size too large (%zu > %zu)", size_arg, MAX_TMPFS_BYTES);
if (size_arg != 0)
mode = xasprintf ("mode=%#o,size=%zu", perms, size_arg);
else
mode = xasprintf ("mode=%#o", perms);
cleanup_free char *opt = label_mount (mode, opt_file_label);
if (mount ("tmpfs", arg1, "tmpfs", MS_NOSUID | MS_NODEV, opt) != 0)
die_with_mount_error ("Can't mount tmpfs on %s", arg1);
break;
}
case PRIV_SEP_OP_DEVPTS_MOUNT:
if (mount ("devpts", arg1, "devpts", MS_NOSUID | MS_NOEXEC,
"newinstance,ptmxmode=0666,mode=620") != 0)
die_with_mount_error ("Can't mount devpts on %s", arg1);
break;
case PRIV_SEP_OP_MQUEUE_MOUNT:
if (mount ("mqueue", arg1, "mqueue", 0, NULL) != 0)
die_with_mount_error ("Can't mount mqueue on %s", arg1);
break;
case PRIV_SEP_OP_OVERLAY_MOUNT:
if (is_privileged)
die ("Overlay mounts are not supported in setuid mode");
if (mount ("overlay", arg2, "overlay", MS_MGC_VAL | MS_NOSUID | MS_NODEV, arg1) != 0)
{
if (errno == ELOOP)
die ("Can't make overlay mount on %s with options %s: "
"Overlay directories may not overlap",
arg2, arg1);
die_with_mount_error ("Can't make overlay mount on %s with options %s",
arg2, arg1);
}
break;
case PRIV_SEP_OP_SET_HOSTNAME:
if (!opt_unshare_uts)
die ("Refusing to set hostname in original namespace");
if (arg1 == NULL)
die ("Hostname argument is NULL");
if (sethostname (arg1, strlen(arg1)) != 0)
die_with_error ("Can't set hostname to %s", arg1);
break;
default:
die ("Unexpected privileged op %d", op);
}
}
static void
setup_newroot (bool unshare_pid,
int privileged_op_socket)
{
SetupOp *op;
int tmp_overlay_idx = 0;
for (op = ops; op != NULL; op = op->next)
{
cleanup_free char *source = NULL;
cleanup_free char *dest = NULL;
int source_mode = 0;
unsigned int i;
if (op->source &&
op->type != SETUP_MAKE_SYMLINK)
{
source = get_oldroot_path (op->source);
source_mode = get_file_mode (source);
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;
if (op->perms >= 0 &&
(op->perms & 0070) == 0)
parent_mode &= ~0050U;
if (op->perms >= 0 &&
(op->perms & 0007) == 0)
parent_mode &= ~0005U;
dest = get_newroot_path (op->dest);
if (mkdir_with_parents (dest, parent_mode, false) != 0)
die_with_error ("Can't mkdir parents for %s", op->dest);
}
switch (op->type)
{
case SETUP_RO_BIND_MOUNT:
case SETUP_DEV_BIND_MOUNT:
case SETUP_BIND_MOUNT:
if (source_mode == S_IFDIR)
{
if (ensure_dir (dest, 0755) != 0)
die_with_error ("Can't mkdir %s", op->dest);
}
else if (ensure_file (dest, 0444) != 0)
die_with_error ("Can't create file at %s", op->dest);
privileged_op (privileged_op_socket,
PRIV_SEP_OP_BIND_MOUNT,
(op->type == SETUP_RO_BIND_MOUNT ? BIND_READONLY : 0) |
(op->type == SETUP_DEV_BIND_MOUNT ? BIND_DEVICES : 0),
0, 0, source, dest);
if (op->fd >= 0)
{
struct stat fd_st, mount_st;
if (fstat(op->fd, &fd_st) != 0)
die_with_error("Can't stat fd %d", op->fd);
if (lstat(dest, &mount_st) != 0)
die_with_error("Can't stat mount at %s", 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;
if (ensure_dir (dest, 0755) != 0)
die_with_error ("Can't mkdir %s", op->dest);
if (op->source != NULL)
{
strappend (&sb, "upperdir=/oldroot");
strappend_escape_for_mount_options (&sb, op->source);
strappend (&sb, ",workdir=/oldroot");
op = op->next;
strappend_escape_for_mount_options (&sb, op->source);
strappend (&sb, ",");
}
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=/oldroot");
while (op->next != NULL && op->next->type == SETUP_OVERLAY_SRC)
{
op = op->next;
if (multi_src)
strappend (&sb, ":/oldroot");
strappend_escape_for_mount_options (&sb, op->source);
multi_src = true;
}
strappend (&sb, ",userxattr");
privileged_op (privileged_op_socket,
PRIV_SEP_OP_OVERLAY_MOUNT, 0, 0, 0, sb.str, dest);
free (sb.str);
}
break;
case SETUP_REMOUNT_RO_NO_RECURSIVE:
privileged_op (privileged_op_socket,
PRIV_SEP_OP_REMOUNT_RO_NO_RECURSIVE, 0, 0, 0, NULL, dest);
break;
case SETUP_MOUNT_PROC:
if (ensure_dir (dest, 0755) != 0)
die_with_error ("Can't mkdir %s", op->dest);
if (unshare_pid || opt_pidns_fd != -1)
{
privileged_op (privileged_op_socket,
PRIV_SEP_OP_PROC_MOUNT, 0, 0, 0,
dest, NULL);
}
else
{
privileged_op (privileged_op_socket,
PRIV_SEP_OP_BIND_MOUNT, 0, 0, 0,
"oldroot/proc", dest);
}
static const char * const cover_proc_dirs[] = { "sys", "sysrq-trigger", "irq", "bus" };
for (i = 0; i < N_ELEMENTS (cover_proc_dirs); i++)
{
cleanup_free char *subdir = strconcat3 (dest, "/", cover_proc_dirs[i]);
if (access (subdir, W_OK) < 0)
{
if (errno == EACCES || errno == ENOENT || errno == EROFS)
continue;
die_with_error ("Can't access %s", subdir);
}
privileged_op (privileged_op_socket,
PRIV_SEP_OP_BIND_MOUNT, BIND_READONLY, 0, 0,
subdir, subdir);
}
break;
case SETUP_MOUNT_DEV:
if (ensure_dir (dest, 0755) != 0)
die_with_error ("Can't mkdir %s", op->dest);
privileged_op (privileged_op_socket,
PRIV_SEP_OP_TMPFS_MOUNT, 0, 0755, 0,
dest, NULL);
static const char *const devnodes[] = { "null", "zero", "full", "random", "urandom", "tty" };
for (i = 0; i < N_ELEMENTS (devnodes); i++)
{
cleanup_free char *node_dest = strconcat3 (dest, "/", devnodes[i]);
cleanup_free char *node_src = strconcat ("/oldroot/dev/", devnodes[i]);
if (create_file (node_dest, 0444, NULL) != 0)
die_with_error ("Can't create file %s/%s", op->dest, devnodes[i]);
privileged_op (privileged_op_socket,
PRIV_SEP_OP_BIND_MOUNT, BIND_DEVICES, 0, 0,
node_src, node_dest);
}
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);
cleanup_free char *node_dest = strconcat3 (dest, "/", stdionodes[i]);
if (symlink (target, node_dest) < 0)
die_with_error ("Can't create symlink %s/%s", op->dest, stdionodes[i]);
}
{ cleanup_free char *dev_fd = strconcat (dest, "/fd");
if (symlink ("/proc/self/fd", dev_fd) < 0)
die_with_error ("Can't create symlink %s", dev_fd);
}
{ cleanup_free char *dev_core = strconcat (dest, "/core");
if (symlink ("/proc/kcore", dev_core) < 0)
die_with_error ("Can't create symlink %s", dev_core);
}
{
cleanup_free char *pts = strconcat (dest, "/pts");
cleanup_free char *ptmx = strconcat (dest, "/ptmx");
cleanup_free char *shm = strconcat (dest, "/shm");
if (mkdir (shm, 0755) == -1)
die_with_error ("Can't create %s/shm", op->dest);
if (mkdir (pts, 0755) == -1)
die_with_error ("Can't create %s/devpts", op->dest);
privileged_op (privileged_op_socket,
PRIV_SEP_OP_DEVPTS_MOUNT, 0, 0, 0, pts, NULL);
if (symlink ("pts/ptmx", ptmx) != 0)
die_with_error ("Can't make symlink at %s/ptmx", op->dest);
}
if (host_tty_dev != NULL && *host_tty_dev != 0)
{
cleanup_free char *src_tty_dev = strconcat ("/oldroot", host_tty_dev);
cleanup_free char *dest_console = strconcat (dest, "/console");
if (create_file (dest_console, 0444, NULL) != 0)
die_with_error ("creating %s/console", op->dest);
privileged_op (privileged_op_socket,
PRIV_SEP_OP_BIND_MOUNT, BIND_DEVICES, 0, 0,
src_tty_dev, dest_console);
}
break;
case SETUP_MOUNT_TMPFS:
assert (dest != NULL);
assert (op->perms >= 0);
assert (op->perms <= 07777);
if (ensure_dir (dest, 0755) != 0)
die_with_error ("Can't mkdir %s", op->dest);
privileged_op (privileged_op_socket,
PRIV_SEP_OP_TMPFS_MOUNT, 0, op->perms, op->size,
dest, NULL);
break;
case SETUP_MOUNT_MQUEUE:
if (ensure_dir (dest, 0755) != 0)
die_with_error ("Can't mkdir %s", op->dest);
privileged_op (privileged_op_socket,
PRIV_SEP_OP_MQUEUE_MOUNT, 0, 0, 0,
dest, NULL);
break;
case SETUP_MAKE_DIR:
assert (dest != NULL);
assert (op->perms >= 0);
assert (op->perms <= 07777);
if (ensure_dir (dest, op->perms) != 0)
die_with_error ("Can't mkdir %s", op->dest);
break;
case SETUP_CHMOD:
assert (op->dest != NULL);
assert (dest == NULL);
dest = get_newroot_path (op->dest);
assert (dest != NULL);
assert (op->perms >= 0);
assert (op->perms <= 07777);
if (chmod (dest, op->perms) != 0)
die_with_error ("Can't chmod %#o %s", op->perms, op->dest);
break;
case SETUP_MAKE_FILE:
{
cleanup_fd int dest_fd = -1;
assert (dest != NULL);
assert (op->perms >= 0);
assert (op->perms <= 07777);
dest_fd = creat (dest, op->perms);
if (dest_fd == -1)
die_with_error ("Can't create file %s", op->dest);
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 dest_fd = -1;
char tempfile[] = "/bindfileXXXXXX";
assert (dest != NULL);
assert (op->perms >= 0);
assert (op->perms <= 07777);
dest_fd = mkstemp (tempfile);
if (dest_fd == -1)
die_with_error ("Can't create tmpfile for %s", op->dest);
if (fchmod (dest_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, dest_fd) != 0)
die_with_error ("Can't write data to file %s", op->dest);
close (op->fd);
op->fd = -1;
assert (dest != NULL);
if (ensure_file (dest, 0444) != 0)
die_with_error ("Can't create file at %s", op->dest);
privileged_op (privileged_op_socket,
PRIV_SEP_OP_BIND_MOUNT,
(op->type == SETUP_MAKE_RO_BIND_FILE ? BIND_READONLY : 0),
0, 0, tempfile, dest);
unlink (tempfile);
}
break;
case SETUP_MAKE_SYMLINK:
assert (op->source != NULL);
if (symlink (op->source, dest) != 0)
{
if (errno == EEXIST)
{
cleanup_free char *existing = readlink_malloc (dest);
if (existing == NULL)
{
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);
}
if (strcmp (existing, op->source) == 0)
break;
die ("Can't make symlink at %s: existing destination is %s", op->dest, existing);
}
die_with_error ("Can't make symlink at %s", op->dest);
}
break;
case SETUP_SET_HOSTNAME:
assert (op->dest != NULL);
privileged_op (privileged_op_socket,
PRIV_SEP_OP_SET_HOSTNAME, 0, 0, 0,
op->dest, NULL);
break;
case SETUP_OVERLAY_SRC:
default:
die ("Unexpected type %d", op->type);
}
}
privileged_op (privileged_op_socket,
PRIV_SEP_OP_DONE, 0, 0, 0, NULL, NULL);
}
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 const char *
resolve_string_offset (void *buffer,
size_t buffer_size,
uint32_t offset)
{
if (offset == 0)
return NULL;
if (offset > buffer_size)
die ("Invalid string offset %d (buffer size %zd)", offset, buffer_size);
return (const char *) buffer + offset;
}
static uint32_t
read_priv_sec_op (int read_socket,
void *buffer,
size_t buffer_size,
uint32_t *flags,
uint32_t *perms,
size_t *size_arg,
const char **arg1,
const char **arg2)
{
const PrivSepOp *op = (const PrivSepOp *) buffer;
ssize_t rec_len;
do
rec_len = read (read_socket, buffer, buffer_size - 1);
while (rec_len == -1 && errno == EINTR);
if (rec_len < 0)
die_with_error ("Can't read from unprivileged helper");
if (rec_len == 0)
exit (1);
if ((size_t)rec_len < sizeof (PrivSepOp))
die ("Invalid size %zd from unprivileged helper", rec_len);
((char *) buffer)[rec_len] = 0;
*flags = op->flags;
*perms = op->perms;
*size_arg = op->size_arg;
*arg1 = resolve_string_offset (buffer, rec_len, op->arg1_offset);
*arg2 = resolve_string_offset (buffer, rec_len, op->arg2_offset);
return op->op;
}
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)
{
if (is_privileged)
die ("The --overlay-src option is not permitted in setuid mode");
next_overlay_src_count++;
argv += 1;
argc -= 1;
}
else if (strcmp (arg, "--overlay") == 0)
{
SetupOp *workdir_op;
if (is_privileged)
die ("The --overlay option is not permitted in setuid mode");
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 (is_privileged)
die ("The --tmp-overlay option is not permitted in setuid mode");
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 (is_privileged)
die ("The --ro-overlay option is not permitted in setuid mode");
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 (is_privileged)
die ("The --size option is not permitted in setuid mode");
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, "--") == 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 ((requested_caps[0] || requested_caps[1]) && is_privileged)
die ("--cap-add in setuid mode can be used only by root");
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 not compatible --unshare-user");
if (opt_userns_fd != -1 && opt_unshare_user_try)
die ("--userns not compatible --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 (opt_userns_fd != -1 && is_privileged)
die ("--userns doesn't work in setuid mode");
if (opt_userns2_fd != -1 && is_privileged)
die ("--userns2 doesn't work in setuid mode");
if (!is_privileged && getuid () != 0 && opt_userns_fd == -1)
opt_unshare_user = true;
#ifdef ENABLE_REQUIRE_USERNS
if (is_privileged && getuid () != 0 && opt_userns_fd == -1)
opt_unshare_user = true;
#endif
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. bwrap must be installed setuid on such systems.");
else if (errno == EPERM && !is_privileged)
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 (is_privileged && opt_unshare_user && opt_userns_block_fd == -1)
{
write_uid_gid_map (ns_uid, real_uid,
ns_gid, real_gid,
pid, true, opt_needs_devpts);
}
if (opt_userns2_fd != -1 && setns (opt_userns2_fd, CLONE_NEWUSER) != 0)
die_with_error ("Setting userns2 failed");
drop_privs (false, false, true);
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 (!is_privileged && 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 (is_privileged)
{
pid_t child;
int privsep_sockets[2];
if (socketpair (AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, privsep_sockets) != 0)
die_with_error ("Can't create privsep socket");
child = fork ();
if (child == -1)
die_with_error ("Can't fork unprivileged helper");
if (child == 0)
{
drop_privs (false, true, false);
close (privsep_sockets[0]);
setup_newroot (opt_unshare_pid, privsep_sockets[1]);
exit (0);
}
else
{
int status;
uint32_t buffer[2048];
uint32_t op, flags, perms;
size_t size_arg;
const char *arg1, *arg2;
cleanup_fd int unpriv_socket = -1;
unpriv_socket = privsep_sockets[0];
close (privsep_sockets[1]);
do
{
op = read_priv_sec_op (unpriv_socket, buffer, sizeof (buffer),
&flags, &perms, &size_arg, &arg1, &arg2);
privileged_op (-1, op, flags, perms, size_arg, arg1, arg2);
if (TEMP_FAILURE_RETRY (write (unpriv_socket, buffer, 1)) != 1)
die ("Can't write to op_socket");
}
while (op != PRIV_SEP_OP_DONE);
TEMP_FAILURE_RETRY (waitpid (child, &status, 0));
}
}
else
{
setup_newroot (opt_unshare_pid, -1);
}
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 (!is_privileged, true, 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 (proc_fd, 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 ();
if (!is_privileged)
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;
}