├── LICENSE ├── .dir-locals.el ├── bubblewrap.jpg ├── demos ├── xdg-app.bpf ├── bubblewrap-shell.sh └── xdg-app-run.sh ├── uncrustify.sh ├── .editorconfig ├── Makefile-bwrap.am ├── .travis.yml ├── autogen.sh ├── Makefile-docs.am ├── network.h ├── Makefile.am ├── tests └── test-basic.sh ├── completions └── bash │ └── bwrap ├── bind-mount.h ├── packaging └── bubblewrap.spec ├── configure.ac ├── uncrustify.cfg ├── utils.h ├── network.c ├── bind-mount.c ├── README.md ├── bwrap.xml ├── git.mk ├── utils.c ├── COPYING └── bubblewrap.c /LICENSE: -------------------------------------------------------------------------------- 1 | COPYING -------------------------------------------------------------------------------- /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ((c-mode . ((indent-tabs-mode . nil) (c-file-style . "gnu")))) 2 | -------------------------------------------------------------------------------- /bubblewrap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/probonopd/bubblewrap/master/bubblewrap.jpg -------------------------------------------------------------------------------- /demos/xdg-app.bpf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/probonopd/bubblewrap/master/demos/xdg-app.bpf -------------------------------------------------------------------------------- /uncrustify.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | uncrustify -c uncrustify.cfg --no-backup `git ls-tree --name-only -r HEAD | grep \\\.[ch]$` 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.[ch]] 2 | indent_style = space 3 | indent_size = 2 4 | trim_trailing_whitespace = true 5 | indent_brace_style = gnu 6 | 7 | -------------------------------------------------------------------------------- /Makefile-bwrap.am: -------------------------------------------------------------------------------- 1 | 2 | bwrap_SOURCES = \ 3 | $(bwrap_srcpath)/bubblewrap.c \ 4 | $(bwrap_srcpath)/bind-mount.h \ 5 | $(bwrap_srcpath)/bind-mount.c \ 6 | $(bwrap_srcpath)/network.h \ 7 | $(bwrap_srcpath)/network.c \ 8 | $(bwrap_srcpath)/utils.h \ 9 | $(bwrap_srcpath)/utils.c \ 10 | $(NULL) 11 | 12 | bwrap_CFLAGS = $(AM_CFLAGS) 13 | bwrap_LDFLAGS = $(SELINUX_LIBS) 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | dist: trusty 3 | addons: 4 | apt: 5 | packages: 6 | - automake 7 | - autotools-dev 8 | - libcap-dev 9 | script: 10 | - env NOCONFIGURE=1 ./autogen.sh 11 | - mkdir build 12 | - cd build && ../configure --prefix=/usr 13 | - make -j 2 14 | - make check 15 | - sudo make install 16 | - sudo strip /usr/bin/bwrap 17 | - curl --upload-file /usr/bin/bwrap https://transfer.sh/bwrap 18 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | test -n "$srcdir" || srcdir=`dirname "$0"` 4 | test -n "$srcdir" || srcdir=. 5 | 6 | olddir=`pwd` 7 | cd $srcdir 8 | 9 | if ! (autoreconf --version >/dev/null 2>&1); then 10 | echo "*** No autoreconf found, please install it ***" 11 | exit 1 12 | fi 13 | 14 | mkdir -p m4 15 | 16 | autoreconf --force --install --verbose 17 | 18 | cd $olddir 19 | test -n "$NOCONFIGURE" || "$srcdir/configure" "$@" 20 | -------------------------------------------------------------------------------- /Makefile-docs.am: -------------------------------------------------------------------------------- 1 | XSLTPROC = xsltproc 2 | 3 | XSLTPROC_FLAGS = \ 4 | --nonet \ 5 | --stringparam man.output.quietly 1 \ 6 | --stringparam funcsynopsis.style ansi \ 7 | --stringparam man.th.extra1.suppress 1 \ 8 | --stringparam man.authors.section.enabled 0 \ 9 | --stringparam man.copyright.section.enabled 0 10 | 11 | .xml.1: 12 | $(XSLTPROC) $(XSLTPROC_FLAGS) http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $< 13 | 14 | if ENABLE_MAN 15 | man_MANS = bwrap.1 16 | CLEANFILES += $(man_MANS) 17 | endif 18 | -------------------------------------------------------------------------------- /demos/bubblewrap-shell.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Use bubblewrap to run /bin/sh in the host's rootfs. 3 | set -euo pipefail 4 | (exec bwrap --ro-bind /usr /usr \ 5 | --dir /tmp \ 6 | --proc /proc \ 7 | --dev /dev \ 8 | --ro-bind /etc/resolv.conf /etc/resolv.conf \ 9 | --symlink usr/lib /lib \ 10 | --symlink usr/lib64 /lib64 \ 11 | --symlink usr/bin /bin \ 12 | --symlink usr/sbin /sbin \ 13 | --chdir / \ 14 | --unshare-pid \ 15 | --dir /run/user/$(id -u) \ 16 | --setenv XDG_RUNTIME_DIR "/run/user/`id -u`" \ 17 | --file 11 /etc/passwd \ 18 | --file 12 /etc/group \ 19 | /bin/sh) \ 20 | 11< <(getent passwd $UID 65534) \ 21 | 12< <(getent group $(id -g) 65534) 22 | -------------------------------------------------------------------------------- /network.h: -------------------------------------------------------------------------------- 1 | /* bubblewrap 2 | * Copyright (C) 2016 Alexander Larsson 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public 15 | * License along with this library. If not, see . 16 | * 17 | */ 18 | 19 | #pragma once 20 | 21 | int loopback_setup (void); 22 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | AM_CFLAGS = $(WARN_CFLAGS) 2 | CLEANFILES = 3 | 4 | GITIGNOREFILES = build-aux/ gtk-doc.make config.h.in aclocal.m4 5 | 6 | bin_PROGRAMS = bwrap 7 | 8 | bwrap_srcpath := $(srcdir) 9 | include Makefile-bwrap.am 10 | 11 | install-exec-hook: 12 | if PRIV_MODE_SETUID 13 | $(SUDO_BIN) chown root $(DESTDIR)$(bindir)/bwrap 14 | $(SUDO_BIN) chmod u+s $(DESTDIR)$(bindir)/bwrap 15 | else 16 | if PRIV_MODE_FILECAPS 17 | $(SUDO_BIN) setcap cap_sys_admin,cap_net_admin,cap_sys_chroot,cap_setuid,cap_setgid+ep $(DESTDIR)$(bindir)/bwrap 18 | endif 19 | endif 20 | 21 | include Makefile-docs.am 22 | 23 | TESTS = tests/test-basic.sh 24 | TESTS_ENVIRONMENT = PATH=$$(cd $(top_builddir) && pwd):$${PATH} 25 | 26 | if ENABLE_BASH_COMPLETION 27 | bashcompletiondir = $(BASH_COMPLETION_DIR) 28 | dist_bashcompletion_DATA = completions/bash/bwrap 29 | endif 30 | 31 | -include $(top_srcdir)/git.mk 32 | -------------------------------------------------------------------------------- /tests/test-basic.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -xeuo pipefail 4 | 5 | srcd=$(cd $(dirname $0) && pwd) 6 | bn=$(basename $0) 7 | tempdir=$(mktemp -d /var/tmp/tap-test.XXXXXX) 8 | touch ${tempdir}/.testtmp 9 | function cleanup () { 10 | if test -n "${TEST_SKIP_CLEANUP:-}"; then 11 | echo "Skipping cleanup of ${test_tmpdir}" 12 | else if test -f ${tempdir}/.test; then 13 | rm "${tempdir}" -rf 14 | fi 15 | fi 16 | } 17 | trap cleanup EXIT 18 | cd ${tempdir} 19 | 20 | assert_not_reached () { 21 | echo $@ 1>&2; exit 1 22 | } 23 | 24 | assert_file_has_content () { 25 | if ! grep -q -e "$2" "$1"; then 26 | echo 1>&2 "File '$1' doesn't match regexp '$2'"; exit 1 27 | fi 28 | } 29 | 30 | # At the moment we're testing in Travis' container infrastructure 31 | # which also uses PR_SET_NO_NEW_PRIVS...but let's at least 32 | # verify --help works! 33 | bwrap --help >out.txt 2>&1 34 | assert_file_has_content out.txt "--lock-file" 35 | 36 | -------------------------------------------------------------------------------- /completions/bash/bwrap: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # bash completion file for bubblewrap commands 4 | # 5 | 6 | _bwrap() { 7 | local cur prev words cword 8 | _init_completion || return 9 | 10 | local boolean_options=" 11 | --help 12 | --share-user 13 | --unshare-ipc 14 | --unshare-net 15 | --unshare-pid 16 | --unshare-uts 17 | --version 18 | " 19 | 20 | local options_with_args=" 21 | $boolean_optons 22 | --args 23 | --bind 24 | --bind-data 25 | --chdir 26 | --dev 27 | --dev-bind 28 | --dir 29 | --exec-label 30 | --file 31 | --file-label 32 | --gid 33 | --lock-file 34 | --proc 35 | --ro-bind 36 | --seccomp 37 | --setenv 38 | --symlink 39 | --sync-fd 40 | --uid 41 | --unsetenv 42 | --seccomp 43 | --symlink 44 | " 45 | 46 | if [[ "$cur" == -* ]]; then 47 | COMPREPLY=( $( compgen -W "$boolean_options $options_with_args" -- "$cur" ) ) 48 | fi 49 | 50 | return 0 51 | } 52 | complete -F _bwrap bwrap 53 | -------------------------------------------------------------------------------- /bind-mount.h: -------------------------------------------------------------------------------- 1 | /* bubblewrap 2 | * Copyright (C) 2016 Alexander Larsson 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public 15 | * License along with this library. If not, see . 16 | * 17 | */ 18 | 19 | #pragma once 20 | 21 | typedef enum { 22 | BIND_READONLY = (1 << 0), 23 | BIND_DEVICES = (1 << 2), 24 | BIND_RECURSIVE = (1 << 3), 25 | } bind_option_t; 26 | 27 | int bind_mount (int proc_fd, 28 | const char *src, 29 | const char *dest, 30 | bind_option_t options); 31 | -------------------------------------------------------------------------------- /packaging/bubblewrap.spec: -------------------------------------------------------------------------------- 1 | %global commit0 66d12bb23b04e201c5846e325f0b10930ed802f8 2 | %global shortcommit0 %(c=%{commit0}; echo ${c:0:7}) 3 | 4 | Summary: Core execution tool for unprivileged containers 5 | Name: bubblewrap 6 | Version: 0 7 | Release: 1%{?dist} 8 | #VCS: git:https://github.com/projectatomic/bubblewrap 9 | Source0: https://github.com/projectatomic/%{name}/archive/%{commit0}.tar.gz#/%{name}-%{shortcommit0}.tar.gz 10 | License: LGPLv2+ 11 | URL: https://github.com/projectatomic/bubblewrap 12 | 13 | BuildRequires: git 14 | # We always run autogen.sh 15 | BuildRequires: autoconf automake libtool 16 | BuildRequires: libcap-devel 17 | BuildRequires: pkgconfig(libselinux) 18 | BuildRequires: libxslt 19 | BuildRequires: docbook-style-xsl 20 | 21 | %description 22 | Bubblewrap (/usr/bin/bwrap) is a core execution engine for unprivileged 23 | containers that works as a setuid binary on kernels without 24 | user namespaces. 25 | 26 | %prep 27 | %autosetup -Sgit -n %{name}-%{version} 28 | 29 | %build 30 | env NOCONFIGURE=1 ./autogen.sh 31 | %configure --disable-silent-rules --with-priv-mode=none 32 | 33 | make %{?_smp_mflags} 34 | 35 | %install 36 | make install DESTDIR=$RPM_BUILD_ROOT INSTALL="install -p -c" 37 | find $RPM_BUILD_ROOT -name '*.la' -delete 38 | 39 | %files 40 | %license COPYING 41 | %doc README.md 42 | %{_datadir}/bash-completion/completions/bwrap 43 | %if (0%{?rhel} != 0 && 0%{?rhel} <= 7) 44 | %attr(0755,root,root) %caps(cap_sys_admin,cap_net_admin,cap_sys_chroot=ep) %{_bindir}/bwrap 45 | %else 46 | %{_bindir}/bwrap 47 | %endif 48 | %{_mandir}/man1/* 49 | 50 | -------------------------------------------------------------------------------- /demos/xdg-app-run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # For this to work you first have to run these commands: 3 | # curl -O http://sdk.gnome.org/nightly/keys/nightly.gpg 4 | # xdg-app --user remote-add --gpg-key=nightly.gpg gnome-nightly http://sdk.gnome.org/nightly/repo/ 5 | # xdg-app --user install gnome-nightly org.gnome.Platform 6 | # xdg-app --user install gnome-nightly org.gnome.Weather 7 | 8 | mkdir -p ~/.var/app/org.gnome.Weather/cache ~/.var/app/org.gnome.Weather/config ~/.var/app/org.gnome.Weather/data 9 | 10 | ( 11 | exec bwrap \ 12 | --ro-bind ~/.local/share/xdg-app/runtime/org.gnome.Platform/x86_64/master/active/files /usr \ 13 | --lock-file /usr/.ref \ 14 | --ro-bind ~/.local/share/xdg-app/app/org.gnome.Weather/x86_64/master/active/files/ /app \ 15 | --lock-file /app/.ref \ 16 | --dev /dev \ 17 | --proc /proc \ 18 | --dir /tmp \ 19 | --symlink /tmp /var/tmp \ 20 | --symlink /run /var/run \ 21 | --symlink usr/lib /lib \ 22 | --symlink usr/lib64 /lib64 \ 23 | --symlink usr/bin /bin \ 24 | --symlink usr/sbin /sbin \ 25 | --symlink usr/etc /etc \ 26 | --dir /run/user/`id -u` \ 27 | --ro-bind /etc/machine-id /usr/etc/machine-id \ 28 | --ro-bind /etc/resolv.conf /run/host/monitor/resolv.conf \ 29 | --ro-bind /sys/block /sys/block \ 30 | --ro-bind /sys/bus /sys/bus \ 31 | --ro-bind /sys/class /sys/class \ 32 | --ro-bind /sys/dev /sys/dev \ 33 | --ro-bind /sys/devices /sys/devices \ 34 | --dev-bind /dev/dri /dev/dri \ 35 | --bind /tmp/.X11-unix/X0 /tmp/.X11-unix/X99 \ 36 | --bind ~/.var/app/org.gnome.Weather ~/.var/app/org.gnome.Weather \ 37 | --bind ~/.config/dconf ~/.config/dconf \ 38 | --bind /run/user/`id -u`/dconf /run/user/`id -u`/dconf \ 39 | --unshare-pid \ 40 | --setenv XDG_RUNTIME_DIR "/run/user/`id -u`" \ 41 | --setenv DISPLAY :99 \ 42 | --setenv GI_TYPELIB_PATH /app/lib/girepository-1.0 \ 43 | --setenv GST_PLUGIN_PATH /app/lib/gstreamer-1.0 \ 44 | --setenv LD_LIBRARY_PATH /app/lib:/usr/lib/GL \ 45 | --setenv DCONF_USER_CONFIG_DIR .config/dconf \ 46 | --setenv PATH /app/bin:/usr/bin \ 47 | --setenv XDG_CONFIG_DIRS /app/etc/xdg:/etc/xdg \ 48 | --setenv XDG_DATA_DIRS /app/share:/usr/share \ 49 | --setenv SHELL /bin/sh \ 50 | --setenv XDG_CACHE_HOME ~/.var/app/org.gnome.Weather/cache \ 51 | --setenv XDG_CONFIG_HOME ~/.var/app/org.gnome.Weather/config \ 52 | --setenv XDG_DATA_HOME ~/.var/app/org.gnome.Weather/data \ 53 | --file 10 /run/user/`id -u`/xdg-app-info \ 54 | --bind-data 11 /usr/etc/passwd \ 55 | --bind-data 12 /usr/etc/group \ 56 | --seccomp 13 \ 57 | /bin/sh) \ 58 | 11< <(getent passwd $UID 65534 ) \ 59 | 12< <(getent group $(id -g) 65534) \ 60 | 13< `dirname $0`/xdg-app.bpf \ 61 | 10<@]), 41 | [], 42 | [with_bash_completion_dir=yes]) 43 | 44 | if test "x$with_bash_completion_dir" = "xyes"; then 45 | PKG_CHECK_MODULES([BASH_COMPLETION], [bash-completion >= 2.0], 46 | [BASH_COMPLETION_DIR="`pkg-config --variable=completionsdir bash-completion`"], 47 | [BASH_COMPLETION_DIR="$datadir/bash-completion/completions"]) 48 | else 49 | BASH_COMPLETION_DIR="$with_bash_completion_dir" 50 | fi 51 | 52 | AC_SUBST([BASH_COMPLETION_DIR]) 53 | AM_CONDITIONAL([ENABLE_BASH_COMPLETION],[test "x$with_bash_completion_dir" != "xno"]) 54 | # ------------------------------------------------------------------------------ 55 | have_selinux=no 56 | AC_ARG_ENABLE(selinux, AS_HELP_STRING([--disable-selinux], [Disable optional SELINUX support])) 57 | if test "x$enable_selinux" != "xno"; then 58 | PKG_CHECK_MODULES([SELINUX], [libselinux >= 2.1.9], 59 | [AC_DEFINE(HAVE_SELINUX, 1, [Define if SELinux is available]) 60 | have_selinux=yes 61 | M4_DEFINES="$M4_DEFINES -DHAVE_SELINUX"], 62 | [have_selinux=no]) 63 | if test "x$have_selinux" = xno -a "x$enable_selinux" = xyes; then 64 | AC_MSG_ERROR([*** SELinux support requested but libraries not found]) 65 | fi 66 | fi 67 | AM_CONDITIONAL(HAVE_SELINUX, [test "$have_selinux" = "yes"]) 68 | 69 | changequote(,)dnl 70 | if test "x$GCC" = "xyes"; then 71 | WARN_CFLAGS="-Wall -Werror=missing-prototypes" 72 | fi 73 | changequote([,])dnl 74 | AC_SUBST(WARN_CFLAGS) 75 | 76 | AC_ARG_WITH(priv-mode, 77 | AS_HELP_STRING([--with-priv-mode=setuid/caps/none], 78 | [How to set privilege-raising during make install)]), 79 | [], 80 | [with_priv_mode="none"]) 81 | 82 | AM_CONDITIONAL(PRIV_MODE_FILECAPS, test "x$with_priv_mode" = "xcaps") 83 | AM_CONDITIONAL(PRIV_MODE_SETUID, test "x$with_priv_mode" = "xsetuid") 84 | 85 | AC_ARG_ENABLE(sudo, 86 | AS_HELP_STRING([--enable-sudo],[Use sudo to set privileged mode on binaries during install (only needed if --with-priv-mode used)]), 87 | [SUDO_BIN="sudo"], [SUDO_BIN=""]) 88 | AC_SUBST([SUDO_BIN]) 89 | 90 | AC_CONFIG_FILES([ 91 | Makefile 92 | ]) 93 | AC_OUTPUT 94 | 95 | echo " 96 | bubblewrap $VERSION 97 | =================== 98 | 99 | man pages (xsltproc): $enable_man 100 | SELinux: $have_selinux 101 | setuid mode on make install: $with_priv_mode 102 | mysteriously satisfying to pop: yes" 103 | echo "" 104 | -------------------------------------------------------------------------------- /uncrustify.cfg: -------------------------------------------------------------------------------- 1 | newlines lf 2 | 3 | input_tab_size 8 4 | output_tab_size 8 5 | 6 | string_escape_char 92 7 | string_escape_char2 0 8 | 9 | # indenting 10 | indent_columns 2 11 | indent_with_tabs 0 12 | indent_align_string True 13 | indent_brace 2 14 | indent_braces false 15 | indent_braces_no_func True 16 | indent_func_call_param false 17 | indent_func_def_param false 18 | indent_func_proto_param false 19 | indent_switch_case 0 20 | indent_case_brace 2 21 | indent_paren_close 1 22 | 23 | # spacing 24 | sp_arith Add 25 | sp_assign Add 26 | sp_enum_assign Add 27 | sp_bool Add 28 | sp_compare Add 29 | sp_inside_paren Remove 30 | sp_inside_fparens Remove 31 | sp_func_def_paren Force 32 | sp_func_proto_paren Force 33 | sp_paren_paren Remove 34 | sp_balance_nested_parens False 35 | sp_paren_brace Remove 36 | sp_before_square Remove 37 | sp_before_squares Remove 38 | sp_inside_square Remove 39 | sp_before_ptr_star Add 40 | sp_between_ptr_star Remove 41 | sp_after_comma Add 42 | sp_before_comma Remove 43 | sp_after_cast Add 44 | sp_sizeof_paren Add 45 | sp_not Remove 46 | sp_inv Remove 47 | sp_addr Remove 48 | sp_member Remove 49 | sp_deref Remove 50 | sp_sign Remove 51 | sp_incdec Remove 52 | sp_attribute_paren remove 53 | sp_macro Force 54 | sp_func_call_paren Force 55 | sp_func_call_user_paren Remove 56 | set func_call_user _ N_ C_ g_autoptr g_auto 57 | sp_brace_typedef add 58 | sp_cond_colon add 59 | sp_cond_question add 60 | sp_defined_paren remove 61 | 62 | # alignment 63 | align_keep_tabs False 64 | align_with_tabs False 65 | align_on_tabstop False 66 | align_number_left True 67 | align_func_params True 68 | align_var_def_span 0 69 | align_var_def_amp_style 1 70 | align_var_def_colon true 71 | align_enum_equ_span 0 72 | align_var_struct_span 2 73 | align_var_def_star_style 2 74 | align_var_def_amp_style 2 75 | align_typedef_span 2 76 | align_typedef_func 0 77 | align_typedef_star_style 2 78 | align_typedef_amp_style 2 79 | 80 | # newlines 81 | nl_assign_leave_one_liners True 82 | nl_enum_leave_one_liners False 83 | nl_func_leave_one_liners False 84 | nl_if_leave_one_liners False 85 | nl_end_of_file Add 86 | nl_assign_brace Remove 87 | nl_func_var_def_blk 1 88 | nl_fcall_brace Add 89 | nl_enum_brace Remove 90 | nl_struct_brace Force 91 | nl_union_brace Force 92 | nl_if_brace Force 93 | nl_brace_else Force 94 | nl_elseif_brace Force 95 | nl_else_brace Add 96 | nl_for_brace Force 97 | nl_while_brace Force 98 | nl_do_brace Force 99 | nl_brace_while Force 100 | nl_switch_brace Force 101 | nl_before_case True 102 | nl_after_case False 103 | nl_func_type_name Force 104 | nl_func_proto_type_name Remove 105 | nl_func_paren Remove 106 | nl_func_decl_start Remove 107 | nl_func_decl_args Force 108 | nl_func_decl_end Remove 109 | nl_fdef_brace Force 110 | nl_after_return False 111 | nl_define_macro False 112 | nl_create_if_one_liner False 113 | nl_create_for_one_liner False 114 | nl_create_while_one_liner False 115 | nl_after_semicolon True 116 | nl_multi_line_cond true 117 | 118 | # mod 119 | # I'd like these to be remove, but that removes brackets in if { if { foo } }, which i dislike 120 | # Not clear what to do about that... 121 | mod_full_brace_for Remove 122 | mod_full_brace_if Remove 123 | mod_full_brace_if_chain True 124 | mod_full_brace_while Remove 125 | mod_full_brace_do Remove 126 | mod_full_brace_nl 3 127 | mod_paren_on_return Remove 128 | 129 | # line splitting 130 | #code_width = 78 131 | ls_for_split_full True 132 | ls_func_split_full True 133 | 134 | # positioning 135 | pos_bool Trail 136 | pos_conditional Trail 137 | -------------------------------------------------------------------------------- /utils.h: -------------------------------------------------------------------------------- 1 | /* bubblewrap 2 | * Copyright (C) 2016 Alexander Larsson 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public 15 | * License along with this library. If not, see . 16 | * 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #if 0 34 | #define __debug__(x) printf x 35 | #else 36 | #define __debug__(x) 37 | #endif 38 | 39 | #define UNUSED __attribute__((__unused__)) 40 | 41 | #define N_ELEMENTS(arr) (sizeof (arr) / sizeof ((arr)[0])) 42 | 43 | #define TRUE 1 44 | #define FALSE 0 45 | typedef int bool; 46 | 47 | #define PIPE_READ_END 0 48 | #define PIPE_WRITE_END 1 49 | 50 | void die_with_error (const char *format, 51 | ...) __attribute__((__noreturn__)) __attribute__((format (printf, 1, 2))); 52 | void die (const char *format, 53 | ...) __attribute__((__noreturn__)); 54 | void die_oom (void) __attribute__((__noreturn__)); 55 | void die_unless_label_valid (const char *label); 56 | 57 | void *xmalloc (size_t size); 58 | void *xcalloc (size_t size); 59 | void *xrealloc (void *ptr, 60 | size_t size); 61 | char *xstrdup (const char *str); 62 | void strfreev (char **str_array); 63 | void xsetenv (const char *name, 64 | const char *value, 65 | int overwrite); 66 | void xunsetenv (const char *name); 67 | char *strconcat (const char *s1, 68 | const char *s2); 69 | char *strconcat3 (const char *s1, 70 | const char *s2, 71 | const char *s3); 72 | char * xasprintf (const char *format, 73 | ...) __attribute__((format (printf, 1, 2))); 74 | bool has_prefix (const char *str, 75 | const char *prefix); 76 | bool has_path_prefix (const char *str, 77 | const char *prefix); 78 | int fdwalk (int proc_fd, 79 | int (*cb)(void *data, 80 | int fd), 81 | void *data); 82 | char *load_file_data (int fd, 83 | size_t *size); 84 | char *load_file_at (int dirfd, 85 | const char *path); 86 | int write_file_at (int dirfd, 87 | const char *path, 88 | const char *content); 89 | int write_to_fd (int fd, 90 | const char *content, 91 | ssize_t len); 92 | int copy_file_data (int sfd, 93 | int dfd); 94 | int copy_file (const char *src_path, 95 | const char *dst_path, 96 | mode_t mode); 97 | int create_file (const char *path, 98 | mode_t mode, 99 | const char *content); 100 | int ensure_file (const char *path, 101 | mode_t mode); 102 | int get_file_mode (const char *pathname); 103 | int mkdir_with_parents (const char *pathname, 104 | int mode, 105 | bool create_last); 106 | 107 | /* syscall wrappers */ 108 | int raw_clone (unsigned long flags, 109 | void *child_stack); 110 | int pivot_root (const char *new_root, 111 | const char *put_old); 112 | char *label_mount (const char *opt, 113 | const char *mount_label); 114 | int label_exec (const char *exec_label); 115 | int label_create_file (const char *file_label); 116 | 117 | static inline void 118 | cleanup_freep (void *p) 119 | { 120 | void **pp = (void **) p; 121 | 122 | if (*pp) 123 | free (*pp); 124 | } 125 | 126 | static inline void 127 | cleanup_strvp (void *p) 128 | { 129 | void **pp = (void **) p; 130 | 131 | strfreev (*pp); 132 | } 133 | 134 | static inline void 135 | cleanup_fdp (int *fdp) 136 | { 137 | int fd; 138 | 139 | assert (fdp); 140 | 141 | fd = *fdp; 142 | if (fd != -1) 143 | (void) close (fd); 144 | } 145 | 146 | #define cleanup_free __attribute__((cleanup (cleanup_freep))) 147 | #define cleanup_fd __attribute__((cleanup (cleanup_fdp))) 148 | #define cleanup_strv __attribute__((cleanup (cleanup_strvp))) 149 | 150 | static inline void * 151 | steal_pointer (void *pp) 152 | { 153 | void **ptr = (void **) pp; 154 | void *ref; 155 | 156 | ref = *ptr; 157 | *ptr = NULL; 158 | 159 | return ref; 160 | } 161 | 162 | /* type safety */ 163 | #define steal_pointer(pp) \ 164 | (0 ? (*(pp)) : (steal_pointer) (pp)) 165 | -------------------------------------------------------------------------------- /network.c: -------------------------------------------------------------------------------- 1 | /* bubblewrap 2 | * Copyright (C) 2016 Alexander Larsson 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public 15 | * License along with this library. If not, see . 16 | * 17 | */ 18 | 19 | #include "config.h" 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "utils.h" 29 | #include "network.h" 30 | 31 | static void * 32 | add_rta (struct nlmsghdr *header, 33 | int type, 34 | size_t size) 35 | { 36 | struct rtattr *rta; 37 | size_t rta_size = RTA_LENGTH (size); 38 | 39 | rta = (struct rtattr *) ((char *) header + NLMSG_ALIGN (header->nlmsg_len)); 40 | rta->rta_type = type; 41 | rta->rta_len = rta_size; 42 | 43 | header->nlmsg_len = NLMSG_ALIGN (header->nlmsg_len) + rta_size; 44 | 45 | return RTA_DATA (rta); 46 | } 47 | 48 | static int 49 | rtnl_send_request (int rtnl_fd, 50 | struct nlmsghdr *header) 51 | { 52 | struct sockaddr_nl dst_addr = { AF_NETLINK, 0 }; 53 | ssize_t sent; 54 | 55 | sent = sendto (rtnl_fd, (void *) header, header->nlmsg_len, 0, 56 | (struct sockaddr *) &dst_addr, sizeof (dst_addr)); 57 | if (sent < 0) 58 | return -1; 59 | 60 | return 0; 61 | } 62 | 63 | static int 64 | rtnl_read_reply (int rtnl_fd, 65 | int seq_nr) 66 | { 67 | char buffer[1024]; 68 | ssize_t received; 69 | struct nlmsghdr *rheader; 70 | 71 | while (1) 72 | { 73 | received = recv (rtnl_fd, buffer, sizeof (buffer), 0); 74 | if (received < 0) 75 | return -1; 76 | 77 | rheader = (struct nlmsghdr *) buffer; 78 | while (received >= NLMSG_HDRLEN) 79 | { 80 | if (rheader->nlmsg_seq != seq_nr) 81 | return -1; 82 | if (rheader->nlmsg_pid != getpid ()) 83 | return -1; 84 | if (rheader->nlmsg_type == NLMSG_ERROR) 85 | { 86 | uint32_t *err = NLMSG_DATA (rheader); 87 | if (*err == 0) 88 | return 0; 89 | 90 | return -1; 91 | } 92 | if (rheader->nlmsg_type == NLMSG_DONE) 93 | return 0; 94 | 95 | rheader = NLMSG_NEXT (rheader, received); 96 | } 97 | } 98 | } 99 | 100 | static int 101 | rtnl_do_request (int rtnl_fd, 102 | struct nlmsghdr *header) 103 | { 104 | if (rtnl_send_request (rtnl_fd, header) != 0) 105 | return -1; 106 | 107 | if (rtnl_read_reply (rtnl_fd, header->nlmsg_seq) != 0) 108 | return -1; 109 | 110 | return 0; 111 | } 112 | 113 | static struct nlmsghdr * 114 | rtnl_setup_request (char *buffer, 115 | int type, 116 | int flags, 117 | size_t size) 118 | { 119 | struct nlmsghdr *header; 120 | size_t len = NLMSG_LENGTH (size); 121 | static uint32_t counter = 0; 122 | 123 | memset (buffer, 0, len); 124 | 125 | header = (struct nlmsghdr *) buffer; 126 | header->nlmsg_len = len; 127 | header->nlmsg_type = type; 128 | header->nlmsg_flags = flags | NLM_F_REQUEST; 129 | header->nlmsg_seq = counter++; 130 | header->nlmsg_pid = getpid (); 131 | 132 | return (struct nlmsghdr *) header; 133 | } 134 | 135 | int 136 | loopback_setup (void) 137 | { 138 | int r, if_loopback; 139 | cleanup_fd int rtnl_fd = -1; 140 | char buffer[1024]; 141 | struct sockaddr_nl src_addr = { AF_NETLINK, 0 }; 142 | struct nlmsghdr *header; 143 | struct ifaddrmsg *addmsg; 144 | struct ifinfomsg *infomsg; 145 | struct in_addr *ip_addr; 146 | 147 | src_addr.nl_pid = getpid (); 148 | 149 | if_loopback = (int) if_nametoindex ("lo"); 150 | if (if_loopback <= 0) 151 | return -1; 152 | 153 | rtnl_fd = socket (PF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE); 154 | if (rtnl_fd < 0) 155 | return -1; 156 | 157 | r = bind (rtnl_fd, (struct sockaddr *) &src_addr, sizeof (src_addr)); 158 | if (r < 0) 159 | return -1; 160 | 161 | header = rtnl_setup_request (buffer, RTM_NEWADDR, 162 | NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK, 163 | sizeof (struct ifaddrmsg)); 164 | addmsg = NLMSG_DATA (header); 165 | 166 | addmsg->ifa_family = AF_INET; 167 | addmsg->ifa_prefixlen = 8; 168 | addmsg->ifa_flags = IFA_F_PERMANENT; 169 | addmsg->ifa_scope = RT_SCOPE_HOST; 170 | addmsg->ifa_index = if_loopback; 171 | 172 | ip_addr = add_rta (header, IFA_LOCAL, sizeof (*ip_addr)); 173 | ip_addr->s_addr = htonl (INADDR_LOOPBACK); 174 | 175 | ip_addr = add_rta (header, IFA_ADDRESS, sizeof (*ip_addr)); 176 | ip_addr->s_addr = htonl (INADDR_LOOPBACK); 177 | 178 | assert (header->nlmsg_len < sizeof (buffer)); 179 | 180 | if (rtnl_do_request (rtnl_fd, header) != 0) 181 | return -1; 182 | 183 | header = rtnl_setup_request (buffer, RTM_NEWLINK, 184 | NLM_F_ACK, 185 | sizeof (struct ifinfomsg)); 186 | infomsg = NLMSG_DATA (header); 187 | 188 | infomsg->ifi_family = AF_UNSPEC; 189 | infomsg->ifi_type = 0; 190 | infomsg->ifi_index = if_loopback; 191 | infomsg->ifi_flags = IFF_UP; 192 | infomsg->ifi_change = IFF_UP; 193 | 194 | assert (header->nlmsg_len < sizeof (buffer)); 195 | 196 | if (rtnl_do_request (rtnl_fd, header) != 0) 197 | return -1; 198 | 199 | return 0; 200 | } 201 | -------------------------------------------------------------------------------- /bind-mount.c: -------------------------------------------------------------------------------- 1 | /* bubblewrap 2 | * Copyright (C) 2016 Alexander Larsson 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public 15 | * License along with this library. If not, see . 16 | * 17 | */ 18 | 19 | #include "config.h" 20 | 21 | #include 22 | 23 | #include "utils.h" 24 | #include "bind-mount.h" 25 | 26 | static char * 27 | skip_line (char *line) 28 | { 29 | while (*line != 0 && *line != '\n') 30 | line++; 31 | 32 | if (*line == '\n') 33 | line++; 34 | 35 | return line; 36 | } 37 | 38 | static char * 39 | skip_token (char *line, bool eat_whitespace) 40 | { 41 | while (*line != ' ' && *line != '\n') 42 | line++; 43 | 44 | if (eat_whitespace && *line == ' ') 45 | line++; 46 | 47 | return line; 48 | } 49 | 50 | static char * 51 | unescape_mountpoint (const char *escaped, ssize_t len) 52 | { 53 | char *unescaped, *res; 54 | const char *end; 55 | 56 | if (len < 0) 57 | len = strlen (escaped); 58 | end = escaped + len; 59 | 60 | unescaped = res = xmalloc (len + 1); 61 | while (escaped < end) 62 | { 63 | if (*escaped == '\\') 64 | { 65 | *unescaped++ = 66 | ((escaped[1] - '0') << 6) | 67 | ((escaped[2] - '0') << 3) | 68 | ((escaped[3] - '0') << 0); 69 | escaped += 4; 70 | } 71 | else 72 | { 73 | *unescaped++ = *escaped++; 74 | } 75 | } 76 | *unescaped = 0; 77 | return res; 78 | } 79 | 80 | static char * 81 | get_mountinfo (int proc_fd, 82 | const char *mountpoint) 83 | { 84 | char *line_mountpoint, *line_mountpoint_end; 85 | cleanup_free char *mountinfo = NULL; 86 | cleanup_free char *free_me = NULL; 87 | char *line, *line_start; 88 | char *res = NULL; 89 | int i; 90 | 91 | if (mountpoint[0] != '/') 92 | { 93 | cleanup_free char *cwd = getcwd (NULL, 0); 94 | if (cwd == NULL) 95 | die_oom (); 96 | 97 | mountpoint = free_me = strconcat3 (cwd, "/", mountpoint); 98 | } 99 | 100 | mountinfo = load_file_at (proc_fd, "self/mountinfo"); 101 | if (mountinfo == NULL) 102 | return NULL; 103 | 104 | line = mountinfo; 105 | 106 | while (*line != 0) 107 | { 108 | cleanup_free char *unescaped = NULL; 109 | 110 | line_start = line; 111 | for (i = 0; i < 4; i++) 112 | line = skip_token (line, TRUE); 113 | line_mountpoint = line; 114 | line = skip_token (line, FALSE); 115 | line_mountpoint_end = line; 116 | line = skip_line (line); 117 | 118 | unescaped = unescape_mountpoint (line_mountpoint, line_mountpoint_end - line_mountpoint); 119 | if (strcmp (mountpoint, unescaped) == 0) 120 | { 121 | res = line_start; 122 | line[-1] = 0; 123 | /* Keep going, because we want to return the *last* match */ 124 | } 125 | } 126 | 127 | if (res) 128 | return xstrdup (res); 129 | return NULL; 130 | } 131 | 132 | static unsigned long 133 | get_mountflags (int proc_fd, 134 | const char *mountpoint) 135 | { 136 | cleanup_free char *line = NULL; 137 | char *token, *end_token; 138 | int i; 139 | unsigned long flags = 0; 140 | static const struct { int flag; 141 | char *name; 142 | } flags_data[] = { 143 | { 0, "rw" }, 144 | { MS_RDONLY, "ro" }, 145 | { MS_NOSUID, "nosuid" }, 146 | { MS_NODEV, "nodev" }, 147 | { MS_NOEXEC, "noexec" }, 148 | { MS_NOATIME, "noatime" }, 149 | { MS_NODIRATIME, "nodiratime" }, 150 | { MS_RELATIME, "relatime" }, 151 | { 0, NULL } 152 | }; 153 | 154 | line = get_mountinfo (proc_fd, mountpoint); 155 | if (line == NULL) 156 | return 0; 157 | 158 | token = line; 159 | for (i = 0; i < 5; i++) 160 | token = skip_token (token, TRUE); 161 | 162 | end_token = skip_token (token, FALSE); 163 | *end_token = 0; 164 | 165 | do 166 | { 167 | end_token = strchr (token, ','); 168 | if (end_token != NULL) 169 | *end_token = 0; 170 | 171 | for (i = 0; flags_data[i].name != NULL; i++) 172 | if (strcmp (token, flags_data[i].name) == 0) 173 | flags |= flags_data[i].flag; 174 | 175 | if (end_token) 176 | token = end_token + 1; 177 | else 178 | token = NULL; 179 | } 180 | while (token != NULL); 181 | 182 | return flags; 183 | } 184 | 185 | 186 | static char ** 187 | get_submounts (int proc_fd, 188 | const char *parent_mount) 189 | { 190 | char *mountpoint, *mountpoint_end; 191 | char **submounts; 192 | int i, n_submounts, submounts_size; 193 | cleanup_free char *mountinfo = NULL; 194 | char *line; 195 | 196 | mountinfo = load_file_at (proc_fd, "self/mountinfo"); 197 | if (mountinfo == NULL) 198 | return NULL; 199 | 200 | submounts_size = 8; 201 | n_submounts = 0; 202 | submounts = xmalloc (sizeof (char *) * submounts_size); 203 | 204 | line = mountinfo; 205 | 206 | while (*line != 0) 207 | { 208 | cleanup_free char *unescaped = NULL; 209 | for (i = 0; i < 4; i++) 210 | line = skip_token (line, TRUE); 211 | mountpoint = line; 212 | line = skip_token (line, FALSE); 213 | mountpoint_end = line; 214 | line = skip_line (line); 215 | *mountpoint_end = 0; 216 | 217 | unescaped = unescape_mountpoint (mountpoint, -1); 218 | 219 | if (has_path_prefix (unescaped, parent_mount)) 220 | { 221 | if (n_submounts + 1 >= submounts_size) 222 | { 223 | submounts_size *= 2; 224 | submounts = xrealloc (submounts, sizeof (char *) * submounts_size); 225 | } 226 | submounts[n_submounts++] = xstrdup (unescaped); 227 | } 228 | } 229 | 230 | submounts[n_submounts] = NULL; 231 | 232 | return submounts; 233 | } 234 | 235 | int 236 | bind_mount (int proc_fd, 237 | const char *src, 238 | const char *dest, 239 | bind_option_t options) 240 | { 241 | bool readonly = (options & BIND_READONLY) != 0; 242 | bool devices = (options & BIND_DEVICES) != 0; 243 | bool recursive = (options & BIND_RECURSIVE) != 0; 244 | unsigned long current_flags, new_flags; 245 | int i; 246 | 247 | if (mount (src, dest, NULL, MS_MGC_VAL | MS_BIND | (recursive ? MS_REC : 0), NULL) != 0) 248 | return 1; 249 | 250 | current_flags = get_mountflags (proc_fd, dest); 251 | 252 | new_flags = current_flags | (devices ? 0 : MS_NODEV) | MS_NOSUID | (readonly ? MS_RDONLY : 0); 253 | if (new_flags != current_flags && 254 | mount ("none", dest, 255 | NULL, MS_MGC_VAL | MS_BIND | MS_REMOUNT | new_flags, NULL) != 0) 256 | return 3; 257 | 258 | /* We need to work around the fact that a bind mount does not apply the flags, so we need to manually 259 | * apply the flags to all submounts in the recursive case. 260 | * Note: This does not apply the flags to mounts which are later propagated into this namespace. 261 | */ 262 | if (recursive) 263 | { 264 | cleanup_strv char **submounts = get_submounts (proc_fd, dest); 265 | if (submounts == NULL) 266 | return 4; 267 | 268 | for (i = 0; submounts[i] != NULL; i++) 269 | { 270 | current_flags = get_mountflags (proc_fd, submounts[i]); 271 | new_flags = current_flags | (devices ? 0 : MS_NODEV) | MS_NOSUID | (readonly ? MS_RDONLY : 0); 272 | if (new_flags != current_flags && 273 | mount ("none", submounts[i], 274 | NULL, MS_MGC_VAL | MS_BIND | MS_REMOUNT | new_flags, NULL) != 0) 275 | { 276 | /* If we can't read the mountpoint we can't remount it, but that should 277 | be safe to ignore because its not something the user can access. */ 278 | if (errno != EACCES) 279 | return 5; 280 | } 281 | } 282 | } 283 | 284 | return 0; 285 | } 286 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Bubblewrap 2 | ========== 3 | 4 | Many container runtime tools like `systemd-nspawn`, `docker`, 5 | etc. focus on providing infrastructure for system administrators and 6 | orchestration tools (e.g. Kubernetes) to run containers. 7 | 8 | These tools are not suitable to give to unprivileged users, because it 9 | is trivial to turn such access into to a fully privileged root shell 10 | on the host. 11 | 12 | User namespaces 13 | --------------- 14 | 15 | There is an effort in the Linux kernel called 16 | [user namespaces](https://www.google.com/search?q=user+namespaces+site%3Ahttps%3A%2F%2Flwn.net) 17 | which attempts to allow unprivileged users to use container features. 18 | While significant progress has been made, there are 19 | [still concerns](https://lwn.net/Articles/673597/) about it, and 20 | it is not available to unprivileged users in several production distributions 21 | such as CentOS/Red Hat Enterprise Linux 7, Debian Jessie, etc. 22 | 23 | See for example 24 | [CVE-2016-3135](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-3135) 25 | which is a local root vulnerability introduced by userns. 26 | [This March 2016 post](https://lkml.org/lkml/2016/3/9/555) has some 27 | more discussion. 28 | 29 | Bubblewrap could be viewed as setuid implementation of a *subset* of 30 | user namespaces. Emphasis on subset - specifically relevant to the 31 | above CVE, bubblewrap does not allow control over iptables. 32 | 33 | The original bubblewrap code existed before user namespaces - it inherits code from 34 | [xdg-app helper](https://cgit.freedesktop.org/xdg-app/xdg-app/tree/common/xdg-app-helper.c) 35 | which in turn distantly derives from 36 | [linux-user-chroot](https://git.gnome.org/browse/linux-user-chroot). 37 | 38 | Security 39 | -------- 40 | 41 | The maintainers of this tool believe that it does not, even when used 42 | in combination with typical software installed on that distribution, 43 | allow privilege escalation. It may increase the ability of a logged 44 | in user to perform denial of service attacks, however. 45 | 46 | In particular, bubblewrap uses `PR_SET_NO_NEW_PRIVS` to turn off 47 | setuid binaries, which is the [traditional way](https://en.wikipedia.org/wiki/Chroot#Limitations) to get out of things 48 | like chroots. 49 | 50 | Users 51 | ----- 52 | 53 | This program can be shared by all container tools which perform 54 | non-root operation, such as: 55 | 56 | - [xdg-app](https://cgit.freedesktop.org/xdg-app/xdg-app) 57 | - [rpm-ostree unprivileged](https://github.com/projectatomic/rpm-ostree/pull/209) 58 | 59 | We would also like to see this be available in Kubernetes/OpenShift 60 | clusters. Having the ability for unprivileged users to use container 61 | features would make it significantly easier to do interactive 62 | debugging scenarios and the like. 63 | 64 | Usage 65 | ----- 66 | 67 | bubblewrap works by creating a new, completely empty, mount 68 | namespace where the root is on a tmpfs that is invisible from the 69 | host, and will be automatically cleaned up when the last process 70 | exists. You can then use commandline options to construct the root 71 | filesystem and process environment and command to run in the 72 | namespace. 73 | 74 | A simple example is 75 | ``` 76 | bwrap --ro-bind / / bash 77 | ``` 78 | This will create a read-only bind mount of the host root at the 79 | sandbox root, and then start a bash. 80 | 81 | Another simple example would be a read-write chroot operation: 82 | ``` 83 | bwrap --bind /some/chroot/dir / bash 84 | ``` 85 | 86 | A more complex example is to run a with a custom (readonly) /usr, 87 | but your own (tmpfs) data, running in a PID and network namespace: 88 | 89 | ``` 90 | bwrap --ro-bind /usr /usr \ 91 | --tmpfs /tmp \ 92 | --proc /proc \ 93 | --dev /dev \ 94 | --ro-bind /etc/resolv.conf /etc/resolv.conf \ 95 | --symlink usr/lib /lib \ 96 | --symlink usr/lib64 /lib64 \ 97 | --symlink usr/bin /bin \ 98 | --symlink usr/sbin /sbin \ 99 | --chdir / \ 100 | --unshare-pid \ 101 | --unshare-net \ 102 | --dir /run/user/$(id -u) \ 103 | --setenv XDG_RUNTIME_DIR "/run/user/`id -u`" \ 104 | /bin/sh 105 | ``` 106 | 107 | Sandboxing 108 | ---------- 109 | 110 | The goal of bubblewrap is to run an application in a sandbox, where it 111 | has restricted access to parts of the operating system or user data 112 | such as the home directory. 113 | 114 | bubblewrap always creates a new mount namespace, and the user can specify 115 | exactly what parts of the filesystem should be visible in the sandbox. 116 | Any such directories you specify mounted `nodev` by default, and can be made readonly. 117 | 118 | Additionally you can use these kernel features: 119 | 120 | User namespaces ([CLONE_NEWUSER](http://linux.die.net/man/2/clone)): This hides all but the current uid and gid from the 121 | sandbox. You can also change what the value of uid/gid should be in the sandbox. 122 | 123 | IPC namespaces ([CLONE_NEWIPC](http://linux.die.net/man/2/clone)): The sandbox will get its own copy of all the 124 | different forms of IPCs, like SysV shared memory and semaphores. 125 | 126 | PID namespaces ([CLONE_NEWPID](http://linux.die.net/man/2/clone)): The sandbox will not see any processes outside the sandbox. Additionally, bubblewrap will run a trivial pid1 inside your container to handle the requirements of reaping children in the sandbox. .This avoids what is known now as the [Docker pid 1 problem](https://blog.phusion.nl/2015/01/20/docker-and-the-pid-1-zombie-reaping-problem/). 127 | 128 | 129 | Network namespaces ([CLONE_NEWNET](http://linux.die.net/man/2/clone)): The sandbox will not see the network. Instead it will have its own network namespace with only a loopback device. 130 | 131 | UTS namespace ([CLONE_NEWUTS](http://linux.die.net/man/2/clone)): The sandbox will have its own hostname. 132 | 133 | Seccomp filters: You can pass in seccomp filters that limit which syscalls can be done in the sandbox. For more information, see [Seccomp](https://en.wikipedia.org/wiki/Seccomp). 134 | 135 | Related project comparison: Firejail 136 | ------------------------------------ 137 | 138 | [Firejail](https://github.com/netblue30/firejail/tree/master/src/firejail) is 139 | similar to xdg-app before bubblewrap was split out in that it combines 140 | a setuid tool with a lot of desktop-specific sandboxing features. For 141 | example, Firejail knows about Pulseaudio, whereas bubblewrap does not. 142 | 143 | The bubblewrap authors believe it's much easier to audit a small 144 | setuid program, and keep features such as Pulseaudio filtering as an 145 | unprivileged process, as now occurs in xdg-app. 146 | 147 | Also, @cgwalters thinks trying to 148 | [whitelist file paths](https://github.com/netblue30/firejail/blob/37a5a3545ef6d8d03dad8bbd888f53e13274c9e5/src/firejail/fs_whitelist.c#L176) 149 | is a bad idea given the myriad ways users have to manipulate paths, 150 | and the myriad ways in which system administrators may configure a 151 | system. The bubblewrap approach is to only retain a few specific 152 | Linux capabilities such as `CAP_SYS_ADMIN`, but to always access the 153 | filesystem as the invoking uid. This entirely closes 154 | [TOCTOCU attacks](https://cwe.mitre.org/data/definitions/367.html) and 155 | such. 156 | 157 | Related project comparison: Sandstorm.io 158 | ---------------------------------------- 159 | 160 | [Sandstorm.io](https://sandstorm.io/) also has a setuid helper 161 | process. @cgwalters believes their setuid code is fairly good, but it 162 | could still make sense to unify on bubblewrap as a setuid core. That 163 | hasn't been ruled out, but neither is it being actively pursued today. 164 | 165 | Related project comparison: runc/binctr 166 | ---------------------------------------- 167 | 168 | [runc](https://github.com/opencontainers/runc) is similar to 169 | [systemd nspawn](https://www.freedesktop.org/software/systemd/man/systemd-nspawn.html) 170 | in that it is tooling intended to be invoked by root. There is an 171 | effort to have runc optionally use 172 | [user namespaces](https://github.com/opencontainers/runc/issues/38), 173 | but no plans for any setuid support. 174 | 175 | The bubblewrap authors believe that runc and systemd-nspawn are not 176 | designed to be made setuid and are distant from supporting such a 177 | mode. 178 | 179 | [binctr](https://github.com/jfrazelle/binctr) is just a wrapper for 180 | runc, so inherits all of its design tradeoffs. 181 | 182 | Whats with the name ?! 183 | ---------------------- 184 | 185 | The name bubblewrap was chosen to convey that this 186 | tool runs as the parent of the application (so wraps it in some sense) and creates 187 | a protective layer (the sandbox) around it. 188 | 189 | ![](bubblewrap.jpg) 190 | 191 | (Bubblewrap cat by [dancing_stupidity](https://www.flickr.com/photos/27549668@N03/)) 192 | -------------------------------------------------------------------------------- /bwrap.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | bwrap 9 | Project Atomic 10 | 11 | 12 | Developer 13 | Alexander 14 | Larsson 15 | 16 | 17 | Developer 18 | Colin 19 | Walters 20 | 21 | 22 | 23 | 24 | 25 | bwrap 26 | 1 27 | User Commands 28 | 29 | 30 | 31 | bwrap 32 | container setup utility 33 | 34 | 35 | 36 | 37 | bwrap 38 | OPTION 39 | COMMAND 40 | 41 | 42 | 43 | Description 44 | 45 | bwrap is a privileged helper for container setup. You 46 | are unlikely to use it directly from the commandline, although that is possible. 47 | 48 | 49 | It works by creating a new, completely empty, filesystem namespace where the root 50 | is on a tmpfs that is invisible from the host, and which will be automatically 51 | cleaned up when the last process exists. You can then use commandline options to 52 | construct the root filesystem and process environment for the command to run in 53 | the namespace. 54 | 55 | 56 | By default, bwrap creates a new user namespace for the sandbox. 57 | Optionally it also sets up new ipc, pid, network and uts namespaces. The application 58 | in the sandbox can be made to run with a different UID and GID. 59 | 60 | 61 | If needed (e.g. when using a PID namespace) bwrap 62 | is running a minimal pid 1 process in the sandbox that is 63 | responsible for reaping zombies. It also detects when the initial 64 | application process (pid 2) dies and reports its exit status back to 65 | the original spawner. The pid 1 process exits to clean up the 66 | sandbox when there are no other processes in the sandbox left. 67 | 68 | 69 | 70 | Options 71 | 72 | When options are used multiple times, the last option wins, unless otherwise 73 | specified. 74 | 75 | General options: 76 | 77 | 78 | 79 | Print help and exit 80 | 81 | 82 | 83 | Print version 84 | 85 | 86 | 87 | 88 | Parse nul-separated arguments from the given file descriptor. 89 | This option can be used multiple times to parse options from 90 | multiple sources. 91 | 92 | 93 | 94 | Options related to kernel namespaces: 95 | 96 | 97 | 98 | Don't create a new user namespace 99 | 100 | 101 | 102 | Create a new ipc namespace 103 | 104 | 105 | 106 | Create a new pid namespace 107 | 108 | 109 | 110 | Create a new network namespace 111 | 112 | 113 | 114 | Create a new uts namespace 115 | 116 | 117 | 118 | Create a new cgroup namespace 119 | 120 | 121 | 122 | Create a new cgroup namespace if possible else skip it 123 | 124 | 125 | 126 | Use a custom user id in the sandbox (incompatible with ) 127 | 128 | 129 | 130 | Use a custom group id in the sandbox (incompatible with ) 131 | 132 | 133 | Options about environment setup: 134 | 135 | 136 | 137 | Change directory to DIR 138 | 139 | 140 | 141 | Set an environment variable 142 | 143 | 144 | 145 | Unset an environment variable 146 | 147 | 148 | Options for monitoring the sandbox from the outside: 149 | 150 | 151 | 152 | 153 | Take a lock on DEST while the sandbox is running. 154 | This option can be used multiple times to take locks on multiple files. 155 | 156 | 157 | 158 | 159 | Keep this file descriptor open while the sandbox is running 160 | 161 | 162 | 163 | Filesystem related options. These are all operations that modify the filesystem directly, or 164 | mounts stuff in the filesystem. These are applied in the order they are given as arguments. 165 | Any missing parent directories that are required to create a specified destination are 166 | automatically created as needed. 167 | 168 | 169 | 170 | 171 | Bind mount the host path SRC on DEST 172 | 173 | 174 | 175 | Bind mount the host path SRC on DEST, allowing device access 176 | 177 | 178 | 179 | Bind mount the host path SRC readonly on DEST 180 | 181 | 182 | 183 | Mount procfs on DEST 184 | 185 | 186 | 187 | Mount new devtmpfs on DEST 188 | 189 | 190 | 191 | Mount new tmpfs on DEST 192 | 193 | 194 | 195 | Mount new mqueue on DEST 196 | 197 | 198 | 199 | Create a directory at DEST 200 | 201 | 202 | 203 | Copy from the file descriptor FD to DEST 204 | 205 | 206 | 207 | Copy from the file descriptor FD to a file which is bind-mounted on DEST 208 | 209 | 210 | 211 | Create a symlink at DEST with target SRC 212 | 213 | 214 | Lockdown options: 215 | 216 | 217 | 218 | 219 | Load and use seccomp rules from FD. 220 | The rules need to be in the form of a compiled eBPF program, 221 | as generated by seccomp_export_bpf. 222 | 223 | 224 | 225 | 226 | 227 | Exec Label from the sandbox. On an SELinux system you can specify the SELinux 228 | context for the sandbox process(s). 229 | 230 | 231 | 232 | 233 | 234 | File label for temporary sandbox content. On an SELinux system you can specify 235 | the SELinux context for the sandbox content. 236 | 237 | 238 | 239 | 240 | 241 | 242 | Environment 243 | 244 | 245 | 246 | HOME 247 | 248 | Used as the cwd in the sandbox if has not been 249 | explicitly specified and the current cwd is not present inside the sandbox. 250 | The option can be used to override the value 251 | that is used here. 252 | 253 | 254 | 255 | 256 | 257 | 258 | Exit status 259 | 260 | 261 | The bwrap command returns the exit status of the 262 | initial application process (pid 2 in the sandbox). 263 | 264 | 265 | 266 | 267 | -------------------------------------------------------------------------------- /git.mk: -------------------------------------------------------------------------------- 1 | # git.mk, a small Makefile to autogenerate .gitignore files 2 | # for autotools-based projects. 3 | # 4 | # Copyright 2009, Red Hat, Inc. 5 | # Copyright 2010,2011,2012,2013 Behdad Esfahbod 6 | # Written by Behdad Esfahbod 7 | # 8 | # Copying and distribution of this file, with or without modification, 9 | # is permitted in any medium without royalty provided the copyright 10 | # notice and this notice are preserved. 11 | # 12 | # The latest version of this file can be downloaded from: 13 | GIT_MK_URL = https://raw.githubusercontent.com/behdad/git.mk/master/git.mk 14 | # 15 | # Bugs, etc, should be reported upstream at: 16 | # https://github.com/behdad/git.mk 17 | # 18 | # To use in your project, import this file in your git repo's toplevel, 19 | # then do "make -f git.mk". This modifies all Makefile.am files in 20 | # your project to -include git.mk. Remember to add that line to new 21 | # Makefile.am files you create in your project, or just rerun the 22 | # "make -f git.mk". 23 | # 24 | # This enables automatic .gitignore generation. If you need to ignore 25 | # more files, add them to the GITIGNOREFILES variable in your Makefile.am. 26 | # But think twice before doing that. If a file has to be in .gitignore, 27 | # chances are very high that it's a generated file and should be in one 28 | # of MOSTLYCLEANFILES, CLEANFILES, DISTCLEANFILES, or MAINTAINERCLEANFILES. 29 | # 30 | # The only case that you need to manually add a file to GITIGNOREFILES is 31 | # when remove files in one of mostlyclean-local, clean-local, distclean-local, 32 | # or maintainer-clean-local make targets. 33 | # 34 | # Note that for files like editor backup, etc, there are better places to 35 | # ignore them. See "man gitignore". 36 | # 37 | # If "make maintainer-clean" removes the files but they are not recognized 38 | # by this script (that is, if "git status" shows untracked files still), send 39 | # me the output of "git status" as well as your Makefile.am and Makefile for 40 | # the directories involved and I'll diagnose. 41 | # 42 | # For a list of toplevel files that should be in MAINTAINERCLEANFILES, see 43 | # Makefile.am.sample in the git.mk git repo. 44 | # 45 | # Don't EXTRA_DIST this file. It is supposed to only live in git clones, 46 | # not tarballs. It serves no useful purpose in tarballs and clutters the 47 | # build dir. 48 | # 49 | # This file knows how to handle autoconf, automake, libtool, gtk-doc, 50 | # gnome-doc-utils, yelp.m4, mallard, intltool, gsettings, dejagnu, appdata, 51 | # appstream. 52 | # 53 | # This makefile provides the following targets: 54 | # 55 | # - all: "make all" will build all gitignore files. 56 | # - gitignore: makes all gitignore files in the current dir and subdirs. 57 | # - .gitignore: make gitignore file for the current dir. 58 | # - gitignore-recurse: makes all gitignore files in the subdirs. 59 | # 60 | # KNOWN ISSUES: 61 | # 62 | # - Recursive configure doesn't work as $(top_srcdir)/git.mk inside the 63 | # submodule doesn't find us. If you have configure.{in,ac} files in 64 | # subdirs, add a proxy git.mk file in those dirs that simply does: 65 | # "include $(top_srcdir)/../git.mk". Add more ..'s to your taste. 66 | # And add those files to git. See vte/gnome-pty-helper/git.mk for 67 | # example. 68 | # 69 | 70 | 71 | 72 | ############################################################################### 73 | # Variables user modules may want to add to toplevel MAINTAINERCLEANFILES: 74 | ############################################################################### 75 | 76 | # 77 | # Most autotools-using modules should be fine including this variable in their 78 | # toplevel MAINTAINERCLEANFILES: 79 | GITIGNORE_MAINTAINERCLEANFILES_TOPLEVEL = \ 80 | $(srcdir)/aclocal.m4 \ 81 | $(srcdir)/autoscan.log \ 82 | $(srcdir)/configure.scan \ 83 | `AUX_DIR=$(srcdir)/$$(cd $(top_srcdir); $(AUTOCONF) --trace 'AC_CONFIG_AUX_DIR:$$1' ./configure.ac); \ 84 | test "x$$AUX_DIR" = "x$(srcdir)/" && AUX_DIR=$(srcdir); \ 85 | for x in \ 86 | ar-lib \ 87 | compile \ 88 | config.guess \ 89 | config.sub \ 90 | depcomp \ 91 | install-sh \ 92 | ltmain.sh \ 93 | missing \ 94 | mkinstalldirs \ 95 | test-driver \ 96 | ylwrap \ 97 | ; do echo "$$AUX_DIR/$$x"; done` \ 98 | `cd $(top_srcdir); $(AUTOCONF) --trace 'AC_CONFIG_HEADERS:$$1' ./configure.ac | \ 99 | head -n 1 | while read f; do echo "$(srcdir)/$$f.in"; done` 100 | # 101 | # All modules should also be fine including the following variable, which 102 | # removes automake-generated Makefile.in files: 103 | GITIGNORE_MAINTAINERCLEANFILES_MAKEFILE_IN = \ 104 | `cd $(top_srcdir); $(AUTOCONF) --trace 'AC_CONFIG_FILES:$$1' ./configure.ac | \ 105 | while read f; do \ 106 | case $$f in Makefile|*/Makefile) \ 107 | test -f "$(srcdir)/$$f.am" && echo "$(srcdir)/$$f.in";; esac; \ 108 | done` 109 | # 110 | # Modules that use libtool and use AC_CONFIG_MACRO_DIR() may also include this, 111 | # though it's harmless to include regardless. 112 | GITIGNORE_MAINTAINERCLEANFILES_M4_LIBTOOL = \ 113 | `MACRO_DIR=$(srcdir)/$$(cd $(top_srcdir); $(AUTOCONF) --trace 'AC_CONFIG_MACRO_DIR:$$1' ./configure.ac); \ 114 | if test "x$$MACRO_DIR" != "x$(srcdir)/"; then \ 115 | for x in \ 116 | libtool.m4 \ 117 | ltoptions.m4 \ 118 | ltsugar.m4 \ 119 | ltversion.m4 \ 120 | lt~obsolete.m4 \ 121 | ; do echo "$$MACRO_DIR/$$x"; done; \ 122 | fi` 123 | 124 | 125 | 126 | ############################################################################### 127 | # Default rule is to install ourselves in all Makefile.am files: 128 | ############################################################################### 129 | 130 | git-all: git-mk-install 131 | 132 | git-mk-install: 133 | @echo "Installing git makefile" 134 | @any_failed=; \ 135 | find "`test -z "$(top_srcdir)" && echo . || echo "$(top_srcdir)"`" -name Makefile.am | while read x; do \ 136 | if grep 'include .*/git.mk' $$x >/dev/null; then \ 137 | echo "$$x already includes git.mk"; \ 138 | else \ 139 | failed=; \ 140 | echo "Updating $$x"; \ 141 | { cat $$x; \ 142 | echo ''; \ 143 | echo '-include $$(top_srcdir)/git.mk'; \ 144 | } > $$x.tmp || failed=1; \ 145 | if test x$$failed = x; then \ 146 | mv $$x.tmp $$x || failed=1; \ 147 | fi; \ 148 | if test x$$failed = x; then : else \ 149 | echo "Failed updating $$x"; >&2 \ 150 | any_failed=1; \ 151 | fi; \ 152 | fi; done; test -z "$$any_failed" 153 | 154 | git-mk-update: 155 | wget $(GIT_MK_URL) -O $(top_srcdir)/git.mk 156 | 157 | .PHONY: git-all git-mk-install git-mk-update 158 | 159 | 160 | 161 | ############################################################################### 162 | # Actual .gitignore generation: 163 | ############################################################################### 164 | 165 | $(srcdir)/.gitignore: Makefile.am $(top_srcdir)/git.mk 166 | @echo "git.mk: Generating $@" 167 | @{ \ 168 | if test "x$(DOC_MODULE)" = x -o "x$(DOC_MAIN_SGML_FILE)" = x; then :; else \ 169 | for x in \ 170 | $(DOC_MODULE)-decl-list.txt \ 171 | $(DOC_MODULE)-decl.txt \ 172 | tmpl/$(DOC_MODULE)-unused.sgml \ 173 | "tmpl/*.bak" \ 174 | $(REPORT_FILES) \ 175 | $(DOC_MODULE).pdf \ 176 | xml html \ 177 | ; do echo "/$$x"; done; \ 178 | FLAVOR=$$(cd $(top_srcdir); $(AUTOCONF) --trace 'GTK_DOC_CHECK:$$2' ./configure.ac); \ 179 | case $$FLAVOR in *no-tmpl*) echo /tmpl;; esac; \ 180 | if echo "$(SCAN_OPTIONS)" | grep -q "\-\-rebuild-types"; then \ 181 | echo "/$(DOC_MODULE).types"; \ 182 | fi; \ 183 | if echo "$(SCAN_OPTIONS)" | grep -q "\-\-rebuild-sections"; then \ 184 | echo "/$(DOC_MODULE)-sections.txt"; \ 185 | fi; \ 186 | if test "$(abs_srcdir)" != "$(abs_builddir)" ; then \ 187 | for x in \ 188 | $(SETUP_FILES) \ 189 | $(DOC_MODULE).types \ 190 | ; do echo "/$$x"; done; \ 191 | fi; \ 192 | fi; \ 193 | if test "x$(DOC_MODULE)$(DOC_ID)" = x -o "x$(DOC_LINGUAS)" = x; then :; else \ 194 | for lc in $(DOC_LINGUAS); do \ 195 | for x in \ 196 | $(if $(DOC_MODULE),$(DOC_MODULE).xml) \ 197 | $(DOC_PAGES) \ 198 | $(DOC_INCLUDES) \ 199 | ; do echo "/$$lc/$$x"; done; \ 200 | done; \ 201 | for x in \ 202 | $(_DOC_OMF_ALL) \ 203 | $(_DOC_DSK_ALL) \ 204 | $(_DOC_HTML_ALL) \ 205 | $(_DOC_MOFILES) \ 206 | $(DOC_H_FILE) \ 207 | "*/.xml2po.mo" \ 208 | "*/*.omf.out" \ 209 | ; do echo /$$x; done; \ 210 | fi; \ 211 | if test "x$(HELP_ID)" = x -o "x$(HELP_LINGUAS)" = x; then :; else \ 212 | for lc in $(HELP_LINGUAS); do \ 213 | for x in \ 214 | $(HELP_FILES) \ 215 | "$$lc.stamp" \ 216 | "$$lc.mo" \ 217 | ; do echo "/$$lc/$$x"; done; \ 218 | done; \ 219 | fi; \ 220 | if test "x$(gsettings_SCHEMAS)" = x; then :; else \ 221 | for x in \ 222 | $(gsettings_SCHEMAS:.xml=.valid) \ 223 | $(gsettings__enum_file) \ 224 | ; do echo "/$$x"; done; \ 225 | fi; \ 226 | if test "x$(appdata_XML)" = x; then :; else \ 227 | for x in \ 228 | $(appdata_XML:.xml=.valid) \ 229 | ; do echo "/$$x"; done; \ 230 | fi; \ 231 | if test "x$(appstream_XML)" = x; then :; else \ 232 | for x in \ 233 | $(appstream_XML:.xml=.valid) \ 234 | ; do echo "/$$x"; done; \ 235 | fi; \ 236 | if test -f $(srcdir)/po/Makefile.in.in; then \ 237 | for x in \ 238 | po/Makefile.in.in \ 239 | po/Makefile.in.in~ \ 240 | po/Makefile.in \ 241 | po/Makefile \ 242 | po/Makevars.template \ 243 | po/POTFILES \ 244 | po/Rules-quot \ 245 | po/stamp-it \ 246 | po/stamp-po \ 247 | po/.intltool-merge-cache \ 248 | "po/*.gmo" \ 249 | "po/*.header" \ 250 | "po/*.mo" \ 251 | "po/*.sed" \ 252 | "po/*.sin" \ 253 | po/$(GETTEXT_PACKAGE).pot \ 254 | intltool-extract.in \ 255 | intltool-merge.in \ 256 | intltool-update.in \ 257 | ; do echo "/$$x"; done; \ 258 | fi; \ 259 | if test -f $(srcdir)/configure; then \ 260 | for x in \ 261 | autom4te.cache \ 262 | configure \ 263 | config.h \ 264 | stamp-h1 \ 265 | libtool \ 266 | config.lt \ 267 | ; do echo "/$$x"; done; \ 268 | fi; \ 269 | if test "x$(DEJATOOL)" = x; then :; else \ 270 | for x in \ 271 | $(DEJATOOL) \ 272 | ; do echo "/$$x.sum"; echo "/$$x.log"; done; \ 273 | echo /site.exp; \ 274 | fi; \ 275 | if test "x$(am__dirstamp)" = x; then :; else \ 276 | echo "$(am__dirstamp)"; \ 277 | fi; \ 278 | if test "x$(LTCOMPILE)" = x -a "x$(LTCXXCOMPILE)" = x -a "x$(GTKDOC_RUN)" = x; then :; else \ 279 | for x in \ 280 | "*.lo" \ 281 | ".libs" "_libs" \ 282 | ; do echo "$$x"; done; \ 283 | fi; \ 284 | for x in \ 285 | .gitignore \ 286 | $(GITIGNOREFILES) \ 287 | $(CLEANFILES) \ 288 | $(PROGRAMS) $(check_PROGRAMS) $(EXTRA_PROGRAMS) \ 289 | $(LIBRARIES) $(check_LIBRARIES) $(EXTRA_LIBRARIES) \ 290 | $(LTLIBRARIES) $(check_LTLIBRARIES) $(EXTRA_LTLIBRARIES) \ 291 | so_locations \ 292 | $(MOSTLYCLEANFILES) \ 293 | $(TEST_LOGS) \ 294 | $(TEST_LOGS:.log=.trs) \ 295 | $(TEST_SUITE_LOG) \ 296 | $(TESTS:=.test) \ 297 | "*.gcda" \ 298 | "*.gcno" \ 299 | $(DISTCLEANFILES) \ 300 | $(am__CONFIG_DISTCLEAN_FILES) \ 301 | $(CONFIG_CLEAN_FILES) \ 302 | TAGS ID GTAGS GRTAGS GSYMS GPATH tags \ 303 | "*.tab.c" \ 304 | $(MAINTAINERCLEANFILES) \ 305 | $(BUILT_SOURCES) \ 306 | $(patsubst %.vala,%.c,$(filter %.vala,$(SOURCES))) \ 307 | $(filter %_vala.stamp,$(DIST_COMMON)) \ 308 | $(filter %.vapi,$(DIST_COMMON)) \ 309 | $(filter $(addprefix %,$(notdir $(patsubst %.vapi,%.h,$(filter %.vapi,$(DIST_COMMON))))),$(DIST_COMMON)) \ 310 | Makefile \ 311 | Makefile.in \ 312 | "*.orig" \ 313 | "*.rej" \ 314 | "*.bak" \ 315 | "*~" \ 316 | ".*.sw[nop]" \ 317 | ".dirstamp" \ 318 | ; do echo "/$$x"; done; \ 319 | for x in \ 320 | "*.$(OBJEXT)" \ 321 | $(DEPDIR) \ 322 | ; do echo "$$x"; done; \ 323 | } | \ 324 | sed "s@^/`echo "$(srcdir)" | sed 's/\(.\)/[\1]/g'`/@/@" | \ 325 | sed 's@/[.]/@/@g' | \ 326 | LC_ALL=C sort | uniq > $@.tmp && \ 327 | mv $@.tmp $@; 328 | 329 | all: $(srcdir)/.gitignore gitignore-recurse-maybe 330 | gitignore: $(srcdir)/.gitignore gitignore-recurse 331 | 332 | gitignore-recurse-maybe: 333 | @for subdir in $(DIST_SUBDIRS); do \ 334 | case " $(SUBDIRS) " in \ 335 | *" $$subdir "*) :;; \ 336 | *) test "$$subdir" = . -o -e "$$subdir/.git" || (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) gitignore || echo "Skipping $$subdir");; \ 337 | esac; \ 338 | done 339 | gitignore-recurse: 340 | @for subdir in $(DIST_SUBDIRS); do \ 341 | test "$$subdir" = . -o -e "$$subdir/.git" || (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) gitignore || echo "Skipping $$subdir"); \ 342 | done 343 | 344 | maintainer-clean: gitignore-clean 345 | gitignore-clean: 346 | -rm -f $(srcdir)/.gitignore 347 | 348 | .PHONY: gitignore-clean gitignore gitignore-recurse gitignore-recurse-maybe 349 | -------------------------------------------------------------------------------- /utils.c: -------------------------------------------------------------------------------- 1 | /* bubblewrap 2 | * Copyright (C) 2016 Alexander Larsson 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public 15 | * License along with this library. If not, see . 16 | * 17 | */ 18 | #include "config.h" 19 | 20 | #include "utils.h" 21 | #include 22 | #ifdef HAVE_SELINUX 23 | #include 24 | #endif 25 | 26 | void 27 | die_with_error (const char *format, ...) 28 | { 29 | va_list args; 30 | int errsv; 31 | 32 | errsv = errno; 33 | 34 | va_start (args, format); 35 | vfprintf (stderr, format, args); 36 | va_end (args); 37 | 38 | fprintf (stderr, ": %s\n", strerror (errsv)); 39 | 40 | exit (1); 41 | } 42 | 43 | void 44 | die (const char *format, ...) 45 | { 46 | va_list args; 47 | 48 | va_start (args, format); 49 | vfprintf (stderr, format, args); 50 | va_end (args); 51 | 52 | fprintf (stderr, "\n"); 53 | 54 | exit (1); 55 | } 56 | 57 | void 58 | die_unless_label_valid (const char *label) 59 | { 60 | #ifdef HAVE_SELINUX 61 | if (is_selinux_enabled () == 1) 62 | { 63 | if (security_check_context ((security_context_t) label) < 0) 64 | die_with_error ("invalid label %s", label); 65 | return; 66 | } 67 | #endif 68 | die ("labeling not supported on this system"); 69 | } 70 | 71 | void 72 | die_oom (void) 73 | { 74 | puts ("Out of memory"); 75 | exit (1); 76 | } 77 | 78 | void * 79 | xmalloc (size_t size) 80 | { 81 | void *res = malloc (size); 82 | 83 | if (res == NULL) 84 | die_oom (); 85 | return res; 86 | } 87 | 88 | void * 89 | xcalloc (size_t size) 90 | { 91 | void *res = calloc (1, size); 92 | 93 | if (res == NULL) 94 | die_oom (); 95 | return res; 96 | } 97 | 98 | void * 99 | xrealloc (void *ptr, size_t size) 100 | { 101 | void *res = realloc (ptr, size); 102 | 103 | if (size != 0 && res == NULL) 104 | die_oom (); 105 | return res; 106 | } 107 | 108 | char * 109 | xstrdup (const char *str) 110 | { 111 | char *res; 112 | 113 | assert (str != NULL); 114 | 115 | res = strdup (str); 116 | if (res == NULL) 117 | die_oom (); 118 | 119 | return res; 120 | } 121 | 122 | void 123 | strfreev (char **str_array) 124 | { 125 | if (str_array) 126 | { 127 | int i; 128 | 129 | for (i = 0; str_array[i] != NULL; i++) 130 | free (str_array[i]); 131 | 132 | free (str_array); 133 | } 134 | } 135 | 136 | /* Compares if str has a specific path prefix. This differs 137 | from a regular prefix in two ways. First of all there may 138 | be multiple slashes separating the path elements, and 139 | secondly, if a prefix is matched that has to be en entire 140 | path element. For instance /a/prefix matches /a/prefix/foo/bar, 141 | but not /a/prefixfoo/bar. */ 142 | bool 143 | has_path_prefix (const char *str, 144 | const char *prefix) 145 | { 146 | while (TRUE) 147 | { 148 | /* Skip consecutive slashes to reach next path 149 | element */ 150 | while (*str == '/') 151 | str++; 152 | while (*prefix == '/') 153 | prefix++; 154 | 155 | /* No more prefix path elements? Done! */ 156 | if (*prefix == 0) 157 | return TRUE; 158 | 159 | /* Compare path element */ 160 | while (*prefix != 0 && *prefix != '/') 161 | { 162 | if (*str != *prefix) 163 | return FALSE; 164 | str++; 165 | prefix++; 166 | } 167 | 168 | /* Matched prefix path element, 169 | must be entire str path element */ 170 | if (*str != '/' && *str != 0) 171 | return FALSE; 172 | } 173 | } 174 | 175 | bool 176 | has_prefix (const char *str, 177 | const char *prefix) 178 | { 179 | return strncmp (str, prefix, strlen (prefix)) == 0; 180 | } 181 | 182 | void 183 | xsetenv (const char *name, const char *value, int overwrite) 184 | { 185 | if (setenv (name, value, overwrite)) 186 | die ("setenv failed"); 187 | } 188 | 189 | void 190 | xunsetenv (const char *name) 191 | { 192 | if (unsetenv (name)) 193 | die ("unsetenv failed"); 194 | } 195 | 196 | char * 197 | strconcat (const char *s1, 198 | const char *s2) 199 | { 200 | size_t len = 0; 201 | char *res; 202 | 203 | if (s1) 204 | len += strlen (s1); 205 | if (s2) 206 | len += strlen (s2); 207 | 208 | res = xmalloc (len + 1); 209 | *res = 0; 210 | if (s1) 211 | strcat (res, s1); 212 | if (s2) 213 | strcat (res, s2); 214 | 215 | return res; 216 | } 217 | 218 | char * 219 | strconcat3 (const char *s1, 220 | const char *s2, 221 | const char *s3) 222 | { 223 | size_t len = 0; 224 | char *res; 225 | 226 | if (s1) 227 | len += strlen (s1); 228 | if (s2) 229 | len += strlen (s2); 230 | if (s3) 231 | len += strlen (s3); 232 | 233 | res = xmalloc (len + 1); 234 | *res = 0; 235 | if (s1) 236 | strcat (res, s1); 237 | if (s2) 238 | strcat (res, s2); 239 | if (s3) 240 | strcat (res, s3); 241 | 242 | return res; 243 | } 244 | 245 | char * 246 | xasprintf (const char *format, 247 | ...) 248 | { 249 | char *buffer = NULL; 250 | va_list args; 251 | 252 | va_start (args, format); 253 | if (vasprintf (&buffer, format, args) == -1) 254 | die_oom (); 255 | va_end (args); 256 | 257 | return buffer; 258 | } 259 | 260 | int 261 | fdwalk (int proc_fd, int (*cb)(void *data, 262 | int fd), void *data) 263 | { 264 | int open_max; 265 | int fd; 266 | int dfd; 267 | int res = 0; 268 | DIR *d; 269 | 270 | dfd = openat (proc_fd, "self/fd", O_DIRECTORY | O_RDONLY | O_NONBLOCK | O_CLOEXEC | O_NOCTTY); 271 | if (dfd == -1) 272 | return res; 273 | 274 | if ((d = fdopendir (dfd))) 275 | { 276 | struct dirent *de; 277 | 278 | while ((de = readdir (d))) 279 | { 280 | long l; 281 | char *e = NULL; 282 | 283 | if (de->d_name[0] == '.') 284 | continue; 285 | 286 | errno = 0; 287 | l = strtol (de->d_name, &e, 10); 288 | if (errno != 0 || !e || *e) 289 | continue; 290 | 291 | fd = (int) l; 292 | 293 | if ((long) fd != l) 294 | continue; 295 | 296 | if (fd == dirfd (d)) 297 | continue; 298 | 299 | if ((res = cb (data, fd)) != 0) 300 | break; 301 | } 302 | 303 | closedir (d); 304 | return res; 305 | } 306 | 307 | open_max = sysconf (_SC_OPEN_MAX); 308 | 309 | for (fd = 0; fd < open_max; fd++) 310 | if ((res = cb (data, fd)) != 0) 311 | break; 312 | 313 | return res; 314 | } 315 | 316 | /* Sets errno on error (!= 0), ENOSPC on short write */ 317 | int 318 | write_to_fd (int fd, 319 | const char *content, 320 | ssize_t len) 321 | { 322 | ssize_t res; 323 | 324 | while (len > 0) 325 | { 326 | res = write (fd, content, len); 327 | if (res < 0 && errno == EINTR) 328 | continue; 329 | if (res <= 0) 330 | { 331 | if (res == 0) /* Unexpected short write, should not happen when writing to a file */ 332 | errno = ENOSPC; 333 | return -1; 334 | } 335 | len -= res; 336 | content += res; 337 | } 338 | 339 | return 0; 340 | } 341 | 342 | /* Sets errno on error (!= 0), ENOSPC on short write */ 343 | int 344 | write_file_at (int dirfd, 345 | const char *path, 346 | const char *content) 347 | { 348 | int fd; 349 | bool res; 350 | int errsv; 351 | 352 | fd = openat (dirfd, path, O_RDWR | O_CLOEXEC, 0); 353 | if (fd == -1) 354 | return -1; 355 | 356 | res = 0; 357 | if (content) 358 | res = write_to_fd (fd, content, strlen (content)); 359 | 360 | errsv = errno; 361 | close (fd); 362 | errno = errsv; 363 | 364 | return res; 365 | } 366 | 367 | /* Sets errno on error (!= 0), ENOSPC on short write */ 368 | int 369 | create_file (const char *path, 370 | mode_t mode, 371 | const char *content) 372 | { 373 | int fd; 374 | int res; 375 | int errsv; 376 | 377 | fd = creat (path, mode); 378 | if (fd == -1) 379 | return -1; 380 | 381 | res = 0; 382 | if (content) 383 | res = write_to_fd (fd, content, strlen (content)); 384 | 385 | errsv = errno; 386 | close (fd); 387 | errno = errsv; 388 | 389 | return res; 390 | } 391 | 392 | int 393 | ensure_file (const char *path, 394 | mode_t mode) 395 | { 396 | struct stat buf; 397 | 398 | /* We check this ahead of time, otherwise 399 | the create file will fail in the read-only 400 | case with EROFD instead of EEXIST */ 401 | if (stat (path, &buf) == 0 && 402 | S_ISREG (buf.st_mode)) 403 | return 0; 404 | 405 | if (create_file (path, mode, NULL) != 0 && errno != EEXIST) 406 | return -1; 407 | 408 | return 0; 409 | } 410 | 411 | 412 | #define BUFSIZE 8192 413 | /* Sets errno on error (!= 0), ENOSPC on short write */ 414 | int 415 | copy_file_data (int sfd, 416 | int dfd) 417 | { 418 | char buffer[BUFSIZE]; 419 | ssize_t bytes_read; 420 | 421 | while (TRUE) 422 | { 423 | bytes_read = read (sfd, buffer, BUFSIZE); 424 | if (bytes_read == -1) 425 | { 426 | if (errno == EINTR) 427 | continue; 428 | 429 | return -1; 430 | } 431 | 432 | if (bytes_read == 0) 433 | break; 434 | 435 | if (write_to_fd (dfd, buffer, bytes_read) != 0) 436 | return -1; 437 | } 438 | 439 | return 0; 440 | } 441 | 442 | /* Sets errno on error (!= 0), ENOSPC on short write */ 443 | int 444 | copy_file (const char *src_path, 445 | const char *dst_path, 446 | mode_t mode) 447 | { 448 | int sfd; 449 | int dfd; 450 | int res; 451 | int errsv; 452 | 453 | sfd = open (src_path, O_CLOEXEC | O_RDONLY); 454 | if (sfd == -1) 455 | return -1; 456 | 457 | dfd = creat (dst_path, mode); 458 | if (dfd == -1) 459 | { 460 | errsv = errno; 461 | close (sfd); 462 | errno = errsv; 463 | return -1; 464 | } 465 | 466 | res = copy_file_data (sfd, dfd); 467 | 468 | errsv = errno; 469 | close (sfd); 470 | close (dfd); 471 | errno = errsv; 472 | 473 | return res; 474 | } 475 | 476 | /* Sets errno on error (== NULL), 477 | * Always ensures terminating zero */ 478 | char * 479 | load_file_data (int fd, 480 | size_t *size) 481 | { 482 | cleanup_free char *data = NULL; 483 | ssize_t data_read; 484 | ssize_t data_len; 485 | ssize_t res; 486 | int errsv; 487 | 488 | data_read = 0; 489 | data_len = 4080; 490 | data = xmalloc (data_len); 491 | 492 | do 493 | { 494 | if (data_len == data_read + 1) 495 | { 496 | data_len *= 2; 497 | data = xrealloc (data, data_len); 498 | } 499 | 500 | do 501 | res = read (fd, data + data_read, data_len - data_read - 1); 502 | while (res < 0 && errno == EINTR); 503 | 504 | if (res < 0) 505 | { 506 | errsv = errno; 507 | close (fd); 508 | errno = errsv; 509 | return NULL; 510 | } 511 | 512 | data_read += res; 513 | } 514 | while (res > 0); 515 | 516 | data[data_read] = 0; 517 | 518 | if (size) 519 | *size = (size_t) data_read; 520 | 521 | return steal_pointer (&data); 522 | } 523 | 524 | /* Sets errno on error (== NULL), 525 | * Always ensures terminating zero */ 526 | char * 527 | load_file_at (int dirfd, 528 | const char *path) 529 | { 530 | int fd; 531 | char *data; 532 | int errsv; 533 | 534 | fd = openat (dirfd, path, O_CLOEXEC | O_RDONLY); 535 | if (fd == -1) 536 | return NULL; 537 | 538 | data = load_file_data (fd, NULL); 539 | 540 | errsv = errno; 541 | close (fd); 542 | errno = errsv; 543 | 544 | return data; 545 | } 546 | 547 | /* Sets errno on error (< 0) */ 548 | int 549 | get_file_mode (const char *pathname) 550 | { 551 | struct stat buf; 552 | 553 | if (stat (pathname, &buf) != 0) 554 | return -1; 555 | 556 | return buf.st_mode & S_IFMT; 557 | } 558 | 559 | /* Sets errno on error (!= 0) */ 560 | int 561 | mkdir_with_parents (const char *pathname, 562 | int mode, 563 | bool create_last) 564 | { 565 | cleanup_free char *fn = NULL; 566 | char *p; 567 | struct stat buf; 568 | 569 | if (pathname == NULL || *pathname == '\0') 570 | { 571 | errno = EINVAL; 572 | return -1; 573 | } 574 | 575 | fn = xstrdup (pathname); 576 | 577 | p = fn; 578 | while (*p == '/') 579 | p++; 580 | 581 | do 582 | { 583 | while (*p && *p != '/') 584 | p++; 585 | 586 | if (!*p) 587 | p = NULL; 588 | else 589 | *p = '\0'; 590 | 591 | if (!create_last && p == NULL) 592 | break; 593 | 594 | if (stat (fn, &buf) != 0) 595 | { 596 | if (mkdir (fn, mode) == -1 && errno != EEXIST) 597 | return -1; 598 | } 599 | else if (!S_ISDIR (buf.st_mode)) 600 | { 601 | errno = ENOTDIR; 602 | return -1; 603 | } 604 | 605 | if (p) 606 | { 607 | *p++ = '/'; 608 | while (*p && *p == '/') 609 | p++; 610 | } 611 | } 612 | while (p); 613 | 614 | return 0; 615 | } 616 | 617 | int 618 | raw_clone (unsigned long flags, 619 | void *child_stack) 620 | { 621 | #if defined(__s390__) || defined(__CRIS__) 622 | /* On s390 and cris the order of the first and second arguments 623 | * of the raw clone() system call is reversed. */ 624 | return (int) syscall (__NR_clone, child_stack, flags); 625 | #else 626 | return (int) syscall (__NR_clone, flags, child_stack); 627 | #endif 628 | } 629 | 630 | int 631 | pivot_root (const char * new_root, const char * put_old) 632 | { 633 | #ifdef __NR_pivot_root 634 | return syscall (__NR_pivot_root, new_root, put_old); 635 | #else 636 | errno = ENOSYS; 637 | return -1; 638 | #endif 639 | } 640 | 641 | char * 642 | label_mount (const char *opt, const char *mount_label) 643 | { 644 | #ifdef HAVE_SELINUX 645 | if (mount_label) 646 | { 647 | if (opt) 648 | return xasprintf ("%s,context=\"%s\"", opt, mount_label); 649 | else 650 | return xasprintf ("context=\"%s\"", mount_label); 651 | } 652 | #endif 653 | if (opt) 654 | return xstrdup (opt); 655 | return NULL; 656 | } 657 | 658 | int 659 | label_create_file (const char *file_label) 660 | { 661 | #ifdef HAVE_SELINUX 662 | if (is_selinux_enabled () > 0 && file_label) 663 | return setfscreatecon ((security_context_t) file_label); 664 | #endif 665 | return 0; 666 | } 667 | 668 | int 669 | label_exec (const char *exec_label) 670 | { 671 | #ifdef HAVE_SELINUX 672 | if (is_selinux_enabled () > 0 && exec_label) 673 | return setexeccon ((security_context_t) exec_label); 674 | #endif 675 | return 0; 676 | } 677 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU LIBRARY GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1991 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | [This is the first released version of the library GPL. It is 10 | numbered 2 because it goes with version 2 of the ordinary GPL.] 11 | 12 | Preamble 13 | 14 | The licenses for most software are designed to take away your 15 | freedom to share and change it. By contrast, the GNU General Public 16 | Licenses are intended to guarantee your freedom to share and change 17 | free software--to make sure the software is free for all its users. 18 | 19 | This license, the Library General Public License, applies to some 20 | specially designated Free Software Foundation software, and to any 21 | other libraries whose authors decide to use it. You can use it for 22 | your libraries, too. 23 | 24 | When we speak of free software, we are referring to freedom, not 25 | price. Our General Public Licenses are designed to make sure that you 26 | have the freedom to distribute copies of free software (and charge for 27 | this service if you wish), that you receive source code or can get it 28 | if you want it, that you can change the software or use pieces of it 29 | in new free programs; and that you know you can do these things. 30 | 31 | To protect your rights, we need to make restrictions that forbid 32 | anyone to deny you these rights or to ask you to surrender the rights. 33 | These restrictions translate to certain responsibilities for you if 34 | you distribute copies of the library, or if you modify it. 35 | 36 | For example, if you distribute copies of the library, whether gratis 37 | or for a fee, you must give the recipients all the rights that we gave 38 | you. You must make sure that they, too, receive or can get the source 39 | code. If you link a program with the library, you must provide 40 | complete object files to the recipients so that they can relink them 41 | with the library, after making changes to the library and recompiling 42 | it. And you must show them these terms so they know their rights. 43 | 44 | Our method of protecting your rights has two steps: (1) copyright 45 | the library, and (2) offer you this license which gives you legal 46 | permission to copy, distribute and/or modify the library. 47 | 48 | Also, for each distributor's protection, we want to make certain 49 | that everyone understands that there is no warranty for this free 50 | library. If the library is modified by someone else and passed on, we 51 | want its recipients to know that what they have is not the original 52 | version, so that any problems introduced by others will not reflect on 53 | the original authors' reputations. 54 | 55 | Finally, any free program is threatened constantly by software 56 | patents. We wish to avoid the danger that companies distributing free 57 | software will individually obtain patent licenses, thus in effect 58 | transforming the program into proprietary software. To prevent this, 59 | we have made it clear that any patent must be licensed for everyone's 60 | free use or not licensed at all. 61 | 62 | Most GNU software, including some libraries, is covered by the ordinary 63 | GNU General Public License, which was designed for utility programs. This 64 | license, the GNU Library General Public License, applies to certain 65 | designated libraries. This license is quite different from the ordinary 66 | one; be sure to read it in full, and don't assume that anything in it is 67 | the same as in the ordinary license. 68 | 69 | The reason we have a separate public license for some libraries is that 70 | they blur the distinction we usually make between modifying or adding to a 71 | program and simply using it. Linking a program with a library, without 72 | changing the library, is in some sense simply using the library, and is 73 | analogous to running a utility program or application program. However, in 74 | a textual and legal sense, the linked executable is a combined work, a 75 | derivative of the original library, and the ordinary General Public License 76 | treats it as such. 77 | 78 | Because of this blurred distinction, using the ordinary General 79 | Public License for libraries did not effectively promote software 80 | sharing, because most developers did not use the libraries. We 81 | concluded that weaker conditions might promote sharing better. 82 | 83 | However, unrestricted linking of non-free programs would deprive the 84 | users of those programs of all benefit from the free status of the 85 | libraries themselves. This Library General Public License is intended to 86 | permit developers of non-free programs to use free libraries, while 87 | preserving your freedom as a user of such programs to change the free 88 | libraries that are incorporated in them. (We have not seen how to achieve 89 | this as regards changes in header files, but we have achieved it as regards 90 | changes in the actual functions of the Library.) The hope is that this 91 | will lead to faster development of free libraries. 92 | 93 | The precise terms and conditions for copying, distribution and 94 | modification follow. Pay close attention to the difference between a 95 | "work based on the library" and a "work that uses the library". The 96 | former contains code derived from the library, while the latter only 97 | works together with the library. 98 | 99 | Note that it is possible for a library to be covered by the ordinary 100 | General Public License rather than by this special one. 101 | 102 | GNU LIBRARY GENERAL PUBLIC LICENSE 103 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 104 | 105 | 0. This License Agreement applies to any software library which 106 | contains a notice placed by the copyright holder or other authorized 107 | party saying it may be distributed under the terms of this Library 108 | General Public License (also called "this License"). Each licensee is 109 | addressed as "you". 110 | 111 | A "library" means a collection of software functions and/or data 112 | prepared so as to be conveniently linked with application programs 113 | (which use some of those functions and data) to form executables. 114 | 115 | The "Library", below, refers to any such software library or work 116 | which has been distributed under these terms. A "work based on the 117 | Library" means either the Library or any derivative work under 118 | copyright law: that is to say, a work containing the Library or a 119 | portion of it, either verbatim or with modifications and/or translated 120 | straightforwardly into another language. (Hereinafter, translation is 121 | included without limitation in the term "modification".) 122 | 123 | "Source code" for a work means the preferred form of the work for 124 | making modifications to it. For a library, complete source code means 125 | all the source code for all modules it contains, plus any associated 126 | interface definition files, plus the scripts used to control compilation 127 | and installation of the library. 128 | 129 | Activities other than copying, distribution and modification are not 130 | covered by this License; they are outside its scope. The act of 131 | running a program using the Library is not restricted, and output from 132 | such a program is covered only if its contents constitute a work based 133 | on the Library (independent of the use of the Library in a tool for 134 | writing it). Whether that is true depends on what the Library does 135 | and what the program that uses the Library does. 136 | 137 | 1. You may copy and distribute verbatim copies of the Library's 138 | complete source code as you receive it, in any medium, provided that 139 | you conspicuously and appropriately publish on each copy an 140 | appropriate copyright notice and disclaimer of warranty; keep intact 141 | all the notices that refer to this License and to the absence of any 142 | warranty; and distribute a copy of this License along with the 143 | Library. 144 | 145 | You may charge a fee for the physical act of transferring a copy, 146 | and you may at your option offer warranty protection in exchange for a 147 | fee. 148 | 149 | 2. You may modify your copy or copies of the Library or any portion 150 | of it, thus forming a work based on the Library, and copy and 151 | distribute such modifications or work under the terms of Section 1 152 | above, provided that you also meet all of these conditions: 153 | 154 | a) The modified work must itself be a software library. 155 | 156 | b) You must cause the files modified to carry prominent notices 157 | stating that you changed the files and the date of any change. 158 | 159 | c) You must cause the whole of the work to be licensed at no 160 | charge to all third parties under the terms of this License. 161 | 162 | d) If a facility in the modified Library refers to a function or a 163 | table of data to be supplied by an application program that uses 164 | the facility, other than as an argument passed when the facility 165 | is invoked, then you must make a good faith effort to ensure that, 166 | in the event an application does not supply such function or 167 | table, the facility still operates, and performs whatever part of 168 | its purpose remains meaningful. 169 | 170 | (For example, a function in a library to compute square roots has 171 | a purpose that is entirely well-defined independent of the 172 | application. Therefore, Subsection 2d requires that any 173 | application-supplied function or table used by this function must 174 | be optional: if the application does not supply it, the square 175 | root function must still compute square roots.) 176 | 177 | These requirements apply to the modified work as a whole. If 178 | identifiable sections of that work are not derived from the Library, 179 | and can be reasonably considered independent and separate works in 180 | themselves, then this License, and its terms, do not apply to those 181 | sections when you distribute them as separate works. But when you 182 | distribute the same sections as part of a whole which is a work based 183 | on the Library, the distribution of the whole must be on the terms of 184 | this License, whose permissions for other licensees extend to the 185 | entire whole, and thus to each and every part regardless of who wrote 186 | it. 187 | 188 | Thus, it is not the intent of this section to claim rights or contest 189 | your rights to work written entirely by you; rather, the intent is to 190 | exercise the right to control the distribution of derivative or 191 | collective works based on the Library. 192 | 193 | In addition, mere aggregation of another work not based on the Library 194 | with the Library (or with a work based on the Library) on a volume of 195 | a storage or distribution medium does not bring the other work under 196 | the scope of this License. 197 | 198 | 3. You may opt to apply the terms of the ordinary GNU General Public 199 | License instead of this License to a given copy of the Library. To do 200 | this, you must alter all the notices that refer to this License, so 201 | that they refer to the ordinary GNU General Public License, version 2, 202 | instead of to this License. (If a newer version than version 2 of the 203 | ordinary GNU General Public License has appeared, then you can specify 204 | that version instead if you wish.) Do not make any other change in 205 | these notices. 206 | 207 | Once this change is made in a given copy, it is irreversible for 208 | that copy, so the ordinary GNU General Public License applies to all 209 | subsequent copies and derivative works made from that copy. 210 | 211 | This option is useful when you wish to copy part of the code of 212 | the Library into a program that is not a library. 213 | 214 | 4. You may copy and distribute the Library (or a portion or 215 | derivative of it, under Section 2) in object code or executable form 216 | under the terms of Sections 1 and 2 above provided that you accompany 217 | it with the complete corresponding machine-readable source code, which 218 | must be distributed under the terms of Sections 1 and 2 above on a 219 | medium customarily used for software interchange. 220 | 221 | If distribution of object code is made by offering access to copy 222 | from a designated place, then offering equivalent access to copy the 223 | source code from the same place satisfies the requirement to 224 | distribute the source code, even though third parties are not 225 | compelled to copy the source along with the object code. 226 | 227 | 5. A program that contains no derivative of any portion of the 228 | Library, but is designed to work with the Library by being compiled or 229 | linked with it, is called a "work that uses the Library". Such a 230 | work, in isolation, is not a derivative work of the Library, and 231 | therefore falls outside the scope of this License. 232 | 233 | However, linking a "work that uses the Library" with the Library 234 | creates an executable that is a derivative of the Library (because it 235 | contains portions of the Library), rather than a "work that uses the 236 | library". The executable is therefore covered by this License. 237 | Section 6 states terms for distribution of such executables. 238 | 239 | When a "work that uses the Library" uses material from a header file 240 | that is part of the Library, the object code for the work may be a 241 | derivative work of the Library even though the source code is not. 242 | Whether this is true is especially significant if the work can be 243 | linked without the Library, or if the work is itself a library. The 244 | threshold for this to be true is not precisely defined by law. 245 | 246 | If such an object file uses only numerical parameters, data 247 | structure layouts and accessors, and small macros and small inline 248 | functions (ten lines or less in length), then the use of the object 249 | file is unrestricted, regardless of whether it is legally a derivative 250 | work. (Executables containing this object code plus portions of the 251 | Library will still fall under Section 6.) 252 | 253 | Otherwise, if the work is a derivative of the Library, you may 254 | distribute the object code for the work under the terms of Section 6. 255 | Any executables containing that work also fall under Section 6, 256 | whether or not they are linked directly with the Library itself. 257 | 258 | 6. As an exception to the Sections above, you may also compile or 259 | link a "work that uses the Library" with the Library to produce a 260 | work containing portions of the Library, and distribute that work 261 | under terms of your choice, provided that the terms permit 262 | modification of the work for the customer's own use and reverse 263 | engineering for debugging such modifications. 264 | 265 | You must give prominent notice with each copy of the work that the 266 | Library is used in it and that the Library and its use are covered by 267 | this License. You must supply a copy of this License. If the work 268 | during execution displays copyright notices, you must include the 269 | copyright notice for the Library among them, as well as a reference 270 | directing the user to the copy of this License. Also, you must do one 271 | of these things: 272 | 273 | a) Accompany the work with the complete corresponding 274 | machine-readable source code for the Library including whatever 275 | changes were used in the work (which must be distributed under 276 | Sections 1 and 2 above); and, if the work is an executable linked 277 | with the Library, with the complete machine-readable "work that 278 | uses the Library", as object code and/or source code, so that the 279 | user can modify the Library and then relink to produce a modified 280 | executable containing the modified Library. (It is understood 281 | that the user who changes the contents of definitions files in the 282 | Library will not necessarily be able to recompile the application 283 | to use the modified definitions.) 284 | 285 | b) Accompany the work with a written offer, valid for at 286 | least three years, to give the same user the materials 287 | specified in Subsection 6a, above, for a charge no more 288 | than the cost of performing this distribution. 289 | 290 | c) If distribution of the work is made by offering access to copy 291 | from a designated place, offer equivalent access to copy the above 292 | specified materials from the same place. 293 | 294 | d) Verify that the user has already received a copy of these 295 | materials or that you have already sent this user a copy. 296 | 297 | For an executable, the required form of the "work that uses the 298 | Library" must include any data and utility programs needed for 299 | reproducing the executable from it. However, as a special exception, 300 | the source code distributed need not include anything that is normally 301 | distributed (in either source or binary form) with the major 302 | components (compiler, kernel, and so on) of the operating system on 303 | which the executable runs, unless that component itself accompanies 304 | the executable. 305 | 306 | It may happen that this requirement contradicts the license 307 | restrictions of other proprietary libraries that do not normally 308 | accompany the operating system. Such a contradiction means you cannot 309 | use both them and the Library together in an executable that you 310 | distribute. 311 | 312 | 7. You may place library facilities that are a work based on the 313 | Library side-by-side in a single library together with other library 314 | facilities not covered by this License, and distribute such a combined 315 | library, provided that the separate distribution of the work based on 316 | the Library and of the other library facilities is otherwise 317 | permitted, and provided that you do these two things: 318 | 319 | a) Accompany the combined library with a copy of the same work 320 | based on the Library, uncombined with any other library 321 | facilities. This must be distributed under the terms of the 322 | Sections above. 323 | 324 | b) Give prominent notice with the combined library of the fact 325 | that part of it is a work based on the Library, and explaining 326 | where to find the accompanying uncombined form of the same work. 327 | 328 | 8. You may not copy, modify, sublicense, link with, or distribute 329 | the Library except as expressly provided under this License. Any 330 | attempt otherwise to copy, modify, sublicense, link with, or 331 | distribute the Library is void, and will automatically terminate your 332 | rights under this License. However, parties who have received copies, 333 | or rights, from you under this License will not have their licenses 334 | terminated so long as such parties remain in full compliance. 335 | 336 | 9. You are not required to accept this License, since you have not 337 | signed it. However, nothing else grants you permission to modify or 338 | distribute the Library or its derivative works. These actions are 339 | prohibited by law if you do not accept this License. Therefore, by 340 | modifying or distributing the Library (or any work based on the 341 | Library), you indicate your acceptance of this License to do so, and 342 | all its terms and conditions for copying, distributing or modifying 343 | the Library or works based on it. 344 | 345 | 10. Each time you redistribute the Library (or any work based on the 346 | Library), the recipient automatically receives a license from the 347 | original licensor to copy, distribute, link with or modify the Library 348 | subject to these terms and conditions. You may not impose any further 349 | restrictions on the recipients' exercise of the rights granted herein. 350 | You are not responsible for enforcing compliance by third parties to 351 | this License. 352 | 353 | 11. If, as a consequence of a court judgment or allegation of patent 354 | infringement or for any other reason (not limited to patent issues), 355 | conditions are imposed on you (whether by court order, agreement or 356 | otherwise) that contradict the conditions of this License, they do not 357 | excuse you from the conditions of this License. If you cannot 358 | distribute so as to satisfy simultaneously your obligations under this 359 | License and any other pertinent obligations, then as a consequence you 360 | may not distribute the Library at all. For example, if a patent 361 | license would not permit royalty-free redistribution of the Library by 362 | all those who receive copies directly or indirectly through you, then 363 | the only way you could satisfy both it and this License would be to 364 | refrain entirely from distribution of the Library. 365 | 366 | If any portion of this section is held invalid or unenforceable under any 367 | particular circumstance, the balance of the section is intended to apply, 368 | and the section as a whole is intended to apply in other circumstances. 369 | 370 | It is not the purpose of this section to induce you to infringe any 371 | patents or other property right claims or to contest validity of any 372 | such claims; this section has the sole purpose of protecting the 373 | integrity of the free software distribution system which is 374 | implemented by public license practices. Many people have made 375 | generous contributions to the wide range of software distributed 376 | through that system in reliance on consistent application of that 377 | system; it is up to the author/donor to decide if he or she is willing 378 | to distribute software through any other system and a licensee cannot 379 | impose that choice. 380 | 381 | This section is intended to make thoroughly clear what is believed to 382 | be a consequence of the rest of this License. 383 | 384 | 12. If the distribution and/or use of the Library is restricted in 385 | certain countries either by patents or by copyrighted interfaces, the 386 | original copyright holder who places the Library under this License may add 387 | an explicit geographical distribution limitation excluding those countries, 388 | so that distribution is permitted only in or among countries not thus 389 | excluded. In such case, this License incorporates the limitation as if 390 | written in the body of this License. 391 | 392 | 13. The Free Software Foundation may publish revised and/or new 393 | versions of the Library General Public License from time to time. 394 | Such new versions will be similar in spirit to the present version, 395 | but may differ in detail to address new problems or concerns. 396 | 397 | Each version is given a distinguishing version number. If the Library 398 | specifies a version number of this License which applies to it and 399 | "any later version", you have the option of following the terms and 400 | conditions either of that version or of any later version published by 401 | the Free Software Foundation. If the Library does not specify a 402 | license version number, you may choose any version ever published by 403 | the Free Software Foundation. 404 | 405 | 14. If you wish to incorporate parts of the Library into other free 406 | programs whose distribution conditions are incompatible with these, 407 | write to the author to ask for permission. For software which is 408 | copyrighted by the Free Software Foundation, write to the Free 409 | Software Foundation; we sometimes make exceptions for this. Our 410 | decision will be guided by the two goals of preserving the free status 411 | of all derivatives of our free software and of promoting the sharing 412 | and reuse of software generally. 413 | 414 | NO WARRANTY 415 | 416 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 417 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 418 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 419 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 420 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 421 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 422 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 423 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 424 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 425 | 426 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 427 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 428 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 429 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 430 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 431 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 432 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 433 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 434 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 435 | DAMAGES. 436 | 437 | END OF TERMS AND CONDITIONS 438 | 439 | How to Apply These Terms to Your New Libraries 440 | 441 | If you develop a new library, and you want it to be of the greatest 442 | possible use to the public, we recommend making it free software that 443 | everyone can redistribute and change. You can do so by permitting 444 | redistribution under these terms (or, alternatively, under the terms of the 445 | ordinary General Public License). 446 | 447 | To apply these terms, attach the following notices to the library. It is 448 | safest to attach them to the start of each source file to most effectively 449 | convey the exclusion of warranty; and each file should have at least the 450 | "copyright" line and a pointer to where the full notice is found. 451 | 452 | 453 | Copyright (C) 454 | 455 | This library is free software; you can redistribute it and/or 456 | modify it under the terms of the GNU Library General Public 457 | License as published by the Free Software Foundation; either 458 | version 2 of the License, or (at your option) any later version. 459 | 460 | This library is distributed in the hope that it will be useful, 461 | but WITHOUT ANY WARRANTY; without even the implied warranty of 462 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 463 | Library General Public License for more details. 464 | 465 | You should have received a copy of the GNU Library General Public 466 | License along with this library; if not, write to the Free Software 467 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 468 | 469 | Also add information on how to contact you by electronic and paper mail. 470 | 471 | You should also get your employer (if you work as a programmer) or your 472 | school, if any, to sign a "copyright disclaimer" for the library, if 473 | necessary. Here is a sample; alter the names: 474 | 475 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 476 | library `Frob' (a library for tweaking knobs) written by James Random Hacker. 477 | 478 | , 1 April 1990 479 | Ty Coon, President of Vice 480 | 481 | That's all there is to it! 482 | -------------------------------------------------------------------------------- /bubblewrap.c: -------------------------------------------------------------------------------- 1 | /* bubblewrap 2 | * Copyright (C) 2016 Alexander Larsson 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public 15 | * License along with this library. If not, see . 16 | * 17 | */ 18 | 19 | #include "config.h" 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #include "utils.h" 37 | #include "network.h" 38 | #include "bind-mount.h" 39 | 40 | #ifndef CLONE_NEWCGROUP 41 | #define CLONE_NEWCGROUP 0x02000000 /* New cgroup namespace */ 42 | #endif 43 | 44 | /* Globals to avoid having to use getuid(), since the uid/gid changes during runtime */ 45 | static uid_t uid; 46 | static gid_t gid; 47 | static bool is_privileged; 48 | static const char *argv0; 49 | static const char *host_tty_dev; 50 | static int proc_fd = -1; 51 | static char *opt_exec_label = NULL; 52 | static char *opt_file_label = NULL; 53 | 54 | typedef enum { 55 | SETUP_BIND_MOUNT, 56 | SETUP_RO_BIND_MOUNT, 57 | SETUP_DEV_BIND_MOUNT, 58 | SETUP_MOUNT_PROC, 59 | SETUP_MOUNT_DEV, 60 | SETUP_MOUNT_TMPFS, 61 | SETUP_MOUNT_MQUEUE, 62 | SETUP_MAKE_DIR, 63 | SETUP_MAKE_FILE, 64 | SETUP_MAKE_BIND_FILE, 65 | SETUP_MAKE_SYMLINK, 66 | } SetupOpType; 67 | 68 | typedef struct _SetupOp SetupOp; 69 | 70 | struct _SetupOp 71 | { 72 | SetupOpType type; 73 | const char *source; 74 | const char *dest; 75 | int fd; 76 | SetupOp *next; 77 | }; 78 | 79 | typedef struct _LockFile LockFile; 80 | 81 | struct _LockFile 82 | { 83 | const char *path; 84 | LockFile *next; 85 | }; 86 | 87 | static SetupOp *ops = NULL; 88 | static SetupOp *last_op = NULL; 89 | static LockFile *lock_files = NULL; 90 | static LockFile *last_lock_file = NULL; 91 | 92 | enum { 93 | PRIV_SEP_OP_DONE, 94 | PRIV_SEP_OP_BIND_MOUNT, 95 | PRIV_SEP_OP_PROC_MOUNT, 96 | PRIV_SEP_OP_TMPFS_MOUNT, 97 | PRIV_SEP_OP_DEVPTS_MOUNT, 98 | PRIV_SEP_OP_MQUEUE_MOUNT, 99 | }; 100 | 101 | typedef struct 102 | { 103 | uint32_t op; 104 | uint32_t flags; 105 | uint32_t arg1_offset; 106 | uint32_t arg2_offset; 107 | } PrivSepOp; 108 | 109 | static SetupOp * 110 | setup_op_new (SetupOpType type) 111 | { 112 | SetupOp *op = xcalloc (sizeof (SetupOp)); 113 | 114 | op->type = type; 115 | op->fd = -1; 116 | if (last_op != NULL) 117 | last_op->next = op; 118 | else 119 | ops = op; 120 | 121 | last_op = op; 122 | return op; 123 | } 124 | 125 | static LockFile * 126 | lock_file_new (const char *path) 127 | { 128 | LockFile *lock = xcalloc (sizeof (LockFile)); 129 | 130 | lock->path = path; 131 | if (last_lock_file != NULL) 132 | last_lock_file->next = lock; 133 | else 134 | lock_files = lock; 135 | 136 | last_lock_file = lock; 137 | return lock; 138 | } 139 | 140 | 141 | static void 142 | usage (int ecode, FILE *out) 143 | { 144 | fprintf (out, "usage: %s [OPTIONS...] COMMAND [ARGS...]\n\n", argv0); 145 | 146 | fprintf (out, 147 | " --help Print this help\n" 148 | " --version Print version\n" 149 | " --args FD Parse nul-separated args from FD\n" 150 | " --unshare-user Create new user namespace (may be automatically implied if not setuid)\n" 151 | " --unshare-user-try Create new user namespace if possible else continue by skipping it\n" 152 | " --unshare-ipc Create new ipc namespace\n" 153 | " --unshare-pid Create new pid namespace\n" 154 | " --unshare-net Create new network namespace\n" 155 | " --unshare-uts Create new uts namespace\n" 156 | " --unshare-cgroup Create new cgroup namespace\n" 157 | " --unshare-cgroup-try Create new cgroup namespace if possible else continue by skipping it\n" 158 | " --uid UID Custom uid in the sandbox (requires --unshare-user)\n" 159 | " --gid GID Custon gid in the sandbox (requires --unshare-user)\n" 160 | " --chdir DIR Change directory to DIR\n" 161 | " --setenv VAR VALUE Set an environment variable\n" 162 | " --unsetenv VAR Unset an environment variable\n" 163 | " --lock-file DEST Take a lock on DEST while sandbox is running\n" 164 | " --sync-fd FD Keep this fd open while sandbox is running\n" 165 | " --bind SRC DEST Bind mount the host path SRC on DEST\n" 166 | " --dev-bind SRC DEST Bind mount the host path SRC on DEST, allowing device access\n" 167 | " --ro-bind SRC DEST Bind mount the host path SRC readonly on DEST\n" 168 | " --exec-label LABEL Exec Label for the sandbox\n" 169 | " --file-label LABEL File label for temporary sandbox content\n" 170 | " --proc DEST Mount procfs on DEST\n" 171 | " --dev DEST Mount new dev on DEST\n" 172 | " --tmpfs DEST Mount new tmpfs on DEST\n" 173 | " --mqueue DEST Mount new mqueue on DEST\n" 174 | " --dir DEST Create dir at DEST\n" 175 | " --file FD DEST Copy from FD to dest DEST\n" 176 | " --bind-data FD DEST Copy from FD to file which is bind-mounted on DEST\n" 177 | " --symlink SRC DEST Create symlink at DEST with target SRC\n" 178 | " --seccomp FD Load and use seccomp rules from FD\n" 179 | ); 180 | exit (ecode); 181 | } 182 | 183 | static void 184 | block_sigchild (void) 185 | { 186 | sigset_t mask; 187 | 188 | sigemptyset (&mask); 189 | sigaddset (&mask, SIGCHLD); 190 | 191 | if (sigprocmask (SIG_BLOCK, &mask, NULL) == -1) 192 | die_with_error ("sigprocmask"); 193 | } 194 | 195 | static void 196 | unblock_sigchild (void) 197 | { 198 | sigset_t mask; 199 | 200 | sigemptyset (&mask); 201 | sigaddset (&mask, SIGCHLD); 202 | 203 | if (sigprocmask (SIG_UNBLOCK, &mask, NULL) == -1) 204 | die_with_error ("sigprocmask"); 205 | } 206 | 207 | /* Closes all fd:s except 0,1,2 and the passed in array of extra fds */ 208 | static int 209 | close_extra_fds (void *data, int fd) 210 | { 211 | int *extra_fds = (int *) data; 212 | int i; 213 | 214 | for (i = 0; extra_fds[i] != -1; i++) 215 | if (fd == extra_fds[i]) 216 | return 0; 217 | 218 | if (fd <= 2) 219 | return 0; 220 | 221 | close (fd); 222 | return 0; 223 | } 224 | 225 | /* This stays around for as long as the initial process in the app does 226 | * and when that exits it exits, propagating the exit status. We do this 227 | * by having pid 1 in the sandbox detect this exit and tell the monitor 228 | * the exit status via a eventfd. We also track the exit of the sandbox 229 | * pid 1 via a signalfd for SIGCHLD, and exit with an error in this case. 230 | * This is to catch e.g. problems during setup. */ 231 | static void 232 | monitor_child (int event_fd) 233 | { 234 | int res; 235 | uint64_t val; 236 | ssize_t s; 237 | int signal_fd; 238 | sigset_t mask; 239 | struct pollfd fds[2]; 240 | int num_fds; 241 | struct signalfd_siginfo fdsi; 242 | int dont_close[] = { event_fd, -1 }; 243 | 244 | /* Close all extra fds in the monitoring process. 245 | Any passed in fds have been passed on to the child anyway. */ 246 | fdwalk (proc_fd, close_extra_fds, dont_close); 247 | 248 | sigemptyset (&mask); 249 | sigaddset (&mask, SIGCHLD); 250 | 251 | signal_fd = signalfd (-1, &mask, SFD_CLOEXEC | SFD_NONBLOCK); 252 | if (signal_fd == -1) 253 | die_with_error ("Can't create signalfd"); 254 | 255 | num_fds = 1; 256 | fds[0].fd = signal_fd; 257 | fds[0].events = POLLIN; 258 | if (event_fd != -1) 259 | { 260 | fds[1].fd = event_fd; 261 | fds[1].events = POLLIN; 262 | num_fds++; 263 | } 264 | 265 | while (1) 266 | { 267 | fds[0].revents = fds[1].revents = 0; 268 | res = poll (fds, num_fds, -1); 269 | if (res == -1 && errno != EINTR) 270 | die_with_error ("poll"); 271 | 272 | /* Always read from the eventfd first, if pid 2 died then pid 1 often 273 | * dies too, and we could race, reporting that first and we'd lose 274 | * the real exit status. */ 275 | if (event_fd != -1) 276 | { 277 | s = read (event_fd, &val, 8); 278 | if (s == -1 && errno != EINTR && errno != EAGAIN) 279 | die_with_error ("read eventfd"); 280 | else if (s == 8) 281 | exit ((int) val - 1); 282 | } 283 | 284 | s = read (signal_fd, &fdsi, sizeof (struct signalfd_siginfo)); 285 | if (s == -1 && errno != EINTR && errno != EAGAIN) 286 | { 287 | die_with_error ("read signalfd"); 288 | } 289 | else if (s == sizeof (struct signalfd_siginfo)) 290 | { 291 | if (fdsi.ssi_signo != SIGCHLD) 292 | die ("Read unexpected signal\n"); 293 | exit (fdsi.ssi_status); 294 | } 295 | } 296 | } 297 | 298 | /* This is pid 1 in the app sandbox. It is needed because we're using 299 | * pid namespaces, and someone has to reap zombies in it. We also detect 300 | * when the initial process (pid 2) dies and report its exit status to 301 | * the monitor so that it can return it to the original spawner. 302 | * 303 | * When there are no other processes in the sandbox the wait will return 304 | * ECHILD, and we then exit pid 1 to clean up the sandbox. */ 305 | static int 306 | do_init (int event_fd, pid_t initial_pid) 307 | { 308 | int initial_exit_status = 1; 309 | LockFile *lock; 310 | 311 | for (lock = lock_files; lock != NULL; lock = lock->next) 312 | { 313 | int fd = open (lock->path, O_RDONLY | O_CLOEXEC); 314 | if (fd == -1) 315 | die_with_error ("Unable to open lock file %s", lock->path); 316 | 317 | struct flock l = { 318 | .l_type = F_RDLCK, 319 | .l_whence = SEEK_SET, 320 | .l_start = 0, 321 | .l_len = 0 322 | }; 323 | 324 | if (fcntl (fd, F_SETLK, &l) < 0) 325 | die_with_error ("Unable to lock file %s", lock->path); 326 | 327 | /* Keep fd open to hang on to lock */ 328 | } 329 | 330 | while (TRUE) 331 | { 332 | pid_t child; 333 | int status; 334 | 335 | child = wait (&status); 336 | if (child == initial_pid && event_fd != -1) 337 | { 338 | uint64_t val; 339 | int res UNUSED; 340 | 341 | if (WIFEXITED (status)) 342 | initial_exit_status = WEXITSTATUS (status); 343 | 344 | val = initial_exit_status + 1; 345 | res = write (event_fd, &val, 8); 346 | /* Ignore res, if e.g. the parent died and closed event_fd 347 | we don't want to error out here */ 348 | } 349 | 350 | if (child == -1 && errno != EINTR) 351 | { 352 | if (errno != ECHILD) 353 | die_with_error ("init wait()"); 354 | break; 355 | } 356 | } 357 | 358 | return initial_exit_status; 359 | } 360 | 361 | /* low 32bit caps needed */ 362 | #define REQUIRED_CAPS_0 (CAP_TO_MASK (CAP_SYS_ADMIN) | CAP_TO_MASK (CAP_SYS_CHROOT) | CAP_TO_MASK (CAP_NET_ADMIN) | CAP_TO_MASK (CAP_SETUID) | CAP_TO_MASK (CAP_SETGID)) 363 | /* high 32bit caps needed */ 364 | #define REQUIRED_CAPS_1 0 365 | 366 | static void 367 | acquire_caps (void) 368 | { 369 | struct __user_cap_header_struct hdr = { _LINUX_CAPABILITY_VERSION_3, 0 }; 370 | struct __user_cap_data_struct data[2] = { { 0 } }; 371 | 372 | if (capget (&hdr, data) < 0) 373 | die_with_error ("capget failed"); 374 | 375 | if (((data[0].effective & REQUIRED_CAPS_0) == REQUIRED_CAPS_0) && 376 | ((data[0].permitted & REQUIRED_CAPS_0) == REQUIRED_CAPS_0) && 377 | ((data[1].effective & REQUIRED_CAPS_1) == REQUIRED_CAPS_1) && 378 | ((data[1].permitted & REQUIRED_CAPS_1) == REQUIRED_CAPS_1)) 379 | is_privileged = TRUE; 380 | 381 | if (getuid () != geteuid ()) 382 | { 383 | /* Tell kernel not clear capabilities when dropping root */ 384 | if (prctl (PR_SET_KEEPCAPS, 1, 0, 0, 0) < 0) 385 | die_with_error ("prctl(PR_SET_KEEPCAPS) failed"); 386 | 387 | /* Drop root uid, but retain the required permitted caps */ 388 | if (setuid (getuid ()) < 0) 389 | die_with_error ("unable to drop privs"); 390 | } 391 | 392 | if (is_privileged) 393 | { 394 | /* Drop all non-require capabilities */ 395 | data[0].effective = REQUIRED_CAPS_0; 396 | data[0].permitted = REQUIRED_CAPS_0; 397 | data[0].inheritable = 0; 398 | data[1].effective = REQUIRED_CAPS_1; 399 | data[1].permitted = REQUIRED_CAPS_1; 400 | data[1].inheritable = 0; 401 | if (capset (&hdr, data) < 0) 402 | die_with_error ("capset failed"); 403 | } 404 | /* Else, we try unprivileged user namespaces */ 405 | 406 | /* We need the process to be dumpable, or we can't access /proc/self/uid_map */ 407 | if (prctl (PR_SET_DUMPABLE, 1, 0, 0, 0) < 0) 408 | die_with_error ("prctl(PR_SET_DUMPABLE) failed"); 409 | } 410 | 411 | static void 412 | drop_caps (void) 413 | { 414 | struct __user_cap_header_struct hdr = { _LINUX_CAPABILITY_VERSION_3, 0 }; 415 | struct __user_cap_data_struct data[2] = { { 0 } }; 416 | 417 | if (!is_privileged) 418 | return; 419 | 420 | if (capset (&hdr, data) < 0) 421 | die_with_error ("capset failed"); 422 | } 423 | 424 | static char * 425 | get_newroot_path (const char *path) 426 | { 427 | while (*path == '/') 428 | path++; 429 | return strconcat ("/newroot/", path); 430 | } 431 | 432 | static char * 433 | get_oldroot_path (const char *path) 434 | { 435 | while (*path == '/') 436 | path++; 437 | return strconcat ("/oldroot/", path); 438 | } 439 | 440 | static void 441 | write_uid_gid_map (uid_t sandbox_uid, 442 | uid_t parent_uid, 443 | uid_t sandbox_gid, 444 | uid_t parent_gid, 445 | pid_t pid, 446 | bool deny_groups, 447 | bool map_root) 448 | { 449 | cleanup_free char *uid_map = NULL; 450 | cleanup_free char *gid_map = NULL; 451 | cleanup_free char *dir = NULL; 452 | cleanup_fd int dir_fd = -1; 453 | 454 | if (pid == -1) 455 | dir = xstrdup ("self"); 456 | else 457 | dir = xasprintf ("%d", pid); 458 | 459 | dir_fd = openat (proc_fd, dir, O_RDONLY | O_PATH); 460 | if (dir_fd < 0) 461 | die_with_error ("open /proc/%s failed", dir); 462 | 463 | if (map_root && parent_uid != 0 && sandbox_uid != 0) 464 | uid_map = xasprintf ("0 0 1\n" 465 | "%d %d 1\n", sandbox_uid, parent_uid); 466 | else 467 | uid_map = xasprintf ("%d %d 1\n", sandbox_uid, parent_uid); 468 | 469 | if (write_file_at (dir_fd, "uid_map", uid_map) != 0) 470 | die_with_error ("setting up uid map"); 471 | 472 | if (deny_groups && 473 | write_file_at (dir_fd, "setgroups", "deny\n") != 0) 474 | { 475 | /* If /proc/[pid]/setgroups does not exist, assume we are 476 | * running a linux kernel < 3.19, i.e. we live with the 477 | * vulnerability known as CVE-2014-8989 in older kernels 478 | * where setgroups does not exist. 479 | */ 480 | if (errno != ENOENT) 481 | die_with_error ("error writing to setgroups"); 482 | } 483 | 484 | if (map_root && parent_gid != 0 && sandbox_gid != 0) 485 | gid_map = xasprintf ("0 0 1\n" 486 | "%d %d 1\n", sandbox_gid, parent_gid); 487 | else 488 | gid_map = xasprintf ("%d %d 1\n", sandbox_gid, parent_gid); 489 | 490 | if (write_file_at (dir_fd, "gid_map", gid_map) != 0) 491 | die_with_error ("setting up gid map"); 492 | } 493 | 494 | static void 495 | privileged_op (int privileged_op_socket, 496 | uint32_t op, 497 | uint32_t flags, 498 | const char *arg1, 499 | const char *arg2) 500 | { 501 | if (privileged_op_socket != -1) 502 | { 503 | uint32_t buffer[2048]; /* 8k, but is int32 to guarantee nice alignment */ 504 | PrivSepOp *op_buffer = (PrivSepOp *) buffer; 505 | size_t buffer_size = sizeof (PrivSepOp); 506 | uint32_t arg1_offset = 0, arg2_offset = 0; 507 | if (arg1 != NULL) 508 | { 509 | arg1_offset = buffer_size; 510 | buffer_size += strlen (arg1) + 1; 511 | } 512 | if (arg2 != NULL) 513 | { 514 | arg2_offset = buffer_size; 515 | buffer_size += strlen (arg2) + 1; 516 | } 517 | 518 | if (buffer_size >= sizeof (buffer)) 519 | die ("privilege separation operation to large"); 520 | 521 | op_buffer->op = op; 522 | op_buffer->flags = flags; 523 | op_buffer->arg1_offset = arg1_offset; 524 | op_buffer->arg2_offset = arg2_offset; 525 | if (arg1 != NULL) 526 | strcpy ((char *) buffer + arg1_offset, arg1); 527 | if (arg2 != NULL) 528 | strcpy ((char *) buffer + arg2_offset, arg2); 529 | 530 | if (write (privileged_op_socket, buffer, buffer_size) != buffer_size) 531 | die ("Can't write to privileged_op_socket"); 532 | 533 | if (read (privileged_op_socket, buffer, 1) != 1) 534 | die ("Can't read from privileged_op_socket"); 535 | 536 | return; 537 | } 538 | 539 | switch (op) 540 | { 541 | case PRIV_SEP_OP_DONE: 542 | break; 543 | 544 | case PRIV_SEP_OP_BIND_MOUNT: 545 | /* We always bind directories recursively, otherwise this would let us 546 | access files that are otherwise covered on the host */ 547 | if (bind_mount (proc_fd, arg1, arg2, BIND_RECURSIVE | flags) != 0) 548 | die_with_error ("Can't bind mount %s on %s", arg1, arg2); 549 | break; 550 | 551 | case PRIV_SEP_OP_PROC_MOUNT: 552 | if (mount ("proc", arg1, "proc", MS_MGC_VAL | MS_NOSUID | MS_NOEXEC | MS_NODEV, NULL) != 0) 553 | die_with_error ("Can't mount proc on %s", arg1); 554 | break; 555 | 556 | case PRIV_SEP_OP_TMPFS_MOUNT: 557 | { 558 | cleanup_free char *opt = label_mount ("mode=0755", opt_file_label); 559 | if (mount ("tmpfs", arg1, "tmpfs", MS_MGC_VAL | MS_NOSUID | MS_NODEV, opt) != 0) 560 | die_with_error ("Can't mount tmpfs on %s", arg1); 561 | break; 562 | } 563 | 564 | case PRIV_SEP_OP_DEVPTS_MOUNT: 565 | if (mount ("devpts", arg1, "devpts", MS_MGC_VAL | MS_NOSUID | MS_NOEXEC, 566 | "newinstance,ptmxmode=0666,mode=620") != 0) 567 | die_with_error ("Can't mount devpts on %s", arg1); 568 | break; 569 | 570 | case PRIV_SEP_OP_MQUEUE_MOUNT: 571 | if (mount ("mqueue", arg1, "mqueue", 0, NULL) != 0) 572 | die_with_error ("Can't mount mqueue on %s", arg1); 573 | break; 574 | 575 | default: 576 | die ("Unexpected privileged op %d", op); 577 | } 578 | } 579 | 580 | static void 581 | setup_newroot (bool unshare_pid, 582 | int privileged_op_socket) 583 | { 584 | SetupOp *op; 585 | 586 | for (op = ops; op != NULL; op = op->next) 587 | { 588 | cleanup_free char *source = NULL; 589 | cleanup_free char *dest = NULL; 590 | int source_mode = 0; 591 | int i; 592 | 593 | if (op->source && 594 | op->type != SETUP_MAKE_SYMLINK) 595 | { 596 | source = get_oldroot_path (op->source); 597 | source_mode = get_file_mode (source); 598 | if (source_mode < 0) 599 | die_with_error ("Can't get type of source %s", op->source); 600 | } 601 | 602 | if (op->dest) 603 | { 604 | dest = get_newroot_path (op->dest); 605 | if (mkdir_with_parents (dest, 0755, FALSE) != 0) 606 | die_with_error ("Can't mkdir parents for %s", op->dest); 607 | } 608 | 609 | switch (op->type) 610 | { 611 | case SETUP_RO_BIND_MOUNT: 612 | case SETUP_DEV_BIND_MOUNT: 613 | case SETUP_BIND_MOUNT: 614 | if (source_mode == S_IFDIR) 615 | { 616 | if (mkdir (dest, 0755) != 0 && errno != EEXIST) 617 | die_with_error ("Can't mkdir %s", op->dest); 618 | } 619 | else if (ensure_file (dest, 0666) != 0) 620 | die_with_error ("Can't create file at %s", op->dest); 621 | 622 | privileged_op (privileged_op_socket, 623 | PRIV_SEP_OP_BIND_MOUNT, 624 | (op->type == SETUP_RO_BIND_MOUNT ? BIND_READONLY : 0) | 625 | (op->type == SETUP_DEV_BIND_MOUNT ? BIND_DEVICES : 0), 626 | source, dest); 627 | break; 628 | 629 | case SETUP_MOUNT_PROC: 630 | if (mkdir (dest, 0755) != 0 && errno != EEXIST) 631 | die_with_error ("Can't mkdir %s", op->dest); 632 | 633 | if (unshare_pid) 634 | { 635 | /* Our own procfs */ 636 | privileged_op (privileged_op_socket, 637 | PRIV_SEP_OP_PROC_MOUNT, 0, 638 | dest, NULL); 639 | } 640 | else 641 | { 642 | /* Use system procfs, as we share pid namespace anyway */ 643 | privileged_op (privileged_op_socket, 644 | PRIV_SEP_OP_BIND_MOUNT, 0, 645 | "oldroot/proc", dest); 646 | } 647 | 648 | /* There are a bunch of weird old subdirs of /proc that could potentially be 649 | problematic (for instance /proc/sysrq-trigger lets you shut down the machine 650 | if you have write access). We should not have access to these as a non-privileged 651 | user, but lets cover them anyway just to make sure */ 652 | const char *cover_proc_dirs[] = { "sys", "sysrq-trigger", "irq", "bus" }; 653 | for (i = 0; i < N_ELEMENTS (cover_proc_dirs); i++) 654 | { 655 | cleanup_free char *subdir = strconcat3 (dest, "/", cover_proc_dirs[i]); 656 | privileged_op (privileged_op_socket, 657 | PRIV_SEP_OP_BIND_MOUNT, BIND_READONLY, 658 | subdir, subdir); 659 | } 660 | 661 | break; 662 | 663 | case SETUP_MOUNT_DEV: 664 | if (mkdir (dest, 0755) != 0 && errno != EEXIST) 665 | die_with_error ("Can't mkdir %s", op->dest); 666 | 667 | privileged_op (privileged_op_socket, 668 | PRIV_SEP_OP_TMPFS_MOUNT, 0, 669 | dest, NULL); 670 | 671 | static const char *const devnodes[] = { "null", "zero", "full", "random", "urandom", "tty" }; 672 | for (i = 0; i < N_ELEMENTS (devnodes); i++) 673 | { 674 | cleanup_free char *node_dest = strconcat3 (dest, "/", devnodes[i]); 675 | cleanup_free char *node_src = strconcat ("/oldroot/dev/", devnodes[i]); 676 | if (create_file (node_dest, 0666, NULL) != 0) 677 | die_with_error ("Can't create file %s/%s", op->dest, devnodes[i]); 678 | privileged_op (privileged_op_socket, 679 | PRIV_SEP_OP_BIND_MOUNT, BIND_DEVICES, 680 | node_src, node_dest); 681 | } 682 | 683 | static const char *const stdionodes[] = { "stdin", "stdout", "stderr" }; 684 | for (i = 0; i < N_ELEMENTS (stdionodes); i++) 685 | { 686 | cleanup_free char *target = xasprintf ("/proc/self/fd/%d", i); 687 | cleanup_free char *node_dest = strconcat3 (dest, "/", stdionodes[i]); 688 | if (symlink (target, node_dest) < 0) 689 | die_with_error ("Can't create symlink %s/%s", op->dest, stdionodes[i]); 690 | } 691 | 692 | { 693 | cleanup_free char *pts = strconcat (dest, "/pts"); 694 | cleanup_free char *ptmx = strconcat (dest, "/ptmx"); 695 | cleanup_free char *shm = strconcat (dest, "/shm"); 696 | 697 | if (mkdir (shm, 0755) == -1) 698 | die_with_error ("Can't create %s/shm", op->dest); 699 | 700 | if (mkdir (pts, 0755) == -1) 701 | die_with_error ("Can't create %s/devpts", op->dest); 702 | privileged_op (privileged_op_socket, 703 | PRIV_SEP_OP_DEVPTS_MOUNT, BIND_DEVICES, 704 | pts, NULL); 705 | 706 | if (symlink ("pts/ptmx", ptmx) != 0) 707 | die_with_error ("Can't make symlink at %s/ptmx", op->dest); 708 | } 709 | 710 | /* If stdout is a tty, that means the sandbox can write to the 711 | outside-sandbox tty. In that case we also create a /dev/console 712 | that points to this tty device. This should not cause any more 713 | access than we already have, and it makes ttyname() work in the 714 | sandbox. */ 715 | if (host_tty_dev != NULL && *host_tty_dev != 0) 716 | { 717 | cleanup_free char *src_tty_dev = strconcat ("/oldroot", host_tty_dev); 718 | cleanup_free char *dest_console = strconcat (dest, "/console"); 719 | 720 | if (create_file (dest_console, 0666, NULL) != 0) 721 | die_with_error ("creating %s/console", op->dest); 722 | 723 | privileged_op (privileged_op_socket, 724 | PRIV_SEP_OP_BIND_MOUNT, BIND_DEVICES, 725 | src_tty_dev, dest_console); 726 | } 727 | 728 | break; 729 | 730 | case SETUP_MOUNT_TMPFS: 731 | if (mkdir (dest, 0755) != 0 && errno != EEXIST) 732 | die_with_error ("Can't mkdir %s", op->dest); 733 | 734 | privileged_op (privileged_op_socket, 735 | PRIV_SEP_OP_TMPFS_MOUNT, 0, 736 | dest, NULL); 737 | break; 738 | 739 | case SETUP_MOUNT_MQUEUE: 740 | if (mkdir (dest, 0755) != 0 && errno != EEXIST) 741 | die_with_error ("Can't mkdir %s", op->dest); 742 | 743 | privileged_op (privileged_op_socket, 744 | PRIV_SEP_OP_MQUEUE_MOUNT, 0, 745 | dest, NULL); 746 | break; 747 | 748 | case SETUP_MAKE_DIR: 749 | if (mkdir (dest, 0755) != 0 && errno != EEXIST) 750 | die_with_error ("Can't mkdir %s", op->dest); 751 | 752 | break; 753 | 754 | case SETUP_MAKE_FILE: 755 | { 756 | cleanup_fd int dest_fd = -1; 757 | 758 | dest_fd = creat (dest, 0666); 759 | if (dest_fd == -1) 760 | die_with_error ("Can't create file %s", op->dest); 761 | 762 | if (copy_file_data (op->fd, dest_fd) != 0) 763 | die_with_error ("Can't write data to file %s", op->dest); 764 | 765 | close (op->fd); 766 | } 767 | break; 768 | 769 | case SETUP_MAKE_BIND_FILE: 770 | { 771 | cleanup_fd int dest_fd = -1; 772 | char tempfile[] = "/bindfileXXXXXX"; 773 | 774 | dest_fd = mkstemp (tempfile); 775 | if (dest_fd == -1) 776 | die_with_error ("Can't create tmpfile for %s", op->dest); 777 | 778 | if (copy_file_data (op->fd, dest_fd) != 0) 779 | die_with_error ("Can't write data to file %s", op->dest); 780 | 781 | close (op->fd); 782 | 783 | if (ensure_file (dest, 0666) != 0) 784 | die_with_error ("Can't create file at %s", op->dest); 785 | 786 | privileged_op (privileged_op_socket, 787 | PRIV_SEP_OP_BIND_MOUNT, 788 | 0, tempfile, dest); 789 | } 790 | break; 791 | 792 | case SETUP_MAKE_SYMLINK: 793 | if (symlink (op->source, dest) != 0) 794 | die_with_error ("Can't make symlink at %s", op->dest); 795 | break; 796 | 797 | default: 798 | die ("Unexpected type %d", op->type); 799 | } 800 | } 801 | privileged_op (privileged_op_socket, 802 | PRIV_SEP_OP_DONE, 0, NULL, NULL); 803 | } 804 | 805 | static const char * 806 | resolve_string_offset (void *buffer, 807 | size_t buffer_size, 808 | uint32_t offset) 809 | { 810 | if (offset == 0) 811 | return NULL; 812 | 813 | if (offset > buffer_size) 814 | die ("Invalid string offset %d (buffer size %zd)", offset, buffer_size); 815 | 816 | return (const char *) buffer + offset; 817 | } 818 | 819 | static uint32_t 820 | read_priv_sec_op (int read_socket, 821 | void *buffer, 822 | size_t buffer_size, 823 | uint32_t *flags, 824 | const char **arg1, 825 | const char **arg2) 826 | { 827 | const PrivSepOp *op = (const PrivSepOp *) buffer; 828 | ssize_t rec_len; 829 | 830 | do 831 | rec_len = read (read_socket, buffer, buffer_size - 1); 832 | while (rec_len == -1 && errno == EINTR); 833 | 834 | if (rec_len < 0) 835 | die_with_error ("Can't read from unprivileged helper"); 836 | 837 | if (rec_len < sizeof (PrivSepOp)) 838 | die ("Invalid size %zd from unprivileged helper", rec_len); 839 | 840 | /* Guarantee zero termination of any strings */ 841 | ((char *) buffer)[rec_len] = 0; 842 | 843 | *flags = op->flags; 844 | *arg1 = resolve_string_offset (buffer, rec_len, op->arg1_offset); 845 | *arg2 = resolve_string_offset (buffer, rec_len, op->arg2_offset); 846 | 847 | return op->op; 848 | } 849 | 850 | char *opt_chdir_path = NULL; 851 | bool opt_unshare_user = FALSE; 852 | bool opt_unshare_user_try = FALSE; 853 | bool opt_unshare_pid = FALSE; 854 | bool opt_unshare_ipc = FALSE; 855 | bool opt_unshare_net = FALSE; 856 | bool opt_unshare_uts = FALSE; 857 | bool opt_unshare_cgroup = FALSE; 858 | bool opt_unshare_cgroup_try = FALSE; 859 | bool opt_needs_devpts = FALSE; 860 | uid_t opt_sandbox_uid = -1; 861 | gid_t opt_sandbox_gid = -1; 862 | int opt_sync_fd = -1; 863 | int opt_seccomp_fd = -1; 864 | 865 | 866 | static void 867 | parse_args_recurse (int *argcp, 868 | char ***argvp, 869 | bool in_file, 870 | int *total_parsed_argc_p) 871 | { 872 | SetupOp *op; 873 | int argc = *argcp; 874 | char **argv = *argvp; 875 | /* I can't imagine a case where someone wants more than this. 876 | * If you do...you should be able to pass multiple files 877 | * via a single tmpfs and linking them there, etc. 878 | * 879 | * We're adding this hardening due to precedent from 880 | * http://googleprojectzero.blogspot.com/2014/08/the-poisoned-nul-byte-2014-edition.html 881 | * 882 | * I picked 9000 because the Internet told me to and it was hard to 883 | * resist. 884 | */ 885 | static const uint32_t MAX_ARGS = 9000; 886 | 887 | if (*total_parsed_argc_p > MAX_ARGS) 888 | die ("Exceeded maximum number of arguments %u", MAX_ARGS); 889 | 890 | while (argc > 0) 891 | { 892 | const char *arg = argv[0]; 893 | 894 | if (strcmp (arg, "--help") == 0) 895 | { 896 | usage (EXIT_SUCCESS, stdout); 897 | } 898 | else if (strcmp (arg, "--version") == 0) 899 | { 900 | printf ("%s\n", PACKAGE_STRING); 901 | exit (0); 902 | } 903 | else if (strcmp (arg, "--args") == 0) 904 | { 905 | int the_fd; 906 | char *endptr; 907 | char *data, *p; 908 | char *data_end; 909 | size_t data_len; 910 | cleanup_free char **data_argv = NULL; 911 | char **data_argv_copy; 912 | int data_argc; 913 | int i; 914 | 915 | if (in_file) 916 | die ("--args not supported in arguments file"); 917 | 918 | if (argc < 2) 919 | die ("--args takes an argument"); 920 | 921 | the_fd = strtol (argv[1], &endptr, 10); 922 | if (argv[1][0] == 0 || endptr[0] != 0 || the_fd < 0) 923 | die ("Invalid fd: %s", argv[1]); 924 | 925 | data = load_file_data (the_fd, &data_len); 926 | if (data == NULL) 927 | die_with_error ("Can't read --args data"); 928 | 929 | data_end = data + data_len; 930 | data_argc = 0; 931 | 932 | p = data; 933 | while (p != NULL && p < data_end) 934 | { 935 | data_argc++; 936 | (*total_parsed_argc_p)++; 937 | if (*total_parsed_argc_p > MAX_ARGS) 938 | die ("Exceeded maximum number of arguments %u", MAX_ARGS); 939 | p = memchr (p, 0, data_end - p); 940 | if (p != NULL) 941 | p++; 942 | } 943 | 944 | data_argv = xcalloc (sizeof (char *) * (data_argc + 1)); 945 | 946 | i = 0; 947 | p = data; 948 | while (p != NULL && p < data_end) 949 | { 950 | /* Note: load_file_data always adds a nul terminator, so this is safe 951 | * even for the last string. */ 952 | data_argv[i++] = p; 953 | p = memchr (p, 0, data_end - p); 954 | if (p != NULL) 955 | p++; 956 | } 957 | 958 | data_argv_copy = data_argv; /* Don't change data_argv, we need to free it */ 959 | parse_args_recurse (&data_argc, &data_argv_copy, TRUE, total_parsed_argc_p); 960 | 961 | argv += 1; 962 | argc -= 1; 963 | } 964 | else if (strcmp (arg, "--unshare-user") == 0) 965 | { 966 | opt_unshare_user = TRUE; 967 | } 968 | else if (strcmp (arg, "--unshare-user-try") == 0) 969 | { 970 | opt_unshare_user_try = TRUE; 971 | } 972 | else if (strcmp (arg, "--unshare-ipc") == 0) 973 | { 974 | opt_unshare_ipc = TRUE; 975 | } 976 | else if (strcmp (arg, "--unshare-pid") == 0) 977 | { 978 | opt_unshare_pid = TRUE; 979 | } 980 | else if (strcmp (arg, "--unshare-net") == 0) 981 | { 982 | opt_unshare_net = TRUE; 983 | } 984 | else if (strcmp (arg, "--unshare-uts") == 0) 985 | { 986 | opt_unshare_uts = TRUE; 987 | } 988 | else if (strcmp (arg, "--unshare-cgroup") == 0) 989 | { 990 | opt_unshare_cgroup = TRUE; 991 | } 992 | else if (strcmp (arg, "--unshare-cgroup-try") == 0) 993 | { 994 | opt_unshare_cgroup_try = TRUE; 995 | } 996 | else if (strcmp (arg, "--chdir") == 0) 997 | { 998 | if (argc < 2) 999 | die ("--chdir takes one argument"); 1000 | 1001 | opt_chdir_path = argv[1]; 1002 | argv++; 1003 | argc--; 1004 | } 1005 | else if (strcmp (arg, "--bind") == 0) 1006 | { 1007 | if (argc < 3) 1008 | die ("--bind takes two arguments"); 1009 | 1010 | op = setup_op_new (SETUP_BIND_MOUNT); 1011 | op->source = canonicalize_file_name (argv[1]); 1012 | if (op->source == NULL) 1013 | die_with_error ("Can't find source path %s", argv[1]); 1014 | op->dest = argv[2]; 1015 | 1016 | argv += 2; 1017 | argc -= 2; 1018 | } 1019 | else if (strcmp (arg, "--ro-bind") == 0) 1020 | { 1021 | if (argc < 3) 1022 | die ("--ro-bind takes two arguments"); 1023 | 1024 | op = setup_op_new (SETUP_RO_BIND_MOUNT); 1025 | op->source = canonicalize_file_name (argv[1]); 1026 | if (op->source == NULL) 1027 | die_with_error ("Can't find source path %s", argv[1]); 1028 | op->dest = argv[2]; 1029 | 1030 | argv += 2; 1031 | argc -= 2; 1032 | } 1033 | else if (strcmp (arg, "--dev-bind") == 0) 1034 | { 1035 | if (argc < 3) 1036 | die ("--dev-bind takes two arguments"); 1037 | 1038 | op = setup_op_new (SETUP_DEV_BIND_MOUNT); 1039 | op->source = canonicalize_file_name (argv[1]); 1040 | if (op->source == NULL) 1041 | die_with_error ("Can't find source path %s", argv[1]); 1042 | op->dest = argv[2]; 1043 | 1044 | argv += 2; 1045 | argc -= 2; 1046 | } 1047 | else if (strcmp (arg, "--proc") == 0) 1048 | { 1049 | if (argc < 2) 1050 | die ("--proc takes an argument"); 1051 | 1052 | op = setup_op_new (SETUP_MOUNT_PROC); 1053 | op->dest = argv[1]; 1054 | 1055 | argv += 1; 1056 | argc -= 1; 1057 | } 1058 | else if (strcmp (arg, "--exec-label") == 0) 1059 | { 1060 | if (argc < 2) 1061 | die ("--exec-label takes an argument"); 1062 | opt_exec_label = argv[1]; 1063 | die_unless_label_valid (opt_exec_label); 1064 | 1065 | argv += 1; 1066 | argc -= 1; 1067 | } 1068 | else if (strcmp (arg, "--file-label") == 0) 1069 | { 1070 | if (argc < 2) 1071 | die ("--file-label takes an argument"); 1072 | opt_file_label = argv[1]; 1073 | die_unless_label_valid (opt_file_label); 1074 | if (label_create_file (opt_file_label)) 1075 | die_with_error ("--file-label setup failed"); 1076 | 1077 | argv += 1; 1078 | argc -= 1; 1079 | } 1080 | else if (strcmp (arg, "--dev") == 0) 1081 | { 1082 | if (argc < 2) 1083 | die ("--dev takes an argument"); 1084 | 1085 | op = setup_op_new (SETUP_MOUNT_DEV); 1086 | op->dest = argv[1]; 1087 | opt_needs_devpts = TRUE; 1088 | 1089 | argv += 1; 1090 | argc -= 1; 1091 | } 1092 | else if (strcmp (arg, "--tmpfs") == 0) 1093 | { 1094 | if (argc < 2) 1095 | die ("--tmpfs takes an argument"); 1096 | 1097 | op = setup_op_new (SETUP_MOUNT_TMPFS); 1098 | op->dest = argv[1]; 1099 | 1100 | argv += 1; 1101 | argc -= 1; 1102 | } 1103 | else if (strcmp (arg, "--mqueue") == 0) 1104 | { 1105 | if (argc < 2) 1106 | die ("--mqueue takes an argument"); 1107 | 1108 | op = setup_op_new (SETUP_MOUNT_MQUEUE); 1109 | op->dest = argv[1]; 1110 | 1111 | argv += 1; 1112 | argc -= 1; 1113 | } 1114 | else if (strcmp (arg, "--dir") == 0) 1115 | { 1116 | if (argc < 2) 1117 | die ("--dir takes an argument"); 1118 | 1119 | op = setup_op_new (SETUP_MAKE_DIR); 1120 | op->dest = argv[1]; 1121 | 1122 | argv += 1; 1123 | argc -= 1; 1124 | } 1125 | else if (strcmp (arg, "--file") == 0) 1126 | { 1127 | int file_fd; 1128 | char *endptr; 1129 | 1130 | if (argc < 3) 1131 | die ("--file takes two arguments"); 1132 | 1133 | file_fd = strtol (argv[1], &endptr, 10); 1134 | if (argv[1][0] == 0 || endptr[0] != 0 || file_fd < 0) 1135 | die ("Invalid fd: %s", argv[1]); 1136 | 1137 | op = setup_op_new (SETUP_MAKE_FILE); 1138 | op->fd = file_fd; 1139 | op->dest = argv[2]; 1140 | 1141 | argv += 2; 1142 | argc -= 2; 1143 | } 1144 | else if (strcmp (arg, "--bind-data") == 0) 1145 | { 1146 | int file_fd; 1147 | char *endptr; 1148 | 1149 | if (argc < 3) 1150 | die ("--bind-data takes two arguments"); 1151 | 1152 | file_fd = strtol (argv[1], &endptr, 10); 1153 | if (argv[1][0] == 0 || endptr[0] != 0 || file_fd < 0) 1154 | die ("Invalid fd: %s", argv[1]); 1155 | 1156 | op = setup_op_new (SETUP_MAKE_BIND_FILE); 1157 | op->fd = file_fd; 1158 | op->dest = argv[2]; 1159 | 1160 | argv += 2; 1161 | argc -= 2; 1162 | } 1163 | else if (strcmp (arg, "--symlink") == 0) 1164 | { 1165 | if (argc < 3) 1166 | die ("--symlink takes two arguments"); 1167 | 1168 | op = setup_op_new (SETUP_MAKE_SYMLINK); 1169 | op->source = argv[1]; 1170 | op->dest = argv[2]; 1171 | 1172 | argv += 2; 1173 | argc -= 2; 1174 | } 1175 | else if (strcmp (arg, "--lock-file") == 0) 1176 | { 1177 | if (argc < 2) 1178 | die ("--lock-file takes an argument"); 1179 | 1180 | (void) lock_file_new (argv[1]); 1181 | 1182 | argv += 1; 1183 | argc -= 1; 1184 | } 1185 | else if (strcmp (arg, "--sync-fd") == 0) 1186 | { 1187 | int the_fd; 1188 | char *endptr; 1189 | 1190 | if (argc < 2) 1191 | die ("--sync-fd takes an argument"); 1192 | 1193 | the_fd = strtol (argv[1], &endptr, 10); 1194 | if (argv[1][0] == 0 || endptr[0] != 0 || the_fd < 0) 1195 | die ("Invalid fd: %s", argv[1]); 1196 | 1197 | opt_sync_fd = the_fd; 1198 | 1199 | argv += 1; 1200 | argc -= 1; 1201 | } 1202 | else if (strcmp (arg, "--seccomp") == 0) 1203 | { 1204 | int the_fd; 1205 | char *endptr; 1206 | 1207 | if (argc < 2) 1208 | die ("--seccomp takes an argument"); 1209 | 1210 | the_fd = strtol (argv[1], &endptr, 10); 1211 | if (argv[1][0] == 0 || endptr[0] != 0 || the_fd < 0) 1212 | die ("Invalid fd: %s", argv[1]); 1213 | 1214 | opt_seccomp_fd = the_fd; 1215 | 1216 | argv += 1; 1217 | argc -= 1; 1218 | } 1219 | else if (strcmp (arg, "--setenv") == 0) 1220 | { 1221 | if (argc < 3) 1222 | die ("--setenv takes two arguments"); 1223 | 1224 | xsetenv (argv[1], argv[2], 1); 1225 | 1226 | argv += 2; 1227 | argc -= 2; 1228 | } 1229 | else if (strcmp (arg, "--unsetenv") == 0) 1230 | { 1231 | if (argc < 2) 1232 | die ("--unsetenv takes an argument"); 1233 | 1234 | xunsetenv (argv[1]); 1235 | 1236 | argv += 1; 1237 | argc -= 1; 1238 | } 1239 | else if (strcmp (arg, "--uid") == 0) 1240 | { 1241 | int the_uid; 1242 | char *endptr; 1243 | 1244 | if (argc < 2) 1245 | die ("--uid takes an argument"); 1246 | 1247 | the_uid = strtol (argv[1], &endptr, 10); 1248 | if (argv[1][0] == 0 || endptr[0] != 0 || the_uid < 0) 1249 | die ("Invalid uid: %s", argv[1]); 1250 | 1251 | opt_sandbox_uid = the_uid; 1252 | 1253 | argv += 1; 1254 | argc -= 1; 1255 | } 1256 | else if (strcmp (arg, "--gid") == 0) 1257 | { 1258 | int the_gid; 1259 | char *endptr; 1260 | 1261 | if (argc < 2) 1262 | die ("--gid takes an argument"); 1263 | 1264 | the_gid = strtol (argv[1], &endptr, 10); 1265 | if (argv[1][0] == 0 || endptr[0] != 0 || the_gid < 0) 1266 | die ("Invalid gid: %s", argv[1]); 1267 | 1268 | opt_sandbox_gid = the_gid; 1269 | 1270 | argv += 1; 1271 | argc -= 1; 1272 | } 1273 | else if (*arg == '-') 1274 | { 1275 | die ("Unknown option %s", arg); 1276 | } 1277 | else 1278 | { 1279 | break; 1280 | } 1281 | 1282 | argv++; 1283 | argc--; 1284 | } 1285 | 1286 | *argcp = argc; 1287 | *argvp = argv; 1288 | } 1289 | 1290 | static void 1291 | parse_args (int *argcp, 1292 | char ***argvp) 1293 | { 1294 | int total_parsed_argc = *argcp; 1295 | 1296 | parse_args_recurse (argcp, argvp, FALSE, &total_parsed_argc); 1297 | } 1298 | 1299 | int 1300 | main (int argc, 1301 | char **argv) 1302 | { 1303 | mode_t old_umask; 1304 | cleanup_free char *base_path = NULL; 1305 | int clone_flags; 1306 | char *old_cwd = NULL; 1307 | pid_t pid; 1308 | int event_fd = -1; 1309 | int child_wait_fd = -1; 1310 | const char *new_cwd; 1311 | uid_t ns_uid; 1312 | gid_t ns_gid; 1313 | struct stat sbuf; 1314 | uint64_t val; 1315 | int res UNUSED; 1316 | 1317 | /* Get the (optional) capabilities we need, drop root */ 1318 | acquire_caps (); 1319 | 1320 | /* Never gain any more privs during exec */ 1321 | if (prctl (PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) 1322 | die_with_error ("prctl(PR_SET_NO_NEW_CAPS) failed"); 1323 | 1324 | /* The initial code is run with high permissions 1325 | (i.e. CAP_SYS_ADMIN), so take lots of care. */ 1326 | 1327 | argv0 = argv[0]; 1328 | 1329 | if (isatty (1)) 1330 | host_tty_dev = ttyname (1); 1331 | 1332 | argv++; 1333 | argc--; 1334 | 1335 | if (argc == 0) 1336 | usage (EXIT_FAILURE, stderr); 1337 | 1338 | parse_args (&argc, &argv); 1339 | 1340 | /* We have to do this if we weren't installed setuid, so let's just DWIM */ 1341 | if (!is_privileged) 1342 | opt_unshare_user = TRUE; 1343 | 1344 | if (opt_unshare_user_try && 1345 | stat ("/proc/self/ns/user", &sbuf) == 0) 1346 | { 1347 | bool disabled = FALSE; 1348 | 1349 | /* RHEL7 has a kernel module parameter that lets you enable user namespaces */ 1350 | if (stat ("/sys/module/user_namespace/parameters/enable", &sbuf) == 0) 1351 | { 1352 | cleanup_free char *enable = NULL; 1353 | enable = load_file_at (AT_FDCWD, "/sys/module/user_namespace/parameters/enable"); 1354 | if (enable != NULL && enable[0] == 'N') 1355 | disabled = TRUE; 1356 | } 1357 | 1358 | /* Debian lets you disable *unprivileged* user namespaces. However this is not 1359 | a problem if we're privileged, and if we're not opt_unshare_user is TRUE 1360 | already, and there is not much we can do, its just a non-working setup. */ 1361 | 1362 | if (!disabled) 1363 | opt_unshare_user = TRUE; 1364 | } 1365 | 1366 | if (argc == 0) 1367 | usage (EXIT_FAILURE, stderr); 1368 | 1369 | __debug__ (("Creating root mount point\n")); 1370 | 1371 | uid = getuid (); 1372 | if (opt_sandbox_uid == -1) 1373 | opt_sandbox_uid = uid; 1374 | gid = getgid (); 1375 | if (opt_sandbox_gid == -1) 1376 | opt_sandbox_gid = gid; 1377 | 1378 | if (!opt_unshare_user && opt_sandbox_uid != uid) 1379 | die ("Specifying --uid requires --unshare-user"); 1380 | 1381 | if (!opt_unshare_user && opt_sandbox_gid != gid) 1382 | die ("Specifying --gid requires --unshare-user"); 1383 | 1384 | /* We need to read stuff from proc during the pivot_root dance, etc. 1385 | Lets keep a fd to it open */ 1386 | proc_fd = open ("/proc", O_RDONLY | O_PATH); 1387 | if (proc_fd == -1) 1388 | die_with_error ("Can't open /proc"); 1389 | 1390 | /* We need *some* mountpoint where we can mount the root tmpfs. 1391 | We first try in /run, and if that fails, try in /tmp. */ 1392 | base_path = xasprintf ("/run/user/%d/.bubblewrap", uid); 1393 | if (mkdir (base_path, 0755) && errno != EEXIST) 1394 | { 1395 | free (base_path); 1396 | base_path = xasprintf ("/tmp/.bubblewrap-%d", uid); 1397 | if (mkdir (base_path, 0755) && errno != EEXIST) 1398 | die_with_error ("Creating root mountpoint failed"); 1399 | } 1400 | 1401 | __debug__ (("creating new namespace\n")); 1402 | 1403 | if (opt_unshare_pid) 1404 | { 1405 | event_fd = eventfd (0, EFD_CLOEXEC | EFD_NONBLOCK); 1406 | if (event_fd == -1) 1407 | die_with_error ("eventfd()"); 1408 | } 1409 | 1410 | /* We block sigchild here so that we can use signalfd in the monitor. */ 1411 | block_sigchild (); 1412 | 1413 | clone_flags = SIGCHLD | CLONE_NEWNS; 1414 | if (opt_unshare_user) 1415 | clone_flags |= CLONE_NEWUSER; 1416 | if (opt_unshare_pid) 1417 | clone_flags |= CLONE_NEWPID; 1418 | if (opt_unshare_net) 1419 | clone_flags |= CLONE_NEWNET; 1420 | if (opt_unshare_ipc) 1421 | clone_flags |= CLONE_NEWIPC; 1422 | if (opt_unshare_uts) 1423 | clone_flags |= CLONE_NEWUTS; 1424 | if (opt_unshare_cgroup) 1425 | { 1426 | if (stat ("/proc/self/ns/cgroup", &sbuf)) 1427 | { 1428 | if (errno == ENOENT) 1429 | die ("Cannot create new cgroup namespace because the kernel does not support it"); 1430 | else 1431 | die_with_error ("stat on /proc/self/ns/cgroup failed"); 1432 | } 1433 | clone_flags |= CLONE_NEWCGROUP; 1434 | } 1435 | if (opt_unshare_cgroup_try) 1436 | if (!stat ("/proc/self/ns/cgroup", &sbuf)) 1437 | clone_flags |= CLONE_NEWCGROUP; 1438 | 1439 | child_wait_fd = eventfd (0, EFD_CLOEXEC); 1440 | if (child_wait_fd == -1) 1441 | die_with_error ("eventfd()"); 1442 | 1443 | pid = raw_clone (clone_flags, NULL); 1444 | if (pid == -1) 1445 | { 1446 | if (opt_unshare_user) 1447 | { 1448 | if (errno == EINVAL) 1449 | die ("Creating new namespace failed, likely because the kernel does not support user namespaces. bwrap must be installed setuid on such systems."); 1450 | else if (errno == EPERM && !is_privileged) 1451 | die ("No permissions to creating 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'."); 1452 | } 1453 | 1454 | die_with_error ("Creating new namespace failed"); 1455 | } 1456 | 1457 | ns_uid = opt_sandbox_uid; 1458 | ns_gid = opt_sandbox_gid; 1459 | 1460 | if (pid != 0) 1461 | { 1462 | if (is_privileged && opt_unshare_user) 1463 | { 1464 | /* Map the uid/gid 0 if opt_needs_devpts, as otherwise 1465 | * mounting it will fail. 1466 | * Due to this non-direct mapping we need to have set[ug]id 1467 | * caps in the parent namespaces, and thus we need to write 1468 | * the map in the parent namespace, not the child. */ 1469 | write_uid_gid_map (ns_uid, uid, 1470 | ns_gid, gid, 1471 | pid, TRUE, opt_needs_devpts); 1472 | } 1473 | 1474 | /* Initial launched process, wait for exec:ed command to exit */ 1475 | 1476 | /* We don't need any caps in the launcher, drop them immediately. */ 1477 | drop_caps (); 1478 | 1479 | /* Let child run */ 1480 | val = 1; 1481 | res = write (child_wait_fd, &val, 8); 1482 | /* Ignore res, if e.g. the child died and closed child_wait_fd we don't want to error out here */ 1483 | close (child_wait_fd); 1484 | 1485 | monitor_child (event_fd); 1486 | exit (0); /* Should not be reached, but better safe... */ 1487 | } 1488 | 1489 | /* Wait for the parent to init uid/gid maps and drop caps */ 1490 | res = read (child_wait_fd, &val, 8); 1491 | close (child_wait_fd); 1492 | 1493 | if (opt_unshare_net && loopback_setup () != 0) 1494 | die ("Can't create loopback device"); 1495 | 1496 | ns_uid = opt_sandbox_uid; 1497 | ns_gid = opt_sandbox_gid; 1498 | if (!is_privileged && opt_unshare_user) 1499 | { 1500 | /* In the unprivileged case we have to write the uid/gid maps in 1501 | * the child, because we have no caps in the parent */ 1502 | 1503 | if (opt_needs_devpts) 1504 | { 1505 | /* This is a bit hacky, but we need to first map the real uid/gid to 1506 | 0, otherwise we can't mount the devpts filesystem because root is 1507 | not mapped. Later we will create another child user namespace and 1508 | map back to the real uid */ 1509 | ns_uid = 0; 1510 | ns_gid = 0; 1511 | } 1512 | 1513 | write_uid_gid_map (ns_uid, uid, 1514 | ns_gid, gid, 1515 | -1, TRUE, FALSE); 1516 | } 1517 | 1518 | old_umask = umask (0); 1519 | 1520 | /* Mark everything as slave, so that we still 1521 | * receive mounts from the real root, but don't 1522 | * propagate mounts to the real root. */ 1523 | if (mount (NULL, "/", NULL, MS_SLAVE | MS_REC, NULL) < 0) 1524 | die_with_error ("Failed to make / slave"); 1525 | 1526 | /* Create a tmpfs which we will use as / in the namespace */ 1527 | if (mount ("", base_path, "tmpfs", MS_NODEV | MS_NOSUID, NULL) != 0) 1528 | die_with_error ("Failed to mount tmpfs"); 1529 | 1530 | old_cwd = get_current_dir_name (); 1531 | 1532 | /* Chdir to the new root tmpfs mount. This will be the CWD during 1533 | the entire setup. Access old or new root via "oldroot" and "newroot". */ 1534 | if (chdir (base_path) != 0) 1535 | die_with_error ("chdir base_path"); 1536 | 1537 | /* We create a subdir "$base_path/newroot" for the new root, that 1538 | * way we can pivot_root to base_path, and put the old root at 1539 | * "$base_path/oldroot". This avoids problems accessing the oldroot 1540 | * dir if the user requested to bind mount something over / */ 1541 | 1542 | if (mkdir ("newroot", 0755)) 1543 | die_with_error ("Creating newroot failed"); 1544 | 1545 | if (mkdir ("oldroot", 0755)) 1546 | die_with_error ("Creating oldroot failed"); 1547 | 1548 | if (pivot_root (base_path, "oldroot")) 1549 | die_with_error ("pivot_root"); 1550 | 1551 | if (chdir ("/") != 0) 1552 | die_with_error ("chdir / (base path)"); 1553 | 1554 | if (is_privileged) 1555 | { 1556 | pid_t child; 1557 | int privsep_sockets[2]; 1558 | 1559 | if (socketpair (AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, privsep_sockets) != 0) 1560 | die_with_error ("Can't create privsep socket"); 1561 | 1562 | child = fork (); 1563 | if (child == -1) 1564 | die_with_error ("Can't fork unprivileged helper"); 1565 | 1566 | if (child == 0) 1567 | { 1568 | /* Unprivileged setup process */ 1569 | drop_caps (); 1570 | close (privsep_sockets[0]); 1571 | setup_newroot (opt_unshare_pid, privsep_sockets[1]); 1572 | exit (0); 1573 | } 1574 | else 1575 | { 1576 | uint32_t buffer[2048]; /* 8k, but is int32 to guarantee nice alignment */ 1577 | uint32_t op, flags; 1578 | const char *arg1, *arg2; 1579 | cleanup_fd int unpriv_socket = -1; 1580 | 1581 | unpriv_socket = privsep_sockets[0]; 1582 | close (privsep_sockets[1]); 1583 | 1584 | do 1585 | { 1586 | op = read_priv_sec_op (unpriv_socket, buffer, sizeof (buffer), 1587 | &flags, &arg1, &arg2); 1588 | privileged_op (-1, op, flags, arg1, arg2); 1589 | if (write (unpriv_socket, buffer, 1) != 1) 1590 | die ("Can't write to op_socket"); 1591 | } 1592 | while (op != PRIV_SEP_OP_DONE); 1593 | 1594 | /* Continue post setup */ 1595 | } 1596 | } 1597 | else 1598 | { 1599 | setup_newroot (opt_unshare_pid, -1); 1600 | } 1601 | 1602 | /* The old root better be rprivate or we will send unmount events to the parent namespace */ 1603 | if (mount ("oldroot", "oldroot", NULL, MS_REC | MS_PRIVATE, NULL) != 0) 1604 | die_with_error ("Failed to make old root rprivate"); 1605 | 1606 | if (umount2 ("oldroot", MNT_DETACH)) 1607 | die_with_error ("unmount old root"); 1608 | 1609 | if (opt_unshare_user && 1610 | (ns_uid != opt_sandbox_uid || ns_gid != opt_sandbox_gid)) 1611 | { 1612 | /* Now that devpts is mounted and we've no need for mount 1613 | permissions we can create a new userspace and map our uid 1614 | 1:1 */ 1615 | 1616 | if (unshare (CLONE_NEWUSER)) 1617 | die_with_error ("unshare user ns"); 1618 | 1619 | write_uid_gid_map (opt_sandbox_uid, ns_uid, 1620 | opt_sandbox_gid, ns_gid, 1621 | -1, FALSE, FALSE); 1622 | } 1623 | 1624 | /* Now make /newroot the real root */ 1625 | if (chdir ("/newroot") != 0) 1626 | die_with_error ("chdir newroot"); 1627 | if (chroot ("/newroot") != 0) 1628 | die_with_error ("chroot /newroot"); 1629 | if (chdir ("/") != 0) 1630 | die_with_error ("chdir /"); 1631 | 1632 | /* Now we have everything we need CAP_SYS_ADMIN for, so drop it */ 1633 | drop_caps (); 1634 | 1635 | if (opt_seccomp_fd != -1) 1636 | { 1637 | cleanup_free char *seccomp_data = NULL; 1638 | size_t seccomp_len; 1639 | struct sock_fprog prog; 1640 | 1641 | seccomp_data = load_file_data (opt_seccomp_fd, &seccomp_len); 1642 | if (seccomp_data == NULL) 1643 | die_with_error ("Can't read seccomp data"); 1644 | 1645 | if (seccomp_len % 8 != 0) 1646 | die ("Invalide seccomp data, must be multiple of 8"); 1647 | 1648 | prog.len = seccomp_len / 8; 1649 | prog.filter = (struct sock_filter *) seccomp_data; 1650 | 1651 | close (opt_seccomp_fd); 1652 | 1653 | if (prctl (PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) != 0) 1654 | die_with_error ("prctl(PR_SET_SECCOMP)"); 1655 | } 1656 | 1657 | umask (old_umask); 1658 | 1659 | new_cwd = "/"; 1660 | if (opt_chdir_path) 1661 | { 1662 | if (chdir (opt_chdir_path)) 1663 | die_with_error ("Can't chdir to %s", opt_chdir_path); 1664 | new_cwd = opt_chdir_path; 1665 | } 1666 | else if (chdir (old_cwd) == 0) 1667 | { 1668 | /* If the old cwd is mapped in the sandbox, go there */ 1669 | new_cwd = old_cwd; 1670 | } 1671 | else 1672 | { 1673 | /* If the old cwd is not mapped, go to home */ 1674 | const char *home = getenv ("HOME"); 1675 | if (home != NULL && 1676 | chdir (home) == 0) 1677 | new_cwd = home; 1678 | } 1679 | xsetenv ("PWD", new_cwd, 1); 1680 | free (old_cwd); 1681 | 1682 | __debug__ (("forking for child\n")); 1683 | 1684 | if (opt_unshare_pid || lock_files != NULL || opt_sync_fd != -1) 1685 | { 1686 | /* We have to have a pid 1 in the pid namespace, because 1687 | * otherwise we'll get a bunch of zombies as nothing reaps 1688 | * them. Alternatively if we're using sync_fd or lock_files we 1689 | * need some process to own these. 1690 | */ 1691 | 1692 | pid = fork (); 1693 | if (pid == -1) 1694 | die_with_error ("Can't fork for pid 1"); 1695 | 1696 | if (pid != 0) 1697 | { 1698 | /* Close fds in pid 1, except stdio and optionally event_fd 1699 | (for syncing pid 2 lifetime with monitor_child) and 1700 | opt_sync_fd (for syncing sandbox lifetime with outside 1701 | process). 1702 | Any other fds will been passed on to the child though. */ 1703 | { 1704 | int dont_close[3]; 1705 | int j = 0; 1706 | if (event_fd != -1) 1707 | dont_close[j++] = event_fd; 1708 | if (opt_sync_fd != -1) 1709 | dont_close[j++] = opt_sync_fd; 1710 | dont_close[j++] = -1; 1711 | fdwalk (proc_fd, close_extra_fds, dont_close); 1712 | } 1713 | 1714 | return do_init (event_fd, pid); 1715 | } 1716 | } 1717 | 1718 | __debug__ (("launch executable %s\n", argv[0])); 1719 | 1720 | if (proc_fd != -1) 1721 | close (proc_fd); 1722 | 1723 | if (opt_sync_fd != -1) 1724 | close (opt_sync_fd); 1725 | 1726 | /* We want sigchild in the child */ 1727 | unblock_sigchild (); 1728 | 1729 | if (label_exec (opt_exec_label) == -1) 1730 | die_with_error ("label_exec %s", argv[0]); 1731 | 1732 | if (execvp (argv[0], argv) == -1) 1733 | die_with_error ("execvp %s", argv[0]); 1734 | 1735 | return 0; 1736 | } 1737 | --------------------------------------------------------------------------------