From 66a2612d0970d013b1d4745cc8d578fa4e1c774b Mon Sep 17 00:00:00 2001 From: "deepin-community-bot[bot]" <156989552+deepin-community-bot[bot]@users.noreply.github.com> Date: Thu, 21 May 2026 12:49:02 +0000 Subject: [PATCH] feat: update bubblewrap to 0.11.0-2+deb13u1 --- Makefile-bwrap.am | 12 - Makefile-docs.am | 18 - Makefile.am | 102 ----- NEWS.md | 51 +++ README.md | 26 +- autogen.sh | 19 - bind-mount.c | 25 +- bubblewrap.c | 376 +++++++++++++----- bwrap.xml | 100 +++++ ci/builddeps.sh | 4 - completions/bash/bwrap | 4 + completions/bash/meson.build | 1 - configure.ac | 158 -------- debian/changelog | 50 +++ debian/control | 4 +- debian/copyright | 48 --- debian/gbp.conf | 2 +- ...he-privilege-separated-code-dumpable.patch | 89 +++++ ...parent-against-unexpected-operations.patch | 36 ++ ...ssage-to-show-Debian-specific-inform.patch | 4 +- debian/patches/series | 2 + debian/salsa-ci.yml | 6 +- debian/tests/control | 11 - debian/tests/upstream-usrmerge | 1 - git.mk | 348 ---------------- m4/attributes.m4 | 292 -------------- meson.build | 2 +- meson_options.txt | 4 +- network.c | 6 +- release-checklist.md | 6 +- tests/test-run.sh | 120 +++++- tests/test-utils.c | 48 ++- utils.c | 211 ++++++++-- utils.h | 43 +- 34 files changed, 1021 insertions(+), 1208 deletions(-) delete mode 100644 Makefile-bwrap.am delete mode 100644 Makefile-docs.am delete mode 100644 Makefile.am create mode 100644 NEWS.md delete mode 100755 autogen.sh delete mode 100644 configure.ac create mode 100644 debian/patches/CVE-2026-41163/Don-t-run-the-privilege-separated-code-dumpable.patch create mode 100644 debian/patches/CVE-2026-41163/fix-harden-privsep-parent-against-unexpected-operations.patch delete mode 120000 debian/tests/upstream-usrmerge delete mode 100644 git.mk delete mode 100644 m4/attributes.m4 diff --git a/Makefile-bwrap.am b/Makefile-bwrap.am deleted file mode 100644 index aa97bd1..0000000 --- a/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/Makefile-docs.am b/Makefile-docs.am deleted file mode 100644 index 4c1f3c5..0000000 --- a/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/Makefile.am b/Makefile.am deleted file mode 100644 index 81a160a..0000000 --- a/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/NEWS.md b/NEWS.md new file mode 100644 index 0000000..da232c4 --- /dev/null +++ b/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 `` for booleans (#660, Simon McVittie) + + * Avoid `-Wshadow` compiler warnings (#661, Simon McVittie) + + * Update Github Actions configuration (#658, Simon McVittie) + +---- + +See also diff --git a/README.md b/README.md index 0d1b0e0..c16cd7d 100644 --- a/README.md +++ b/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/autogen.sh b/autogen.sh deleted file mode 100755 index 2d83faf..0000000 --- a/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/bind-mount.c b/bind-mount.c index 2757cae..a2e1ac6 100644 --- a/bind-mount.c +++ b/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/bubblewrap.c b/bubblewrap.c index bc75da4..f8728c7 100644 --- a/bubblewrap.c +++ b/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/bwrap.xml b/bwrap.xml index 3bb5082..f379f0f 100644 --- a/bwrap.xml +++ b/bwrap.xml @@ -96,6 +96,26 @@ Set argv[0] to the value VALUE before running the program + + + + + 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 + syslog3: + for example, <4> indicates a warning, + because LOG_WARNING 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 + logger --prio-prefix (see + logger1) + or systemd-cat --level-prefix=1 (see + systemd-cat1). + + + Options related to kernel namespaces: @@ -297,6 +317,86 @@ Remount the path DEST as readonly. It works only on the specified mount point, without changing any other mount point under the specified path + + + + + This option does nothing on its own, and must be followed by one of + the other overlay options. It specifies a host + path from which files should be read if they aren't present in a + higher layer. + + + 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. + + + (For readers familiar with overlayfs, note that this is the + reverse of the order used by the kernel's lowerdir + mount option.) + + + + + + + + + + + + + + Use overlayfs to mount the host paths specified by + RWSRC and all immediately preceding + on DEST. + DEST will contain the union of all the files + in all the layers. + + + With --overlay all writes will go to + RWSRC. Reads will come preferentially from + RWSRC, and then from any + paths. + WORKDIR must be an empty directory on the + same filesystem as RWSRC, and is used + internally by the kernel. + + + With --tmp-overlay 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. + + + With --ro-overlay the filesystem will be + mounted read-only. This option requires at least two + to precede it. + + + None of these options are available in the setuid version of + bubblewrap. Using --ro-overlay or providing + more than one requires a Linux kernel + version of 4.0 or later. + + + Due to limitations of overlayfs, no host directory given via + --overlay-src or + --overlay 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. + + + For more information see the Overlay Filesystem documentation in the + Linux kernel at + https://www.kernel.org/doc/Documentation/filesystems/overlayfs.txt + + + Mount procfs on DEST diff --git a/ci/builddeps.sh b/ci/builddeps.sh index 4accd2e..5bca51e 100755 --- a/ci/builddeps.sh +++ b/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/completions/bash/bwrap b/completions/bash/bwrap index e6d2846..e7a523c 100644 --- a/completions/bash/bwrap +++ b/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/completions/bash/meson.build b/completions/bash/meson.build index 1dd946f..323ad39 100644 --- a/completions/bash/meson.build +++ b/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/configure.ac b/configure.ac deleted file mode 100644 index 7fe9c04..0000000 --- a/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/debian/changelog b/debian/changelog index 14da32d..b6d208e 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,53 @@ +bubblewrap (0.11.0-2+deb13u1) trixie; urgency=medium + + * d/control, d/gbp.conf: Branch for Debian 13 stable updates + * d/patches: Fix privilege escalation if bubblewrap is setuid root. + /usr/bin/bwrap has not been installed setuid-root by default since + Debian 11, but if it was made setuid via a dpkg-statoverride set up + by the local sysadmin (most likely in conjunction with turning off + the ability for unprivileged users to create new user namespaces), + then the version included in Debian 13.4 would be vulnerable. + (CVE-2026-41163, Closes: #1134704) + Note that the ability to install bubblewrap setuid-root has been + deprecated upstream, and the version included in Debian 14 will + refuse to run if it is setuid. + + -- Simon McVittie Sun, 26 Apr 2026 14:05:43 +0100 + +bubblewrap (0.11.0-2) unstable; urgency=medium + + [ Helmut Grohne ] + * d/tests: Drop obsolete upstream-usrmerge autopkgtest. + This test was added to additionally run the upstream test suite on + a /usr-merged system. Now that all systems are merged-/usr, this + test no longer provides any benefit, and removing it will unblock + removal of the usrmerge package. (Closes: #1091676) + + [ Simon McVittie ] + * Also remove d/tests/upstream-usrmerge symlink + + -- Simon McVittie Mon, 30 Dec 2024 11:17:50 +0000 + +bubblewrap (0.11.0-1) unstable; urgency=medium + + * New upstream release + + -- Simon McVittie Wed, 30 Oct 2024 16:15:12 +0000 + +bubblewrap (0.10.0+30+g0545e72-1) experimental; urgency=low + + * New upstream snapshot + - New options to create overlay mounts (--overlay, etc.) + - New option --level-prefix + - Handle EINTR + - Ensure socket control message data is aligned correctly + - Remove Autotools build system + - Silence Meson and compiler warnings + * d/salsa-ci.yml: Don't run Salsa-CI for branches targeting Ubuntu + * Standards-Version: 4.7.0 (no changes required) + + -- Simon McVittie Fri, 18 Oct 2024 19:52:17 +0100 + bubblewrap (0.10.0-1) unstable; urgency=high * New upstream release diff --git a/debian/control b/debian/control index c7d9294..4275573 100644 --- a/debian/control +++ b/debian/control @@ -20,9 +20,9 @@ Build-Depends: pkgconf, python3:any , xsltproc, -Standards-Version: 4.6.2 +Standards-Version: 4.7.0 Homepage: https://github.com/containers/bubblewrap -Vcs-Git: https://salsa.debian.org/debian/bubblewrap.git +Vcs-Git: https://salsa.debian.org/debian/bubblewrap.git -b debian/trixie Vcs-Browser: https://salsa.debian.org/debian/bubblewrap Rules-Requires-Root: no diff --git a/debian/copyright b/debian/copyright index 74c43c0..6a184a0 100644 --- a/debian/copyright +++ b/debian/copyright @@ -21,23 +21,6 @@ Copyright: 2016-2024 Collabora Ltd. License: LGPL-2+ -Files: - git.mk -Copyright: - 2009, Red Hat, Inc. - 2010-2013 Behdad Esfahbod -License: permissive-git.mk - 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. - -Files: m4/attributes.m4 -Copyright: - 2006-2008 Diego Pettenò - 2006-2008 xine project - 2012 Lucas De Marchi -License: GPL-2+ with Autoconf exception - License: LGPL-2+ This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public @@ -56,34 +39,3 @@ License: LGPL-2+ . On Debian systems, the full text of the GNU Library General Public License version 2 can be found in the file `/usr/share/common-licenses/LGPL-2'. - -License: GPL-2+ with Autoconf exception - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2, or (at your option) - any later version. - . - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - . - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - 02110-1301, USA. - . - As a special exception, the copyright owners of the - macro gives unlimited permission to copy, distribute and modify the - configure scripts that are the output of Autoconf when processing the - Macro. You need not follow the terms of the GNU General Public - License when using or distributing such scripts, even though portions - of the text of the Macro appear in them. The GNU General Public - License (GPL) does govern all other use of the material that - constitutes the Autoconf Macro. - . - This special exception to the GPL applies to versions of the - Autoconf Macro released by this project. When you make and - distribute a modified version of the Autoconf Macro, you may extend - this special exception to the GPL to apply to your modified version as - well. diff --git a/debian/gbp.conf b/debian/gbp.conf index 4b25d13..4363d96 100644 --- a/debian/gbp.conf +++ b/debian/gbp.conf @@ -1,6 +1,6 @@ [DEFAULT] pristine-tar = True -debian-branch = debian/latest +debian-branch = debian/trixie upstream-branch = upstream/latest patch-numbers = False upstream-vcs-tag = v%(version)s diff --git a/debian/patches/CVE-2026-41163/Don-t-run-the-privilege-separated-code-dumpable.patch b/debian/patches/CVE-2026-41163/Don-t-run-the-privilege-separated-code-dumpable.patch new file mode 100644 index 0000000..277a74c --- /dev/null +++ b/debian/patches/CVE-2026-41163/Don-t-run-the-privilege-separated-code-dumpable.patch @@ -0,0 +1,89 @@ +From: Alexander Larsson +Date: Tue, 14 Apr 2026 11:46:12 +0200 +Subject: Don't run the privilege separated code dumpable +MIME-Version: 1.0 +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 8bit + +If the unprivileged code is dumpable, then it can be attached to with +ptrace and execute arbitrary requests to the privileged part of the +setup. In some cases this allows privilege escalation, such as using +overlay mounts which would otherwise not be allowed in setuid mode. + +Note: We still make the monitor process and the main child process +dumpable, because otherwise the owner of /proc/self is root which +makes these processed not able to use features that are needed for +e.g. detarmining portal access. + +Initially reported by François Diakhate + +Signed-off-by: Alexander Larsson +Origin: upstream, 0.11.2, commit:10ed62f34a4565188887b06df1438ef8002c3c80 +Bug: https://github.com/containers/bubblewrap/security/advisories/GHSA-xq78-7hw4-5jvp +Bug-CVE: CVE-2026-41163 +Bug-Debian: https://bugs.debian.org/1134704 +--- + bubblewrap.c | 22 ++++++++++++++-------- + 1 file changed, 14 insertions(+), 8 deletions(-) + +diff --git a/bubblewrap.c b/bubblewrap.c +index f8728c7..0d8d54d 100644 +--- a/bubblewrap.c ++++ b/bubblewrap.c +@@ -937,7 +937,8 @@ switch_to_user_with_privs (void) + /* Call setuid() and use capset() to adjust capabilities */ + static void + drop_privs (bool keep_requested_caps, +- bool already_changed_uid) ++ bool already_changed_uid, ++ bool set_dumpable) + { + assert (!keep_requested_caps || !is_privileged); + /* Drop root uid */ +@@ -947,9 +948,12 @@ drop_privs (bool keep_requested_caps, + + drop_all_caps (keep_requested_caps); + +- /* We don't have any privs now, so mark us dumpable which makes /proc/self be owned by the user instead of root */ +- if (prctl (PR_SET_DUMPABLE, 1, 0, 0, 0) != 0) +- die_with_error ("can't set dumpable"); ++ if (set_dumpable) ++ { ++ /* We don't have any privs now, so mark us dumpable which makes /proc/self be owned by the user instead of root */ ++ if (prctl (PR_SET_DUMPABLE, 1, 0, 0, 0) != 0) ++ die_with_error ("can't set dumpable"); ++ } + } + + static void +@@ -3175,7 +3179,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, true); + + /* Optionally bind our lifecycle to that of the parent */ + handle_die_with_parent (); +@@ -3362,8 +3366,10 @@ main (int argc, + + if (child == 0) + { +- /* Unprivileged setup process */ +- drop_privs (false, true); ++ /* Unprivileged setup process. ++ * Note: Don't set dumpable, because we can still perform privileged ++ * operations via privileged_op(). */ ++ drop_privs (false, true, false); + close (privsep_sockets[0]); + setup_newroot (opt_unshare_pid, privsep_sockets[1]); + exit (0); +@@ -3492,7 +3498,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, true); + + if (opt_block_fd != -1) + { diff --git a/debian/patches/CVE-2026-41163/fix-harden-privsep-parent-against-unexpected-operations.patch b/debian/patches/CVE-2026-41163/fix-harden-privsep-parent-against-unexpected-operations.patch new file mode 100644 index 0000000..8ecec43 --- /dev/null +++ b/debian/patches/CVE-2026-41163/fix-harden-privsep-parent-against-unexpected-operations.patch @@ -0,0 +1,36 @@ +From: =?utf-8?q?Fran=C3=A7ois_Diakhat=C3=A9?= +Date: Thu, 9 Apr 2026 18:17:33 +0000 +Subject: fix: harden privsep parent against unexpected operations + +Origin: upstream, 0.11.2, commit:9e16c35582af7704958dd2b2ec3cba524c9605b6 +Bug: https://github.com/containers/bubblewrap/security/advisories/GHSA-xq78-7hw4-5jvp +Bug-CVE: CVE-2026-41163 +Bug-Debian: https://bugs.debian.org/1134704 +--- + bubblewrap.c | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/bubblewrap.c b/bubblewrap.c +index 0d8d54d..42efe2a 100644 +--- a/bubblewrap.c ++++ b/bubblewrap.c +@@ -1158,7 +1158,9 @@ privileged_op (int privileged_op_socket, + break; + + case PRIV_SEP_OP_OVERLAY_MOUNT: +- if (mount ("overlay", arg2, "overlay", MS_MGC_VAL, arg1) != 0) ++ if (is_privileged) ++ die ("Overlay mounts are not supported in setuid mode"); ++ if (mount ("overlay", arg2, "overlay", MS_MGC_VAL | MS_NOSUID | MS_NODEV, arg1) != 0) + { + /* The standard message for ELOOP, "Too many levels of symbolic + * links", is not helpful here. */ +@@ -1176,6 +1178,8 @@ privileged_op (int privileged_op_socket, + something manages to send hacked priv-sep operation requests. */ + if (!opt_unshare_uts) + die ("Refusing to set hostname in original namespace"); ++ if (arg1 == NULL) ++ die ("Hostname argument is NULL"); + if (sethostname (arg1, strlen(arg1)) != 0) + die_with_error ("Can't set hostname to %s", arg1); + break; diff --git a/debian/patches/debian/Change-EPERM-error-message-to-show-Debian-specific-inform.patch b/debian/patches/debian/Change-EPERM-error-message-to-show-Debian-specific-inform.patch index 788d158..0b410a4 100644 --- a/debian/patches/debian/Change-EPERM-error-message-to-show-Debian-specific-inform.patch +++ b/debian/patches/debian/Change-EPERM-error-message-to-show-Debian-specific-inform.patch @@ -8,10 +8,10 @@ Forwarded: not-needed 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bubblewrap.c b/bubblewrap.c -index 9b78a9a..1ea16c9 100644 +index 42efe2a..3e48d26 100644 --- a/bubblewrap.c +++ b/bubblewrap.c -@@ -2905,7 +2905,7 @@ main (int argc, +@@ -3136,7 +3136,7 @@ main (int argc, if (errno == EINVAL) die ("Creating new namespace failed, likely because the kernel does not support user namespaces. bwrap must be installed setuid on such systems."); else if (errno == EPERM && !is_privileged) diff --git a/debian/patches/series b/debian/patches/series index 4e2353c..f8536fd 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -1 +1,3 @@ +CVE-2026-41163/Don-t-run-the-privilege-separated-code-dumpable.patch +CVE-2026-41163/fix-harden-privsep-parent-against-unexpected-operations.patch debian/Change-EPERM-error-message-to-show-Debian-specific-inform.patch diff --git a/debian/salsa-ci.yml b/debian/salsa-ci.yml index 0c22dc4..0669c88 100644 --- a/debian/salsa-ci.yml +++ b/debian/salsa-ci.yml @@ -1,3 +1,5 @@ include: - - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/salsa-ci.yml - - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/pipeline-jobs.yml + - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/recipes/debian.yml + +variables: + SALSA_CI_IGNORED_BRANCHES: '/^(ppa|ubuntu)\//' diff --git a/debian/tests/control b/debian/tests/control index a64434e..d6267f3 100644 --- a/debian/tests/control +++ b/debian/tests/control @@ -13,17 +13,6 @@ Depends: perl:native, python3:native, -Tests: upstream-usrmerge -Restrictions: allow-stderr, isolation-machine, breaks-testbed -Depends: - bubblewrap, - iproute2:native, - libcap2-bin:native, - libipc-run-perl:native, - perl:native, - python3:native, - usrmerge - Tests: upstream-as-root Restrictions: allow-stderr, isolation-machine, needs-root diff --git a/debian/tests/upstream-usrmerge b/debian/tests/upstream-usrmerge deleted file mode 120000 index 2a767e2..0000000 --- a/debian/tests/upstream-usrmerge +++ /dev/null @@ -1 +0,0 @@ -upstream \ No newline at end of file diff --git a/git.mk b/git.mk deleted file mode 100644 index 0a73115..0000000 --- a/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/m4/attributes.m4 b/m4/attributes.m4 deleted file mode 100644 index 51ac88b..0000000 --- a/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ò -dnl Copyright (c) 2006-2008 xine project -dnl Copyright (c) 2012 Lucas De Marchi -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/meson.build b/meson.build index fb3af4e..78678d0 100644 --- a/meson.build +++ b/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/meson_options.txt b/meson_options.txt index 10a0a20..5e25ee8 100644 --- a/meson_options.txt +++ b/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/network.c b/network.c index f6d58a6..373d606 100644 --- a/network.c +++ b/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/release-checklist.md b/release-checklist.md index 4385b09..5b2119c 100644 --- a/release-checklist.md +++ b/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/tests/test-run.sh b/tests/test-run.sh index 8d063b5..1003e8c 100755 --- a/tests/test-run.sh +++ b/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/tests/test-utils.c b/tests/test-utils.c index 41874a1..64f42ff 100644 --- a/tests/test-utils.c +++ b/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/utils.c b/utils.c index 43c8d79..51875ae 100644 --- a/utils.c +++ b/utils.c @@ -23,6 +23,7 @@ #include #include #include +#include #ifdef HAVE_SELINUX #include #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/utils.h b/utils.h index 9f17297..079fe7c 100644 --- a/utils.h +++ b/utils.h @@ -24,26 +24,33 @@ #include #include #include +#include #include #include #include +#include #include #include #include #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);