├── 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 | 
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 |
--------------------------------------------------------------------------------