Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package bubblewrap for openSUSE:Factory checked in at 2026-10-23 20:21:57 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/bubblewrap (Old) and /work/SRC/openSUSE:Factory/.bubblewrap.new.2252680 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "bubblewrap" Fri Oct 23 20:21:57 2026 rev:26 rq:1387012 version:0.13.1 Changes: -------- --- /work/SRC/openSUSE:Factory/bubblewrap/bubblewrap.changes 2026-09-08 16:53:40.891868413 +0200 +++ /work/SRC/openSUSE:Factory/.bubblewrap.new.2252680/bubblewrap.changes 2026-10-23 20:22:21.780667878 +0200 @@ -1,0 +2,42 @@ +Thu Oct 15 02:32:00 UTC 2026 - Kyle Scheuing <[email protected]> + +- update to 0.13.1: + * bwrap now accepts multiple --sync-fd arguments and will handle + all of the fds. + * O_PATH is used when opening parent directories in the new root, + fixing an error when read permission is missing. + + +------------------------------------------------------------------- +Tue Oct 13 22:24:12 UTC 2026 - Kyle Scheuing <[email protected]> + +- update to 0.13.0: + * Linux kernel headers with __NR_pivot_root (Linux 2.3.41+) are + required at build-time on all architectures + * If compiled with -Dassume_kernel=5.12.0 or newer, then a kernel with + the mount_setattr syscall is required at runtime, and kernel headers + with __NR_mount_setattr are required at build-time on most + architectures. On the x86_64, i386 and aarch64 architectures, a + fallback is provided to be able to compile with pre-5.12 kernel + headers. + * On kernels that support it (5.12+), use mount_setattr() to remount + filesystems with ro, nodev and/or nosuid. + * Arguments that expect a path argument (--bind, --overlay-src, etc.) + now reject empty strings. Previously they would sometimes be treated + as the root directory due to implementation details, but this was + unintended. + * A new build option -Ddebug_logging=true can be used to enable + verbose debug logging (not recommended for distro builds). These + debug messages don't appear unless environment variable + DEBUG_INVOCATION is set at runtime. + * Fix build failures with older gcc or older -std argument + * Fix build failure with musl + * Fix test failure on systems that lack hostname(1) + * Fix test failure on systems with a mount point containing a + backslash + * Fix test failure on systems with older xdg-desktop-portal + * Don't leave a temporary file behind after running the test suite + * CI improvements + + +------------------------------------------------------------------- Old: ---- bubblewrap-0.12.0.tar.xz bubblewrap-0.12.0.tar.xz.sha256sum New: ---- bubblewrap-0.13.1.tar.xz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ bubblewrap.spec ++++++ --- /var/tmp/diff_new_pack.ne9zx3/_old 2026-10-23 20:22:22.419694414 +0200 +++ /var/tmp/diff_new_pack.ne9zx3/_new 2026-10-23 20:22:22.420694456 +0200 @@ -18,14 +18,13 @@ Name: bubblewrap -Version: 0.12.0 +Version: 0.13.1 Release: 0 Summary: Core execution tool for unprivileged containers License: LGPL-2.1-or-later Group: Productivity/Security URL: https://github.com/containers/bubblewrap Source0: %{url}/releases/download/v%{version}/%{name}-%{version}.tar.xz -Source1: %{url}/releases/download/v%{version}/%{name}-%{version}.tar.xz.sha256sum # https://www.pseudorandom.co.uk/2003/contact/ # 0x4DE8FF2A63C7CC90, fingerprint: DA98 F25C 0871 C49A 59EA FF2C 4DE8 FF2A 63C7 CC90 BuildRequires: docbook-xsl-stylesheets ++++++ bubblewrap-0.12.0.tar.xz -> bubblewrap-0.13.1.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bubblewrap-0.12.0/.editorconfig new/bubblewrap-0.13.1/.editorconfig --- old/bubblewrap-0.12.0/.editorconfig 2026-08-26 12:08:29.000000000 +0200 +++ new/bubblewrap-0.13.1/.editorconfig 2026-10-14 14:13:57.000000000 +0200 @@ -1,5 +1,15 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + [*.[ch]] indent_style = space indent_size = 2 -trim_trailing_whitespace = true indent_brace_style = gnu + +[*.py] +indent_style = space +indent_size = 4 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bubblewrap-0.12.0/.github/dependabot.yml new/bubblewrap-0.13.1/.github/dependabot.yml --- old/bubblewrap-0.12.0/.github/dependabot.yml 1970-01-01 01:00:00.000000000 +0100 +++ new/bubblewrap-0.13.1/.github/dependabot.yml 2026-10-14 14:13:57.000000000 +0200 @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" + groups: + actions: + patterns: + - "*" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bubblewrap-0.12.0/.github/workflows/check.yml new/bubblewrap-0.13.1/.github/workflows/check.yml --- old/bubblewrap-0.12.0/.github/workflows/check.yml 2026-08-26 12:08:29.000000000 +0200 +++ new/bubblewrap-0.13.1/.github/workflows/check.yml 2026-10-14 14:13:57.000000000 +0200 @@ -8,13 +8,16 @@ branches: - main +permissions: + contents: read + jobs: meson: name: Build with Meson and gcc, and test runs-on: ubuntu-latest steps: - name: Check out - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Install build-dependencies run: sudo ./ci/builddeps.sh - name: Enable user namespaces @@ -23,13 +26,10 @@ run: mkdir test-logs - name: setup run: | - meson _build + meson setup _build -Db_sanitize=address,undefined -Ddebug_logging=true -Doptimization=2 env: CFLAGS: >- - -O2 -Wp,-D_FORTIFY_SOURCE=2 - -fsanitize=address - -fsanitize=undefined - name: compile run: ninja -C _build -v - name: smoke-test @@ -38,11 +38,13 @@ ./_build/bwrap --bind / / --tmpfs /tmp true env: ASAN_OPTIONS: detect_leaks=0 + DEBUG_INVOCATION: '1' - name: test run: | BWRAP_MUST_WORK=1 meson test -C _build env: ASAN_OPTIONS: detect_leaks=0 + DEBUG_INVOCATION: '1' - name: Collect overall test logs on failure if: failure() run: mv _build/meson-logs/testlog.txt test-logs/ || true @@ -71,7 +73,7 @@ test ! -e DESTDIR-as-subproject/usr/local/libexec/bwrap tests/use-as-subproject/assert-correct-rpath.py DESTDIR-as-subproject/usr/local/libexec/not-flatpak-bwrap - name: Upload test logs - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 if: failure() || cancelled() with: name: test logs @@ -80,6 +82,8 @@ clang: name: Build with clang and analyze runs-on: ubuntu-latest + permissions: + security-events: write strategy: fail-fast: false matrix: @@ -87,19 +91,18 @@ - cpp steps: - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} - name: Check out - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Install build-dependencies run: sudo ./ci/builddeps.sh --clang - - run: meson build -Dselinux=enabled + - run: meson setup build -Doptimization=2 -Dselinux=enabled env: CC: clang CFLAGS: >- - -O2 -Werror=unused-variable - run: meson compile -C build - name: CodeQL analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v4 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bubblewrap-0.12.0/.gitignore new/bubblewrap-0.13.1/.gitignore --- old/bubblewrap-0.12.0/.gitignore 1970-01-01 01:00:00.000000000 +0100 +++ new/bubblewrap-0.13.1/.gitignore 2026-10-14 14:13:57.000000000 +0200 @@ -0,0 +1 @@ +__pycache__/ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bubblewrap-0.12.0/NEWS.md new/bubblewrap-0.13.1/NEWS.md --- old/bubblewrap-0.12.0/NEWS.md 2026-08-26 12:08:29.000000000 +0200 +++ new/bubblewrap-0.13.1/NEWS.md 2026-10-14 14:13:57.000000000 +0200 @@ -1,8 +1,84 @@ +bubblewrap 0.13.1 +================= + +Released: 2026-10-14 + +Enhancements: + + * bwrap now accepts multiple --sync-fd arguments and will handle + all of the fds. + +Bug fixes: + + * O_PATH is used when opening parent directories in the new root, + fixing an error when read permission is missing. (#806, #855). + +Thanks: smcv, swick + +bubblewrap 0.13.0 +================= + +Released: 2026-09-22 + +Dependencies: + + * Linux kernel headers with `__NR_pivot_root` (Linux 2.3.41+) + are required at build-time on all architectures (#789). + + * If compiled with `-Dassume_kernel=5.12.0` or newer, + then a kernel with the `mount_setattr` syscall is required at runtime, + and kernel headers with `__NR_mount_setattr` are required at build-time + on most architectures. + On the `x86_64`, `i386` and `aarch64` architectures, + a fallback is provided to be able to compile with pre-5.12 kernel headers. + (#756, #785) + +Enhancements: + + * On kernels that support it (5.12+), use `mount_setattr()` to remount + filesystems with `ro`, `nodev` and/or `nosuid`. (#756) + + * Arguments that expect a path argument (`--bind`, `--overlay-src`, etc.) + now reject empty strings. + Previously they would sometimes be treated as the root directory + due to implementation details, but this was unintended. + (#771) + + * A new build option `-Ddebug_logging=true` can be used to enable + verbose debug logging (not recommended for distro builds). + These debug messages don't appear unless environment variable + `DEBUG_INVOCATION` is set at runtime. + +Bug fixes: + + * Fix build failures with older gcc or older `-std` argument (#773, #786) + + * Fix build failure with musl (#782) + + * Fix test failure on systems that lack hostname(1) (#775) + + * Fix test failure on systems with a mount point containing a backslash + (#778) + + * Fix test failure on systems with older xdg-desktop-portal (#794) + + * Don't leave a temporary file behind after running the test suite (#775) + + * CI improvements + +Thanks: abhinavmir, ao2, smcv, vaibhav8a, xxyzz + bubblewrap 0.12.0 ================= Released: 2026-08-26 +Dependencies: + + * If compiled with `-Dassume_kernel=5.6.0` or newer, + then a kernel with the `openat2` syscall is required at runtime, + and kernel headers with `__NR_openat2` are required at build-time. + Enhancements: * The flag --not-a-security-boundary was added. If this is enabled diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bubblewrap-0.12.0/README.md new/bubblewrap-0.13.1/README.md --- old/bubblewrap-0.12.0/README.md 2026-08-26 12:08:29.000000000 +0200 +++ new/bubblewrap-0.13.1/README.md 2026-10-14 14:13:57.000000000 +0200 @@ -86,7 +86,7 @@ If you need to build bubblewrap from source, you can do this with meson: ```sh -meson _builddir +meson setup _builddir meson compile -C _builddir meson test -C _builddir meson install -C _builddir diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bubblewrap-0.12.0/bind-mount.c new/bubblewrap-0.13.1/bind-mount.c --- old/bubblewrap-0.12.0/bind-mount.c 2026-08-26 12:08:29.000000000 +0200 +++ new/bubblewrap-0.13.1/bind-mount.c 2026-10-14 14:13:57.000000000 +0200 @@ -24,6 +24,23 @@ #include "utils.h" #include "bind-mount.h" +#if ASSUMED_KERNEL < BWRAP_KERNEL_VERSION (5, 12, 0) +# define USE_MOUNT_SETATTR_FALLBACK 1 +#else +# define USE_MOUNT_SETATTR_FALLBACK 0 +#endif + +static bind_mount_result mount_setattr_setup (const char *resolved_dest, + bind_option_t options, + char **failing_path); + +static bind_mount_result mount_setattr_fallback (const char *resolved_dest, + bind_option_t options, + char **failing_path); + +bool opt_force_mount_setattr_fallback = false; + +#if USE_MOUNT_SETATTR_FALLBACK static char * skip_token (char *line, bool eat_whitespace) { @@ -372,6 +389,7 @@ return steal_pointer (&mount_tab); } +#endif /* USE_MOUNT_SETATTR_FALLBACK */ bind_mount_result bind_mount (const char *src, @@ -408,14 +426,9 @@ bind_option_t options, char **failing_path) { - bool readonly = (options & BIND_READONLY) != 0; - bool devices = (options & BIND_DEVICES) != 0; bool recursive = (options & BIND_RECURSIVE) != 0; - unsigned long current_flags, new_flags; - cleanup_mount_tab MountTab mount_tab = NULL; cleanup_free char *resolved_dest = NULL; cleanup_free char *dest_proc = NULL; - int i; dest_proc = fd_to_proc_path (dest_fd); @@ -451,11 +464,43 @@ } } + return mount_setattr_setup (resolved_dest, options, failing_path); +} + +static bind_mount_result +mount_setattr_fallback (const char *resolved_dest, + bind_option_t options, + char **failing_path) +{ +#if !USE_MOUNT_SETATTR_FALLBACK + debug ("No mount_setattr fallback"); + (void) resolved_dest; + (void) options; + + if (failing_path != NULL) + *failing_path = xstrdup (resolved_dest); + + errno = ENOSYS; + return BIND_MOUNT_ERROR_MOUNT_SETATTR; +#else + bool readonly = (options & BIND_READONLY) != 0; + bool devices = (options & BIND_DEVICES) != 0; + bool recursive = (options & BIND_RECURSIVE) != 0; + unsigned long current_flags, new_flags; + cleanup_mount_tab MountTab mount_tab = NULL; + int i; + + debug ("Remounting mount table entries (%s %s) on \"%s\" %s", + readonly ? "ro" : "rw", + devices ? "dev" : "nodev", + resolved_dest, + recursive ? "recursively" : "only"); + mount_tab = parse_mountinfo (resolved_dest); if (mount_tab[0].mountpoint == NULL) { if (failing_path != NULL) - *failing_path = steal_pointer (&resolved_dest); + *failing_path = xstrdup (resolved_dest); errno = EINVAL; return BIND_MOUNT_ERROR_FIND_DEST_MOUNT; @@ -469,7 +514,7 @@ NULL, MS_SILENT | MS_BIND | MS_REMOUNT | new_flags, NULL) != 0) { if (failing_path != NULL) - *failing_path = steal_pointer (&resolved_dest); + *failing_path = xstrdup (resolved_dest); return BIND_MOUNT_ERROR_REMOUNT_DEST; } @@ -482,6 +527,7 @@ { for (i = 1; mount_tab[i].mountpoint != NULL; i++) { + debug ("Acting on submount %s", mount_tab[i].mountpoint); current_flags = mount_tab[i].options; new_flags = current_flags | (devices ? 0 : MS_NODEV) | MS_NOSUID | (readonly ? MS_RDONLY : 0); if (new_flags != current_flags && @@ -510,7 +556,9 @@ } } + debug ("-> success"); return BIND_MOUNT_SUCCESS; +#endif /* USE_MOUNT_SETATTR_FALLBACK */ } /** @@ -556,6 +604,10 @@ failing_path); break; + case BIND_MOUNT_ERROR_MOUNT_SETATTR: + string = xasprintf ("mount_setattr() failed at \"%s\"", failing_path); + break; + case BIND_MOUNT_SUCCESS: string = xstrdup ("Success"); break; @@ -608,6 +660,7 @@ case BIND_MOUNT_ERROR_READLINK_DEST_PROC_FD: case BIND_MOUNT_ERROR_FIND_DEST_MOUNT: case BIND_MOUNT_ERROR_OPEN_FD: + case BIND_MOUNT_ERROR_MOUNT_SETATTR: case BIND_MOUNT_SUCCESS: default: fprintf (stderr, ": %s", strerror (saved_errno)); @@ -617,3 +670,67 @@ fprintf (stderr, "\n"); exit (1); } + +static bind_mount_result +mount_setattr_setup (const char *resolved_dest, + bind_option_t options, + char **failing_path) +{ + static bool mount_attr_supported = true; + bool readonly = (options & BIND_READONLY) != 0; + bool devices = (options & BIND_DEVICES) != 0; + bool recursive = (options & BIND_RECURSIVE) != 0; + + if (mount_attr_supported && !opt_force_mount_setattr_fallback) + { + struct mount_attr attr = { + .attr_clr = 0, + .attr_set = MOUNT_ATTR_NOSUID, + }; + + debug ("Setting mount attributes (%s %s) on \"%s\" %s", + readonly ? "ro" : "rw", + devices ? "dev" : "nodev", + resolved_dest, + recursive ? "recursively" : "only"); + + if (!devices) + attr.attr_set |= MOUNT_ATTR_NODEV; + + if (readonly) + attr.attr_set |= MOUNT_ATTR_RDONLY; + + unsigned int setattr_flags = AT_EMPTY_PATH; + + if (recursive) + setattr_flags |= AT_RECURSIVE; + + /* reopen dest_fd after mount() */ + cleanup_fd int resolved_dest_fd = + TEMP_FAILURE_RETRY(open(resolved_dest, O_PATH | O_CLOEXEC)); + if (resolved_dest_fd < 0) + { + if (failing_path != NULL) + *failing_path = xstrdup (resolved_dest); + + return BIND_MOUNT_ERROR_OPEN_FD; + } + + if (mount_setattr_wrapper (resolved_dest_fd, "", setattr_flags, &attr, sizeof(attr)) == 0) + { + debug ("-> success"); + return BIND_MOUNT_SUCCESS; + } + else if (errno != ENOSYS) + { + if (failing_path != NULL) + *failing_path = xstrdup (resolved_dest); + return BIND_MOUNT_ERROR_MOUNT_SETATTR; + } + + debug ("-> Falling back"); + } + /* mount_setattr(2) isn't available, so we'll have to do this the hard way: */ + mount_attr_supported = false; + return mount_setattr_fallback (resolved_dest, options, failing_path); +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bubblewrap-0.12.0/bind-mount.h new/bubblewrap-0.13.1/bind-mount.h --- old/bubblewrap-0.12.0/bind-mount.h 2026-08-26 12:08:29.000000000 +0200 +++ new/bubblewrap-0.13.1/bind-mount.h 2026-10-14 14:13:57.000000000 +0200 @@ -37,6 +37,7 @@ BIND_MOUNT_ERROR_REMOUNT_DEST, BIND_MOUNT_ERROR_REMOUNT_SUBMOUNT, BIND_MOUNT_ERROR_OPEN_FD, + BIND_MOUNT_ERROR_MOUNT_SETATTR, } bind_mount_result; bind_mount_result bind_mount (const char *src, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bubblewrap-0.12.0/bubblewrap.c new/bubblewrap-0.13.1/bubblewrap.c --- old/bubblewrap-0.12.0/bubblewrap.c 2026-08-26 12:08:29.000000000 +0200 +++ new/bubblewrap-0.13.1/bubblewrap.c 2026-10-14 14:13:57.000000000 +0200 @@ -21,6 +21,7 @@ #include <poll.h> #include <sched.h> +#include <limits.h> #include <pwd.h> #include <grp.h> #include <ctype.h> @@ -78,7 +79,8 @@ 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; +static int *opt_sync_fd = NULL; +static int opt_sync_fd_count = 0; static int opt_block_fd = -1; static int opt_userns_block_fd = -1; static int opt_info_fd = -1; @@ -1041,7 +1043,7 @@ fn = xstrdup (path); p = fn; - cleanup_fd int parent_fd = openat_in_root ("/newroot", "/", O_DIRECTORY); + cleanup_fd int parent_fd = openat_in_root ("/newroot", "/", O_DIRECTORY | O_PATH | O_CLOEXEC); while (*p == '/') p++; @@ -1071,7 +1073,7 @@ char saved = *after_component; *after_component = 0; - cleanup_fd int new_parent_fd = openat_in_root ("/newroot", fn, O_DIRECTORY); + cleanup_fd int new_parent_fd = openat_in_root ("/newroot", fn, O_DIRECTORY | O_PATH | O_CLOEXEC); *after_component = saved; if (new_parent_fd < 0) return -1; @@ -1707,6 +1709,20 @@ warn ("Only the last %s option will take effect", name); } +/* Path arguments are turned into real filenames by concatenating them onto + "/oldroot/" or "/newroot/", so an empty path would silently be taken to + mean the root directory. That is never what the caller meant, so treat it + as a usage error. */ +static const char * +path_argument (const char *option, + const char *path) +{ + if (path[0] == '\0') + die ("%s does not take an empty path argument", option); + + return path; +} + static void make_setup_overlay_src_ops (const char *const *const argv) { @@ -1728,7 +1744,7 @@ for (i = 1; i <= next_overlay_src_count; i++) { op = setup_op_new (SETUP_OVERLAY_SRC); - op->source = argv[1 - 2 * i]; + op->source = path_argument ("--overlay-src", argv[1 - 2 * i]); } next_overlay_src_count = 0; } @@ -1906,7 +1922,7 @@ if (opt_chdir_path != NULL) warn_only_last_option ("--chdir"); - opt_chdir_path = argv[1]; + opt_chdir_path = path_argument (arg, argv[1]); argv++; argc--; } @@ -1924,7 +1940,7 @@ die ("--remount-ro takes one argument"); op = setup_op_new (SETUP_REMOUNT_RO_NO_RECURSIVE); - op->dest = argv[1]; + op->dest = path_argument (arg, argv[1]); argv++; argc--; @@ -1936,8 +1952,8 @@ die ("%s takes two arguments", arg); op = setup_op_new (SETUP_BIND_MOUNT); - op->source = argv[1]; - op->dest = argv[2]; + op->source = path_argument (arg, argv[1]); + op->dest = path_argument (arg, argv[2]); if (strcmp(arg, "--bind-try") == 0) op->flags = ALLOW_NOTEXIST; @@ -1951,8 +1967,8 @@ die ("%s takes two arguments", arg); op = setup_op_new (SETUP_RO_BIND_MOUNT); - op->source = argv[1]; - op->dest = argv[2]; + op->source = path_argument (arg, argv[1]); + op->dest = path_argument (arg, argv[2]); if (strcmp(arg, "--ro-bind-try") == 0) op->flags = ALLOW_NOTEXIST; @@ -1966,8 +1982,8 @@ die ("%s takes two arguments", arg); op = setup_op_new (SETUP_DEV_BIND_MOUNT); - op->source = argv[1]; - op->dest = argv[2]; + op->source = path_argument (arg, argv[1]); + op->dest = path_argument (arg, argv[2]); if (strcmp(arg, "--dev-bind-try") == 0) op->flags = ALLOW_NOTEXIST; @@ -1993,7 +2009,7 @@ op = setup_op_new (SETUP_BIND_MOUNT); op->source = xasprintf ("/proc/self/fd/%d", src_fd); op->fd = src_fd; - op->dest = argv[2]; + op->dest = path_argument (arg, argv[2]); argv += 2; argc -= 2; @@ -2016,10 +2032,10 @@ die ("--overlay requires at least one --overlay-src"); op = setup_op_new (SETUP_OVERLAY_MOUNT); - op->source = argv[1]; + op->source = path_argument (arg, argv[1]); workdir_op = setup_op_new (SETUP_OVERLAY_SRC); - workdir_op->source = argv[2]; - op->dest = argv[3]; + workdir_op->source = path_argument (arg, argv[2]); + op->dest = path_argument (arg, argv[3]); make_setup_overlay_src_ops (argv); argv += 3; @@ -2034,7 +2050,7 @@ die ("--tmp-overlay requires at least one --overlay-src"); op = setup_op_new (SETUP_TMP_OVERLAY_MOUNT); - op->dest = argv[1]; + op->dest = path_argument (arg, argv[1]); make_setup_overlay_src_ops (argv); opt_tmp_overlay_count++; @@ -2050,7 +2066,7 @@ die ("--ro-overlay requires at least two --overlay-src"); op = setup_op_new (SETUP_RO_OVERLAY_MOUNT); - op->dest = argv[1]; + op->dest = path_argument (arg, argv[1]); make_setup_overlay_src_ops (argv); argv += 1; @@ -2062,7 +2078,7 @@ die ("--proc takes an argument"); op = setup_op_new (SETUP_MOUNT_PROC); - op->dest = argv[1]; + op->dest = path_argument (arg, argv[1]); argv += 1; argc -= 1; @@ -2103,7 +2119,7 @@ die ("--dev takes an argument"); op = setup_op_new (SETUP_MOUNT_DEV); - op->dest = argv[1]; + op->dest = path_argument (arg, argv[1]); opt_needs_devpts = true; argv += 1; @@ -2115,7 +2131,7 @@ die ("--tmpfs takes an argument"); op = setup_op_new (SETUP_MOUNT_TMPFS); - op->dest = argv[1]; + op->dest = path_argument (arg, argv[1]); /* We historically hard-coded the mode of a tmpfs as 0755. */ if (next_perms >= 0) @@ -2140,7 +2156,7 @@ die ("--mqueue takes an argument"); op = setup_op_new (SETUP_MOUNT_MQUEUE); - op->dest = argv[1]; + op->dest = path_argument (arg, argv[1]); argv += 1; argc -= 1; @@ -2151,7 +2167,7 @@ die ("--dir takes an argument"); op = setup_op_new (SETUP_MAKE_DIR); - op->dest = argv[1]; + op->dest = path_argument (arg, argv[1]); /* We historically hard-coded the mode of a --dir as 0755. */ if (next_perms >= 0) @@ -2177,7 +2193,7 @@ op = setup_op_new (SETUP_MAKE_FILE); op->fd = file_fd; - op->dest = argv[2]; + op->dest = path_argument (arg, argv[2]); /* We historically hard-coded the mode of a --file as 0666. */ if (next_perms >= 0) @@ -2203,7 +2219,7 @@ op = setup_op_new (SETUP_MAKE_BIND_FILE); op->fd = file_fd; - op->dest = argv[2]; + op->dest = path_argument (arg, argv[2]); /* This is consistent with previous bubblewrap behaviour: * before implementing --perms, we took the permissions @@ -2231,7 +2247,7 @@ op = setup_op_new (SETUP_MAKE_RO_BIND_FILE); op->fd = file_fd; - op->dest = argv[2]; + op->dest = path_argument (arg, argv[2]); /* This is consistent with previous bubblewrap behaviour: * before implementing --perms, we took the permissions @@ -2251,8 +2267,8 @@ die ("--symlink takes two arguments"); op = setup_op_new (SETUP_MAKE_SYMLINK); - op->source = argv[1]; - op->dest = argv[2]; + op->source = path_argument (arg, argv[1]); + op->dest = path_argument (arg, argv[2]); argv += 2; argc -= 2; @@ -2262,7 +2278,7 @@ if (argc < 2) die ("--lock-file takes an argument"); - (void) lock_file_new (argv[1]); + (void) lock_file_new (path_argument (arg, argv[1])); argv += 1; argc -= 1; @@ -2275,14 +2291,12 @@ if (argc < 2) die ("--sync-fd takes an argument"); - if (opt_sync_fd != -1) - warn_only_last_option ("--sync-fd"); - the_fd = strtol (argv[1], &endptr, 10); if (argv[1][0] == 0 || endptr[0] != 0 || the_fd < 0) die ("Invalid fd: %s", argv[1]); - opt_sync_fd = the_fd; + opt_sync_fd = xrealloc (opt_sync_fd, sizeof (&opt_sync_fd[0]) * (opt_sync_fd_count + 1)); + opt_sync_fd[opt_sync_fd_count++] = the_fd; argv += 1; argc -= 1; @@ -2692,7 +2706,7 @@ op = setup_op_new (SETUP_CHMOD); op->flags = NO_CREATE_DEST; op->perms = (int) perms; - op->dest = argv[2]; + op->dest = path_argument (arg, argv[2]); argv += 2; argc -= 2; @@ -2704,10 +2718,31 @@ else if (has_prefix (arg, "--debug-opt=")) { const char *val = arg + strlen ("--debug-opt="); + if (strcmp (val, "force-openat-fallback") == 0) - opt_force_openat_fallback = true; + { + opt_force_openat_fallback = true; + } + else if (strcmp (val, "force-mount-setattr-fallback") == 0) + { + opt_force_mount_setattr_fallback = true; + } + else if (strcmp (val, "print-assumed-kernel") == 0) + { +#ifdef HAVE_ASSUMED_KERNEL + printf ("%u.%u.%u\n", + ASSUMED_KERNEL_MAJOR, + ASSUMED_KERNEL_MINOR, + ASSUMED_KERNEL_PATCH); +#else + printf ("0.0.0\n"); +#endif + exit (0); + } else - die ("Unknown --debug-opt value: %s", val); + { + die ("Unknown --debug-opt value: %s", val); + } } else if (strcmp (arg, "--") == 0) { @@ -2872,7 +2907,7 @@ if (argc == 2 && (strcmp (argv[1], "--version") == 0)) print_version_and_exit (); - /* Reset SIGCHILD to SIG_DFL allowing signalfd working propertly + /* Reset SIGCHILD to SIG_DFL allowing signalfd working properly * if the parent process had set SIGCHLD to SIG_IGN. */ sigemptyset (&sa.sa_mask); sa.sa_handler = SIG_DFL; @@ -2884,6 +2919,11 @@ /* Get the (optional) privileges we need */ acquire_privs (); +#ifdef BWRAP_DEBUG + if (getenv ("DEBUG_INVOCATION") != NULL) + bwrap_is_debugging = true; +#endif + /* Never gain any more privs during exec */ if (prctl (PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) die_with_error ("prctl(PR_SET_NO_NEW_PRIVS) failed"); @@ -3436,7 +3476,7 @@ debug ("forking for child"); - if (!opt_as_pid_1 && (opt_unshare_pid || lock_files != NULL || opt_sync_fd != -1)) + if (!opt_as_pid_1 && (opt_unshare_pid || lock_files != NULL || opt_sync_fd_count > 0)) { /* We have to have a pid 1 in the pid namespace, because * otherwise we'll get a bunch of zombies as nothing reaps @@ -3458,14 +3498,16 @@ process). Any other fds will been passed on to the child though. */ { - int dont_close[3]; + int *dont_close = xcalloc (opt_sync_fd_count + 2, sizeof (int)); int j = 0; + int k; if (event_fd != -1) dont_close[j++] = event_fd; - if (opt_sync_fd != -1) - dont_close[j++] = opt_sync_fd; + for (k = 0; k < opt_sync_fd_count; k++) + dont_close[j++] = opt_sync_fd[k]; dont_close[j++] = -1; fdwalk (close_extra_fds, dont_close); + free (dont_close); } return do_init (event_fd, pid); @@ -3481,8 +3523,9 @@ --sync-fd will still work unless the container process doesn't close this file. */ if (!opt_as_pid_1) { - if (opt_sync_fd != -1) - close (opt_sync_fd); + int k; + for (k = 0; k < opt_sync_fd_count; k++) + close (opt_sync_fd[k]); } /* We want sigchild in the child */ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bubblewrap-0.12.0/bwrap.xml new/bubblewrap-0.13.1/bwrap.xml --- old/bubblewrap-0.12.0/bwrap.xml 2026-08-26 12:08:29.000000000 +0200 +++ new/bubblewrap-0.13.1/bwrap.xml 2026-10-14 14:13:57.000000000 +0200 @@ -73,6 +73,10 @@ When options are used multiple times, the last option wins, unless otherwise specified. </para> + <para> + An empty string is not a valid path, so options that take a path + reject it with an error. + </para> <para>General options:</para> <variablelist> <varlistentry> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bubblewrap-0.12.0/ci/builddeps.sh new/bubblewrap-0.13.1/ci/builddeps.sh --- old/bubblewrap-0.12.0/ci/builddeps.sh 2026-08-26 12:08:29.000000000 +0200 +++ new/bubblewrap-0.13.1/ci/builddeps.sh 2026-10-14 14:13:57.000000000 +0200 @@ -66,6 +66,7 @@ pkg-config \ python3 \ xsltproc \ + python3-seccomp \ ${NULL+} if [ -n "${opt_clang}" ]; then diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bubblewrap-0.12.0/meson.build new/bubblewrap-0.13.1/meson.build --- old/bubblewrap-0.12.0/meson.build 2026-08-26 12:08:29.000000000 +0200 +++ new/bubblewrap-0.13.1/meson.build 2026-10-14 14:13:57.000000000 +0200 @@ -1,7 +1,7 @@ project( 'bubblewrap', 'c', - version : '0.12.0', + version : '0.13.1', meson_version : '>=0.49.0', default_options : [ 'warning_level=2', @@ -87,6 +87,10 @@ endif endif +if get_option('debug_logging') + cdata.set('BWRAP_DEBUG', 1) +endif + assume_kernel = get_option('assume_kernel') if assume_kernel != '' kernel_parts = assume_kernel.split('.') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bubblewrap-0.12.0/meson_options.txt new/bubblewrap-0.13.1/meson_options.txt --- old/bubblewrap-0.12.0/meson_options.txt 2026-08-26 12:08:29.000000000 +0200 +++ new/bubblewrap-0.13.1/meson_options.txt 2026-10-14 14:13:57.000000000 +0200 @@ -32,6 +32,12 @@ value : 'auto', ) option( + 'debug_logging', + type : 'boolean', + description : 'Provide debug messages, off-by-default', + value : false, +) +option( 'program_prefix', type : 'string', description : 'Prepend string to bwrap executable name, for use with subprojects', diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bubblewrap-0.12.0/safe_openat.c new/bubblewrap-0.13.1/safe_openat.c --- old/bubblewrap-0.12.0/safe_openat.c 2026-08-26 12:08:29.000000000 +0200 +++ new/bubblewrap-0.13.1/safe_openat.c 2026-10-14 14:13:57.000000000 +0200 @@ -21,6 +21,7 @@ #include "utils.h" #include <sys/syscall.h> +#include <limits.h> #include <unistd.h> #include <errno.h> #include <stdint.h> @@ -89,7 +90,7 @@ #define consume_slashes(t) \ ({ \ - typeof (t) _s = (t); \ + __typeof__ (t) _s = (t); \ while (*_s == '/') \ _s++; \ _s; \ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bubblewrap-0.12.0/tests/test-helper.py new/bubblewrap-0.13.1/tests/test-helper.py --- old/bubblewrap-0.12.0/tests/test-helper.py 2026-08-26 12:08:29.000000000 +0200 +++ new/bubblewrap-0.13.1/tests/test-helper.py 2026-10-14 14:13:57.000000000 +0200 @@ -1,5 +1,6 @@ #!/usr/bin/env python3 # Copyright 2024 Alexander Larsson +# Copyright 2026 Collabora Ltd. # SPDX-License-Identifier: LGPL-2.1-or-later # # Test infrastructure for bwrap sandbox integration tests. @@ -8,6 +9,7 @@ import json import os +import re import socket import subprocess import sys @@ -205,10 +207,29 @@ os.close(fd) +# A sequence of one or more octal escapes such as \12\345 +_OCTAL_ESCAPES = re.compile(r'(?:\\[0-7]{1,3})+') + + +def _octal_unescape_char(match): + """Replace _OCTAL_ESCAPES with their expansion""" + text = match.group(0) + assert text[0] == '\\' + sequences = text[1:].split('\\') + return bytes( + [int(seq, base=8) for seq in sequences] + ).decode('utf-8', errors='surrogateescape') + + +def unescape_mount(escaped): + """Unescape a mount point as shown in /proc/self/mounts""" + return _OCTAL_ESCAPES.sub(_octal_unescape_char, escaped) + + def list_mounts(): """Return list of mountpoints from /proc/self/mounts.""" with open('/proc/self/mounts') as f: - return [line.split()[1] for line in f] + return [unescape_mount(line.split()[1]) for line in f] def can_run_bwrap(): @@ -225,6 +246,18 @@ return False +def get_assumed_kernel(): + """Return the minimum kernel supported by BWRAP, e.g. 5.10.0""" + r = subprocess.run( + [BWRAP, '--debug-opt=print-assumed-kernel'], + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + check=True, + ) + return tuple([int(n) for n in r.stdout.decode('ascii').strip().split('.')]) + + class TAPResult(unittest.TestResult): def __init__(self): super().__init__() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bubblewrap-0.12.0/tests/test-run.sh new/bubblewrap-0.13.1/tests/test-run.sh --- old/bubblewrap-0.12.0/tests/test-run.sh 2026-08-26 12:08:29.000000000 +0200 +++ new/bubblewrap-0.13.1/tests/test-run.sh 2026-10-14 14:13:57.000000000 +0200 @@ -172,6 +172,34 @@ assert_file_has_content err.txt "^bwrap: Can't find source path.*source-enoent" ok "error prefixing" +# Test that an empty path argument is rejected. Empty paths used to be +# interpreted as the root directory, so for example --bind-try "" DEST +# silently gave the sandbox access to the whole filesystem. +for opt in --bind --bind-try --dev-bind --dev-bind-try --ro-bind --ro-bind-try; do + if $RUN "$opt" "" /mnt true 2>err.txt; then + assert_not_reached "$opt accepted an empty source" + fi + assert_file_has_content err.txt "^bwrap: $opt does not take an empty path argument" + + if $RUN "$opt" / "" true 2>err.txt; then + assert_not_reached "$opt accepted an empty destination" + fi + assert_file_has_content err.txt "^bwrap: $opt does not take an empty path argument" +done + +for opt in --chdir --dev --dir --mqueue --proc --remount-ro --tmpfs; do + if $RUN "$opt" "" true 2>err.txt; then + assert_not_reached "$opt accepted an empty path" + fi + assert_file_has_content err.txt "^bwrap: $opt does not take an empty path argument" +done + +if $RUN --symlink /usr "" true 2>err.txt; then + assert_not_reached "--symlink accepted an empty destination" +fi +assert_file_has_content err.txt "^bwrap: --symlink does not take an empty path argument" +ok "empty path arguments are rejected" + if ! ${is_uidzero}; then # When invoked as non-root, check that by default we have no caps left for OPT in "" "--unshare-user-try --as-pid-1" "--unshare-user-try" "--as-pid-1"; do @@ -292,6 +320,7 @@ else ok_skip "/mnt does not exist or is a symlink" fi + rm "$tempfile" else ok_skip "mktemp not found" ok_skip "mktemp not found" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bubblewrap-0.12.0/tests/test-sandbox.py new/bubblewrap-0.13.1/tests/test-sandbox.py --- old/bubblewrap-0.12.0/tests/test-sandbox.py 2026-08-26 12:08:29.000000000 +0200 +++ new/bubblewrap-0.13.1/tests/test-sandbox.py 2026-10-14 14:13:57.000000000 +0200 @@ -26,6 +26,7 @@ BWRAP = _helper.BWRAP can_run_bwrap = _helper.can_run_bwrap +get_assumed_kernel = _helper.get_assumed_kernel in_sandbox = _helper.in_sandbox list_mounts = _helper.list_mounts DataFd = _helper.DataFd @@ -137,7 +138,15 @@ f'expected bwrap to fail, stderr: {result.stderr!r}') def assertMountFlags(self, path, expected, unexpected): - flags = os.statvfs(path).f_flag + try: + flags = os.statvfs(path).f_flag + except PermissionError: + if path.endswith('/doc'): + # https://github.com/flatpak/xdg-desktop-portal/issues/553 + return + else: + raise + for f in expected: self.assertTrue(flags & f, f'{path}: expected flag {f:#x} not set') for f in unexpected: @@ -308,13 +317,35 @@ @in_sandbox() def test_ro_bind_root_is_recursive(self): + self._test_ro_bind_root_is_recursive() + + @in_sandbox('--debug-opt=force-mount-setattr-fallback') + def _test_ro_bind_root_is_recursive_fallback(self): + self._test_ro_bind_root_is_recursive() + + def test_ro_bind_root_is_recursive_fallback(self): + if get_assumed_kernel() >= (5, 12, 0): + self.skipTest('mount_setattr() fallback not compiled') + else: + self._test_ro_bind_root_is_recursive_fallback() + + def _test_ro_bind_root_is_recursive(self): """The base --ro-bind / / should apply read-only to sub-mounts.""" skip_prefixes = ('/dev', '/proc', '/sys', '/tmp') for mountpoint in list_mounts(): if any(mountpoint == p or mountpoint.startswith(p + '/') for p in skip_prefixes): continue - flags = os.statvfs(mountpoint).f_flag + + try: + flags = os.statvfs(mountpoint).f_flag + except PermissionError: + if mountpoint.endswith('/doc'): + # https://github.com/flatpak/xdg-desktop-portal/issues/553 + return + else: + raise + self.assertTrue( flags & os.ST_RDONLY, f'{mountpoint} is not read-only', @@ -481,7 +512,7 @@ def test_hostname(self): result = run_bwrap('--unshare-uts', '--hostname', 'testhost', - 'hostname') + 'uname', '-n') self.assertEqual(result.returncode, 0) self.assertEqual(result.stdout.strip(), b'testhost') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bubblewrap-0.12.0/utils.c new/bubblewrap-0.13.1/utils.c --- old/bubblewrap-0.12.0/utils.c 2026-08-26 12:08:29.000000000 +0200 +++ new/bubblewrap-0.13.1/utils.c 2026-10-14 14:13:57.000000000 +0200 @@ -28,6 +28,10 @@ #include <selinux/selinux.h> #endif +#ifndef __NR_pivot_root +#error Linux kernel headers with __NR_pivot_root are required +#endif + #ifndef HAVE_SELINUX_2_3 /* libselinux older than 2.3 weren't const-correct */ #define setexeccon(x) setexeccon ((security_context_t) x) @@ -35,6 +39,10 @@ #define security_check_context(x) security_check_context ((security_context_t) x) #endif +#ifdef BWRAP_DEBUG +bool bwrap_is_debugging = false; +#endif + bool bwrap_level_prefix = false; int proc_fd = -1; @@ -44,6 +52,11 @@ va_list args, const char *detail) { +#ifdef BWRAP_DEBUG + if (severity == LOG_DEBUG && !bwrap_is_debugging) + return; +#endif + if (bwrap_level_prefix) fprintf (stderr, "<%d>", severity); @@ -900,12 +913,7 @@ int pivot_root (const char * new_root, const char * put_old) { -#ifdef __NR_pivot_root return syscall (__NR_pivot_root, new_root, put_old); -#else - errno = ENOSYS; - return -1; -#endif } char * @@ -1096,3 +1104,34 @@ src++; } } + + +/* Enable mount_setattr for systems where the kernel is new enough but + * the glibc may lag behind, like it might happen with the Steam runtime. + * + * This could be expanded for other architectures, but these seem like + * the most likely to be backported to old LTS operating systems */ +#ifndef __NR_mount_setattr +# if (defined(__x86_64__) && defined(__LP64__)) \ + || defined(__i386__) \ + || (defined(__aarch64__) && defined(__LP64__)) +# define __NR_mount_setattr 442 +# endif +#endif + +int +mount_setattr_wrapper (int dirfd, const char *path, unsigned int flags, + struct mount_attr *attr, size_t size) +{ +#ifdef __NR_mount_setattr + return syscall (__NR_mount_setattr, dirfd, path, flags, attr, size); +#else + (void) dirfd; + (void) path; + (void) flags; + (void) attr; + (void) size; + errno = ENOSYS; + return -1; +#endif +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bubblewrap-0.12.0/utils.h new/bubblewrap-0.13.1/utils.h --- old/bubblewrap-0.12.0/utils.h 2026-08-26 12:08:29.000000000 +0200 +++ new/bubblewrap-0.13.1/utils.h 2026-10-14 14:13:57.000000000 +0200 @@ -33,7 +33,8 @@ #include <sys/types.h> #include <sys/stat.h> -#if 0 +#ifdef BWRAP_DEBUG +extern bool bwrap_is_debugging; #define debug(...) bwrap_log (LOG_DEBUG, __VA_ARGS__) #else #define debug(...) @@ -158,6 +159,7 @@ int label_create_file (const char *file_label); extern bool opt_force_openat_fallback; +extern bool opt_force_mount_setattr_fallback; int safe_openat (int dirfd, const char *rootfs, @@ -254,7 +256,9 @@ static inline void cleanup_fdsetp (FdSet *set) { - for (size_t i = 0; i < set->len; i++) + size_t i; + + for (i = 0; i < set->len; i++) if (set->fds[i] >= 0) close (set->fds[i]); free (set->fds); @@ -284,3 +288,35 @@ ...); void strappend_escape_for_mount_options (StringBuilder *dest, const char *src); + +#ifndef MOUNT_ATTR_RDONLY + +#include <linux/types.h> + +struct mount_attr +{ + __u64 attr_set; + __u64 attr_clr; + __u64 propagation; + __u64 userns_fd; +}; + +#define MOUNT_ATTR_RDONLY 0x00000001 +#define MOUNT_ATTR_NOSUID 0x00000002 +#define MOUNT_ATTR_NODEV 0x00000004 +#define MOUNT_ATTR_NOEXEC 0x00000008 +#define MOUNT_ATTR__ATIME 0x00000070 +#define MOUNT_ATTR_RELATIME 0x00000000 +#define MOUNT_ATTR_NOATIME 0x00000010 +#define MOUNT_ATTR_STRICTATIME 0x00000020 +#define MOUNT_ATTR_NODIRATIME 0x00000080 +#define MOUNT_ATTR_IDMAP 0x00100000 +#define MOUNT_ATTR_NOSYMFOLLOW 0x00200000 +#endif + +#ifndef AT_RECURSIVE +#define AT_RECURSIVE 0x8000 +#endif + +int mount_setattr_wrapper (int dirfd, const char *path, unsigned int flags, + struct mount_attr *attr, size_t size);
