Where
-Infinity
0

Qualys Security Advisory

Local Privilege Escalation in set-capabilities versions of snap-confine (CVE-2026-8933)

======================================================================== Contents ========================================================================

Summary Analysis Exploitation Acknowledgments Timeline

======================================================================== Summary ========================================================================

We discovered a vulnerability (a Local Privilege Escalation from any user to full root) in snap-confine, "a program used internally by snapd to construct the execution environment for snap applications."

Counter-intuitively, only set-capabilities versions of snap-confine are vulnerable, set-uid-root versions are not; in particular:

- default installations of Ubuntu Desktop 26.04 and 25.10 are vulnerable and exploitable (they ship a set-capabilities /usr/lib/snapd/snap-confine);

- default, up-to-date installations of Ubuntu Desktop 24.04 are also vulnerable and exploitable (they ship a set-capabilities /snap/snapd/current/usr/lib/snapd/snap-confine).

======================================================================== Analysis ========================================================================

After the publication of CVE-2026-3888 and the release of Ubuntu 26.04 Beta, we decided to re-assess the security of snap-confine. One of the most fundamental changes between Ubuntu 24.04 and Ubuntu 26.04 is that /usr/lib/snapd/snap-confine is not set-uid-root anymore; instead, it is set-capabilities now, undoubtedly for security reasons (the Principle of Least Privilege):

------------------------------------------------------------------------ $ cat /etc/os-release PRETTYNAME="Ubuntu Resolute Raccoon (development branch)" VERSIONID="26.04"

$ stat /usr/lib/snapd/snap-confine Access: (0755/-rwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root)

$ getcap /usr/lib/snapd/snap-confine /usr/lib/snapd/snap-confine capchown,capdacoverride,capdacreadsearch,capfowner,capsetgid,capsetuid,capsyschroot,capsysptrace,capsysadmin,capsysresource=p ------------------------------------------------------------------------

------------------------------------------------------------------------ $ cat /etc/os-release PRETTYNAME="Ubuntu 24.04.3 LTS"

$ stat /usr/lib/snapd/snap-confine Access: (4755/-rwsr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root)

$ stat /snap/snapd/current/usr/lib/snapd/snap-confine Access: (0755/-rwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root)

$ getcap /snap/snapd/current/usr/lib/snapd/snap-confine /snap/snapd/current/usr/lib/snapd/snap-confine capchown,capdacoverride,capdacreadsearch,capfowner,capsyschroot,capsysptrace,capsysadmin=p ------------------------------------------------------------------------

Unfortunately, belatedly we realized that this change has a potentially dangerous consequence: because snap-confine is not set-uid-root anymore, it runs with the effective uid of our unprivileged user (but with near- root capabilities), and all the files and directories that it creates therefore do not belong to root anymore, but to our unprivileged user.

For example, snap-confine creates a /tmp/snap.rootfsXXXXXX directory at line 511, and a file inside this directory at line 450, and even though snap-confine quickly changes the ownership of this directory and file to root at lines 362 and 454, a small window of time exists when they are under the complete control of our unprivileged user (between lines 511 and 362, and between lines 450 and 454):

