diff --git a/subprojects/bubblewrap/.github/workflows/check.yml b/subprojects/bubblewrap/.github/workflows/check.yml index be21f8a8bb4f80343c203b407606b563447253bd..7bc034b554b40609f361fd90a0b609b6cf1874fb 100644 --- a/subprojects/bubblewrap/.github/workflows/check.yml +++ b/subprojects/bubblewrap/.github/workflows/check.yml @@ -9,72 +9,12 @@ on: - main jobs: - check: - name: Build with Autotools and gcc, and test - runs-on: ubuntu-latest - steps: - - name: Check out - uses: actions/checkout@v1 - - name: Install build-dependencies - run: sudo ./ci/builddeps.sh - - name: Create logs dir - run: mkdir test-logs - - name: autogen.sh - run: NOCONFIGURE=1 ./autogen.sh - - name: configure - run: | - mkdir _build - pushd _build - ../configure \ - --enable-man \ - --enable-selinux \ - ${NULL+} - popd - env: - CFLAGS: >- - -O2 - -Wp,-D_FORTIFY_SOURCE=2 - -fsanitize=address - -fsanitize=undefined - - name: make - run: make -C _build -j $(getconf _NPROCESSORS_ONLN) V=1 - - name: smoke-test - run: | - set -x - ./_build/bwrap --bind / / --tmpfs /tmp true - env: - ASAN_OPTIONS: detect_leaks=0 - - name: check - run: | - make -C _build -j $(getconf _NPROCESSORS_ONLN) check VERBOSE=1 BWRAP_MUST_WORK=1 - env: - ASAN_OPTIONS: detect_leaks=0 - - name: Collect overall test logs on failure - if: failure() - run: mv _build/test-suite.log test-logs/ || true - - name: Collect individual test logs on cancel - if: failure() || cancelled() - run: mv _build/tests/*.log test-logs/ || true - - name: Upload test logs - uses: actions/upload-artifact@v1 - if: failure() || cancelled() - with: - name: test logs - path: test-logs - - name: install - run: | - make -C _build install DESTDIR="$(pwd)/DESTDIR" - ( cd DESTDIR && find -ls ) - - name: distcheck - run: | - make -C _build -j $(getconf _NPROCESSORS_ONLN) distcheck VERBOSE=1 BWRAP_MUST_WORK=1 - meson: name: Build with Meson and gcc, and test runs-on: ubuntu-latest steps: - name: Check out - uses: actions/checkout@v1 + uses: actions/checkout@v4 - name: Install build-dependencies run: sudo ./ci/builddeps.sh - name: Create logs dir @@ -129,7 +69,7 @@ jobs: test ! -e DESTDIR-as-subproject/usr/local/libexec/bwrap tests/use-as-subproject/assert-correct-rpath.py DESTDIR-as-subproject/usr/local/libexec/not-flatpak-bwrap - name: Upload test logs - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v4 if: failure() || cancelled() with: name: test logs @@ -149,19 +89,15 @@ jobs: with: languages: ${{ matrix.language }} - name: Check out - uses: actions/checkout@v1 + uses: actions/checkout@v4 - name: Install build-dependencies run: sudo ./ci/builddeps.sh --clang - - name: autogen.sh - run: NOCONFIGURE=1 ./autogen.sh - - name: configure - run: ./configure --enable-selinux + - run: meson build -Dselinux=enabled env: CC: clang CFLAGS: >- -O2 -Werror=unused-variable - - name: make - run: make -j $(getconf _NPROCESSORS_ONLN) V=1 + - run: meson compile -C build - name: CodeQL analysis uses: github/codeql-action/analyze@v2 diff --git a/subprojects/bubblewrap/Makefile-bwrap.am b/subprojects/bubblewrap/Makefile-bwrap.am deleted file mode 100644 index aa97bd17ac4b0c7a6f555d6f5e5a06da57c30d5b..0000000000000000000000000000000000000000 --- a/subprojects/bubblewrap/Makefile-bwrap.am +++ /dev/null @@ -1,12 +0,0 @@ -bwrap_SOURCES = \ - $(bwrap_srcpath)/bubblewrap.c \ - $(bwrap_srcpath)/bind-mount.h \ - $(bwrap_srcpath)/bind-mount.c \ - $(bwrap_srcpath)/network.h \ - $(bwrap_srcpath)/network.c \ - $(bwrap_srcpath)/utils.h \ - $(bwrap_srcpath)/utils.c \ - $(NULL) - -bwrap_CFLAGS = $(AM_CFLAGS) -bwrap_LDADD = $(SELINUX_LIBS) diff --git a/subprojects/bubblewrap/Makefile-docs.am b/subprojects/bubblewrap/Makefile-docs.am deleted file mode 100644 index 4c1f3c5ccf5ca1cfb745766a7944afa41c176a23..0000000000000000000000000000000000000000 --- a/subprojects/bubblewrap/Makefile-docs.am +++ /dev/null @@ -1,18 +0,0 @@ -XSLTPROC = xsltproc - -XSLTPROC_FLAGS = \ - --nonet \ - --stringparam man.output.quietly 1 \ - --stringparam funcsynopsis.style ansi \ - --stringparam man.th.extra1.suppress 1 \ - --stringparam man.authors.section.enabled 0 \ - --stringparam man.copyright.section.enabled 0 - -.xml.1: - $(XSLTPROC) $(XSLTPROC_FLAGS) http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $< - -if ENABLE_MAN -man_MANS = bwrap.1 -CLEANFILES += $(man_MANS) -endif -EXTRA_DIST += bwrap.xml diff --git a/subprojects/bubblewrap/Makefile.am b/subprojects/bubblewrap/Makefile.am deleted file mode 100644 index 81a160aa76c51fb05f3285d96f9ebad3b0322f45..0000000000000000000000000000000000000000 --- a/subprojects/bubblewrap/Makefile.am +++ /dev/null @@ -1,102 +0,0 @@ -AM_CFLAGS = $(WARN_CFLAGS) -CLEANFILES = -EXTRA_DIST = \ - .dir-locals.el \ - .editorconfig \ - README.md \ - autogen.sh \ - completions/bash/meson.build \ - completions/meson.build \ - completions/zsh/meson.build \ - demos/bubblewrap-shell.sh \ - demos/flatpak-run.sh \ - demos/flatpak.bpf \ - demos/userns-block-fd.py \ - meson.build \ - meson_options.txt \ - packaging/bubblewrap.spec \ - tests/meson.build \ - tests/use-as-subproject/README \ - tests/use-as-subproject/config.h \ - tests/use-as-subproject/dummy-config.h.in \ - tests/use-as-subproject/meson.build \ - uncrustify.cfg \ - uncrustify.sh \ - $(NULL) - -GITIGNOREFILES = build-aux/ gtk-doc.make config.h.in aclocal.m4 - -bin_PROGRAMS = bwrap - -bwrap_srcpath := $(srcdir) -include Makefile-bwrap.am - -install-exec-hook: -if PRIV_MODE_SETUID - $(SUDO_BIN) chown root $(DESTDIR)$(bindir)/bwrap - $(SUDO_BIN) chmod u+s $(DESTDIR)$(bindir)/bwrap -endif - -test_programs = \ - tests/test-utils \ - $(NULL) -test_scripts = \ - tests/test-run.sh \ - tests/test-seccomp.py \ - tests/test-specifying-userns.sh \ - tests/test-specifying-pidns.sh \ - $(NULL) -test_extra_programs = \ - test-bwrap \ - tests/try-syscall \ - $(NULL) - -test-bwrap$(EXEEXT): bwrap - rm -rf test-bwrap - cp bwrap test-bwrap - chmod 0755 test-bwrap -if PRIV_MODE_SETUID - $(SUDO_BIN) chown root test-bwrap - $(SUDO_BIN) chmod u+s test-bwrap -endif - -tests_test_utils_SOURCES = \ - tests/test-utils.c \ - utils.h \ - utils.c \ - $(NULL) -tests_test_utils_LDADD = $(SELINUX_LIBS) - -test_bwrap_SOURCES= - -include Makefile-docs.am - -LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) $(top_srcdir)/build-aux/tap-driver.sh -LOG_COMPILER = -TESTS_ENVIRONMENT = \ - BWRAP=$(abs_top_builddir)/test-bwrap \ - G_TEST_BUILDDIR=$(abs_top_builddir) \ - G_TEST_SRCDIR=$(abs_top_srcdir) \ - $(NULL) -check_PROGRAMS = $(test_programs) $(test_extra_programs) -TESTS = $(test_programs) $(test_scripts) - -EXTRA_DIST += $(test_scripts) -EXTRA_DIST += tests/libtest-core.sh -EXTRA_DIST += tests/libtest.sh - -if ENABLE_BASH_COMPLETION -bashcompletiondir = $(BASH_COMPLETION_DIR) -dist_bashcompletion_DATA = completions/bash/bwrap -endif - -if ENABLE_ZSH_COMPLETION -zshcompletiondir = $(ZSH_COMPLETION_DIR) -dist_zshcompletion_DATA = completions/zsh/_bwrap -endif - --include $(top_srcdir)/git.mk - -AM_DISTCHECK_CONFIGURE_FLAGS = \ - --with-bash-completion-dir="\$(datadir)"/bash-completion/ \ - $(NULL) diff --git a/subprojects/bubblewrap/NEWS.md b/subprojects/bubblewrap/NEWS.md new file mode 100644 index 0000000000000000000000000000000000000000..da232c4bd77846e040411949cd24a96a1e85952c --- /dev/null +++ b/subprojects/bubblewrap/NEWS.md @@ -0,0 +1,51 @@ +bubblewrap 0.11.0 +================= + +Released: 2024-10-30 + +Dependencies: + + * Remove the Autotools build system. Meson ≥ 0.49.0 is now required + at build-time. (#625, Hugo Osvaldo Barrera) + + * For users of bash-completion, bash-completion ≥ 2.10 is recommended. + With older bash-completion, bubblewrap might install completions + outside its `${prefix}` unless overridden with `-Dbash_completion_dir=…`. + +Enhancements: + + * New `--overlay`, `--tmp-overlay`, `--ro-overlay` and `--overlay-src` + options allow creation of overlay mounts. + This feature is not available when bubblewrap is installed setuid. + (#412, #663; Ryan Hendrickson, William Manley, Simon McVittie) + + * New `--level-prefix` option produces output that can be parsed by + tools like `logger --prio-prefix` and `systemd-cat --level-prefix=1` + (#646, Simon McVittie) + +Bug fixes: + + * Handle `EINTR` when doing I/O on files or sockets (#657, Simon McVittie) + + * Don't make assumptions about alignment of socket control message data + (#637, Simon McVittie) + + * Silence some Meson deprecation warnings (#647, @Sertonix) + + * Update URLs in documentation to https (#566, @TotalCaesar659) + + * Improve tests' compatibility with busybox (#627, @Sertonix) + + * Improve compatibility with Meson < 1.3.0 (#664, Simon McVittie) + +Internal changes: + + * Consistently use `<stdbool.h>` for booleans (#660, Simon McVittie) + + * Avoid `-Wshadow` compiler warnings (#661, Simon McVittie) + + * Update Github Actions configuration (#658, Simon McVittie) + +---- + +See also <https://github.com/containers/bubblewrap/releases> diff --git a/subprojects/bubblewrap/README.md b/subprojects/bubblewrap/README.md index 0d1b0e0a156fa35a5afb6e5eb353d584fde62c47..c16cd7d89ad2ff842ba2adff5433f0881aeccafd 100644 --- a/subprojects/bubblewrap/README.md +++ b/subprojects/bubblewrap/README.md @@ -77,7 +77,7 @@ Users This program can be shared by all container tools which perform non-root operation, such as: - - [Flatpak](http://www.flatpak.org) + - [Flatpak](https://www.flatpak.org) - [rpm-ostree unprivileged](https://github.com/projectatomic/rpm-ostree/pull/209) - [bwrap-oci](https://github.com/projectatomic/bwrap-oci) @@ -92,25 +92,15 @@ Installation bubblewrap is available in the package repositories of the most Linux distributions and can be installed from there. -If you need to build bubblewrap from source, you can do this with meson or autotools. +If you need to build bubblewrap from source, you can do this with meson: -meson: - -``` +```sh meson _builddir meson compile -C _builddir meson test -C _builddir meson install -C _builddir ``` -autotools: - -``` -./autogen.sh -make -sudo make install -``` - Usage ----- @@ -155,18 +145,18 @@ Any such directories you specify mounted `nodev` by default, and can be made rea Additionally you can use these kernel features: -User namespaces ([CLONE_NEWUSER](http://linux.die.net/man/2/clone)): This hides all but the current uid and gid from the +User namespaces ([CLONE_NEWUSER](https://linux.die.net/man/2/clone)): This hides all but the current uid and gid from the sandbox. You can also change what the value of uid/gid should be in the sandbox. -IPC namespaces ([CLONE_NEWIPC](http://linux.die.net/man/2/clone)): The sandbox will get its own copy of all the +IPC namespaces ([CLONE_NEWIPC](https://linux.die.net/man/2/clone)): The sandbox will get its own copy of all the different forms of IPCs, like SysV shared memory and semaphores. -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/). +PID namespaces ([CLONE_NEWPID](https://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/). -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. +Network namespaces ([CLONE_NEWNET](https://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. -UTS namespace ([CLONE_NEWUTS](http://linux.die.net/man/2/clone)): The sandbox will have its own hostname. +UTS namespace ([CLONE_NEWUTS](https://linux.die.net/man/2/clone)): The sandbox will have its own hostname. 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). diff --git a/subprojects/bubblewrap/autogen.sh b/subprojects/bubblewrap/autogen.sh deleted file mode 100755 index 2d83fafaef9267337398b05435232e457254812c..0000000000000000000000000000000000000000 --- a/subprojects/bubblewrap/autogen.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh - -test -n "$srcdir" || srcdir=$(dirname "$0") -test -n "$srcdir" || srcdir=. - -olddir=$(pwd) -cd "$srcdir" - -if ! (autoreconf --version >/dev/null 2>&1); then - echo "*** No autoreconf found, please install it ***" - exit 1 -fi - -mkdir -p m4 - -autoreconf --force --install --verbose - -cd "$olddir" -test -n "$NOCONFIGURE" || "$srcdir/configure" "$@" diff --git a/subprojects/bubblewrap/bind-mount.c b/subprojects/bubblewrap/bind-mount.c index 2757caea8f16fe463b30234ee09822b8fe0fed74..a2e1ac6c40741aba9d566fad54691a6552bc859e 100644 --- a/subprojects/bubblewrap/bind-mount.c +++ b/subprojects/bubblewrap/bind-mount.c @@ -76,7 +76,7 @@ match_token (const char *token, const char *token_end, const char *str) if (token == token_end) return *str == 0; - return FALSE; + return false; } static unsigned long @@ -281,12 +281,12 @@ parse_mountinfo (int proc_fd, die ("Can't parse mountinfo line"); rest = line + consumed; - rest = skip_token (rest, TRUE); /* mountroot */ + rest = skip_token (rest, true); /* mountroot */ mountpoint = rest; - rest = skip_token (rest, FALSE); /* mountpoint */ + rest = skip_token (rest, false); /* mountpoint */ mountpoint_end = rest++; options = rest; - rest = skip_token (rest, FALSE); /* vfs options */ + rest = skip_token (rest, false); /* vfs options */ options_end = rest; *mountpoint_end = 0; @@ -324,7 +324,7 @@ parse_mountinfo (int proc_fd, MountInfoLine *parent = by_id[this->parent_id]; MountInfoLine **to_sibling; MountInfoLine *sibling; - bool covered = FALSE; + bool covered = false; if (!has_path_prefix (this->mountpoint, root_mount)) continue; @@ -333,7 +333,7 @@ parse_mountinfo (int proc_fd, continue; if (strcmp (parent->mountpoint, this->mountpoint) == 0) - parent->covered = TRUE; + parent->covered = true; to_sibling = &parent->first_child; sibling = parent->first_child; @@ -344,7 +344,7 @@ parse_mountinfo (int proc_fd, * covered by the sibling, and we drop it. */ if (has_path_prefix (this->mountpoint, sibling->mountpoint)) { - covered = TRUE; + covered = true; break; } @@ -405,7 +405,7 @@ bind_mount (int proc_fd, if (resolved_dest == NULL) return BIND_MOUNT_ERROR_REALPATH_DEST; - dest_fd = open (resolved_dest, O_PATH | O_CLOEXEC); + dest_fd = TEMP_FAILURE_RETRY (open (resolved_dest, O_PATH | O_CLOEXEC)); if (dest_fd < 0) { if (failing_path != NULL) @@ -499,7 +499,7 @@ bind_mount_result_to_string (bind_mount_result res, bool *want_errno_p) { char *string = NULL; - bool want_errno = TRUE; + bool want_errno = true; switch (res) { @@ -521,7 +521,7 @@ bind_mount_result_to_string (bind_mount_result res, case BIND_MOUNT_ERROR_FIND_DEST_MOUNT: string = xasprintf ("Unable to find \"%s\" in mount table", failing_path); - want_errno = FALSE; + want_errno = false; break; case BIND_MOUNT_ERROR_REMOUNT_DEST: @@ -557,9 +557,12 @@ die_with_bind_result (bind_mount_result res, ...) { va_list args; - bool want_errno = TRUE; + bool want_errno = true; char *message; + if (bwrap_level_prefix) + fprintf (stderr, "<%d>", LOG_ERR); + fprintf (stderr, "bwrap: "); va_start (args, format); diff --git a/subprojects/bubblewrap/bubblewrap.c b/subprojects/bubblewrap/bubblewrap.c index bc75da470aec092ec9bdd08c240870507acedd47..f8728c7e127838201061318c32ee42fe4533eeae 100644 --- a/subprojects/bubblewrap/bubblewrap.c +++ b/subprojects/bubblewrap/bubblewrap.c @@ -44,15 +44,6 @@ #define CLONE_NEWCGROUP 0x02000000 /* New cgroup namespace */ #endif -#ifndef TEMP_FAILURE_RETRY -#define TEMP_FAILURE_RETRY(expression) \ - (__extension__ \ - ({ long int __result; \ - do __result = (long int) (expression); \ - while (__result == -1L && errno == EINTR); \ - __result; })) -#endif - /* We limit the size of a tmpfs to half the architecture's address space, * to avoid hitting arbitrary limits in the kernel. * For example, on at least one x86_64 machine, the actual limit seems to be @@ -74,19 +65,19 @@ static bool opt_as_pid_1; static const char *opt_argv0 = NULL; static const char *opt_chdir_path = NULL; -static bool opt_assert_userns_disabled = FALSE; -static bool opt_disable_userns = FALSE; -static bool opt_unshare_user = FALSE; -static bool opt_unshare_user_try = FALSE; -static bool opt_unshare_pid = FALSE; -static bool opt_unshare_ipc = FALSE; -static bool opt_unshare_net = FALSE; -static bool opt_unshare_uts = FALSE; -static bool opt_unshare_cgroup = FALSE; -static bool opt_unshare_cgroup_try = FALSE; -static bool opt_needs_devpts = FALSE; -static bool opt_new_session = FALSE; -static bool opt_die_with_parent = FALSE; +static bool opt_assert_userns_disabled = false; +static bool opt_disable_userns = false; +static bool opt_unshare_user = false; +static bool opt_unshare_user_try = false; +static bool opt_unshare_pid = false; +static bool opt_unshare_ipc = false; +static bool opt_unshare_net = false; +static bool opt_unshare_uts = false; +static bool opt_unshare_cgroup = false; +static bool opt_unshare_cgroup_try = false; +static bool opt_needs_devpts = false; +static bool opt_new_session = false; +static bool opt_die_with_parent = false; static uid_t opt_sandbox_uid = -1; static gid_t opt_sandbox_gid = -1; static int opt_sync_fd = -1; @@ -100,8 +91,10 @@ static char *opt_args_data = NULL; /* owned */ static int opt_userns_fd = -1; static int opt_userns2_fd = -1; static int opt_pidns_fd = -1; +static int opt_tmp_overlay_count = 0; static int next_perms = -1; static size_t next_size_arg = 0; +static int next_overlay_src_count = 0; #define CAP_TO_MASK_0(x) (1L << ((x) & 31)) #define CAP_TO_MASK_1(x) CAP_TO_MASK_0(x - 32) @@ -131,6 +124,10 @@ typedef enum { SETUP_BIND_MOUNT, SETUP_RO_BIND_MOUNT, SETUP_DEV_BIND_MOUNT, + SETUP_OVERLAY_MOUNT, + SETUP_TMP_OVERLAY_MOUNT, + SETUP_RO_OVERLAY_MOUNT, + SETUP_OVERLAY_SRC, SETUP_MOUNT_PROC, SETUP_MOUNT_DEV, SETUP_MOUNT_TMPFS, @@ -176,6 +173,7 @@ struct _LockFile enum { PRIV_SEP_OP_DONE, PRIV_SEP_OP_BIND_MOUNT, + PRIV_SEP_OP_OVERLAY_MOUNT, PRIV_SEP_OP_PROC_MOUNT, PRIV_SEP_OP_TMPFS_MOUNT, PRIV_SEP_OP_DEVPTS_MOUNT, @@ -311,6 +309,7 @@ usage (int ecode, FILE *out) " --version Print version\n" " --args FD Parse NUL-separated args from FD\n" " --argv0 VALUE Set argv[0] to the value VALUE before running the program\n" + " --level-prefix Prepend e.g. <3> to diagnostic messages\n" " --unshare-all Unshare every namespace we support by default\n" " --share-net Retain the network namespace (can only combine with --unshare-all)\n" " --unshare-user Create new user namespace (may be automatically implied if not setuid)\n" @@ -344,6 +343,11 @@ usage (int ecode, FILE *out) " --bind-fd FD DEST Bind open directory or path fd on DEST\n" " --ro-bind-fd FD DEST Bind open directory or path fd read-only on DEST\n" " --remount-ro DEST Remount DEST as readonly; does not recursively remount\n" + " --overlay-src SRC Read files from SRC in the following overlay\n" + " --overlay RWSRC WORKDIR DEST Mount overlayfs on DEST, with RWSRC as the host path for writes and\n" + " WORKDIR an empty directory on the same filesystem as RWSRC\n" + " --tmp-overlay DEST Mount overlayfs on DEST, with writes going to an invisible tmpfs\n" + " --ro-overlay DEST Mount overlayfs read-only on DEST\n" " --exec-label LABEL Exec label for the sandbox\n" " --file-label LABEL File label for temporary sandbox content\n" " --proc DEST Mount new procfs on DEST\n" @@ -476,7 +480,7 @@ report_child_exit_status (int exitc, int setup_finished_fd) return; output = xasprintf ("{ \"exit-code\": %i }\n", exitc); - dump_info (opt_json_status_fd, output, FALSE); + dump_info (opt_json_status_fd, output, false); close (opt_json_status_fd); opt_json_status_fd = -1; close (setup_finished_fd); @@ -598,7 +602,7 @@ do_init (int event_fd, pid_t initial_pid) for (lock = lock_files; lock != NULL; lock = lock->next) { - int fd = open (lock->path, O_RDONLY | O_CLOEXEC); + int fd = TEMP_FAILURE_RETRY (open (lock->path, O_RDONLY | O_CLOEXEC)); if (fd == -1) die_with_error ("Unable to open lock file %s", lock->path); @@ -609,7 +613,7 @@ do_init (int event_fd, pid_t initial_pid) .l_len = 0 }; - if (fcntl (fd, F_SETLK, &l) < 0) + if (TEMP_FAILURE_RETRY (fcntl (fd, F_SETLK, &l)) < 0) die_with_error ("Unable to lock file %s", lock->path); /* Keep fd open to hang on to lock */ @@ -621,12 +625,12 @@ do_init (int event_fd, pid_t initial_pid) seccomp_programs_apply (); - while (TRUE) + while (true) { pid_t child; int status; - child = wait (&status); + child = TEMP_FAILURE_RETRY (wait (&status)); if (child == initial_pid) { initial_exit_status = propagate_exit_status (status); @@ -637,7 +641,7 @@ do_init (int event_fd, pid_t initial_pid) int res UNUSED; val = initial_exit_status + 1; - res = write (event_fd, &val, 8); + res = TEMP_FAILURE_RETRY (write (event_fd, &val, 8)); /* Ignore res, if e.g. the parent died and closed event_fd we don't want to error out here */ } @@ -765,16 +769,16 @@ prctl_caps (uint32_t *caps, bool do_cap_bounding, bool do_set_ambient) */ for (cap = 0; cap <= CAP_LAST_CAP; cap++) { - bool keep = FALSE; + bool keep = false; if (cap < 32) { if (CAP_TO_MASK_0 (cap) & caps[0]) - keep = TRUE; + keep = true; } else { if (CAP_TO_MASK_1 (cap) & caps[1]) - keep = TRUE; + keep = true; } if (keep && do_set_ambient) @@ -803,11 +807,11 @@ static void drop_cap_bounding_set (bool drop_all) { if (!drop_all) - prctl_caps (requested_caps, TRUE, FALSE); + prctl_caps (requested_caps, true, false); else { uint32_t no_caps[2] = {0, 0}; - prctl_caps (no_caps, TRUE, FALSE); + prctl_caps (no_caps, true, false); } } @@ -816,13 +820,13 @@ set_ambient_capabilities (void) { if (is_privileged) return; - prctl_caps (requested_caps, FALSE, TRUE); + prctl_caps (requested_caps, false, true); } /* This acquires the privileges that the bwrap will need it to work. * If bwrap is not setuid, then this does nothing, and it relies on * unprivileged user namespaces to be used. This case is - * "is_privileged = FALSE". + * "is_privileged = false". * * If bwrap is setuid, then we do things in phases. * The first part is run as euid 0, but with fsuid as the real user. @@ -846,7 +850,7 @@ acquire_privs (void) if (euid != 0) die ("Unexpected setuid user %d, should be 0", euid); - is_privileged = TRUE; + is_privileged = true; /* We want to keep running as euid=0 until at the clone() * operation because doing so will make the user namespace be * owned by root, which makes it not ptrace:able by the user as @@ -867,7 +871,7 @@ acquire_privs (void) die ("Unable to set fsuid (was %d)", (int)new_fsuid); /* We never need capabilities after execve(), so lets drop everything from the bounding set */ - drop_cap_bounding_set (TRUE); + drop_cap_bounding_set (true); /* Keep only the required capabilities for setup */ set_required_caps (); @@ -904,7 +908,7 @@ switch_to_user_with_privs (void) { /* If we're in a new user namespace, we got back the bounding set, clear it again */ if (opt_unshare_user || opt_userns_fd != -1) - drop_cap_bounding_set (FALSE); + drop_cap_bounding_set (false); /* If we switched to a new user namespace it may allow other uids/gids, so switch to the target one */ if (opt_userns_fd != -1) @@ -1061,10 +1065,10 @@ privileged_op (int privileged_op_socket, if (arg2 != NULL) strcpy ((char *) buffer + arg2_offset, arg2); - if (write (privileged_op_socket, buffer, buffer_size) != (ssize_t)buffer_size) + if (TEMP_FAILURE_RETRY (write (privileged_op_socket, buffer, buffer_size)) != (ssize_t)buffer_size) die ("Can't write to privileged_op_socket"); - if (read (privileged_op_socket, buffer, 1) != 1) + if (TEMP_FAILURE_RETRY (read (privileged_op_socket, buffer, 1)) != 1) die ("Can't read from privileged_op_socket"); return; @@ -1149,6 +1153,20 @@ privileged_op (int privileged_op_socket, die_with_mount_error ("Can't mount mqueue on %s", arg1); break; + case PRIV_SEP_OP_OVERLAY_MOUNT: + if (mount ("overlay", arg2, "overlay", MS_MGC_VAL, arg1) != 0) + { + /* The standard message for ELOOP, "Too many levels of symbolic + * links", is not helpful here. */ + if (errno == ELOOP) + die ("Can't make overlay mount on %s with options %s: " + "Overlay directories may not overlap", + arg2, arg1); + die_with_mount_error ("Can't make overlay mount on %s with options %s", + arg2, arg1); + } + break; + case PRIV_SEP_OP_SET_HOSTNAME: /* This is checked at the start, but lets verify it here in case something manages to send hacked priv-sep operation requests. */ @@ -1172,6 +1190,7 @@ setup_newroot (bool unshare_pid, int privileged_op_socket) { SetupOp *op; + int tmp_overlay_idx = 0; for (op = ops; op != NULL; op = op->next) { @@ -1211,7 +1230,7 @@ setup_newroot (bool unshare_pid, parent_mode &= ~0005U; dest = get_newroot_path (op->dest); - if (mkdir_with_parents (dest, parent_mode, FALSE) != 0) + if (mkdir_with_parents (dest, parent_mode, false) != 0) die_with_error ("Can't mkdir parents for %s", op->dest); } @@ -1259,6 +1278,47 @@ setup_newroot (bool unshare_pid, break; + case SETUP_OVERLAY_MOUNT: + case SETUP_RO_OVERLAY_MOUNT: + case SETUP_TMP_OVERLAY_MOUNT: + { + StringBuilder sb = {0}; + bool multi_src = false; + + if (ensure_dir (dest, 0755) != 0) + die_with_error ("Can't mkdir %s", op->dest); + + if (op->source != NULL) + { + strappend (&sb, "upperdir=/oldroot"); + strappend_escape_for_mount_options (&sb, op->source); + strappend (&sb, ",workdir=/oldroot"); + op = op->next; + strappend_escape_for_mount_options (&sb, op->source); + strappend (&sb, ","); + } + else if (op->type == SETUP_TMP_OVERLAY_MOUNT) + strappendf (&sb, "upperdir=/tmp-overlay-upper-%1$d,workdir=/tmp-overlay-work-%1$d,", + tmp_overlay_idx++); + + strappend (&sb, "lowerdir=/oldroot"); + while (op->next != NULL && op->next->type == SETUP_OVERLAY_SRC) + { + op = op->next; + if (multi_src) + strappend (&sb, ":/oldroot"); + strappend_escape_for_mount_options (&sb, op->source); + multi_src = true; + } + + strappend (&sb, ",userxattr"); + + privileged_op (privileged_op_socket, + PRIV_SEP_OP_OVERLAY_MOUNT, 0, 0, 0, sb.str, dest); + free (sb.str); + } + break; + case SETUP_REMOUNT_RO_NO_RECURSIVE: privileged_op (privileged_op_socket, PRIV_SEP_OP_REMOUNT_RO_NO_RECURSIVE, 0, 0, 0, NULL, dest); @@ -1522,6 +1582,7 @@ setup_newroot (bool unshare_pid, op->dest, NULL); break; + case SETUP_OVERLAY_SRC: /* handled by SETUP_OVERLAY_MOUNT */ default: die ("Unexpected type %d", op->type); } @@ -1564,6 +1625,8 @@ resolve_symlinks_in_ops (void) case SETUP_RO_BIND_MOUNT: case SETUP_DEV_BIND_MOUNT: case SETUP_BIND_MOUNT: + case SETUP_OVERLAY_SRC: + case SETUP_OVERLAY_MOUNT: old_source = op->source; op->source = realpath (old_source, NULL); if (op->source == NULL) @@ -1575,6 +1638,8 @@ resolve_symlinks_in_ops (void) } break; + case SETUP_RO_OVERLAY_MOUNT: + case SETUP_TMP_OVERLAY_MOUNT: case SETUP_MOUNT_PROC: case SETUP_MOUNT_DEV: case SETUP_MOUNT_TMPFS: @@ -1666,6 +1731,32 @@ warn_only_last_option (const char *name) warn ("Only the last %s option will take effect", name); } +static void +make_setup_overlay_src_ops (const char *const *const argv) +{ + /* SETUP_OVERLAY_SRC is unlike other SETUP_* ops in that it exists to hold + * data for SETUP_{,TMP_,RO_}OVERLAY_MOUNT ops, not to be its own operation. + * This lets us reuse existing code paths to handle resolving the realpaths + * of each source, as no other operations involve multiple sources the way + * the *_OVERLAY_MOUNT ops do. + * + * While the --overlay-src arguments are expected to (directly) precede the + * --overlay argument, in bottom-to-top order, the SETUP_OVERLAY_SRC ops + * follow their corresponding *_OVERLAY_MOUNT op, in top-to-bottom order + * (the order in which overlayfs will want them). They are handled specially + * in setup_new_root () during the processing of *_OVERLAY_MOUNT. + */ + int i; + SetupOp *op; + + for (i = 1; i <= next_overlay_src_count; i++) + { + op = setup_op_new (SETUP_OVERLAY_SRC); + op->source = argv[1 - 2 * i]; + } + next_overlay_src_count = 0; +} + static void parse_args_recurse (int *argcp, const char ***argvp, @@ -1761,7 +1852,7 @@ parse_args_recurse (int *argcp, } data_argv_copy = data_argv; /* Don't change data_argv, we need to free it */ - parse_args_recurse (&data_argc, &data_argv_copy, TRUE, total_parsed_argc_p); + parse_args_recurse (&data_argc, &data_argv_copy, true, total_parsed_argc_p); argv += 1; argc -= 1; @@ -1778,6 +1869,10 @@ parse_args_recurse (int *argcp, argv++; argc--; } + else if (strcmp (arg, "--level-prefix") == 0) + { + bwrap_level_prefix = true; + } else if (strcmp (arg, "--unshare-all") == 0) { /* Keep this in order with the older (legacy) --unshare arguments, @@ -1786,45 +1881,45 @@ parse_args_recurse (int *argcp, */ opt_unshare_user_try = opt_unshare_ipc = opt_unshare_pid = opt_unshare_uts = opt_unshare_cgroup_try = - opt_unshare_net = TRUE; + opt_unshare_net = true; } /* Begin here the older individual --unshare variants */ else if (strcmp (arg, "--unshare-user") == 0) { - opt_unshare_user = TRUE; + opt_unshare_user = true; } else if (strcmp (arg, "--unshare-user-try") == 0) { - opt_unshare_user_try = TRUE; + opt_unshare_user_try = true; } else if (strcmp (arg, "--unshare-ipc") == 0) { - opt_unshare_ipc = TRUE; + opt_unshare_ipc = true; } else if (strcmp (arg, "--unshare-pid") == 0) { - opt_unshare_pid = TRUE; + opt_unshare_pid = true; } else if (strcmp (arg, "--unshare-net") == 0) { - opt_unshare_net = TRUE; + opt_unshare_net = true; } else if (strcmp (arg, "--unshare-uts") == 0) { - opt_unshare_uts = TRUE; + opt_unshare_uts = true; } else if (strcmp (arg, "--unshare-cgroup") == 0) { - opt_unshare_cgroup = TRUE; + opt_unshare_cgroup = true; } else if (strcmp (arg, "--unshare-cgroup-try") == 0) { - opt_unshare_cgroup_try = TRUE; + opt_unshare_cgroup_try = true; } /* Begin here the newer --share variants */ else if (strcmp (arg, "--share-net") == 0) { - opt_unshare_net = FALSE; + opt_unshare_net = false; } /* End --share variants, other arguments begin */ else if (strcmp (arg, "--chdir") == 0) @@ -1841,11 +1936,11 @@ parse_args_recurse (int *argcp, } else if (strcmp (arg, "--disable-userns") == 0) { - opt_disable_userns = TRUE; + opt_disable_userns = true; } else if (strcmp (arg, "--assert-userns-disabled") == 0) { - opt_assert_userns_disabled = TRUE; + opt_assert_userns_disabled = true; } else if (strcmp (arg, "--remount-ro") == 0) { @@ -1927,6 +2022,76 @@ parse_args_recurse (int *argcp, argv += 2; argc -= 2; } + else if (strcmp (arg, "--overlay-src") == 0) + { + if (is_privileged) + die ("The --overlay-src option is not permitted in setuid mode"); + + next_overlay_src_count++; + + argv += 1; + argc -= 1; + } + else if (strcmp (arg, "--overlay") == 0) + { + SetupOp *workdir_op; + + if (is_privileged) + die ("The --overlay option is not permitted in setuid mode"); + + if (argc < 4) + die ("--overlay takes three arguments"); + + if (next_overlay_src_count < 1) + die ("--overlay requires at least one --overlay-src"); + + op = setup_op_new (SETUP_OVERLAY_MOUNT); + op->source = argv[1]; + workdir_op = setup_op_new (SETUP_OVERLAY_SRC); + workdir_op->source = argv[2]; + op->dest = argv[3]; + make_setup_overlay_src_ops (argv); + + argv += 3; + argc -= 3; + } + else if (strcmp (arg, "--tmp-overlay") == 0) + { + if (is_privileged) + die ("The --tmp-overlay option is not permitted in setuid mode"); + + if (argc < 2) + die ("--tmp-overlay takes an argument"); + + if (next_overlay_src_count < 1) + die ("--tmp-overlay requires at least one --overlay-src"); + + op = setup_op_new (SETUP_TMP_OVERLAY_MOUNT); + op->dest = argv[1]; + make_setup_overlay_src_ops (argv); + opt_tmp_overlay_count++; + + argv += 1; + argc -= 1; + } + else if (strcmp (arg, "--ro-overlay") == 0) + { + if (is_privileged) + die ("The --ro-overlay option is not permitted in setuid mode"); + + if (argc < 2) + die ("--ro-overlay takes an argument"); + + if (next_overlay_src_count < 2) + die ("--ro-overlay requires at least two --overlay-src"); + + op = setup_op_new (SETUP_RO_OVERLAY_MOUNT); + op->dest = argv[1]; + make_setup_overlay_src_ops (argv); + + argv += 1; + argc -= 1; + } else if (strcmp (arg, "--proc") == 0) { if (argc < 2) @@ -1975,7 +2140,7 @@ parse_args_recurse (int *argcp, op = setup_op_new (SETUP_MOUNT_DEV); op->dest = argv[1]; - opt_needs_devpts = TRUE; + opt_needs_devpts = true; argv += 1; argc -= 1; @@ -2425,15 +2590,15 @@ parse_args_recurse (int *argcp, } else if (strcmp (arg, "--new-session") == 0) { - opt_new_session = TRUE; + opt_new_session = true; } else if (strcmp (arg, "--die-with-parent") == 0) { - opt_die_with_parent = TRUE; + opt_die_with_parent = true; } else if (strcmp (arg, "--as-pid-1") == 0) { - opt_as_pid_1 = TRUE; + opt_as_pid_1 = true; } else if (strcmp (arg, "--cap-add") == 0) { @@ -2441,7 +2606,7 @@ parse_args_recurse (int *argcp, if (argc < 2) die ("--cap-add takes an argument"); - opt_cap_add_or_drop_used = TRUE; + opt_cap_add_or_drop_used = true; if (strcasecmp (argv[1], "ALL") == 0) { @@ -2467,7 +2632,7 @@ parse_args_recurse (int *argcp, if (argc < 2) die ("--cap-drop takes an argument"); - opt_cap_add_or_drop_used = TRUE; + opt_cap_add_or_drop_used = true; if (strcasecmp (argv[1], "ALL") == 0) { @@ -2596,6 +2761,10 @@ parse_args_recurse (int *argcp, if (!is_modifier_option(arg) && next_size_arg != 0) die ("--size must be followed by --tmpfs"); + /* Similarly for --overlay-src. */ + if (strcmp (arg, "--overlay-src") != 0 && next_overlay_src_count > 0) + die ("--overlay-src must be followed by another --overlay-src or one of --overlay, --tmp-overlay, or --ro-overlay"); + argv++; argc--; } @@ -2610,7 +2779,10 @@ parse_args (int *argcp, { int total_parsed_argc = *argcp; - parse_args_recurse (argcp, argvp, FALSE, &total_parsed_argc); + parse_args_recurse (argcp, argvp, false, &total_parsed_argc); + + if (next_overlay_src_count > 0) + die ("--overlay-src must be followed by another --overlay-src or one of --overlay, --tmp-overlay, or --ro-overlay"); } static void @@ -2644,7 +2816,7 @@ namespace_ids_read (pid_t pid) NsInfo *info; dir = xasprintf ("%d/ns", pid); - ns_fd = openat (proc_fd, dir, O_PATH); + ns_fd = TEMP_FAILURE_RETRY (openat (proc_fd, dir, O_PATH)); if (ns_fd < 0) die_with_error ("open /proc/%s/ns failed", dir); @@ -2656,7 +2828,7 @@ namespace_ids_read (pid_t pid) int r; /* if we don't unshare this ns, ignore it */ - if (do_unshare && *do_unshare == FALSE) + if (do_unshare && *do_unshare == false) continue; r = fstatat (ns_fd, info->name, &st, 0); @@ -2691,7 +2863,7 @@ namespace_ids_write (int fd, output = xasprintf (",%s\"%s-namespace\": %ju", indent, info->name, nsid); - dump_info (fd, output, TRUE); + dump_info (fd, output, true); } } @@ -2716,6 +2888,7 @@ main (int argc, cleanup_free char *args_data UNUSED = NULL; int intermediate_pids_sockets[2] = {-1, -1}; const char *exec_path = NULL; + int i; /* Handle --version early on before we try to acquire/drop * any capabilities so it works in a build environment; @@ -2799,18 +2972,18 @@ main (int argc, /* We have to do this if we weren't installed setuid (and we're not * root), so let's just DWIM */ if (!is_privileged && getuid () != 0 && opt_userns_fd == -1) - opt_unshare_user = TRUE; + opt_unshare_user = true; #ifdef ENABLE_REQUIRE_USERNS /* In this build option, we require userns. */ if (is_privileged && getuid () != 0 && opt_userns_fd == -1) - opt_unshare_user = TRUE; + opt_unshare_user = true; #endif if (opt_unshare_user_try && stat ("/proc/self/ns/user", &sbuf) == 0) { - bool disabled = FALSE; + bool disabled = false; /* RHEL7 has a kernel module parameter that lets you enable user namespaces */ if (stat ("/sys/module/user_namespace/parameters/enable", &sbuf) == 0) @@ -2818,7 +2991,7 @@ main (int argc, cleanup_free char *enable = NULL; enable = load_file_at (AT_FDCWD, "/sys/module/user_namespace/parameters/enable"); if (enable != NULL && enable[0] == 'N') - disabled = TRUE; + disabled = true; } /* Check for max_user_namespaces */ @@ -2827,21 +3000,21 @@ main (int argc, cleanup_free char *max_user_ns = NULL; max_user_ns = load_file_at (AT_FDCWD, "/proc/sys/user/max_user_namespaces"); if (max_user_ns != NULL && strcmp(max_user_ns, "0\n") == 0) - disabled = TRUE; + disabled = true; } /* Debian lets you disable *unprivileged* user namespaces. However this is not - a problem if we're privileged, and if we're not opt_unshare_user is TRUE + a problem if we're privileged, and if we're not opt_unshare_user is true already, and there is not much we can do, its just a non-working setup. */ if (!disabled) - opt_unshare_user = TRUE; + opt_unshare_user = true; } if (argc <= 0) usage (EXIT_FAILURE, stderr); - __debug__ (("Creating root mount point\n")); + debug ("Creating root mount point"); if (opt_sandbox_uid == (uid_t)-1) opt_sandbox_uid = real_uid; @@ -2865,7 +3038,7 @@ main (int argc, /* We need to read stuff from proc during the pivot_root dance, etc. Lets keep a fd to it open */ - proc_fd = open ("/proc", O_PATH); + proc_fd = TEMP_FAILURE_RETRY (open ("/proc", O_PATH)); if (proc_fd == -1) die_with_error ("Can't open /proc"); @@ -2877,7 +3050,7 @@ main (int argc, * access ourselves. */ base_path = "/tmp"; - __debug__ (("creating new namespace\n")); + debug ("creating new namespace"); if (opt_unshare_pid && !opt_as_pid_1) { @@ -2993,7 +3166,7 @@ main (int argc, */ write_uid_gid_map (ns_uid, real_uid, ns_gid, real_gid, - pid, TRUE, opt_needs_devpts); + pid, true, opt_needs_devpts); } /* Initial launched process, wait for pid 1 or exec:ed command to exit */ @@ -3002,7 +3175,7 @@ main (int argc, die_with_error ("Setting userns2 failed"); /* We don't need any privileges in the launcher, drop them immediately. */ - drop_privs (FALSE, FALSE); + drop_privs (false, false); /* Optionally bind our lifecycle to that of the parent */ handle_die_with_parent (); @@ -3010,17 +3183,17 @@ main (int argc, if (opt_info_fd != -1) { cleanup_free char *output = xasprintf ("{\n \"child-pid\": %i", pid); - dump_info (opt_info_fd, output, TRUE); - namespace_ids_write (opt_info_fd, FALSE); - dump_info (opt_info_fd, "\n}\n", TRUE); + dump_info (opt_info_fd, output, true); + namespace_ids_write (opt_info_fd, false); + dump_info (opt_info_fd, "\n}\n", true); close (opt_info_fd); } if (opt_json_status_fd != -1) { cleanup_free char *output = xasprintf ("{ \"child-pid\": %i", pid); - dump_info (opt_json_status_fd, output, TRUE); - namespace_ids_write (opt_json_status_fd, TRUE); - dump_info (opt_json_status_fd, " }\n", TRUE); + dump_info (opt_json_status_fd, output, true); + namespace_ids_write (opt_json_status_fd, true); + dump_info (opt_json_status_fd, " }\n", true); } if (opt_userns_block_fd != -1) @@ -3032,7 +3205,7 @@ main (int argc, /* Let child run now that the uid maps are set up */ val = 1; - res = write (child_wait_fd, &val, 8); + res = TEMP_FAILURE_RETRY (write (child_wait_fd, &val, 8)); /* Ignore res, if e.g. the child died and closed child_wait_fd we don't want to error out here */ close (child_wait_fd); @@ -3116,7 +3289,7 @@ main (int argc, write_uid_gid_map (ns_uid, real_uid, ns_gid, real_gid, - -1, TRUE, FALSE); + -1, true, false); } old_umask = umask (0); @@ -3156,6 +3329,19 @@ main (int argc, if (mkdir ("oldroot", 0755)) die_with_error ("Creating oldroot failed"); + for (i = 0; i < opt_tmp_overlay_count; i++) + { + char *dirname; + dirname = xasprintf ("tmp-overlay-upper-%d", i); + if (mkdir (dirname, 0755)) + die_with_error ("Creating --tmp-overlay upperdir failed"); + free (dirname); + dirname = xasprintf ("tmp-overlay-work-%d", i); + if (mkdir (dirname, 0755)) + die_with_error ("Creating --tmp-overlay workdir failed"); + free (dirname); + } + if (pivot_root (base_path, "oldroot")) die_with_error ("pivot_root"); @@ -3177,7 +3363,7 @@ main (int argc, if (child == 0) { /* Unprivileged setup process */ - drop_privs (FALSE, TRUE); + drop_privs (false, true); close (privsep_sockets[0]); setup_newroot (opt_unshare_pid, privsep_sockets[1]); exit (0); @@ -3199,12 +3385,12 @@ main (int argc, op = read_priv_sec_op (unpriv_socket, buffer, sizeof (buffer), &flags, &perms, &size_arg, &arg1, &arg2); privileged_op (-1, op, flags, perms, size_arg, arg1, arg2); - if (write (unpriv_socket, buffer, 1) != 1) + if (TEMP_FAILURE_RETRY (write (unpriv_socket, buffer, 1)) != 1) die ("Can't write to op_socket"); } while (op != PRIV_SEP_OP_DONE); - waitpid (child, &status, 0); + TEMP_FAILURE_RETRY (waitpid (child, &status, 0)); /* Continue post setup */ } } @@ -3228,7 +3414,7 @@ main (int argc, * We're aiming to make /newroot the real root, and get rid of /oldroot. To do * that we need a temporary place to store it before we can unmount it. */ - { cleanup_fd int oldrootfd = open ("/", O_DIRECTORY | O_RDONLY); + { cleanup_fd int oldrootfd = TEMP_FAILURE_RETRY (open ("/", O_DIRECTORY | O_RDONLY)); if (oldrootfd < 0) die_with_error ("can't open /"); if (chdir ("/newroot") != 0) @@ -3276,7 +3462,7 @@ main (int argc, { cleanup_fd int sysctl_fd = -1; - sysctl_fd = openat (proc_fd, "sys/user/max_user_namespaces", O_WRONLY); + sysctl_fd = TEMP_FAILURE_RETRY (openat (proc_fd, "sys/user/max_user_namespaces", O_WRONLY)); if (sysctl_fd < 0) die_with_error ("cannot open /proc/sys/user/max_user_namespaces"); @@ -3289,11 +3475,11 @@ main (int argc, die_with_error ("unshare user ns"); /* We're in a new user namespace, we got back the bounding set, clear it again */ - drop_cap_bounding_set (FALSE); + drop_cap_bounding_set (false); write_uid_gid_map (opt_sandbox_uid, ns_uid, opt_sandbox_gid, ns_gid, - -1, FALSE, FALSE); + -1, false, false); } if (opt_disable_userns || opt_assert_userns_disabled) @@ -3306,7 +3492,7 @@ main (int argc, } /* All privileged ops are done now, so drop caps we don't need */ - drop_privs (!is_privileged, TRUE); + drop_privs (!is_privileged, true); if (opt_block_fd != -1) { @@ -3354,7 +3540,7 @@ main (int argc, if (label_exec (opt_exec_label) == -1) die_with_error ("label_exec %s", argv[0]); - __debug__ (("forking for child\n")); + debug ("forking for child"); if (!opt_as_pid_1 && (opt_unshare_pid || lock_files != NULL || opt_sync_fd != -1)) { @@ -3370,7 +3556,7 @@ main (int argc, if (pid != 0) { - drop_all_caps (FALSE); + drop_all_caps (false); /* Close fds in pid 1, except stdio and optionally event_fd (for syncing pid 2 lifetime with monitor_child) and @@ -3392,7 +3578,7 @@ main (int argc, } } - __debug__ (("launch executable %s\n", argv[0])); + debug ("launch executable %s", argv[0]); if (proc_fd != -1) close (proc_fd); diff --git a/subprojects/bubblewrap/bwrap.xml b/subprojects/bubblewrap/bwrap.xml index 3bb50820de2668f190098d67128296b42a4ad8d2..f379f0fa46a98ddb648efef6ca9e49e29b7e8c4f 100644 --- a/subprojects/bubblewrap/bwrap.xml +++ b/subprojects/bubblewrap/bwrap.xml @@ -96,6 +96,26 @@ <term><option>--argv0 <arg choice="plain">VALUE</arg></option></term> <listitem><para>Set argv[0] to the value <arg choice="plain">VALUE</arg> before running the program</para></listitem> </varlistentry> + <varlistentry> + <term><option>--level-prefix</option></term> + <listitem> + <para> + Prefix each line of diagnostic output with a numeric severity + level enclosed in angle brackets. + The severity levels used are based on the constants used by + <citerefentry><refentrytitle>syslog</refentrytitle><manvolnum>3</manvolnum></citerefentry>: + for example, <literal><4></literal> indicates a warning, + because <literal>LOG_WARNING</literal> has numeric value 4. + Numbers smaller than 4 indicate fatal errors, and numbers larger + than 4 indicate informational messages. + These prefixes can be parsed by tools compatible with + <literal>logger --prio-prefix</literal> (see + <citerefentry><refentrytitle>logger</refentrytitle><manvolnum>1</manvolnum></citerefentry>) + or <literal>systemd-cat --level-prefix=1</literal> (see + <citerefentry><refentrytitle>systemd-cat</refentrytitle><manvolnum>1</manvolnum></citerefentry>). + </para> + </listitem> + </varlistentry> </variablelist> <para>Options related to kernel namespaces:</para> <variablelist> @@ -297,6 +317,86 @@ <term><option>--remount-ro <arg choice="plain">DEST</arg></option></term> <listitem><para>Remount the path <arg choice="plain">DEST</arg> as readonly. It works only on the specified mount point, without changing any other mount point under the specified path</para></listitem> </varlistentry> + <varlistentry> + <term><option>--overlay-src <arg choice="plain">SRC</arg></option></term> + <listitem> + <para> + This option does nothing on its own, and must be followed by one of + the other <literal>overlay</literal> options. It specifies a host + path from which files should be read if they aren't present in a + higher layer. + </para> + <para> + This option can be used multiple times to provide multiple sources. + The sources are overlaid in the order given, with the first source on + the command line at the bottom of the stack: if a given path to be + read exists in more than one source, the file is read from the last + such source specified. + </para> + <para> + (For readers familiar with overlayfs, note that this is the + reverse of the order used by the kernel's <literal>lowerdir</literal> + mount option.) + </para> + </listitem> + </varlistentry> + <varlistentry> + <term><option>--overlay <arg choice="plain">RWSRC</arg> <arg choice="plain">WORKDIR</arg> <arg choice="plain">DEST</arg></option></term> + </varlistentry> + <varlistentry> + <term><option>--tmp-overlay <arg choice="plain">DEST</arg></option></term> + </varlistentry> + <varlistentry> + <term><option>--ro-overlay <arg choice="plain">DEST</arg></option></term> + <listitem> + <para> + Use overlayfs to mount the host paths specified by + <arg choice="plain">RWSRC</arg> and all immediately preceding + <option>--overlay-src</option> on <arg choice="plain">DEST</arg>. + <arg choice="plain">DEST</arg> will contain the union of all the files + in all the layers. + </para> + <para> + With <arg choice="plain">--overlay</arg> all writes will go to + <arg choice="plain">RWSRC</arg>. Reads will come preferentially from + <arg choice="plain">RWSRC</arg>, and then from any + <option>--overlay-src</option> paths. + <arg choice="plain">WORKDIR</arg> must be an empty directory on the + same filesystem as <arg choice="plain">RWSRC</arg>, and is used + internally by the kernel. + </para> + <para> + With <arg choice="plain">--tmp-overlay</arg> all writes will go to + the tmpfs that hosts the sandbox root, in a location not accessible + from either the host or the child process. Writes will therefore not + be persisted across multiple runs. + </para> + <para> + With <arg choice="plain">--ro-overlay</arg> the filesystem will be + mounted read-only. This option requires at least two + <option>--overlay-src</option> to precede it. + </para> + <para> + None of these options are available in the setuid version of + bubblewrap. Using <arg choice="plain">--ro-overlay</arg> or providing + more than one <option>--overlay-src</option> requires a Linux kernel + version of 4.0 or later. + </para> + <para> + Due to limitations of overlayfs, no host directory given via + <arg choice="plain">--overlay-src</arg> or + <arg choice="plain">--overlay</arg> may be an ancestor of another, + after resolving symlinks. Depending on version, the Linux kernel may + or may not enforce this, but if not then overlayfs's behavior is + undefined. + </para> + <para> + For more information see the Overlay Filesystem documentation in the + Linux kernel at + https://www.kernel.org/doc/Documentation/filesystems/overlayfs.txt + </para> + </listitem> + </varlistentry> <varlistentry> <term><option>--proc <arg choice="plain">DEST</arg></option></term> <listitem><para>Mount procfs on <arg choice="plain">DEST</arg></para></listitem> diff --git a/subprojects/bubblewrap/ci/builddeps.sh b/subprojects/bubblewrap/ci/builddeps.sh index 4accd2e315506f8ead4db1994095640ec3b77350..5bca51e255dc2148d660df24d72f98302b8841e1 100755 --- a/subprojects/bubblewrap/ci/builddeps.sh +++ b/subprojects/bubblewrap/ci/builddeps.sh @@ -56,8 +56,6 @@ done if dpkg-vendor --derives-from Debian; then apt-get -y update apt-get -q -y install \ - autoconf \ - automake \ build-essential \ docbook-xml \ docbook-xsl \ @@ -81,8 +79,6 @@ if command -v yum; then yum -y install \ 'pkgconfig(libselinux)' \ /usr/bin/eu-readelf \ - autoconf \ - automake \ docbook-style-xsl \ gcc \ git \ diff --git a/subprojects/bubblewrap/completions/bash/bwrap b/subprojects/bubblewrap/completions/bash/bwrap index e6d28462b58df64833896d59cb13abfd4fee60ae..e7a523c2850036b4e882e795e7b7aab5603087e8 100644 --- a/subprojects/bubblewrap/completions/bash/bwrap +++ b/subprojects/bubblewrap/completions/bash/bwrap @@ -51,15 +51,19 @@ _bwrap() { --hostname --info-fd --lock-file + --overlay + --overlay-src --perms --proc --remount-ro --ro-bind + --ro-overlay --seccomp --setenv --size --symlink --sync-fd + --tmp-overlay --uid --unsetenv --userns-block-fd diff --git a/subprojects/bubblewrap/completions/bash/meson.build b/subprojects/bubblewrap/completions/bash/meson.build index 1dd946f2ac4c905f1694903ad0d99589bd015d40..323ad3973bcacc41bfae7f0a864f6fc9005c7ec3 100644 --- a/subprojects/bubblewrap/completions/bash/meson.build +++ b/subprojects/bubblewrap/completions/bash/meson.build @@ -13,7 +13,6 @@ if bash_completion_dir == '' default_value: '', pkgconfig: 'completionsdir', pkgconfig_define: [ - 'prefix', get_option('prefix'), 'datadir', get_option('prefix') / get_option('datadir'), ], ) diff --git a/subprojects/bubblewrap/configure.ac b/subprojects/bubblewrap/configure.ac deleted file mode 100644 index 7fe9c0448119baf1b02aed9463a4524ee9ef0626..0000000000000000000000000000000000000000 --- a/subprojects/bubblewrap/configure.ac +++ /dev/null @@ -1,158 +0,0 @@ -AC_PREREQ([2.63]) -AC_INIT([bubblewrap], [0.10.0], [atomic-devel@projectatomic.io]) -AC_CONFIG_HEADER([config.h]) -AC_CONFIG_MACRO_DIR([m4]) -AC_CONFIG_AUX_DIR([build-aux]) - -AC_USE_SYSTEM_EXTENSIONS - -AM_INIT_AUTOMAKE([1.11 -Wno-portability foreign no-define tar-ustar no-dist-gzip dist-xz]) -AM_MAINTAINER_MODE([enable]) -AM_SILENT_RULES([yes]) - -AC_SYS_LARGEFILE - -AC_PROG_CC -AM_PROG_CC_C_O -PKG_PROG_PKG_CONFIG([]) - -AC_CHECK_HEADERS([sys/capability.h], [], [AC_MSG_ERROR([*** POSIX caps headers not found])]) - -AC_ARG_ENABLE(man, - [AS_HELP_STRING([--enable-man], - [generate man pages [default=auto]])],, - enable_man=maybe) - -AS_IF([test "$enable_man" != no], [ - AC_PATH_PROG([XSLTPROC], [xsltproc], []) - AS_IF([test -z "$XSLTPROC"], [ - AS_IF([test "$enable_man" = yes], [ - AC_MSG_ERROR([xsltproc is required for --enable-man]) - ]) - enable_man=no - ], [ - enable_man=yes - ]) -]) -AM_CONDITIONAL(ENABLE_MAN, test "$enable_man" != no) - -AC_ARG_WITH([bash-completion-dir], - AS_HELP_STRING([--with-bash-completion-dir[=PATH]], - [Install the bash auto-completion script in this directory. @<:@default=yes@:>@]), - [], - [with_bash_completion_dir=yes]) - -AS_IF([test "x$with_bash_completion_dir" = "xyes"], - [ - PKG_CHECK_MODULES([BASH_COMPLETION], [bash-completion >= 2.0], - [BASH_COMPLETION_DIR="`pkg-config --variable=completionsdir bash-completion`"], - [BASH_COMPLETION_DIR="$datadir/bash-completion/completions"]) - ], - [ - BASH_COMPLETION_DIR="$with_bash_completion_dir" - ]) - -AC_SUBST([BASH_COMPLETION_DIR]) -AM_CONDITIONAL([ENABLE_BASH_COMPLETION],[test "x$with_bash_completion_dir" != "xno"]) - -AC_ARG_WITH([zsh-completion-dir], - AS_HELP_STRING([--with-zsh-completion-dir[=PATH]], - [Install the zsh auto-completion script in this directory. @<:@default=yes@:>@]), - [], - [with_zsh_completion_dir=yes]) - -AS_IF([test "x$with_zsh_completion_dir" = "xyes"], - [ZSH_COMPLETION_DIR="$datadir/zsh/site-functions"], - [ZSH_COMPLETION_DIR="$with_zsh_completion_dir"]) - - -AC_SUBST([ZSH_COMPLETION_DIR]) -AM_CONDITIONAL([ENABLE_ZSH_COMPLETION], [test "x$with_zsh_completion_dir" != "xno"]) - -# ------------------------------------------------------------------------------ -have_selinux=no -AC_ARG_ENABLE(selinux, AS_HELP_STRING([--disable-selinux], [Disable optional SELINUX support])) -AS_IF([test "x$enable_selinux" != "xno"], [ - PKG_CHECK_MODULES([SELINUX], [libselinux >= 2.1.9], - [AC_DEFINE(HAVE_SELINUX, 1, [Define if SELinux is available]) - have_selinux=yes - M4_DEFINES="$M4_DEFINES -DHAVE_SELINUX"], - [have_selinux=no]) - AS_IF([test "x$have_selinux" = xno && test "x$enable_selinux" = xyes], - [AC_MSG_ERROR([*** SELinux support requested but libraries not found])]) - PKG_CHECK_MODULES([SELINUX_2_3], [libselinux >= 2.3], - [AC_DEFINE(HAVE_SELINUX_2_3, 1, [Define if SELinux is version >= 2.3])], - [:]) -]) -AM_CONDITIONAL(HAVE_SELINUX, [test "$have_selinux" = "yes"]) - -dnl Keep this in sync with ostree, except remove -Werror=declaration-after-statement -CC_CHECK_FLAGS_APPEND([WARN_CFLAGS], [CFLAGS], [\ - -pipe \ - -Wall \ - -Werror=shadow \ - -Werror=empty-body \ - -Werror=strict-prototypes \ - -Werror=missing-prototypes \ - -Werror=implicit-function-declaration \ - "-Werror=format=2 -Werror=format-security -Werror=format-nonliteral" \ - -Werror=pointer-arith -Werror=init-self \ - -Werror=missing-declarations \ - -Werror=return-type \ - -Werror=overflow \ - -Werror=int-conversion \ - -Werror=parenthesis \ - -Werror=incompatible-pointer-types \ - -Werror=misleading-indentation \ - -Werror=missing-include-dirs -Werror=aggregate-return \ - -Werror=switch-default \ - -Wswitch-enum \ -]) -AC_SUBST(WARN_CFLAGS) - -AC_CHECK_LIB(cap, cap_from_text) - -AS_IF([test "$ac_cv_lib_cap_cap_from_text" != "yes"], - [AC_MSG_ERROR([*** libcap requested but not found])]) - -AC_ARG_WITH(priv-mode, - AS_HELP_STRING([--with-priv-mode=setuid/none], - [How to set privilege-raising during make install]), - [], - [with_priv_mode="none"]) - -AM_CONDITIONAL(PRIV_MODE_SETUID, test "x$with_priv_mode" = "xsetuid") - -AC_ARG_ENABLE(sudo, - AS_HELP_STRING([--enable-sudo],[Use sudo to set privileged mode on binaries during install (only needed if --with-priv-mode used)]), - [SUDO_BIN="sudo"], [SUDO_BIN=""]) -AC_SUBST([SUDO_BIN]) - -AC_ARG_ENABLE(require-userns, - AS_HELP_STRING([--enable-require-userns=yes/no (default no)], - [Require user namespaces by default when installed suid]), - [], - [enable_require_userns="no"]) - -AS_IF([ test "x$enable_require_userns" = "xyes" ], [ - AC_DEFINE(ENABLE_REQUIRE_USERNS, 1, [Define if userns should be used by default in suid mode]) - ]) - -AC_PROG_AWK -AC_REQUIRE_AUX_FILE([tap-driver.sh]) - -AC_CONFIG_FILES([ -Makefile -]) -AC_OUTPUT - -echo " - bubblewrap $VERSION - =================== - - man pages (xsltproc): $enable_man - SELinux: $have_selinux - setuid mode on make install: $with_priv_mode - require default userns: $enable_require_userns - mysteriously satisfying to pop: yes" -echo "" diff --git a/subprojects/bubblewrap/git.mk b/subprojects/bubblewrap/git.mk deleted file mode 100644 index 0a7311592fbf27d1fd7957b1d33aa147260d9c56..0000000000000000000000000000000000000000 --- a/subprojects/bubblewrap/git.mk +++ /dev/null @@ -1,348 +0,0 @@ -# git.mk, a small Makefile to autogenerate .gitignore files -# for autotools-based projects. -# -# Copyright 2009, Red Hat, Inc. -# Copyright 2010,2011,2012,2013 Behdad Esfahbod -# Written by Behdad Esfahbod -# -# Copying and distribution of this file, with or without modification, -# is permitted in any medium without royalty provided the copyright -# notice and this notice are preserved. -# -# The latest version of this file can be downloaded from: -GIT_MK_URL = https://raw.githubusercontent.com/behdad/git.mk/master/git.mk -# -# Bugs, etc, should be reported upstream at: -# https://github.com/behdad/git.mk -# -# To use in your project, import this file in your git repo's toplevel, -# then do "make -f git.mk". This modifies all Makefile.am files in -# your project to -include git.mk. Remember to add that line to new -# Makefile.am files you create in your project, or just rerun the -# "make -f git.mk". -# -# This enables automatic .gitignore generation. If you need to ignore -# more files, add them to the GITIGNOREFILES variable in your Makefile.am. -# But think twice before doing that. If a file has to be in .gitignore, -# chances are very high that it's a generated file and should be in one -# of MOSTLYCLEANFILES, CLEANFILES, DISTCLEANFILES, or MAINTAINERCLEANFILES. -# -# The only case that you need to manually add a file to GITIGNOREFILES is -# when remove files in one of mostlyclean-local, clean-local, distclean-local, -# or maintainer-clean-local make targets. -# -# Note that for files like editor backup, etc, there are better places to -# ignore them. See "man gitignore". -# -# If "make maintainer-clean" removes the files but they are not recognized -# by this script (that is, if "git status" shows untracked files still), send -# me the output of "git status" as well as your Makefile.am and Makefile for -# the directories involved and I'll diagnose. -# -# For a list of toplevel files that should be in MAINTAINERCLEANFILES, see -# Makefile.am.sample in the git.mk git repo. -# -# Don't EXTRA_DIST this file. It is supposed to only live in git clones, -# not tarballs. It serves no useful purpose in tarballs and clutters the -# build dir. -# -# This file knows how to handle autoconf, automake, libtool, gtk-doc, -# gnome-doc-utils, yelp.m4, mallard, intltool, gsettings, dejagnu, appdata, -# appstream. -# -# This makefile provides the following targets: -# -# - all: "make all" will build all gitignore files. -# - gitignore: makes all gitignore files in the current dir and subdirs. -# - .gitignore: make gitignore file for the current dir. -# - gitignore-recurse: makes all gitignore files in the subdirs. -# -# KNOWN ISSUES: -# -# - Recursive configure doesn't work as $(top_srcdir)/git.mk inside the -# submodule doesn't find us. If you have configure.{in,ac} files in -# subdirs, add a proxy git.mk file in those dirs that simply does: -# "include $(top_srcdir)/../git.mk". Add more ..'s to your taste. -# And add those files to git. See vte/gnome-pty-helper/git.mk for -# example. -# - - - -############################################################################### -# Variables user modules may want to add to toplevel MAINTAINERCLEANFILES: -############################################################################### - -# -# Most autotools-using modules should be fine including this variable in their -# toplevel MAINTAINERCLEANFILES: -GITIGNORE_MAINTAINERCLEANFILES_TOPLEVEL = \ - $(srcdir)/aclocal.m4 \ - $(srcdir)/autoscan.log \ - $(srcdir)/configure.scan \ - `AUX_DIR=$(srcdir)/$$(cd $(top_srcdir); $(AUTOCONF) --trace 'AC_CONFIG_AUX_DIR:$$1' ./configure.ac); \ - test "x$$AUX_DIR" = "x$(srcdir)/" && AUX_DIR=$(srcdir); \ - for x in \ - ar-lib \ - compile \ - config.guess \ - config.sub \ - depcomp \ - install-sh \ - ltmain.sh \ - missing \ - mkinstalldirs \ - test-driver \ - ylwrap \ - ; do echo "$$AUX_DIR/$$x"; done` \ - `cd $(top_srcdir); $(AUTOCONF) --trace 'AC_CONFIG_HEADERS:$$1' ./configure.ac | \ - head -n 1 | while read f; do echo "$(srcdir)/$$f.in"; done` -# -# All modules should also be fine including the following variable, which -# removes automake-generated Makefile.in files: -GITIGNORE_MAINTAINERCLEANFILES_MAKEFILE_IN = \ - `cd $(top_srcdir); $(AUTOCONF) --trace 'AC_CONFIG_FILES:$$1' ./configure.ac | \ - while read f; do \ - case $$f in Makefile|*/Makefile) \ - test -f "$(srcdir)/$$f.am" && echo "$(srcdir)/$$f.in";; esac; \ - done` -# -# Modules that use libtool and use AC_CONFIG_MACRO_DIR() may also include this, -# though it's harmless to include regardless. -GITIGNORE_MAINTAINERCLEANFILES_M4_LIBTOOL = \ - `MACRO_DIR=$(srcdir)/$$(cd $(top_srcdir); $(AUTOCONF) --trace 'AC_CONFIG_MACRO_DIR:$$1' ./configure.ac); \ - if test "x$$MACRO_DIR" != "x$(srcdir)/"; then \ - for x in \ - libtool.m4 \ - ltoptions.m4 \ - ltsugar.m4 \ - ltversion.m4 \ - lt~obsolete.m4 \ - ; do echo "$$MACRO_DIR/$$x"; done; \ - fi` - - - -############################################################################### -# Default rule is to install ourselves in all Makefile.am files: -############################################################################### - -git-all: git-mk-install - -git-mk-install: - @echo "Installing git makefile" - @any_failed=; \ - find "`test -z "$(top_srcdir)" && echo . || echo "$(top_srcdir)"`" -name Makefile.am | while read x; do \ - if grep 'include .*/git.mk' $$x >/dev/null; then \ - echo "$$x already includes git.mk"; \ - else \ - failed=; \ - echo "Updating $$x"; \ - { cat $$x; \ - echo ''; \ - echo '-include $$(top_srcdir)/git.mk'; \ - } > $$x.tmp || failed=1; \ - if test x$$failed = x; then \ - mv $$x.tmp $$x || failed=1; \ - fi; \ - if test x$$failed = x; then : else \ - echo "Failed updating $$x"; >&2 \ - any_failed=1; \ - fi; \ - fi; done; test -z "$$any_failed" - -git-mk-update: - wget $(GIT_MK_URL) -O $(top_srcdir)/git.mk - -.PHONY: git-all git-mk-install git-mk-update - - - -############################################################################### -# Actual .gitignore generation: -############################################################################### - -$(srcdir)/.gitignore: Makefile.am $(top_srcdir)/git.mk - @echo "git.mk: Generating $@" - @{ \ - if test "x$(DOC_MODULE)" = x -o "x$(DOC_MAIN_SGML_FILE)" = x; then :; else \ - for x in \ - $(DOC_MODULE)-decl-list.txt \ - $(DOC_MODULE)-decl.txt \ - tmpl/$(DOC_MODULE)-unused.sgml \ - "tmpl/*.bak" \ - $(REPORT_FILES) \ - $(DOC_MODULE).pdf \ - xml html \ - ; do echo "/$$x"; done; \ - FLAVOR=$$(cd $(top_srcdir); $(AUTOCONF) --trace 'GTK_DOC_CHECK:$$2' ./configure.ac); \ - case $$FLAVOR in *no-tmpl*) echo /tmpl;; esac; \ - if echo "$(SCAN_OPTIONS)" | grep -q "\-\-rebuild-types"; then \ - echo "/$(DOC_MODULE).types"; \ - fi; \ - if echo "$(SCAN_OPTIONS)" | grep -q "\-\-rebuild-sections"; then \ - echo "/$(DOC_MODULE)-sections.txt"; \ - fi; \ - if test "$(abs_srcdir)" != "$(abs_builddir)" ; then \ - for x in \ - $(SETUP_FILES) \ - $(DOC_MODULE).types \ - ; do echo "/$$x"; done; \ - fi; \ - fi; \ - if test "x$(DOC_MODULE)$(DOC_ID)" = x -o "x$(DOC_LINGUAS)" = x; then :; else \ - for lc in $(DOC_LINGUAS); do \ - for x in \ - $(if $(DOC_MODULE),$(DOC_MODULE).xml) \ - $(DOC_PAGES) \ - $(DOC_INCLUDES) \ - ; do echo "/$$lc/$$x"; done; \ - done; \ - for x in \ - $(_DOC_OMF_ALL) \ - $(_DOC_DSK_ALL) \ - $(_DOC_HTML_ALL) \ - $(_DOC_MOFILES) \ - $(DOC_H_FILE) \ - "*/.xml2po.mo" \ - "*/*.omf.out" \ - ; do echo /$$x; done; \ - fi; \ - if test "x$(HELP_ID)" = x -o "x$(HELP_LINGUAS)" = x; then :; else \ - for lc in $(HELP_LINGUAS); do \ - for x in \ - $(HELP_FILES) \ - "$$lc.stamp" \ - "$$lc.mo" \ - ; do echo "/$$lc/$$x"; done; \ - done; \ - fi; \ - if test "x$(gsettings_SCHEMAS)" = x; then :; else \ - for x in \ - $(gsettings_SCHEMAS:.xml=.valid) \ - $(gsettings__enum_file) \ - ; do echo "/$$x"; done; \ - fi; \ - if test "x$(appdata_XML)" = x; then :; else \ - for x in \ - $(appdata_XML:.xml=.valid) \ - ; do echo "/$$x"; done; \ - fi; \ - if test "x$(appstream_XML)" = x; then :; else \ - for x in \ - $(appstream_XML:.xml=.valid) \ - ; do echo "/$$x"; done; \ - fi; \ - if test -f $(srcdir)/po/Makefile.in.in; then \ - for x in \ - po/Makefile.in.in \ - po/Makefile.in.in~ \ - po/Makefile.in \ - po/Makefile \ - po/Makevars.template \ - po/POTFILES \ - po/Rules-quot \ - po/stamp-it \ - po/stamp-po \ - po/.intltool-merge-cache \ - "po/*.gmo" \ - "po/*.header" \ - "po/*.mo" \ - "po/*.sed" \ - "po/*.sin" \ - po/$(GETTEXT_PACKAGE).pot \ - intltool-extract.in \ - intltool-merge.in \ - intltool-update.in \ - ; do echo "/$$x"; done; \ - fi; \ - if test -f $(srcdir)/configure; then \ - for x in \ - autom4te.cache \ - configure \ - config.h \ - stamp-h1 \ - libtool \ - config.lt \ - ; do echo "/$$x"; done; \ - fi; \ - if test "x$(DEJATOOL)" = x; then :; else \ - for x in \ - $(DEJATOOL) \ - ; do echo "/$$x.sum"; echo "/$$x.log"; done; \ - echo /site.exp; \ - fi; \ - if test "x$(am__dirstamp)" = x; then :; else \ - echo "$(am__dirstamp)"; \ - fi; \ - if test "x$(LTCOMPILE)" = x -a "x$(LTCXXCOMPILE)" = x -a "x$(GTKDOC_RUN)" = x; then :; else \ - for x in \ - "*.lo" \ - ".libs" "_libs" \ - ; do echo "$$x"; done; \ - fi; \ - for x in \ - .gitignore \ - $(GITIGNOREFILES) \ - $(CLEANFILES) \ - $(PROGRAMS) $(check_PROGRAMS) $(EXTRA_PROGRAMS) \ - $(LIBRARIES) $(check_LIBRARIES) $(EXTRA_LIBRARIES) \ - $(LTLIBRARIES) $(check_LTLIBRARIES) $(EXTRA_LTLIBRARIES) \ - so_locations \ - $(MOSTLYCLEANFILES) \ - $(TEST_LOGS) \ - $(TEST_LOGS:.log=.trs) \ - $(TEST_SUITE_LOG) \ - $(TESTS:=.test) \ - "*.gcda" \ - "*.gcno" \ - $(DISTCLEANFILES) \ - $(am__CONFIG_DISTCLEAN_FILES) \ - $(CONFIG_CLEAN_FILES) \ - TAGS ID GTAGS GRTAGS GSYMS GPATH tags \ - "*.tab.c" \ - $(MAINTAINERCLEANFILES) \ - $(BUILT_SOURCES) \ - $(patsubst %.vala,%.c,$(filter %.vala,$(SOURCES))) \ - $(filter %_vala.stamp,$(DIST_COMMON)) \ - $(filter %.vapi,$(DIST_COMMON)) \ - $(filter $(addprefix %,$(notdir $(patsubst %.vapi,%.h,$(filter %.vapi,$(DIST_COMMON))))),$(DIST_COMMON)) \ - Makefile \ - Makefile.in \ - "*.orig" \ - "*.rej" \ - "*.bak" \ - "*~" \ - ".*.sw[nop]" \ - ".dirstamp" \ - ; do echo "/$$x"; done; \ - for x in \ - "*.$(OBJEXT)" \ - $(DEPDIR) \ - ; do echo "$$x"; done; \ - } | \ - sed "s@^/`echo "$(srcdir)" | sed 's/\(.\)/[\1]/g'`/@/@" | \ - sed 's@/[.]/@/@g' | \ - LC_ALL=C sort | uniq > $@.tmp && \ - mv $@.tmp $@; - -all: $(srcdir)/.gitignore gitignore-recurse-maybe -gitignore: $(srcdir)/.gitignore gitignore-recurse - -gitignore-recurse-maybe: - @for subdir in $(DIST_SUBDIRS); do \ - case " $(SUBDIRS) " in \ - *" $$subdir "*) :;; \ - *) test "$$subdir" = . -o -e "$$subdir/.git" || (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) gitignore || echo "Skipping $$subdir");; \ - esac; \ - done -gitignore-recurse: - @for subdir in $(DIST_SUBDIRS); do \ - test "$$subdir" = . -o -e "$$subdir/.git" || (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) gitignore || echo "Skipping $$subdir"); \ - done - -maintainer-clean: gitignore-clean -gitignore-clean: - -rm -f $(srcdir)/.gitignore - -.PHONY: gitignore-clean gitignore gitignore-recurse gitignore-recurse-maybe diff --git a/subprojects/bubblewrap/m4/attributes.m4 b/subprojects/bubblewrap/m4/attributes.m4 deleted file mode 100644 index 51ac88be61875444b52737cf85141d8de2255adf..0000000000000000000000000000000000000000 --- a/subprojects/bubblewrap/m4/attributes.m4 +++ /dev/null @@ -1,292 +0,0 @@ -dnl Macros to check the presence of generic (non-typed) symbols. -dnl Copyright (c) 2006-2008 Diego Pettenò <flameeyes@gmail.com> -dnl Copyright (c) 2006-2008 xine project -dnl Copyright (c) 2012 Lucas De Marchi <lucas.de.marchi@gmail.com> -dnl -dnl This program is free software; you can redistribute it and/or modify -dnl it under the terms of the GNU General Public License as published by -dnl the Free Software Foundation; either version 2, or (at your option) -dnl any later version. -dnl -dnl This program is distributed in the hope that it will be useful, -dnl but WITHOUT ANY WARRANTY; without even the implied warranty of -dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -dnl GNU General Public License for more details. -dnl -dnl You should have received a copy of the GNU General Public License -dnl along with this program; if not, write to the Free Software -dnl Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -dnl 02110-1301, USA. -dnl -dnl As a special exception, the copyright owners of the -dnl macro gives unlimited permission to copy, distribute and modify the -dnl configure scripts that are the output of Autoconf when processing the -dnl Macro. You need not follow the terms of the GNU General Public -dnl License when using or distributing such scripts, even though portions -dnl of the text of the Macro appear in them. The GNU General Public -dnl License (GPL) does govern all other use of the material that -dnl constitutes the Autoconf Macro. -dnl -dnl This special exception to the GPL applies to versions of the -dnl Autoconf Macro released by this project. When you make and -dnl distribute a modified version of the Autoconf Macro, you may extend -dnl this special exception to the GPL to apply to your modified version as -dnl well. - -dnl Check if FLAG in ENV-VAR is supported by compiler and append it -dnl to WHERE-TO-APPEND variable. Note that we invert -Wno-* checks to -dnl -W* as gcc cannot test for negated warnings. If a C snippet is passed, -dnl use it, otherwise use a simple main() definition that just returns 0. -dnl CC_CHECK_FLAG_APPEND([WHERE-TO-APPEND], [ENV-VAR], [FLAG], [C-SNIPPET]) - -AC_DEFUN([CC_CHECK_FLAG_APPEND], [ - AC_CACHE_CHECK([if $CC supports flag $3 in envvar $2], - AS_TR_SH([cc_cv_$2_$3]), - [eval "AS_TR_SH([cc_save_$2])='${$2}'" - eval "AS_TR_SH([$2])='${cc_save_$2} -Werror `echo "$3" | sed 's/^-Wno-/-W/'`'" - AC_LINK_IFELSE([AC_LANG_SOURCE(ifelse([$4], [], - [int main(void) { return 0; } ], - [$4]))], - [eval "AS_TR_SH([cc_cv_$2_$3])='yes'"], - [eval "AS_TR_SH([cc_cv_$2_$3])='no'"]) - eval "AS_TR_SH([$2])='$cc_save_$2'"]) - - AS_IF([eval test x$]AS_TR_SH([cc_cv_$2_$3])[ = xyes], - [eval "$1='${$1} $3'"]) -]) - -dnl CC_CHECK_FLAGS_APPEND([WHERE-TO-APPEND], [ENV-VAR], [FLAG1 FLAG2], [C-SNIPPET]) -AC_DEFUN([CC_CHECK_FLAGS_APPEND], [ - for flag in [$3]; do - CC_CHECK_FLAG_APPEND([$1], [$2], $flag, [$4]) - done -]) - -dnl Check if the flag is supported by linker (cacheable) -dnl CC_CHECK_LDFLAGS([FLAG], [ACTION-IF-FOUND],[ACTION-IF-NOT-FOUND]) - -AC_DEFUN([CC_CHECK_LDFLAGS], [ - AC_CACHE_CHECK([if $CC supports $1 flag], - AS_TR_SH([cc_cv_ldflags_$1]), - [ac_save_LDFLAGS="$LDFLAGS" - LDFLAGS="$LDFLAGS $1" - AC_LINK_IFELSE([int main() { return 1; }], - [eval "AS_TR_SH([cc_cv_ldflags_$1])='yes'"], - [eval "AS_TR_SH([cc_cv_ldflags_$1])="]) - LDFLAGS="$ac_save_LDFLAGS" - ]) - - AS_IF([eval test x$]AS_TR_SH([cc_cv_ldflags_$1])[ = xyes], - [$2], [$3]) -]) - -dnl define the LDFLAGS_NOUNDEFINED variable with the correct value for -dnl the current linker to avoid undefined references in a shared object. -AC_DEFUN([CC_NOUNDEFINED], [ - dnl We check $host for which systems to enable this for. - AC_REQUIRE([AC_CANONICAL_HOST]) - - case $host in - dnl FreeBSD (et al.) does not complete linking for shared objects when pthreads - dnl are requested, as different implementations are present; to avoid problems - dnl use -Wl,-z,defs only for those platform not behaving this way. - *-freebsd* | *-openbsd*) ;; - *) - dnl First of all check for the --no-undefined variant of GNU ld. This allows - dnl for a much more readable command line, so that people can understand what - dnl it does without going to look for what the heck -z defs does. - for possible_flags in "-Wl,--no-undefined" "-Wl,-z,defs"; do - CC_CHECK_LDFLAGS([$possible_flags], [LDFLAGS_NOUNDEFINED="$possible_flags"]) - break - done - ;; - esac - - AC_SUBST([LDFLAGS_NOUNDEFINED]) -]) - -dnl Check for a -Werror flag or equivalent. -Werror is the GCC -dnl and ICC flag that tells the compiler to treat all the warnings -dnl as fatal. We usually need this option to make sure that some -dnl constructs (like attributes) are not simply ignored. -dnl -dnl Other compilers don't support -Werror per se, but they support -dnl an equivalent flag: -dnl - Sun Studio compiler supports -errwarn=%all -AC_DEFUN([CC_CHECK_WERROR], [ - AC_CACHE_CHECK( - [for $CC way to treat warnings as errors], - [cc_cv_werror], - [CC_CHECK_CFLAGS_SILENT([-Werror], [cc_cv_werror=-Werror], - [CC_CHECK_CFLAGS_SILENT([-errwarn=%all], [cc_cv_werror=-errwarn=%all])]) - ]) -]) - -AC_DEFUN([CC_CHECK_ATTRIBUTE], [ - AC_REQUIRE([CC_CHECK_WERROR]) - AC_CACHE_CHECK([if $CC supports __attribute__(( ifelse([$2], , [$1], [$2]) ))], - AS_TR_SH([cc_cv_attribute_$1]), - [ac_save_CFLAGS="$CFLAGS" - CFLAGS="$CFLAGS $cc_cv_werror" - AC_COMPILE_IFELSE([AC_LANG_SOURCE([$3])], - [eval "AS_TR_SH([cc_cv_attribute_$1])='yes'"], - [eval "AS_TR_SH([cc_cv_attribute_$1])='no'"]) - CFLAGS="$ac_save_CFLAGS" - ]) - - AS_IF([eval test x$]AS_TR_SH([cc_cv_attribute_$1])[ = xyes], - [AC_DEFINE( - AS_TR_CPP([SUPPORT_ATTRIBUTE_$1]), 1, - [Define this if the compiler supports __attribute__(( ifelse([$2], , [$1], [$2]) ))] - ) - $4], - [$5]) -]) - -AC_DEFUN([CC_ATTRIBUTE_CONSTRUCTOR], [ - CC_CHECK_ATTRIBUTE( - [constructor],, - [void __attribute__((constructor)) ctor() { int a; }], - [$1], [$2]) -]) - -AC_DEFUN([CC_ATTRIBUTE_FORMAT], [ - CC_CHECK_ATTRIBUTE( - [format], [format(printf, n, n)], - [void __attribute__((format(printf, 1, 2))) printflike(const char *fmt, ...) { fmt = (void *)0; }], - [$1], [$2]) -]) - -AC_DEFUN([CC_ATTRIBUTE_FORMAT_ARG], [ - CC_CHECK_ATTRIBUTE( - [format_arg], [format_arg(printf)], - [char *__attribute__((format_arg(1))) gettextlike(const char *fmt) { fmt = (void *)0; }], - [$1], [$2]) -]) - -AC_DEFUN([CC_ATTRIBUTE_VISIBILITY], [ - CC_CHECK_ATTRIBUTE( - [visibility_$1], [visibility("$1")], - [void __attribute__((visibility("$1"))) $1_function() { }], - [$2], [$3]) -]) - -AC_DEFUN([CC_ATTRIBUTE_NONNULL], [ - CC_CHECK_ATTRIBUTE( - [nonnull], [nonnull()], - [void __attribute__((nonnull())) some_function(void *foo, void *bar) { foo = (void*)0; bar = (void*)0; }], - [$1], [$2]) -]) - -AC_DEFUN([CC_ATTRIBUTE_UNUSED], [ - CC_CHECK_ATTRIBUTE( - [unused], , - [void some_function(void *foo, __attribute__((unused)) void *bar);], - [$1], [$2]) -]) - -AC_DEFUN([CC_ATTRIBUTE_SENTINEL], [ - CC_CHECK_ATTRIBUTE( - [sentinel], , - [void some_function(void *foo, ...) __attribute__((sentinel));], - [$1], [$2]) -]) - -AC_DEFUN([CC_ATTRIBUTE_DEPRECATED], [ - CC_CHECK_ATTRIBUTE( - [deprecated], , - [void some_function(void *foo, ...) __attribute__((deprecated));], - [$1], [$2]) -]) - -AC_DEFUN([CC_ATTRIBUTE_ALIAS], [ - CC_CHECK_ATTRIBUTE( - [alias], [weak, alias], - [void other_function(void *foo) { } - void some_function(void *foo) __attribute__((weak, alias("other_function")));], - [$1], [$2]) -]) - -AC_DEFUN([CC_ATTRIBUTE_MALLOC], [ - CC_CHECK_ATTRIBUTE( - [malloc], , - [void * __attribute__((malloc)) my_alloc(int n);], - [$1], [$2]) -]) - -AC_DEFUN([CC_ATTRIBUTE_PACKED], [ - CC_CHECK_ATTRIBUTE( - [packed], , - [struct astructure { char a; int b; long c; void *d; } __attribute__((packed));], - [$1], [$2]) -]) - -AC_DEFUN([CC_ATTRIBUTE_CONST], [ - CC_CHECK_ATTRIBUTE( - [const], , - [int __attribute__((const)) twopow(int n) { return 1 << n; } ], - [$1], [$2]) -]) - -AC_DEFUN([CC_FLAG_VISIBILITY], [ - AC_REQUIRE([CC_CHECK_WERROR]) - AC_CACHE_CHECK([if $CC supports -fvisibility=hidden], - [cc_cv_flag_visibility], - [cc_flag_visibility_save_CFLAGS="$CFLAGS" - CFLAGS="$CFLAGS $cc_cv_werror" - CC_CHECK_CFLAGS_SILENT([-fvisibility=hidden], - cc_cv_flag_visibility='yes', - cc_cv_flag_visibility='no') - CFLAGS="$cc_flag_visibility_save_CFLAGS"]) - - AS_IF([test "x$cc_cv_flag_visibility" = "xyes"], - [AC_DEFINE([SUPPORT_FLAG_VISIBILITY], 1, - [Define this if the compiler supports the -fvisibility flag]) - $1], - [$2]) -]) - -AC_DEFUN([CC_FUNC_EXPECT], [ - AC_REQUIRE([CC_CHECK_WERROR]) - AC_CACHE_CHECK([if compiler has __builtin_expect function], - [cc_cv_func_expect], - [ac_save_CFLAGS="$CFLAGS" - CFLAGS="$CFLAGS $cc_cv_werror" - AC_COMPILE_IFELSE([AC_LANG_SOURCE( - [int some_function() { - int a = 3; - return (int)__builtin_expect(a, 3); - }])], - [cc_cv_func_expect=yes], - [cc_cv_func_expect=no]) - CFLAGS="$ac_save_CFLAGS" - ]) - - AS_IF([test "x$cc_cv_func_expect" = "xyes"], - [AC_DEFINE([SUPPORT__BUILTIN_EXPECT], 1, - [Define this if the compiler supports __builtin_expect() function]) - $1], - [$2]) -]) - -AC_DEFUN([CC_ATTRIBUTE_ALIGNED], [ - AC_REQUIRE([CC_CHECK_WERROR]) - AC_CACHE_CHECK([highest __attribute__ ((aligned ())) supported], - [cc_cv_attribute_aligned], - [ac_save_CFLAGS="$CFLAGS" - CFLAGS="$CFLAGS $cc_cv_werror" - for cc_attribute_align_try in 64 32 16 8 4 2; do - AC_COMPILE_IFELSE([AC_LANG_SOURCE([ - int main() { - static char c __attribute__ ((aligned($cc_attribute_align_try))) = 0; - return c; - }])], [cc_cv_attribute_aligned=$cc_attribute_align_try; break]) - done - CFLAGS="$ac_save_CFLAGS" - ]) - - if test "x$cc_cv_attribute_aligned" != "x"; then - AC_DEFINE_UNQUOTED([ATTRIBUTE_ALIGNED_MAX], [$cc_cv_attribute_aligned], - [Define the highest alignment supported]) - fi -]) diff --git a/subprojects/bubblewrap/meson.build b/subprojects/bubblewrap/meson.build index fb3af4ec44d85df22cb800ea2d1d25304e102f37..78678d0973035a04cb77635d34e576fc5e6bc236 100644 --- a/subprojects/bubblewrap/meson.build +++ b/subprojects/bubblewrap/meson.build @@ -1,7 +1,7 @@ project( 'bubblewrap', 'c', - version : '0.10.0', + version : '0.11.0', meson_version : '>=0.49.0', default_options : [ 'warning_level=2', diff --git a/subprojects/bubblewrap/meson_options.txt b/subprojects/bubblewrap/meson_options.txt index 10a0a200203155afc83a427e0cc3dd929296893a..5e25ee86f86ba2c31a61ba10de9402acd3c956d6 100644 --- a/subprojects/bubblewrap/meson_options.txt +++ b/subprojects/bubblewrap/meson_options.txt @@ -45,7 +45,7 @@ option( 'require_userns', type : 'boolean', description : 'require user namespaces by default when installed setuid', - value : 'false', + value : false, ) option( 'selinux', @@ -57,7 +57,7 @@ option( 'tests', type : 'boolean', description : 'build tests', - value : 'true', + value : true, ) option( 'zsh_completion', diff --git a/subprojects/bubblewrap/network.c b/subprojects/bubblewrap/network.c index f6d58a6c0137e6c5c6873295cca808605221b1d5..373d606a0abe3435fd5909ba8910e25fab4140be 100644 --- a/subprojects/bubblewrap/network.c +++ b/subprojects/bubblewrap/network.c @@ -53,8 +53,8 @@ rtnl_send_request (int rtnl_fd, struct sockaddr_nl dst_addr = { AF_NETLINK, 0 }; ssize_t sent; - sent = sendto (rtnl_fd, (void *) header, header->nlmsg_len, 0, - (struct sockaddr *) &dst_addr, sizeof (dst_addr)); + sent = TEMP_FAILURE_RETRY (sendto (rtnl_fd, (void *) header, header->nlmsg_len, 0, + (struct sockaddr *) &dst_addr, sizeof (dst_addr))); if (sent < 0) return -1; @@ -71,7 +71,7 @@ rtnl_read_reply (int rtnl_fd, while (1) { - received = recv (rtnl_fd, buffer, sizeof (buffer), 0); + received = TEMP_FAILURE_RETRY (recv (rtnl_fd, buffer, sizeof (buffer), 0)); if (received < 0) return -1; diff --git a/subprojects/bubblewrap/release-checklist.md b/subprojects/bubblewrap/release-checklist.md index 4385b09bde9ebdfc943d707db68412ac614d25b9..5b2119c8af251dbba456b1df4d840d10a1b1a82a 100644 --- a/subprojects/bubblewrap/release-checklist.md +++ b/subprojects/bubblewrap/release-checklist.md @@ -1,13 +1,13 @@ bubblewrap release checklist ============================ -* Collect release notes -* Update version number in `configure.ac` **and** `meson.build` +* Collect release notes in `NEWS` +* Update version number in `meson.build` and release date in `NEWS` * Commit the changes * `meson dist -C ${builddir}` * Do any final smoke-testing, e.g. update a package, install and test it * `git evtag sign v$VERSION` - * Include the release notes in the tag message + * Include the release notes from `NEWS` in the tag message * `git push --atomic origin main v$VERSION` * https://github.com/containers/bubblewrap/releases/new * Fill in the new version's tag in the "Tag version" box diff --git a/subprojects/bubblewrap/tests/test-run.sh b/subprojects/bubblewrap/tests/test-run.sh index 8d063b57ced0c078853e6c553cb8e32e55d452c1..1003e8c4b1d593dae19cae1d55239085ae112780 100755 --- a/subprojects/bubblewrap/tests/test-run.sh +++ b/subprojects/bubblewrap/tests/test-run.sh @@ -109,7 +109,7 @@ assert_file_has_content json-status.json '"child-pid": [0-9]' assert_file_has_content_literal json-status.json '"exit-code": 42' ok "info and json-status fd" -DATA=$($RUN --proc /proc --unshare-all --info-fd 42 --json-status-fd 43 -- bash -c 'stat -L --format "%n %i" /proc/self/ns/*' 42>info.json 43>json-status.json 2>err.txt) +DATA=$($RUN --proc /proc --unshare-all --info-fd 42 --json-status-fd 43 -- bash -c 'stat -L -c "%n %i" /proc/self/ns/*' 42>info.json 43>json-status.json 2>err.txt) for NS in "ipc" "mnt" "net" "pid" "uts"; do @@ -571,4 +571,122 @@ assert_file_has_content stdout foobar ok "bind-fd" +$RUN --chdir / --chdir / true > stdout 2>&1 +assert_file_has_content stdout '^bwrap: Only the last --chdir option will take effect$' +ok "warning logged for redundant --chdir" + +$RUN --level-prefix --chdir / --chdir / true > stdout 2>&1 +assert_file_has_content stdout '^<4>bwrap: Only the last --chdir option will take effect$' +ok "--level-prefix" + +if test -n "${bwrap_is_suid:-}"; then + ok_skip "no --overlay support" + ok_skip "no --overlay support" + ok_skip "no --tmp-overlay support" + ok_skip "no --ro-overlay support" + ok_skip "no --overlay-src support" +else + mkdir lower1 lower2 upper work + printf 1 > lower1/a + printf 2 > lower1/b + printf 3 > lower2/b + printf 4 > upper/a + + # Check if unprivileged overlayfs is available + if ! unshare -rm mount -t overlay -o lowerdir=lower1,upperdir=upper,workdir=work,userxattr overlay lower2; then + ok_skip "no kernel support for unprivileged overlayfs" + ok_skip "no kernel support for unprivileged overlayfs" + ok_skip "no kernel support for unprivileged overlayfs" + ok_skip "no kernel support for unprivileged overlayfs" + ok_skip "no kernel support for unprivileged overlayfs" + else + + # Test --overlay + if $RUN --overlay upper work /tmp true 2>err.txt; then + assert_not_reached At least one --overlay-src not required + fi + assert_file_has_content err.txt "^bwrap: --overlay requires at least one --overlay-src" + $RUN --overlay-src lower1 --overlay upper work /tmp/x/y/z cat /tmp/x/y/z/a > stdout + assert_file_has_content stdout '^4$' + $RUN --overlay-src lower1 --overlay upper work /tmp/x/y/z cat /tmp/x/y/z/b > stdout + assert_file_has_content stdout '^2$' + $RUN --overlay-src lower1 --overlay-src lower2 --overlay upper work /tmp/x/y/z cat /tmp/x/y/z/a > stdout + assert_file_has_content stdout '^4$' + $RUN --overlay-src lower1 --overlay-src lower2 --overlay upper work /tmp/x/y/z cat /tmp/x/y/z/b > stdout + assert_file_has_content stdout '^3$' + $RUN --overlay-src lower1 --overlay-src lower2 --overlay upper work /tmp/x/y/z sh -c 'printf 5 > /tmp/x/y/z/b; cat /tmp/x/y/z/b' > stdout + assert_file_has_content stdout '^5$' + assert_file_has_content upper/b '^5$' + ok "--overlay" + + # Test --overlay path escaping + # Coincidentally, ":,\ is the face I make contemplating anyone who might + # need this functionality, not that that's going to stop me from supporting + # it. + mkdir 'lower ":,\' 'upper ":,\' 'work ":,\' + printf 1 > 'lower ":,\'/a + $RUN --overlay-src 'lower ":,\' --overlay 'upper ":,\' 'work ":,\' /tmp/x sh -c 'cat /tmp/x/a; printf 2 > /tmp/x/a; cat /tmp/x/a' > stdout + assert_file_has_content stdout '^12$' + assert_file_has_content 'lower ":,\'/a '^1$' + assert_file_has_content 'upper ":,\'/a '^2$' + ok "--overlay path escaping" + + # Test --tmp-overlay + printf 1 > lower1/a + printf 2 > lower1/b + printf 3 > lower2/b + if $RUN --tmp-overlay /tmp true 2>err.txt; then + assert_not_reached At least one --overlay-src not required + fi + assert_file_has_content err.txt "^bwrap: --tmp-overlay requires at least one --overlay-src" + $RUN --overlay-src lower1 --tmp-overlay /tmp/x/y/z cat /tmp/x/y/z/a > stdout + assert_file_has_content stdout '^1$' + $RUN --overlay-src lower1 --tmp-overlay /tmp/x/y/z cat /tmp/x/y/z/b > stdout + assert_file_has_content stdout '^2$' + $RUN --overlay-src lower1 --overlay-src lower2 --tmp-overlay /tmp/x/y/z cat /tmp/x/y/z/a > stdout + assert_file_has_content stdout '^1$' + $RUN --overlay-src lower1 --overlay-src lower2 --tmp-overlay /tmp/x/y/z cat /tmp/x/y/z/b > stdout + assert_file_has_content stdout '^3$' + $RUN --overlay-src lower1 --overlay-src lower2 --tmp-overlay /tmp/x/y/z sh -c 'printf 4 > /tmp/x/y/z/b; cat /tmp/x/y/z/b' > stdout + assert_file_has_content stdout '^4$' + $RUN --overlay-src lower1 --tmp-overlay /tmp/x --overlay-src lower2 --tmp-overlay /tmp/y sh -c 'cat /tmp/x/b; printf 4 > /tmp/x/b; cat /tmp/x/b; cat /tmp/y/b' > stdout + assert_file_has_content stdout '^243$' + assert_file_has_content lower1/b '^2$' + assert_file_has_content lower2/b '^3$' + ok "--tmp-overlay" + + # Test --ro-overlay + printf 1 > lower1/a + printf 2 > lower1/b + printf 3 > lower2/b + if $RUN --ro-overlay /tmp true 2>err.txt; then + assert_not_reached At least two --overlay-src not required + fi + assert_file_has_content err.txt "^bwrap: --ro-overlay requires at least two --overlay-src" + if $RUN --overlay-src lower1 --ro-overlay /tmp true 2>err.txt; then + assert_not_reached At least two --overlay-src not required + fi + assert_file_has_content err.txt "^bwrap: --ro-overlay requires at least two --overlay-src" + $RUN --overlay-src lower1 --overlay-src lower2 --ro-overlay /tmp/x/y/z cat /tmp/x/y/z/a > stdout + assert_file_has_content stdout '^1$' + $RUN --overlay-src lower1 --overlay-src lower2 --ro-overlay /tmp/x/y/z cat /tmp/x/y/z/b > stdout + assert_file_has_content stdout '^3$' + $RUN --overlay-src lower1 --overlay-src lower2 --ro-overlay /tmp/x/y/z sh -c 'printf 4 > /tmp/x/y/z/b; cat /tmp/x/y/z/b' > stdout + assert_file_has_content stdout '^3$' + ok "--ro-overlay" + + # Test --overlay-src restrictions + if $RUN --overlay-src /tmp true 2>err.txt; then + assert_not_reached Trailing --overlay-src allowed + fi + assert_file_has_content err.txt "^bwrap: --overlay-src must be followed by another --overlay-src or one of --overlay, --tmp-overlay, or --ro-overlay" + if $RUN --overlay-src /tmp --chdir / true 2>err.txt; then + assert_not_reached --overlay-src allowed to precede non-overlay options + fi + assert_file_has_content err.txt "^bwrap: --overlay-src must be followed by another --overlay-src or one of --overlay, --tmp-overlay, or --ro-overlay" + ok "--overlay-src restrictions" + + fi +fi + done_testing diff --git a/subprojects/bubblewrap/tests/test-utils.c b/subprojects/bubblewrap/tests/test-utils.c index 41874a15cbdb840bf122751592a987d799ecb4c4..64f42ff18c0dba3877e9bf2f3eb92b02a48f1a39 100644 --- a/subprojects/bubblewrap/tests/test-utils.c +++ b/subprojects/bubblewrap/tests/test-utils.c @@ -170,15 +170,15 @@ test_has_path_prefix (void) bool expected; } tests[] = { - { "/run/host/usr", "/run/host", TRUE }, - { "/run/host/usr", "/run/host/", TRUE }, - { "/run/host", "/run/host", TRUE }, - { "////run///host////usr", "//run//host", TRUE }, - { "////run///host////usr", "//run//host////", TRUE }, - { "/run/hostage", "/run/host", FALSE }, + { "/run/host/usr", "/run/host", true }, + { "/run/host/usr", "/run/host/", true }, + { "/run/host", "/run/host", true }, + { "////run///host////usr", "//run//host", true }, + { "////run///host////usr", "//run//host////", true }, + { "/run/hostage", "/run/host", false }, /* Any number of leading slashes is ignored, even zero */ - { "foo/bar", "/foo", TRUE }, - { "/foo/bar", "foo", TRUE }, + { "foo/bar", "/foo", true }, + { "/foo/bar", "foo", true }, }; size_t i; @@ -200,6 +200,37 @@ test_has_path_prefix (void) } } +static void +test_string_builder (void) +{ + StringBuilder sb = {0}; + + strappend (&sb, "aaa"); + g_assert_cmpstr (sb.str, ==, "aaa"); + strappend (&sb, "bbb"); + g_assert_cmpstr (sb.str, ==, "aaabbb"); + strappendf (&sb, "c%dc%s", 9, "x"); + g_assert_cmpstr (sb.str, ==, "aaabbbc9cx"); + strappend_escape_for_mount_options (&sb, "/path :,\\"); + g_assert_cmpstr (sb.str, ==, "aaabbbc9cx/path \\:\\,\\\\"); + strappend (&sb, "zzz"); + g_assert_cmpstr (sb.str, ==, "aaabbbc9cx/path \\:\\,\\\\zzz"); + + free (sb.str); + sb = (StringBuilder){0}; + + strappend_escape_for_mount_options (&sb, "aaa"); + g_assert_cmpstr (sb.str, ==, "aaa"); + + free (sb.str); + sb = (StringBuilder){0}; + + strappend_escape_for_mount_options (&sb, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + g_assert_cmpstr (sb.str, ==, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + + free (sb.str); +} + int main (int argc UNUSED, char **argv UNUSED) @@ -210,6 +241,7 @@ main (int argc UNUSED, test_strconcat3 (); test_has_prefix (); test_has_path_prefix (); + test_string_builder (); printf ("1..%u\n", test_number); return 0; } diff --git a/subprojects/bubblewrap/utils.c b/subprojects/bubblewrap/utils.c index 43c8d798a9428536c509ed534e639dc0de442483..51875aea9a1a60dcdddb649b7d138f4cd76d93ce 100644 --- a/subprojects/bubblewrap/utils.c +++ b/subprojects/bubblewrap/utils.c @@ -23,6 +23,7 @@ #include <stdint.h> #include <sys/syscall.h> #include <sys/socket.h> +#include <sys/param.h> #ifdef HAVE_SELINUX #include <selinux/selinux.h> #endif @@ -34,11 +35,17 @@ #define security_check_context(x) security_check_context ((security_context_t) x) #endif -__attribute__((format(printf, 1, 0))) static void -warnv (const char *format, - va_list args, - const char *detail) +bool bwrap_level_prefix = false; + +__attribute__((format(printf, 2, 0))) static void +bwrap_logv (int severity, + const char *format, + va_list args, + const char *detail) { + if (bwrap_level_prefix) + fprintf (stderr, "<%d>", severity); + fprintf (stderr, "bwrap: "); vfprintf (stderr, format, args); @@ -49,12 +56,13 @@ warnv (const char *format, } void -warn (const char *format, ...) +bwrap_log (int severity, + const char *format, ...) { va_list args; va_start (args, format); - warnv (format, args, NULL); + bwrap_logv (severity, format, args, NULL); va_end (args); } @@ -67,7 +75,7 @@ die_with_error (const char *format, ...) errsv = errno; va_start (args, format); - warnv (format, args, strerror (errsv)); + bwrap_logv (LOG_ERR, format, args, strerror (errsv)); va_end (args); exit (1); @@ -82,7 +90,7 @@ die_with_mount_error (const char *format, ...) errsv = errno; va_start (args, format); - warnv (format, args, mount_strerror (errsv)); + bwrap_logv (LOG_ERR, format, args, mount_strerror (errsv)); va_end (args); exit (1); @@ -94,7 +102,7 @@ die (const char *format, ...) va_list args; va_start (args, format); - warnv (format, args, NULL); + bwrap_logv (LOG_ERR, format, args, NULL); va_end (args); exit (1); @@ -206,7 +214,7 @@ bool has_path_prefix (const char *str, const char *prefix) { - while (TRUE) + while (true) { /* Skip consecutive slashes to reach next path element */ @@ -217,13 +225,13 @@ has_path_prefix (const char *str, /* No more prefix path elements? Done! */ if (*prefix == 0) - return TRUE; + return true; /* Compare path element */ while (*prefix != 0 && *prefix != '/') { if (*str != *prefix) - return FALSE; + return false; str++; prefix++; } @@ -231,7 +239,7 @@ has_path_prefix (const char *str, /* Matched prefix path element, must be entire str path element */ if (*str != '/' && *str != 0) - return FALSE; + return false; } } @@ -239,7 +247,7 @@ bool path_equal (const char *path1, const char *path2) { - while (TRUE) + while (true) { /* Skip consecutive slashes to reach next path element */ @@ -256,14 +264,14 @@ path_equal (const char *path1, while (*path1 != 0 && *path1 != '/') { if (*path1 != *path2) - return FALSE; + return false; path1++; path2++; } /* Matched path1 path element, must be entire path element */ if (*path2 != '/' && *path2 != 0) - return FALSE; + return false; } } @@ -370,7 +378,7 @@ fdwalk (int proc_fd, int (*cb)(void *data, int res = 0; DIR *d; - dfd = openat (proc_fd, "self/fd", O_DIRECTORY | O_RDONLY | O_NONBLOCK | O_CLOEXEC | O_NOCTTY); + dfd = TEMP_FAILURE_RETRY (openat (proc_fd, "self/fd", O_DIRECTORY | O_RDONLY | O_NONBLOCK | O_CLOEXEC | O_NOCTTY)); if (dfd == -1) return res; @@ -444,7 +452,7 @@ write_to_fd (int fd, /* Sets errno on error (!= 0), ENOSPC on short write */ int -write_file_at (int dirfd, +write_file_at (int dfd, const char *path, const char *content) { @@ -452,7 +460,7 @@ write_file_at (int dirfd, bool res; int errsv; - fd = openat (dirfd, path, O_RDWR | O_CLOEXEC, 0); + fd = TEMP_FAILURE_RETRY (openat (dfd, path, O_RDWR | O_CLOEXEC, 0)); if (fd == -1) return -1; @@ -477,7 +485,7 @@ create_file (const char *path, int res; int errsv; - fd = creat (path, mode); + fd = TEMP_FAILURE_RETRY (creat (path, mode)); if (fd == -1) return -1; @@ -526,7 +534,7 @@ copy_file_data (int sfd, char buffer[BUFSIZE]; ssize_t bytes_read; - while (TRUE) + while (true) { bytes_read = read (sfd, buffer, BUFSIZE); if (bytes_read == -1) @@ -558,11 +566,11 @@ copy_file (const char *src_path, int res; int errsv; - sfd = open (src_path, O_CLOEXEC | O_RDONLY); + sfd = TEMP_FAILURE_RETRY (open (src_path, O_CLOEXEC | O_RDONLY)); if (sfd == -1) return -1; - dfd = creat (dst_path, mode); + dfd = TEMP_FAILURE_RETRY (creat (dst_path, mode)); if (dfd == -1) { errsv = errno; @@ -632,14 +640,14 @@ load_file_data (int fd, /* Sets errno on error (== NULL), * Always ensures terminating zero */ char * -load_file_at (int dirfd, +load_file_at (int dfd, const char *path) { int fd; char *data; int errsv; - fd = openat (dirfd, path, O_CLOEXEC | O_RDONLY); + fd = TEMP_FAILURE_RETRY (openat (dfd, path, O_CLOEXEC | O_RDONLY)); if (fd == -1) return NULL; @@ -745,15 +753,15 @@ mkdir_with_parents (const char *pathname, read back with read_pid_from_socket(), and then the kernel has translated it between namespaces as needed. */ void -send_pid_on_socket (int socket) +send_pid_on_socket (int sockfd) { char buf[1] = { 0 }; struct msghdr msg = {}; struct iovec iov = { buf, sizeof (buf) }; const ssize_t control_len_snd = CMSG_SPACE(sizeof(struct ucred)); - char control_buf_snd[control_len_snd]; + _Alignas(struct cmsghdr) char control_buf_snd[control_len_snd]; struct cmsghdr *cmsg; - struct ucred *cred; + struct ucred cred; msg.msg_iov = &iov; msg.msg_iovlen = 1; @@ -764,13 +772,13 @@ send_pid_on_socket (int socket) cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_CREDENTIALS; cmsg->cmsg_len = CMSG_LEN(sizeof(struct ucred)); - cred = (struct ucred *)CMSG_DATA(cmsg); - cred->pid = getpid (); - cred->uid = geteuid (); - cred->gid = getegid (); + cred.pid = getpid (); + cred.uid = geteuid (); + cred.gid = getegid (); + memcpy (CMSG_DATA (cmsg), &cred, sizeof (cred)); - if (sendmsg (socket, &msg, 0) < 0) + if (TEMP_FAILURE_RETRY (sendmsg (sockfd, &msg, 0)) < 0) die_with_error ("Can't send pid"); } @@ -787,13 +795,13 @@ create_pid_socketpair (int sockets[2]) } int -read_pid_from_socket (int socket) +read_pid_from_socket (int sockfd) { char recv_buf[1] = { 0 }; struct msghdr msg = {}; struct iovec iov = { recv_buf, sizeof (recv_buf) }; const ssize_t control_len_rcv = CMSG_SPACE(sizeof(struct ucred)); - char control_buf_rcv[control_len_rcv]; + _Alignas(struct cmsghdr) char control_buf_rcv[control_len_rcv]; struct cmsghdr* cmsg; msg.msg_iov = &iov; @@ -801,7 +809,7 @@ read_pid_from_socket (int socket) msg.msg_control = control_buf_rcv; msg.msg_controllen = control_len_rcv; - if (recvmsg (socket, &msg, 0) < 0) + if (TEMP_FAILURE_RETRY (recvmsg (sockfd, &msg, 0)) < 0) die_with_error ("Can't read pid from socket"); if (msg.msg_controllen <= 0) @@ -814,8 +822,10 @@ read_pid_from_socket (int socket) cmsg->cmsg_type == SCM_CREDENTIALS && payload_len == sizeof(struct ucred)) { - struct ucred *cred = (struct ucred *)CMSG_DATA(cmsg); - return cred->pid; + struct ucred cred; + + memcpy (&cred, CMSG_DATA (cmsg), sizeof (cred)); + return cred.pid; } } die ("No pid returned on socket"); @@ -943,3 +953,128 @@ mount_strerror (int errsv) return strerror (errsv); } } + +/* + * Return a + b if it would not overflow. + * Die with an "out of memory" error if it would. + */ +static size_t +xadd (size_t a, size_t b) +{ +#if defined(__GNUC__) && __GNUC__ >= 5 + size_t result; + if (__builtin_add_overflow (a, b, &result)) + die_oom (); + return result; +#else + if (a > SIZE_MAX - b) + die_oom (); + + return a + b; +#endif +} + +/* + * Return a * b if it would not overflow. + * Die with an "out of memory" error if it would. + */ +static size_t +xmul (size_t a, size_t b) +{ +#if defined(__GNUC__) && __GNUC__ >= 5 + size_t result; + if (__builtin_mul_overflow (a, b, &result)) + die_oom (); + return result; +#else + if (b != 0 && a > SIZE_MAX / b) + die_oom (); + + return a * b; +#endif +} + +void +strappend (StringBuilder *dest, const char *src) +{ + size_t len = strlen (src); + size_t new_offset = xadd (dest->offset, len); + + if (new_offset >= dest->size) + { + dest->size = xmul (xadd (new_offset, 1), 2); + dest->str = xrealloc (dest->str, dest->size); + } + + /* Preserves the invariant that dest->str is always null-terminated, even + * though the offset is positioned at the null byte for the next write. + */ + strncpy (dest->str + dest->offset, src, len + 1); + dest->offset = new_offset; +} + +__attribute__((format (printf, 2, 3))) +void +strappendf (StringBuilder *dest, const char *fmt, ...) +{ + va_list args; + int len; + size_t new_offset; + + va_start (args, fmt); + len = vsnprintf (dest->str + dest->offset, dest->size - dest->offset, fmt, args); + va_end (args); + if (len < 0) + die_with_error ("vsnprintf"); + new_offset = xadd (dest->offset, len); + if (new_offset >= dest->size) + { + dest->size = xmul (xadd (new_offset, 1), 2); + dest->str = xrealloc (dest->str, dest->size); + va_start (args, fmt); + len = vsnprintf (dest->str + dest->offset, dest->size - dest->offset, fmt, args); + va_end (args); + if (len < 0) + die_with_error ("vsnprintf"); + } + + dest->offset = new_offset; +} + +void +strappend_escape_for_mount_options (StringBuilder *dest, const char *src) +{ + bool unescaped = true; + + for (;;) + { + if (dest->offset == dest->size) + { + dest->size = MAX (64, xmul (dest->size, 2)); + dest->str = xrealloc (dest->str, dest->size); + } + switch (*src) + { + case '\0': + dest->str[dest->offset] = '\0'; + return; + + case '\\': + case ',': + case ':': + if (unescaped) + { + dest->str[dest->offset++] = '\\'; + unescaped = false; + continue; + } + /* else fall through */ + + default: + dest->str[dest->offset++] = *src; + unescaped = true; + break; + } + src++; + } +} diff --git a/subprojects/bubblewrap/utils.h b/subprojects/bubblewrap/utils.h index 9f17297d2a7c0b0bb0f4ba22468fffc25a6daab1..079fe7c9362e04b952544b1daab0331c2dbe16f1 100644 --- a/subprojects/bubblewrap/utils.h +++ b/subprojects/bubblewrap/utils.h @@ -24,26 +24,33 @@ #include <errno.h> #include <fcntl.h> #include <stdarg.h> +#include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <syslog.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #if 0 -#define __debug__(x) printf x +#define debug(...) bwrap_log (LOG_DEBUG, __VA_ARGS__) #else -#define __debug__(x) +#define debug(...) #endif #define UNUSED __attribute__((__unused__)) #define N_ELEMENTS(arr) (sizeof (arr) / sizeof ((arr)[0])) -#define TRUE 1 -#define FALSE 0 -typedef int bool; +#ifndef TEMP_FAILURE_RETRY +#define TEMP_FAILURE_RETRY(expression) \ + (__extension__ \ + ({ long int __result; \ + do __result = (long int) (expression); \ + while (__result == -1L && errno == EINTR); \ + __result; })) +#endif #define PIPE_READ_END 0 #define PIPE_WRITE_END 1 @@ -52,8 +59,13 @@ typedef int bool; #define PR_SET_CHILD_SUBREAPER 36 #endif -void warn (const char *format, - ...) __attribute__((format (printf, 1, 2))); +extern bool bwrap_level_prefix; + +void bwrap_log (int severity, + const char *format, + ...) __attribute__((format (printf, 2, 3))); +#define warn(...) bwrap_log (LOG_WARNING, __VA_ARGS__) + void die_with_error (const char *format, ...) __attribute__((__noreturn__)) __attribute__((format (printf, 1, 2))); void die_with_mount_error (const char *format, @@ -186,3 +198,20 @@ steal_pointer (void *pp) /* type safety */ #define steal_pointer(pp) \ (0 ? (*(pp)) : (steal_pointer) (pp)) + +typedef struct _StringBuilder StringBuilder; + +struct _StringBuilder +{ + char * str; + size_t size; + size_t offset; +}; + +void strappend (StringBuilder *dest, + const char *src); +void strappendf (StringBuilder *dest, + const char *fmt, + ...); +void strappend_escape_for_mount_options (StringBuilder *dest, + const char *src);