vendor: update bubblewrap to 0.11.2 (#21389)

## Why

`codex-rs/vendor/bubblewrap` had fallen behind upstream, and upstream
`v0.11.2` is the current Bubblewrap release. The release is a security
update for `CVE-2026-41163`, affecting setuid Bubblewrap builds, and
deprecates setuid support in favor of the default non-setuid build mode.

## What changed

- Refreshed the vendored Bubblewrap sources under
`codex-rs/vendor/bubblewrap` to upstream `v0.11.2`.
- Brought in the upstream `-Dsupport_setuid` build option, which
defaults setuid support off.
- Updated vendored release notes and documentation files included with
Bubblewrap.

## Verification

Not run locally; this PR only refreshes the vendored upstream Bubblewrap
source snapshot.

Upstream release:
https://github.com/containers/bubblewrap/releases/tag/v0.11.2
This commit is contained in:
Michael Bolin
2026-05-06 11:10:30 -07:00
committed by GitHub
parent e97610cf3b
commit 123ec8b035
9 changed files with 131 additions and 40 deletions

View File

@@ -1,3 +1,50 @@
bubblewrap 0.11.2
=================
Released: 2026-04-23
Bug fixes:
* In setuid mode, don't run the low-privileged parts parts of the setup
as dumpable, as that allows it to be ptraced which can lead to problems.
This is CVE-2026-41163, and was reported by François Diakhate.
Enhancements:
* New build option `-Dsupport_setuid`, which if set to false (which
is the default) disables the support for setuid. Binaries built
with this will refuse to run if made setuid. We recommend building
normal bubblewrap binaries like this, which allows you to safely
ignore any security issues that only affect setuid mode.
bubblewrap 0.11.1
=================
Released: 2026-03-21
Bug fixes:
* Reset disposition of `SIGCHLD`, restoring normal subprocess management
if bwrap was run from a process that was ignoring that signal,
such as Erlang or volumeicon (#705, Joel Pelaez Jorge)
* Don't ignore `--userns 0`, `--userns2 0` or `--pidns 0` if used
(#731, Daniel Cazares).
Note that using a fd number ≥ 3 for these purposes is still
preferred, to avoid confusion with the stdin, stdout, stderr
that will be inherited by the command inside the container.
* Fix grammar in an error message (#694, J. Neuschäfer)
* Fix a broken link in the documentation (#729, Aaron Brooks)
Internal changes:
* Enable user namespaces in Github Actions configuration, fixing a CI
regression with newer Ubuntu (#728, Joel Pelaez Jorge)
* Clarify comments (#737, Simon McVittie)
bubblewrap 0.11.0
=================

View File

@@ -12,23 +12,24 @@ on the host.
User namespaces
---------------
There is an effort in the Linux kernel called
There is an feature in the Linux kernel called
[user namespaces](https://www.google.com/search?q=user+namespaces+site%3Ahttps%3A%2F%2Flwn.net)
which attempts to allow unprivileged users to use container features.
While significant progress has been made, there are
[still concerns](https://lwn.net/Articles/673597/) about it, and
it is not available to unprivileged users in several production distributions
such as CentOS/Red Hat Enterprise Linux 7, Debian Jessie, etc.
which allows unprivileged users to use container features. Bubblewrap uses these to
build the sandbox, allowing any user to use the tool.
See for example
[CVE-2016-3135](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-3135)
which is a local root vulnerability introduced by userns.
[This March 2016 post](https://lkml.org/lkml/2016/3/9/555) has some
more discussion.
Historically, not all Linux distributions supported (at least by
default) unprivileged user namespaces, so bubblewrap supports a second
mode of operation when the binary is setuid root. In that setup
bubblewrap could be viewed as setuid implementation of a *subset* of
user namespaces. However, not all features of bubblewrap work in
this mode.
Bubblewrap could be viewed as setuid implementation of a *subset* of
user namespaces. Emphasis on subset - specifically relevant to the
above CVE, bubblewrap does not allow control over iptables.
However, setuid mode is deprecated, as most recent Linux distributions
support unprivileged user namespaces, and setuid binaries carry
significant risks. By default, bubblewrap binaries refuse to work if
setuid, and you must build explicitly with ` -Dsupport_setuid=true` to
enable it to work. Later versions of bubblewrap aims to completely
remove this support.
The original bubblewrap code existed before user namespaces - it inherits code from
[xdg-app helper](https://cgit.freedesktop.org/xdg-app/xdg-app/tree/common/xdg-app-helper.c?id=4c3bf179e2e4a2a298cd1db1d045adaf3f564532)
@@ -151,7 +152,7 @@ sandbox. You can also change what the value of uid/gid should be in the sandbox.
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](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/).
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/docker-and-the-pid-1-zombie-reaping-problem/).
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.

View File

@@ -15,6 +15,13 @@ between the user and the OS, because anything bubblewrap could do, a
malicious user could equally well do by writing their own tool equivalent
to bubblewrap.
Since 0.11.2, unless compiled with the `-Dsupport_setuid=true` option,
setuid root support is disabled. In this mode bubblewrap will refuse
to operate if the binary has been made setuid. For binaries built like
this it is safe to ignore any bubblewrap CVEs that are described as
affecting setuid mode only. This is the recommended way to package
bubblewrap.
### Sandbox security
bubblewrap is a toolkit for constructing sandbox environments.

View File

@@ -55,7 +55,11 @@ static uid_t real_uid;
static gid_t real_gid;
static uid_t overflow_uid;
static gid_t overflow_gid;
#ifdef ENABLE_SUPPORT_SETUID
static bool is_privileged; /* See acquire_privs() */
#else
#define is_privileged 0
#endif
static const char *argv0;
static const char *host_tty_dev;
static int proc_fd = -1;
@@ -840,13 +844,16 @@ set_ambient_capabilities (void)
static void
acquire_privs (void)
{
uid_t euid, new_fsuid;
uid_t euid;
euid = geteuid ();
/* Are we setuid ? */
if (real_uid != euid)
{
#ifdef ENABLE_SUPPORT_SETUID
uid_t new_fsuid;
if (euid != 0)
die ("Unexpected setuid user %d, should be 0", euid);
@@ -868,13 +875,16 @@ acquire_privs (void)
/* setfsuid can't properly report errors, check that it worked (as per manpage) */
new_fsuid = setfsuid (-1);
if (new_fsuid != real_uid)
die ("Unable to set fsuid (was %d)", (int)new_fsuid);
die_with_error ("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);
/* Keep only the required capabilities for setup */
set_required_caps ();
#else
die ("setuid use of bubblewrap is not supported in this build");
#endif
}
else if (real_uid != 0 && has_caps ())
{
@@ -937,7 +947,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 +958,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
@@ -1154,7 +1168,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. */
@@ -1172,6 +1188,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;
@@ -3112,7 +3130,7 @@ main (int argc,
}
/* Switch to the custom user ns before the clone, gets us privs in that ns (assuming its a child of the current and thus allowed) */
if (opt_userns_fd > 0 && setns (opt_userns_fd, CLONE_NEWUSER) != 0)
if (opt_userns_fd != -1 && setns (opt_userns_fd, CLONE_NEWUSER) != 0)
{
if (errno == EINVAL)
die ("Joining the specified user namespace failed, it might not be a descendant of the current user namespace.");
@@ -3178,11 +3196,11 @@ main (int argc,
/* Initial launched process, wait for pid 1 or exec:ed command to exit */
if (opt_userns2_fd > 0 && setns (opt_userns2_fd, CLONE_NEWUSER) != 0)
if (opt_userns2_fd != -1 && setns (opt_userns2_fd, CLONE_NEWUSER) != 0)
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 ();
@@ -3219,7 +3237,7 @@ main (int argc,
return monitor_child (event_fd, pid, setup_finished_pipe[0]);
}
if (opt_pidns_fd > 0)
if (opt_pidns_fd != -1)
{
if (setns (opt_pidns_fd, CLONE_NEWPID) != 0)
die_with_error ("Setting pidns failed");
@@ -3369,8 +3387,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);
@@ -3446,7 +3466,7 @@ main (int argc,
die_with_error ("chdir /");
}
if (opt_userns2_fd > 0 && setns (opt_userns2_fd, CLONE_NEWUSER) != 0)
if (opt_userns2_fd != -1 && setns (opt_userns2_fd, CLONE_NEWUSER) != 0)
die_with_error ("Setting userns2 failed");
if (opt_unshare_user && opt_userns_block_fd == -1 &&
@@ -3499,7 +3519,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)
{

View File

@@ -1,7 +1,7 @@
project(
'bubblewrap',
'c',
version : '0.11.0',
version : '0.11.2',
meson_version : '>=0.49.0',
default_options : [
'warning_level=2',
@@ -91,6 +91,11 @@ if get_option('require_userns')
cdata.set('ENABLE_REQUIRE_USERNS', 1)
endif
if get_option('support_setuid')
cdata.set('ENABLE_SUPPORT_SETUID', 1)
warning('running bubblewrap setuid is deprecated and risky. Most recent operating systems support unprivileged user namespaces and we recommend using that. Support for this will be removed in the next version.')
endif
configure_file(
output : 'config.h',
configuration : cdata,

View File

@@ -41,6 +41,12 @@ option(
type : 'string',
description : 'Path to Python 3, or empty to use python3',
)
option(
'support_setuid',
type : 'boolean',
description : 'Support setuid mode (deprecated)',
value : false,
)
option(
'require_userns',
type : 'boolean',

View File

@@ -50,7 +50,7 @@ static int
rtnl_send_request (int rtnl_fd,
struct nlmsghdr *header)
{
struct sockaddr_nl dst_addr = { .nl_family = AF_NETLINK, .nl_pid = 0, .nl_groups = 0 };
struct sockaddr_nl dst_addr = { AF_NETLINK, 0 };
ssize_t sent;
sent = TEMP_FAILURE_RETRY (sendto (rtnl_fd, (void *) header, header->nlmsg_len, 0,
@@ -139,7 +139,7 @@ loopback_setup (void)
int r, if_loopback;
cleanup_fd int rtnl_fd = -1;
char buffer[1024];
struct sockaddr_nl src_addr = { .nl_family = AF_NETLINK, .nl_pid = 0, .nl_groups = 0 };
struct sockaddr_nl src_addr = { AF_NETLINK, 0 };
struct nlmsghdr *header;
struct ifaddrmsg *addmsg;
struct ifinfomsg *infomsg;

View File

@@ -1,13 +1,13 @@
bubblewrap release checklist
============================
* Collect release notes in `NEWS`
* Update version number in `meson.build` and release date in `NEWS`
* Collect release notes in `NEWS.md`
* Update version number in `meson.build` and release date in `NEWS.md`
* 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 from `NEWS` in the tag message
* Include the release notes from `NEWS.md` 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

View File

@@ -510,14 +510,18 @@ ensure_file (const char *path,
the create file will fail in the read-only
case with EROFS instead of EEXIST.
We're trying to set up a mount point for a non-directory, so any
non-directory, non-symlink is acceptable - it doesn't necessarily
have to be a regular file. */
We're trying to set up a mount point for a non-directory, for which
the kernel will accept any non-directory. If it's a symlink, follow
it and look at the target: again, any non-directory is good enough.
We'll only get S_ISLNK if the path is a dangling symlink (target
doesn't exist). */
if (stat (path, &buf) == 0 &&
!S_ISDIR (buf.st_mode) &&
!S_ISLNK (buf.st_mode))
return 0;
/* If the file didn't exist, create it. If it was a dangling symlink
* (S_ISLNK above) then this will create the target of the symlink. */
if (create_file (path, mode, NULL) != 0 && errno != EEXIST)
return -1;
@@ -681,7 +685,8 @@ ensure_dir (const char *path,
/* We check this ahead of time, otherwise
the mkdir call can fail in the read-only
case with EROFS instead of EEXIST on some
filesystems (such as NFS) */
filesystems (such as NFS).
We follow symlinks: it's OK if path is a symlink to a directory. */
if (stat (path, &buf) == 0)
{
if (!S_ISDIR (buf.st_mode))