"Fossies" - the Fresh Open Source Software Archive  

Source code changes of the file "subprojects/bubblewrap/bubblewrap.c" between
flatpak-1.15.1.tar.xz and flatpak-1.15.2.tar.xz

About: Flatpak is a Linux application sandboxing and distribution framework. Pre-release.

bubblewrap.c  (flatpak-1.15.1.tar.xz):bubblewrap.c  (flatpak-1.15.2.tar.xz)
skipping to change at line 26 skipping to change at line 26
* License along with this library. If not, see <http://www.gnu.org/licenses/>. * License along with this library. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
#include "config.h" #include "config.h"
#include <poll.h> #include <poll.h>
#include <sched.h> #include <sched.h>
#include <pwd.h> #include <pwd.h>
#include <grp.h> #include <grp.h>
#include <ctype.h>
#include <sys/mount.h> #include <sys/mount.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <sys/eventfd.h> #include <sys/eventfd.h>
#include <sys/fsuid.h> #include <sys/fsuid.h>
#include <sys/signalfd.h> #include <sys/signalfd.h>
#include <sys/capability.h> #include <sys/capability.h>
#include <sys/prctl.h> #include <sys/prctl.h>
#include <linux/sched.h> #include <linux/sched.h>
#include <linux/seccomp.h> #include <linux/seccomp.h>
skipping to change at line 55 skipping to change at line 56
#ifndef TEMP_FAILURE_RETRY #ifndef TEMP_FAILURE_RETRY
#define TEMP_FAILURE_RETRY(expression) \ #define TEMP_FAILURE_RETRY(expression) \
(__extension__ \ (__extension__ \
({ long int __result; \ ({ long int __result; \
do __result = (long int) (expression); \ do __result = (long int) (expression); \
while (__result == -1L && errno == EINTR); \ while (__result == -1L && errno == EINTR); \
__result; })) __result; }))
#endif #endif
/* We limit the size of a tmpfs to half the architecture's address space,
* to avoid hitting arbitrary limits in the kernel.
* For example, on at least one x86_64 machine, the actual limit seems to be
* 2^64 - 2^12. */
#define MAX_TMPFS_BYTES ((size_t) (SIZE_MAX >> 1))
/* Globals to avoid having to use getuid(), since the uid/gid changes during run time */ /* Globals to avoid having to use getuid(), since the uid/gid changes during run time */
static uid_t real_uid; static uid_t real_uid;
static gid_t real_gid; static gid_t real_gid;
static uid_t overflow_uid; static uid_t overflow_uid;
static gid_t overflow_gid; static gid_t overflow_gid;
static bool is_privileged; /* See acquire_privs() */ static bool is_privileged; /* See acquire_privs() */
static const char *argv0; static const char *argv0;
static const char *host_tty_dev; static const char *host_tty_dev;
static int proc_fd = -1; static int proc_fd = -1;
static const char *opt_exec_label = NULL; static const char *opt_exec_label = NULL;
skipping to change at line 94 skipping to change at line 101
int opt_userns_block_fd = -1; int opt_userns_block_fd = -1;
int opt_info_fd = -1; int opt_info_fd = -1;
int opt_json_status_fd = -1; int opt_json_status_fd = -1;
int opt_seccomp_fd = -1; int opt_seccomp_fd = -1;
const char *opt_sandbox_hostname = NULL; const char *opt_sandbox_hostname = NULL;
char *opt_args_data = NULL; /* owned */ char *opt_args_data = NULL; /* owned */
int opt_userns_fd = -1; int opt_userns_fd = -1;
int opt_userns2_fd = -1; int opt_userns2_fd = -1;
int opt_pidns_fd = -1; int opt_pidns_fd = -1;
int next_perms = -1; int next_perms = -1;
size_t next_size_arg = 0;
#define CAP_TO_MASK_0(x) (1L << ((x) & 31)) #define CAP_TO_MASK_0(x) (1L << ((x) & 31))
#define CAP_TO_MASK_1(x) CAP_TO_MASK_0(x - 32) #define CAP_TO_MASK_1(x) CAP_TO_MASK_0(x - 32)
typedef struct _NsInfo NsInfo; typedef struct _NsInfo NsInfo;
struct _NsInfo { struct _NsInfo {
const char *name; const char *name;
bool *do_unshare; bool *do_unshare;
ino_t id; ino_t id;
skipping to change at line 152 skipping to change at line 160
typedef struct _SetupOp SetupOp; typedef struct _SetupOp SetupOp;
struct _SetupOp struct _SetupOp
{ {
SetupOpType type; SetupOpType type;
const char *source; const char *source;
const char *dest; const char *dest;
int fd; int fd;
SetupOpFlag flags; SetupOpFlag flags;
int perms; int perms;
size_t size; /* number of bytes, zero means unset/default */
SetupOp *next; SetupOp *next;
}; };
typedef struct _LockFile LockFile; typedef struct _LockFile LockFile;
struct _LockFile struct _LockFile
{ {
const char *path; const char *path;
int fd; int fd;
LockFile *next; LockFile *next;
skipping to change at line 180 skipping to change at line 189
PRIV_SEP_OP_MQUEUE_MOUNT, PRIV_SEP_OP_MQUEUE_MOUNT,
PRIV_SEP_OP_REMOUNT_RO_NO_RECURSIVE, PRIV_SEP_OP_REMOUNT_RO_NO_RECURSIVE,
PRIV_SEP_OP_SET_HOSTNAME, PRIV_SEP_OP_SET_HOSTNAME,
}; };
typedef struct typedef struct
{ {
uint32_t op; uint32_t op;
uint32_t flags; uint32_t flags;
uint32_t perms; uint32_t perms;
size_t size_arg;
uint32_t arg1_offset; uint32_t arg1_offset;
uint32_t arg2_offset; uint32_t arg2_offset;
} PrivSepOp; } PrivSepOp;
/* /*
* DEFINE_LINKED_LIST: * DEFINE_LINKED_LIST:
* @Type: A struct with a `Type *next` member * @Type: A struct with a `Type *next` member
* @name: Used to form the names of variables and functions * @name: Used to form the names of variables and functions
* *
* Define a global linked list of @Type structures, with pointers * Define a global linked list of @Type structures, with pointers
skipping to change at line 304 skipping to change at line 314
" --unshare-user Create new user namespace (may be a utomatically implied if not setuid)\n" " --unshare-user Create new user namespace (may be a utomatically implied if not setuid)\n"
" --unshare-user-try Create new user namespace if possib le else continue by skipping it\n" " --unshare-user-try Create new user namespace if possib le else continue by skipping it\n"
" --unshare-ipc Create new ipc namespace\n" " --unshare-ipc Create new ipc namespace\n"
" --unshare-pid Create new pid namespace\n" " --unshare-pid Create new pid namespace\n"
" --unshare-net Create new network namespace\n" " --unshare-net Create new network namespace\n"
" --unshare-uts Create new uts namespace\n" " --unshare-uts Create new uts namespace\n"
" --unshare-cgroup Create new cgroup namespace\n" " --unshare-cgroup Create new cgroup namespace\n"
" --unshare-cgroup-try Create new cgroup namespace if poss ible else continue by skipping it\n" " --unshare-cgroup-try Create new cgroup namespace if poss ible else continue by skipping it\n"
" --userns FD Use this user namespace (cannot com bine with --unshare-user)\n" " --userns FD Use this user namespace (cannot com bine with --unshare-user)\n"
" --userns2 FD After setup switch to this user nam espace, only useful with --userns\n" " --userns2 FD After setup switch to this user nam espace, only useful with --userns\n"
" --pidns FD Use this user namespace (as parent namespace if using --unshare-pid)\n" " --pidns FD Use this pid namespace (as parent n amespace if using --unshare-pid)\n"
" --uid UID Custom uid in the sandbox (requires --unshare-user or --userns)\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" " --gid GID Custom gid in the sandbox (requires --unshare-user or --userns)\n"
" --hostname NAME Custom hostname in the sandbox (req uires --unshare-uts)\n" " --hostname NAME Custom hostname in the sandbox (req uires --unshare-uts)\n"
" --chdir DIR Change directory to DIR\n" " --chdir DIR Change directory to DIR\n"
" --clearenv Unset all environment variables\n" " --clearenv Unset all environment variables\n"
" --setenv VAR VALUE Set an environment variable\n" " --setenv VAR VALUE Set an environment variable\n"
" --unsetenv VAR Unset an environment variable\n" " --unsetenv VAR Unset an environment variable\n"
" --lock-file DEST Take a lock on DEST while sandbox i s running\n" " --lock-file DEST Take a lock on DEST while sandbox i s running\n"
" --sync-fd FD Keep this fd open 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 DES T\n" " --bind SRC DEST Bind mount the host path SRC on DES T\n"
skipping to change at line 344 skipping to change at line 354
" --block-fd FD Block on FD until some data to read is available\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 namespac e is ready\n" " --userns-block-fd FD Block on FD until the user namespac e is ready\n"
" --info-fd FD Write information about the running container to FD\n" " --info-fd FD Write information about the running container to FD\n"
" --json-status-fd FD Write container status to FD as mul tiple JSON documents\n" " --json-status-fd FD Write container status to FD as mul tiple JSON documents\n"
" --new-session Create a new terminal session\n" " --new-session Create a new terminal session\n"
" --die-with-parent Kills with SIGKILL child process (C OMMAND) when bwrap or bwrap's parent dies.\n" " --die-with-parent Kills with SIGKILL child process (C OMMAND) when bwrap or bwrap's parent dies.\n"
" --as-pid-1 Do not install a reaper process wit h PID=1\n" " --as-pid-1 Do not install a reaper process wit h PID=1\n"
" --cap-add CAP Add cap CAP when running as privile ged user\n" " --cap-add CAP Add cap CAP when running as privile ged user\n"
" --cap-drop CAP Drop cap CAP when running as privil eged user\n" " --cap-drop CAP Drop cap CAP when running as privil eged user\n"
" --perms OCTAL Set permissions of next argument (- -bind-data, --file, etc.)\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 al ready exist)\n" " --chmod OCTAL PATH Change permissions of PATH (must al ready exist)\n"
); );
exit (ecode); exit (ecode);
} }
/* If --die-with-parent was specified, use PDEATHSIG to ensure SIGKILL /* If --die-with-parent was specified, use PDEATHSIG to ensure SIGKILL
* is sent to the current process when our parent dies. * is sent to the current process when our parent dies.
*/ */
static void static void
handle_die_with_parent (void) handle_die_with_parent (void)
skipping to change at line 1004 skipping to change at line 1015
if (setfsuid (-1) != real_uid) if (setfsuid (-1) != real_uid)
die ("Unable to re-set fsuid"); die ("Unable to re-set fsuid");
} }
} }
static void static void
privileged_op (int privileged_op_socket, privileged_op (int privileged_op_socket,
uint32_t op, uint32_t op,
uint32_t flags, uint32_t flags,
uint32_t perms, uint32_t perms,
size_t size_arg,
const char *arg1, const char *arg1,
const char *arg2) const char *arg2)
{ {
bind_mount_result bind_result; bind_mount_result bind_result;
char *failing_path = NULL;
if (privileged_op_socket != -1) if (privileged_op_socket != -1)
{ {
uint32_t buffer[2048]; /* 8k, but is int32 to guarantee nice alignment */ uint32_t buffer[2048]; /* 8k, but is int32 to guarantee nice alignment */
PrivSepOp *op_buffer = (PrivSepOp *) buffer; PrivSepOp *op_buffer = (PrivSepOp *) buffer;
size_t buffer_size = sizeof (PrivSepOp); size_t buffer_size = sizeof (PrivSepOp);
uint32_t arg1_offset = 0, arg2_offset = 0; uint32_t arg1_offset = 0, arg2_offset = 0;
/* We're unprivileged, send this request to the privileged part */ /* We're unprivileged, send this request to the privileged part */
skipping to change at line 1035 skipping to change at line 1048
arg2_offset = buffer_size; arg2_offset = buffer_size;
buffer_size += strlen (arg2) + 1; buffer_size += strlen (arg2) + 1;
} }
if (buffer_size >= sizeof (buffer)) if (buffer_size >= sizeof (buffer))
die ("privilege separation operation to large"); die ("privilege separation operation to large");
op_buffer->op = op; op_buffer->op = op;
op_buffer->flags = flags; op_buffer->flags = flags;
op_buffer->perms = perms; op_buffer->perms = perms;
op_buffer->size_arg = size_arg;
op_buffer->arg1_offset = arg1_offset; op_buffer->arg1_offset = arg1_offset;
op_buffer->arg2_offset = arg2_offset; op_buffer->arg2_offset = arg2_offset;
if (arg1 != NULL) if (arg1 != NULL)
strcpy ((char *) buffer + arg1_offset, arg1); strcpy ((char *) buffer + arg1_offset, arg1);
if (arg2 != NULL) if (arg2 != NULL)
strcpy ((char *) buffer + arg2_offset, arg2); strcpy ((char *) buffer + arg2_offset, arg2);
if (write (privileged_op_socket, buffer, buffer_size) != buffer_size) if (write (privileged_op_socket, buffer, buffer_size) != buffer_size)
die ("Can't write to privileged_op_socket"); die ("Can't write to privileged_op_socket");
skipping to change at line 1073 skipping to change at line 1087
* be safe. * be safe.
* * Remounting RO (even non-recursive) is safe because it decreases privileg es. * * Remounting RO (even non-recursive) is safe because it decreases privileg es.
* * sethostname() is safe only if we set up a UTS namespace * * sethostname() is safe only if we set up a UTS namespace
*/ */
switch (op) switch (op)
{ {
case PRIV_SEP_OP_DONE: case PRIV_SEP_OP_DONE:
break; break;
case PRIV_SEP_OP_REMOUNT_RO_NO_RECURSIVE: case PRIV_SEP_OP_REMOUNT_RO_NO_RECURSIVE:
bind_result = bind_mount (proc_fd, NULL, arg2, BIND_READONLY); bind_result = bind_mount (proc_fd, NULL, arg2, BIND_READONLY, &failing_pat h);
if (bind_result != BIND_MOUNT_SUCCESS) if (bind_result != BIND_MOUNT_SUCCESS)
die_with_bind_result (bind_result, errno, die_with_bind_result (bind_result, errno, failing_path,
"Can't remount readonly on %s", arg2); "Can't remount readonly on %s", arg2);
assert (failing_path == NULL); /* otherwise we would have died */
break; break;
case PRIV_SEP_OP_BIND_MOUNT: case PRIV_SEP_OP_BIND_MOUNT:
/* We always bind directories recursively, otherwise this would let us /* We always bind directories recursively, otherwise this would let us
access files that are otherwise covered on the host */ access files that are otherwise covered on the host */
bind_result = bind_mount (proc_fd, arg1, arg2, BIND_RECURSIVE | flags); bind_result = bind_mount (proc_fd, arg1, arg2, BIND_RECURSIVE | flags, &fa iling_path);
if (bind_result != BIND_MOUNT_SUCCESS) if (bind_result != BIND_MOUNT_SUCCESS)
die_with_bind_result (bind_result, errno, die_with_bind_result (bind_result, errno, failing_path,
"Can't bind mount %s on %s", arg1, arg2); "Can't bind mount %s on %s", arg1, arg2);
assert (failing_path == NULL); /* otherwise we would have died */
break; break;
case PRIV_SEP_OP_PROC_MOUNT: case PRIV_SEP_OP_PROC_MOUNT:
if (mount ("proc", arg1, "proc", MS_NOSUID | MS_NOEXEC | MS_NODEV, NULL) ! = 0) if (mount ("proc", arg1, "proc", MS_NOSUID | MS_NOEXEC | MS_NODEV, NULL) ! = 0)
die_with_error ("Can't mount proc on %s", arg1); die_with_error ("Can't mount proc on %s", arg1);
break; break;
case PRIV_SEP_OP_TMPFS_MOUNT: case PRIV_SEP_OP_TMPFS_MOUNT:
{ {
cleanup_free char *mode = xasprintf ("mode=%#o", perms); cleanup_free char *mode = NULL;
/* This check should be unnecessary since we checked this when parsing
* the --size option as well. However, better be safe than sorry. */
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); cleanup_free char *opt = label_mount (mode, opt_file_label);
if (mount ("tmpfs", arg1, "tmpfs", MS_NOSUID | MS_NODEV, opt) != 0) if (mount ("tmpfs", arg1, "tmpfs", MS_NOSUID | MS_NODEV, opt) != 0)
die_with_error ("Can't mount tmpfs on %s", arg1); die_with_error ("Can't mount tmpfs on %s", arg1);
break; break;
} }
case PRIV_SEP_OP_DEVPTS_MOUNT: case PRIV_SEP_OP_DEVPTS_MOUNT:
if (mount ("devpts", arg1, "devpts", MS_NOSUID | MS_NOEXEC, if (mount ("devpts", arg1, "devpts", MS_NOSUID | MS_NOEXEC,
"newinstance,ptmxmode=0666,mode=620") != 0) "newinstance,ptmxmode=0666,mode=620") != 0)
die_with_error ("Can't mount devpts on %s", arg1); die_with_error ("Can't mount devpts on %s", arg1);
skipping to change at line 1200 skipping to change at line 1227
if (ensure_dir (dest, 0755) != 0) if (ensure_dir (dest, 0755) != 0)
die_with_error ("Can't mkdir %s", op->dest); die_with_error ("Can't mkdir %s", op->dest);
} }
else if (ensure_file (dest, 0444) != 0) else if (ensure_file (dest, 0444) != 0)
die_with_error ("Can't create file at %s", op->dest); die_with_error ("Can't create file at %s", op->dest);
privileged_op (privileged_op_socket, privileged_op (privileged_op_socket,
PRIV_SEP_OP_BIND_MOUNT, PRIV_SEP_OP_BIND_MOUNT,
(op->type == SETUP_RO_BIND_MOUNT ? BIND_READONLY : 0) | (op->type == SETUP_RO_BIND_MOUNT ? BIND_READONLY : 0) |
(op->type == SETUP_DEV_BIND_MOUNT ? BIND_DEVICES : 0), (op->type == SETUP_DEV_BIND_MOUNT ? BIND_DEVICES : 0),
0, source, dest); 0, 0, source, dest);
break; break;
case SETUP_REMOUNT_RO_NO_RECURSIVE: case SETUP_REMOUNT_RO_NO_RECURSIVE:
privileged_op (privileged_op_socket, privileged_op (privileged_op_socket,
PRIV_SEP_OP_REMOUNT_RO_NO_RECURSIVE, 0, 0, NULL, dest); PRIV_SEP_OP_REMOUNT_RO_NO_RECURSIVE, 0, 0, 0, NULL, des t);
break; break;
case SETUP_MOUNT_PROC: case SETUP_MOUNT_PROC:
if (ensure_dir (dest, 0755) != 0) if (ensure_dir (dest, 0755) != 0)
die_with_error ("Can't mkdir %s", op->dest); die_with_error ("Can't mkdir %s", op->dest);
if (unshare_pid || opt_pidns_fd != -1) if (unshare_pid || opt_pidns_fd != -1)
{ {
/* Our own procfs */ /* Our own procfs */
privileged_op (privileged_op_socket, privileged_op (privileged_op_socket,
PRIV_SEP_OP_PROC_MOUNT, 0, 0, PRIV_SEP_OP_PROC_MOUNT, 0, 0, 0,
dest, NULL); dest, NULL);
} }
else else
{ {
/* Use system procfs, as we share pid namespace anyway */ /* Use system procfs, as we share pid namespace anyway */
privileged_op (privileged_op_socket, privileged_op (privileged_op_socket,
PRIV_SEP_OP_BIND_MOUNT, 0, 0, PRIV_SEP_OP_BIND_MOUNT, 0, 0, 0,
"oldroot/proc", dest); "oldroot/proc", dest);
} }
/* There are a bunch of weird old subdirs of /proc that could potentia lly be /* There are a bunch of weird old subdirs of /proc that could potentia lly be
problematic (for instance /proc/sysrq-trigger lets you shut down th e machine problematic (for instance /proc/sysrq-trigger lets you shut down th e machine
if you have write access). We should not have access to these as a non-privileged if you have write access). We should not have access to these as a non-privileged
user, but lets cover them anyway just to make sure */ user, but lets cover them anyway just to make sure */
static const char * const cover_proc_dirs[] = { "sys", "sysrq-trigger" , "irq", "bus" }; static const char * const cover_proc_dirs[] = { "sys", "sysrq-trigger" , "irq", "bus" };
for (i = 0; i < N_ELEMENTS (cover_proc_dirs); i++) for (i = 0; i < N_ELEMENTS (cover_proc_dirs); i++)
{ {
skipping to change at line 1245 skipping to change at line 1272
if (access (subdir, W_OK) < 0) if (access (subdir, W_OK) < 0)
{ {
/* The file is already read-only or doesn't exist. */ /* The file is already read-only or doesn't exist. */
if (errno == EACCES || errno == ENOENT || errno == EROFS) if (errno == EACCES || errno == ENOENT || errno == EROFS)
continue; continue;
die_with_error ("Can't access %s", subdir); die_with_error ("Can't access %s", subdir);
} }
privileged_op (privileged_op_socket, privileged_op (privileged_op_socket,
PRIV_SEP_OP_BIND_MOUNT, BIND_READONLY, 0, PRIV_SEP_OP_BIND_MOUNT, BIND_READONLY, 0, 0,
subdir, subdir); subdir, subdir);
} }
break; break;
case SETUP_MOUNT_DEV: case SETUP_MOUNT_DEV:
if (ensure_dir (dest, 0755) != 0) if (ensure_dir (dest, 0755) != 0)
die_with_error ("Can't mkdir %s", op->dest); die_with_error ("Can't mkdir %s", op->dest);
privileged_op (privileged_op_socket, privileged_op (privileged_op_socket,
PRIV_SEP_OP_TMPFS_MOUNT, 0, 0755, PRIV_SEP_OP_TMPFS_MOUNT, 0, 0755, 0,
dest, NULL); dest, NULL);
static const char *const devnodes[] = { "null", "zero", "full", "rando m", "urandom", "tty" }; static const char *const devnodes[] = { "null", "zero", "full", "rando m", "urandom", "tty" };
for (i = 0; i < N_ELEMENTS (devnodes); i++) for (i = 0; i < N_ELEMENTS (devnodes); i++)
{ {
cleanup_free char *node_dest = strconcat3 (dest, "/", devnodes[i]) ; cleanup_free char *node_dest = strconcat3 (dest, "/", devnodes[i]) ;
cleanup_free char *node_src = strconcat ("/oldroot/dev/", devnodes [i]); cleanup_free char *node_src = strconcat ("/oldroot/dev/", devnodes [i]);
if (create_file (node_dest, 0444, NULL) != 0) if (create_file (node_dest, 0444, NULL) != 0)
die_with_error ("Can't create file %s/%s", op->dest, devnodes[i] ); die_with_error ("Can't create file %s/%s", op->dest, devnodes[i] );
privileged_op (privileged_op_socket, privileged_op (privileged_op_socket,
PRIV_SEP_OP_BIND_MOUNT, BIND_DEVICES, 0, PRIV_SEP_OP_BIND_MOUNT, BIND_DEVICES, 0, 0,
node_src, node_dest); node_src, node_dest);
} }
static const char *const stdionodes[] = { "stdin", "stdout", "stderr" }; static const char *const stdionodes[] = { "stdin", "stdout", "stderr" };
for (i = 0; i < N_ELEMENTS (stdionodes); i++) for (i = 0; i < N_ELEMENTS (stdionodes); i++)
{ {
cleanup_free char *target = xasprintf ("/proc/self/fd/%d", i); cleanup_free char *target = xasprintf ("/proc/self/fd/%d", i);
cleanup_free char *node_dest = strconcat3 (dest, "/", stdionodes[i ]); cleanup_free char *node_dest = strconcat3 (dest, "/", stdionodes[i ]);
if (symlink (target, node_dest) < 0) if (symlink (target, node_dest) < 0)
die_with_error ("Can't create symlink %s/%s", op->dest, stdionod es[i]); die_with_error ("Can't create symlink %s/%s", op->dest, stdionod es[i]);
skipping to change at line 1301 skipping to change at line 1328
cleanup_free char *pts = strconcat (dest, "/pts"); cleanup_free char *pts = strconcat (dest, "/pts");
cleanup_free char *ptmx = strconcat (dest, "/ptmx"); cleanup_free char *ptmx = strconcat (dest, "/ptmx");
cleanup_free char *shm = strconcat (dest, "/shm"); cleanup_free char *shm = strconcat (dest, "/shm");
if (mkdir (shm, 0755) == -1) if (mkdir (shm, 0755) == -1)
die_with_error ("Can't create %s/shm", op->dest); die_with_error ("Can't create %s/shm", op->dest);
if (mkdir (pts, 0755) == -1) if (mkdir (pts, 0755) == -1)
die_with_error ("Can't create %s/devpts", op->dest); die_with_error ("Can't create %s/devpts", op->dest);
privileged_op (privileged_op_socket, privileged_op (privileged_op_socket,
PRIV_SEP_OP_DEVPTS_MOUNT, 0, 0, pts, NULL); PRIV_SEP_OP_DEVPTS_MOUNT, 0, 0, 0, pts, NULL);
if (symlink ("pts/ptmx", ptmx) != 0) if (symlink ("pts/ptmx", ptmx) != 0)
die_with_error ("Can't make symlink at %s/ptmx", op->dest); die_with_error ("Can't make symlink at %s/ptmx", op->dest);
} }
/* If stdout is a tty, that means the sandbox can write to the /* If stdout is a tty, that means the sandbox can write to the
outside-sandbox tty. In that case we also create a /dev/console outside-sandbox tty. In that case we also create a /dev/console
that points to this tty device. This should not cause any more that points to this tty device. This should not cause any more
access than we already have, and it makes ttyname() work in the access than we already have, and it makes ttyname() work in the
sandbox. */ sandbox. */
if (host_tty_dev != NULL && *host_tty_dev != 0) if (host_tty_dev != NULL && *host_tty_dev != 0)
{ {
cleanup_free char *src_tty_dev = strconcat ("/oldroot", host_tty_d ev); cleanup_free char *src_tty_dev = strconcat ("/oldroot", host_tty_d ev);
cleanup_free char *dest_console = strconcat (dest, "/console"); cleanup_free char *dest_console = strconcat (dest, "/console");
if (create_file (dest_console, 0444, NULL) != 0) if (create_file (dest_console, 0444, NULL) != 0)
die_with_error ("creating %s/console", op->dest); die_with_error ("creating %s/console", op->dest);
privileged_op (privileged_op_socket, privileged_op (privileged_op_socket,
PRIV_SEP_OP_BIND_MOUNT, BIND_DEVICES, 0, PRIV_SEP_OP_BIND_MOUNT, BIND_DEVICES, 0, 0,
src_tty_dev, dest_console); src_tty_dev, dest_console);
} }
break; break;
case SETUP_MOUNT_TMPFS: case SETUP_MOUNT_TMPFS:
assert (dest != NULL); assert (dest != NULL);
assert (op->perms >= 0); assert (op->perms >= 0);
assert (op->perms <= 07777); assert (op->perms <= 07777);
if (ensure_dir (dest, 0755) != 0) if (ensure_dir (dest, 0755) != 0)
die_with_error ("Can't mkdir %s", op->dest); die_with_error ("Can't mkdir %s", op->dest);
privileged_op (privileged_op_socket, privileged_op (privileged_op_socket,
PRIV_SEP_OP_TMPFS_MOUNT, 0, op->perms, PRIV_SEP_OP_TMPFS_MOUNT, 0, op->perms, op->size,
dest, NULL); dest, NULL);
break; break;
case SETUP_MOUNT_MQUEUE: case SETUP_MOUNT_MQUEUE:
if (ensure_dir (dest, 0755) != 0) if (ensure_dir (dest, 0755) != 0)
die_with_error ("Can't mkdir %s", op->dest); die_with_error ("Can't mkdir %s", op->dest);
privileged_op (privileged_op_socket, privileged_op (privileged_op_socket,
PRIV_SEP_OP_MQUEUE_MOUNT, 0, 0, PRIV_SEP_OP_MQUEUE_MOUNT, 0, 0, 0,
dest, NULL); dest, NULL);
break; break;
case SETUP_MAKE_DIR: case SETUP_MAKE_DIR:
assert (dest != NULL); assert (dest != NULL);
assert (op->perms >= 0); assert (op->perms >= 0);
assert (op->perms <= 07777); assert (op->perms <= 07777);
if (ensure_dir (dest, op->perms) != 0) if (ensure_dir (dest, op->perms) != 0)
die_with_error ("Can't mkdir %s", op->dest); die_with_error ("Can't mkdir %s", op->dest);
skipping to change at line 1426 skipping to change at line 1453
op->fd = -1; op->fd = -1;
assert (dest != NULL); assert (dest != NULL);
if (ensure_file (dest, 0444) != 0) if (ensure_file (dest, 0444) != 0)
die_with_error ("Can't create file at %s", op->dest); die_with_error ("Can't create file at %s", op->dest);
privileged_op (privileged_op_socket, privileged_op (privileged_op_socket,
PRIV_SEP_OP_BIND_MOUNT, PRIV_SEP_OP_BIND_MOUNT,
(op->type == SETUP_MAKE_RO_BIND_FILE ? BIND_READONLY : 0), (op->type == SETUP_MAKE_RO_BIND_FILE ? BIND_READONLY : 0),
0, tempfile, dest); 0, 0, tempfile, dest);
/* Remove the file so we're sure the app can't get to it in any othe r way. /* Remove the file so we're sure the app can't get to it in any othe r way.
Its outside the container chroot, so it shouldn't be possible, bu t lets Its outside the container chroot, so it shouldn't be possible, bu t lets
make it really sure. */ make it really sure. */
unlink (tempfile); unlink (tempfile);
} }
break; break;
case SETUP_MAKE_SYMLINK: case SETUP_MAKE_SYMLINK:
assert (op->source != NULL); /* guaranteed by the constructor */ assert (op->source != NULL); /* guaranteed by the constructor */
if (symlink (op->source, dest) != 0) if (symlink (op->source, dest) != 0)
die_with_error ("Can't make symlink at %s", op->dest); die_with_error ("Can't make symlink at %s", op->dest);
break; break;
case SETUP_SET_HOSTNAME: case SETUP_SET_HOSTNAME:
assert (op->dest != NULL); /* guaranteed by the constructor */ assert (op->dest != NULL); /* guaranteed by the constructor */
privileged_op (privileged_op_socket, privileged_op (privileged_op_socket,
PRIV_SEP_OP_SET_HOSTNAME, 0, 0, PRIV_SEP_OP_SET_HOSTNAME, 0, 0, 0,
op->dest, NULL); op->dest, NULL);
break; break;
default: default:
die ("Unexpected type %d", op->type); die ("Unexpected type %d", op->type);
} }
} }
privileged_op (privileged_op_socket, privileged_op (privileged_op_socket,
PRIV_SEP_OP_DONE, 0, 0, NULL, NULL); PRIV_SEP_OP_DONE, 0, 0, 0, NULL, NULL);
} }
/* Do not leak file descriptors already used by setup_newroot () */ /* Do not leak file descriptors already used by setup_newroot () */
static void static void
close_ops_fd (void) close_ops_fd (void)
{ {
SetupOp *op; SetupOp *op;
for (op = ops; op != NULL; op = op->next) for (op = ops; op != NULL; op = op->next)
{ {
skipping to change at line 1539 skipping to change at line 1566
return (const char *) buffer + offset; return (const char *) buffer + offset;
} }
static uint32_t static uint32_t
read_priv_sec_op (int read_socket, read_priv_sec_op (int read_socket,
void *buffer, void *buffer,
size_t buffer_size, size_t buffer_size,
uint32_t *flags, uint32_t *flags,
uint32_t *perms, uint32_t *perms,
size_t *size_arg,
const char **arg1, const char **arg1,
const char **arg2) const char **arg2)
{ {
const PrivSepOp *op = (const PrivSepOp *) buffer; const PrivSepOp *op = (const PrivSepOp *) buffer;
ssize_t rec_len; ssize_t rec_len;
do do
rec_len = read (read_socket, buffer, buffer_size - 1); rec_len = read (read_socket, buffer, buffer_size - 1);
while (rec_len == -1 && errno == EINTR); while (rec_len == -1 && errno == EINTR);
skipping to change at line 1563 skipping to change at line 1591
exit (1); /* Privileged helper died and printed error, so exit silently */ exit (1); /* Privileged helper died and printed error, so exit silently */
if (rec_len < sizeof (PrivSepOp)) if (rec_len < sizeof (PrivSepOp))
die ("Invalid size %zd from unprivileged helper", rec_len); die ("Invalid size %zd from unprivileged helper", rec_len);
/* Guarantee zero termination of any strings */ /* Guarantee zero termination of any strings */
((char *) buffer)[rec_len] = 0; ((char *) buffer)[rec_len] = 0;
*flags = op->flags; *flags = op->flags;
*perms = op->perms; *perms = op->perms;
*size_arg = op->size_arg;
*arg1 = resolve_string_offset (buffer, rec_len, op->arg1_offset); *arg1 = resolve_string_offset (buffer, rec_len, op->arg1_offset);
*arg2 = resolve_string_offset (buffer, rec_len, op->arg2_offset); *arg2 = resolve_string_offset (buffer, rec_len, op->arg2_offset);
return op->op; return op->op;
} }
static void __attribute__ ((noreturn)) static void __attribute__ ((noreturn))
print_version_and_exit (void) print_version_and_exit (void)
{ {
printf ("%s\n", PACKAGE_STRING); printf ("%s\n", PACKAGE_STRING);
exit (0); exit (0);
} }
static int static int
takes_perms (const char *next_option) is_modifier_option (const char *option)
{ {
static const char *const options_that_take_perms[] = return strcmp (option, "--perms") == 0
{ || strcmp(option, "--size") == 0;
"--bind-data",
"--dir",
"--file",
"--ro-bind-data",
"--tmpfs",
};
size_t i;
for (i = 0; i < N_ELEMENTS (options_that_take_perms); i++)
{
if (strcmp (options_that_take_perms[i], next_option) == 0)
return 1;
}
return 0;
} }
static void static void
warn_only_last_option (const char *name) warn_only_last_option (const char *name)
{ {
warn ("Only the last %s option will take effect", name); warn ("Only the last %s option will take effect", name);
} }
static void static void
parse_args_recurse (int *argcp, parse_args_recurse (int *argcp,
skipping to change at line 1632 skipping to change at line 1646
*/ */
static const uint32_t MAX_ARGS = 9000; static const uint32_t MAX_ARGS = 9000;
if (*total_parsed_argc_p > MAX_ARGS) if (*total_parsed_argc_p > MAX_ARGS)
die ("Exceeded maximum number of arguments %u", MAX_ARGS); die ("Exceeded maximum number of arguments %u", MAX_ARGS);
while (argc > 0) while (argc > 0)
{ {
const char *arg = argv[0]; const char *arg = argv[0];
if (next_perms >= 0 && !takes_perms (arg))
die ("--perms must be followed by an option that creates a file");
if (strcmp (arg, "--help") == 0) if (strcmp (arg, "--help") == 0)
{ {
usage (EXIT_SUCCESS, stdout); usage (EXIT_SUCCESS, stdout);
} }
else if (strcmp (arg, "--version") == 0) else if (strcmp (arg, "--version") == 0)
{ {
print_version_and_exit (); print_version_and_exit ();
} }
else if (strcmp (arg, "--args") == 0) else if (strcmp (arg, "--args") == 0)
{ {
skipping to change at line 1892 skipping to change at line 1903
op = setup_op_new (SETUP_MOUNT_TMPFS); op = setup_op_new (SETUP_MOUNT_TMPFS);
op->dest = argv[1]; op->dest = argv[1];
/* We historically hard-coded the mode of a tmpfs as 0755. */ /* We historically hard-coded the mode of a tmpfs as 0755. */
if (next_perms >= 0) if (next_perms >= 0)
op->perms = next_perms; op->perms = next_perms;
else else
op->perms = 0755; op->perms = 0755;
next_perms = -1; next_perms = -1;
/* If the option is unset, next_size_arg is zero, which results in
* the default tmpfs size. This is exactly what we want. */
op->size = next_size_arg;
next_size_arg = 0;
argv += 1; argv += 1;
argc -= 1; argc -= 1;
} }
else if (strcmp (arg, "--mqueue") == 0) else if (strcmp (arg, "--mqueue") == 0)
{ {
if (argc < 2) if (argc < 2)
die ("--mqueue takes an argument"); die ("--mqueue takes an argument");
op = setup_op_new (SETUP_MOUNT_MQUEUE); op = setup_op_new (SETUP_MOUNT_MQUEUE);
op->dest = argv[1]; op->dest = argv[1];
skipping to change at line 2385 skipping to change at line 2403
argc -= 1; argc -= 1;
} }
else if (strcmp (arg, "--perms") == 0) else if (strcmp (arg, "--perms") == 0)
{ {
unsigned long perms; unsigned long perms;
char *endptr = NULL; char *endptr = NULL;
if (argc < 2) if (argc < 2)
die ("--perms takes an argument"); die ("--perms takes an argument");
if (next_perms != -1)
die ("--perms given twice for the same action");
perms = strtoul (argv[1], &endptr, 8); perms = strtoul (argv[1], &endptr, 8);
if (argv[1][0] == '\0' if (argv[1][0] == '\0'
|| endptr == NULL || endptr == NULL
|| *endptr != '\0' || *endptr != '\0'
|| perms > 07777) || perms > 07777)
die ("--perms takes an octal argument <= 07777"); die ("--perms takes an octal argument <= 07777");
next_perms = (int) perms; next_perms = (int) perms;
argv += 1; argv += 1;
argc -= 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; /* reset errno so we can detect ERANGE from strtoull */
size = strtoull (argv[1], &endptr, 0);
/* isdigit: Not only check that the first digit is not '\0', but
* simultaneously guard against negative numbers or preceding
* spaces. */
if (errno != 0 /* from strtoull */
|| !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) else if (strcmp (arg, "--chmod") == 0)
{ {
unsigned long perms; unsigned long perms;
char *endptr = NULL; char *endptr = NULL;
if (argc < 3) if (argc < 3)
die ("--chmod takes two arguments"); die ("--chmod takes two arguments");
perms = strtoul (argv[1], &endptr, 8); perms = strtoul (argv[1], &endptr, 8);
skipping to change at line 2437 skipping to change at line 2494
} }
else if (*arg == '-') else if (*arg == '-')
{ {
die ("Unknown option %s", arg); die ("Unknown option %s", arg);
} }
else else
{ {
break; break;
} }
/* If --perms was set for the current action but the current action
* didn't consume the setting, apparently --perms wasn't suitable for
* this action. */
if (!is_modifier_option(arg) && next_perms >= 0)
die ("--perms must be followed by an option that creates a file");
/* Similarly for --size. */
if (!is_modifier_option(arg) && next_size_arg != 0)
die ("--size must be followed by --tmpfs");
argv++; argv++;
argc--; argc--;
} }
*argcp = argc; *argcp = argc;
*argvp = argv; *argvp = argv;
} }
static void static void
parse_args (int *argcp, parse_args (int *argcp,
skipping to change at line 2792 skipping to change at line 2859
if (pid == -1) if (pid == -1)
{ {
if (opt_unshare_user) if (opt_unshare_user)
{ {
if (errno == EINVAL) 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."); 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) else if (errno == EPERM && !is_privileged)
die ("No permissions to creating new namespace, likely because the k ernel does not allow non-privileged user namespaces. On e.g. debian this can be enabled with 'sysctl kernel.unprivileged_userns_clone=1'."); die ("No permissions to creating new namespace, likely because the k ernel 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"); die_with_error ("Creating new namespace failed");
} }
ns_uid = opt_sandbox_uid; ns_uid = opt_sandbox_uid;
ns_gid = opt_sandbox_gid; ns_gid = opt_sandbox_gid;
if (pid != 0) if (pid != 0)
{ {
/* Parent, outside sandbox, privileged (initially) */ /* Parent, outside sandbox, privileged (initially) */
skipping to change at line 3018 skipping to change at line 3088
drop_privs (FALSE, TRUE); drop_privs (FALSE, TRUE);
close (privsep_sockets[0]); close (privsep_sockets[0]);
setup_newroot (opt_unshare_pid, privsep_sockets[1]); setup_newroot (opt_unshare_pid, privsep_sockets[1]);
exit (0); exit (0);
} }
else else
{ {
int status; int status;
uint32_t buffer[2048]; /* 8k, but is int32 to guarantee nice alignmen t */ uint32_t buffer[2048]; /* 8k, but is int32 to guarantee nice alignmen t */
uint32_t op, flags, perms; uint32_t op, flags, perms;
size_t size_arg;
const char *arg1, *arg2; const char *arg1, *arg2;
cleanup_fd int unpriv_socket = -1; cleanup_fd int unpriv_socket = -1;
unpriv_socket = privsep_sockets[0]; unpriv_socket = privsep_sockets[0];
close (privsep_sockets[1]); close (privsep_sockets[1]);
do do
{ {
op = read_priv_sec_op (unpriv_socket, buffer, sizeof (buffer), op = read_priv_sec_op (unpriv_socket, buffer, sizeof (buffer),
&flags, &perms, &arg1, &arg2); &flags, &perms, &size_arg, &arg1, &arg2);
privileged_op (-1, op, flags, perms, arg1, arg2); privileged_op (-1, op, flags, perms, size_arg, arg1, arg2);
if (write (unpriv_socket, buffer, 1) != 1) if (write (unpriv_socket, buffer, 1) != 1)
die ("Can't write to op_socket"); die ("Can't write to op_socket");
} }
while (op != PRIV_SEP_OP_DONE); while (op != PRIV_SEP_OP_DONE);
waitpid (child, &status, 0); waitpid (child, &status, 0);
/* Continue post setup */ /* Continue post setup */
} }
} }
else else
 End of changes. 43 change blocks. 
43 lines changed or deleted 116 lines changed or added

Home  |  About  |  Features  |  All  |  Newest  |  Dox  |  Diffs  |  RSS Feeds  |  Screenshots  |  Comments  |  Imprint  |  Privacy  |  HTTP(S)