------------------------------------------------------------------------ 507 static void scbootstrapmountnamespace(const struct scmountconfig config) { 508 char scratchdir[] = "/tmp/snap.rootfsXXXXXX"; ... 511 if (mkdtemp(scratchdir) == NULL) { ... 523 scdomount(scratchdir, scratchdir, NULL, MSBIND, NULL); ... 530 scdomount("none", scratchdir, NULL, MSUNBINDABLE, NULL); ... 535 scdomount("none", scratchdir, "tmpfs", 0, "uid=0,gid=0"); 536 screplicatebaserootfs(scratchdir, config->rootfsdir, config->mounts); ------------------------------------------------------------------------ 354 static void screplicatebaserootfs(const char scratchdir, const char rootfsdir, 355 const struct scmount rootmounts) { ... 362 if (chown(scratchdir, 0, 0) < 0) { ... 397 scmustsnprintf(fullpath, sizeof(fullpath), "%s/%s", scratchdir, ent->dname); ... 446 } else if (ent->dtype == DTREG) { ... 450 int fd = open(fullpath, OCREAT | OTRUNC, 0644); ... 454 if (fchown(fd, 0, 0) < 0) { ------------------------------------------------------------------------

======================================================================== Exploitation ========================================================================

Our basic idea for exploiting this vulnerability is to transform it into an arbitrary file creation, by winning these two race conditions:

- first, immediately after the creation of the /tmp/snap.rootfsXXXXXX directory at line 511, and while this directory still belongs to our unprivileged user, we quickly create a symlink inside this directory, whose target is an arbitrary file in the filesystem, and whose name is the name of one of the files that snap-confine will create at line 450 (for example, when setting up the sandbox for the firefox snap, snap-confine will create a file named default256.png);

- second, immediately after the creation of our arbitrary file at line 450 (which succeeds because this open() does not specify ONOFOLLOW, and because snap-confine runs with near-root capabilities), and while this file still belongs to our unprivileged user, we quickly change its permissions to 0666 so that we can still write to it even after snap-confine changes its ownership to root at line 454.

However, to implement this exploit in practice, we must overcome two major problems:

1/ After the creation of the /tmp/snap.rootfsXXXXXX directory at line 511, but before the creation of our arbitrary file at line 450, snap- confine mounts a temporary filesystem onto /tmp/snap.rootfsXXXXXX at line 535; but we cannot create the symlink to our arbitrary file inside this directory because it is not visible from outside the sandbox that snap-confine is setting up (because of the two mounts at lines 523 and 530, which make this temporary filesystem private).

The solution to this first problem is surprisingly simple: immediately after the creation of the /tmp/snap.rootfsXXXXXX directory, and while this directory still belongs to our unprivileged user, we quickly mount a FUSE filesystem onto it (with fusermount), which allows us to unmount (with fusermount -u -z) every filesystem that snap-confine later mounts onto it, at lines 523 and 535.

As a result, when snap-confine reaches line 450, /tmp/snap.rootfsXXXXXX is simply back to the directory that was originally created at line 511 and that still belongs to our unprivileged user: inside this directory we can create the symlink to our arbitrary file, and finally snap-confine creates this arbitrary file at line 450.

2/ In reality, this arbitrary file creation is not entirely arbitrary, because snap-confine itself is confined by a highly restrictive AppArmor profile; for example, we cannot create or write to any file in /etc. To solve this second problem we inspected snap-confine's AppArmor profile, and one of the few allow-read-write rules caught our attention:

/run/udev/ rw,

We therefore exploit our arbitrary file creation to create a .rules file in /run/udev/rules.d, write PROGRAM="/bin/sh -c id>>/tmp/pwned" into it, and mount and unmount a FUSE filesystem (any FUSE filesystem) to trigger the execution of this arbitrary command with full root privileges (via the systemd-udevd daemon):

------------------------------------------------------------------------ $ cat /etc/os-release PRETTYNAME="Ubuntu Resolute Raccoon (development branch)" VERSIONID="26.04"

$ id uid=1001(jane) gid=1001(jane) groups=1001(jane),100(users)

$ ./exploit scratch directory for constructing namespace: /tmp/snap.rootfsZW0eZT ndirs 1 nregs 0 -rw-r--r-- 1 root root 78 Apr 21 21:00 /tmp/pwned /run/udev/rules.d: total 12 -rw-rw-rw- 1 root root 36 Apr 21 21:00 2064396357-default256.png.rules -rw-r----- 1 root root 265 Apr 21 20:20 90-netplan.rules -rw-r----- 1 root root 98 Apr 21 20:20 99-netplan-enp0s3.rules uid=0(root) gid=0(root) groups=0(root) uid=0(root) gid=0(root) groups=0(root) ------------------------------------------------------------------------

------------------------------------------------------------------------ $ cat /etc/os-release PRETTYNAME="Ubuntu 24.04.3 LTS"

$ id uid=1001(jane) gid=1001(jane) groups=1001(jane),100(users)

$ ./exploit scratch directory for constructing namespace: /tmp/snap.rootfsAkYmf6 ndirs 1 nregs 0 -rw-r--r-- 1 root root 78 Apr 21 13:09 /tmp/pwned /run/udev/rules.d: total 8 -rw-rw-rw- 1 root root 36 Apr 21 13:09 429214770-default256.png.rules -rw-r----- 1 root root 162 Apr 21 12:47 90-netplan.rules uid=0(root) gid=0(root) groups=0(root) uid=0(root) gid=0(root) groups=0(root) ------------------------------------------------------------------------

======================================================================== Acknowledgments ========================================================================

We thank everyone at Canonical who worked on this release (Eduardo Barretto and Zygmunt Krynicki in particular). We also thank the members of the linux-distros list (Caryl Takvorian in particular).

======================================================================== Timeline ========================================================================

2026-04-22: We sent our advisory and exploit to the Ubuntu Security Team.

2026-07-13: The Ubuntu Security Team sent their patches to the linux-distros list.

2026-07-14: We sent our advisory to the linux-distros list.

2026-07-21: Coordinated Release Date (14:00 UTC).

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203