Skip to content

linnemanlabs/dirtyfrag-arm64

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 

Repository files navigation

dirtyfrag-arm64

arm64/aarch64 port of V4bel/dirtyfrag (CVE-2026-43284, CVE-2026-43500).

Tested on Ubuntu 24.04.4 LTS with linux-aws 6.17.0-1013-aws on AWS Graviton (newest available as of this writing).

Full writeup with AppArmor bypass analysis, hardening notes, and detection notes: linnemanlabs.com/posts/porting-dirtyfrag-arm64

⚠️ Ubuntu AppArmor userns restrictions do not reliably prevent this exploit

Ubuntu has two AppArmor sysctls:

  • kernel.apparmor_restrict_unprivileged_userns
  • kernel.apparmor_restrict_unprivileged_unconfined

Both can be bypassed by chaining aa-exec with itself using profiles present on the standard Ubuntu cloud and installer images I tested:

aa-exec -p crun -- aa-exec -p crun -- ./dirtyfrag_arm64 --force-esp

For more info see Two Hops and a Shell for the full analysis of the Ubuntu AppArmor bypass.

What's different on arm64

The upstream x86_64 PoC uses two exploit paths: an ESP/xfrm path that corrupts /usr/bin/su, and an rxrpc/rxkad fallback that corrupts /etc/passwd. On arm64 the rxrpc path kernel-oopses and cannot be used. The ESP path works cleanly.

rxrpc crash: flush_dcache_page

On x86_64, flush_dcache_page() is a no-op. x86 has hardware-coherent data/instruction caches. On arm64, it performs real dcache maintenance and dereferences the struct page* metadata. When the rxrpc crypto path (rxkad_secure_packet -> crypto_pcbc_encrypt -> skcipher_walk_done) calls flush_dcache_page on a page whose reference has been manipulated through the splice/vmsplice chain, x86_64 silently skips it but arm64 hits a translation fault and oopses:

pc : flush_dcache_page+0x18/0x58
lr : skcipher_walk_done+0xbc/0x260
     crypto_pcbc_encrypt+0xe8/0x1c8 [pcbc]
     crypto_skcipher_encrypt+0x48/0xb8
     rxkad_secure_packet+0x108/0x270 [rxrpc]
     rxrpc_send_data+0x264/0x550 [rxrpc]

On the arm64 systems I tested, denying the uid_map write removed the working ESP path. The namespace may still be created, but the process cannot map itself to root inside it or gain the namespaced capabilities needed for XFRM setup. The rxrpc fallback did not provide a working namespace-free privilege-escalation path on arm64, it oopsed the kernel instead.

ESP-only operation

On arm64, only the ESP path was viable in my testing. This path requires creating a user and network namespace and then successfully mapping the calling user to root inside that namespace. Distro hardening can break that path in different ways: Ubuntu can deny the uid_map write through AppArmor userns restrictions, I have not tested on Debian/RHEL.

AppArmor: blocked by default, bypassable on Ubuntu

On the Ubuntu 24.04 AWS image I tested, apparmor_restrict_unprivileged_userns=1 blocked direct exploitation from my normal SSH shell by denying the uid_map write inside the new namespace.

However, with the default apparmor_restrict_unprivileged_unconfined=0, an unconfined user can transition into an existing complain-mode profile (e.g. runc) via aa-exec and bypass the restriction:

aa-exec -p runc -- ./dirtyfrag_arm64 --force-esp

Setting kernel.apparmor_restrict_unprivileged_unconfined=1 blocks this path, and is widely recommended currently as a solution to block all paths. However, adding another aa-exec bypasses that also:

aa-exec -p crun -- aa-exec -p crun -- ./dirtyfrag_arm64 --force-esp

See the earlier linked posts for details.

Architecture-specific payload

The exploit overwrites /usr/bin/su in the page cache with a minimal static ELF. The upstream PoC embeds an x86_64 ELF with x86_64 shellcode. This port replaces it with an equivalent aarch64 ELF:

  • ELF e_machine: EM_AARCH64 (183) instead of EM_X86_64 (62)
  • Shellcode: aarch64 instructions using svc #0 instead of syscall
  • Syscall numbers: setgid=144, setuid=146, setgroups=159, execve=221 (vs 106, 105, 116, 59 on x86_64)
  • Fixed 4-byte instruction width (vs x86_64 variable-length), resulting in a slightly larger payload (~216 bytes vs 192)

Build & run

# Clone
git clone https://github.com/linnemanlabs/dirtyfrag-arm64.git

# Build
cd dirtyfrag-arm64
gcc -O0 -Wall -o dirtyfrag_arm64 dirtyfrag_arm64.c -lutil

# Run
./dirtyfrag_arm64 --force-esp

The --force-esp flag skips the rxrpc path entirely to ensure avoiding the arm64 kernel oops.

Tested environment

Property Value
Instance AWS t4g.micro (Graviton2)
OS Ubuntu 24.04.4 LTS
Kernel 6.17.0-1013-aws #13~24.04.1-Ubuntu (built 2026-04-24)
Architecture aarch64
unprivileged_userns_clone 1 (enabled)
esp4 module available, loadable
rxrpc module available, loadable (but crashes on arm64)
Configuration stock ubuntu 24.04 cloud image, default kernel modules

As of 2026-05-09, the latest available Ubuntu 24.04 aws kernel (6.17.0-1013-aws, built April 24) ships without patches for either Copy Fail (CVE-2026-31431, disclosed April 29) or Dirty Frag (CVE-2026-43284/43500, disclosed May 7).

Immediate mitigation

Blacklist the vulnerable modules, apply appropriate system hardening for your distro.

Blacklist the vulnerable modules

Safe on any system not actively using IPsec transport mode or AFS. To prevent loading of the modules put the following into /etc/modprobe.d/dirtyfrag.conf:

install esp4 /bin/false
install esp6 /bin/false
install rxrpc /bin/false

Update initramfs

Ubuntu also recommends regenerating initramfs so the blacklist is present during early boot:

update-initramfs -u -k all
``

### Remove read permissions from SUID binaries

Blocks this splice-based page-cache attack class against those SUID binary targets. The exploit needs read permissions to the target file for `splice()`. Users can still execute the binaries.

**Do not implement this without testing it in your environment and across all of your tooling.**

```bash
chmod o-r /usr/bin/su

This is not a fix, only a mitigation. There are many more paths to privilege escalation.

Unload the modules

To unload the modules from the running system:

rmmod esp4 esp6 ipcomp4 ipcomp6 rxrpc 2>/dev/null

Ensure they are unloaded:

grep -qE '^(esp4|esp6|rxrpc) ' /proc/modules \
  && echo "Affected modules are loaded" \
  || echo "Affected modules are NOT loaded"

Flush page cache

Flushing the page cache should remove the malicious contents and cause the files to be read from disk again.

echo 3 > /proc/sys/vm/drop_caches

Note: I have had some inconsistent results with this, but the more I try to reproduce it the more it's working as expected. For this PoC, you can check the md5sum on /usr/bin/su, and if it doesn't match, reboot.

Cleaning up after testing

Run the flush page cache step, then verify after with:

sha256sum /usr/bin/su

# Or from package manager:

dpkg -V util-linux    # Debian/Ubuntu
rpm -V util-linux     # RHEL/Amazon Linux

Proactive moves

For a more proactive posture that addresses this entire vulnerability class (not just the specific CVEs), see the full writeup for AppArmor userns restrictions, module preloading prevention, and Tetragon-based runtime detection as well as YARA rules.

Credits

Legal

This tool is intended for authorized security testing and research only.

Unauthorized use against systems you do not own or have explicit permission to test is illegal and unethical.

License

MIT. Copy it, steal it, modify it, learn from it, share your improvements with me. Or don't. It's code, do what you want with it.

About

arm64/aarch64 port of V4bel/dirtyfrag (CVE-2026-43284). ESP-only - rxrpc path kernel-oopses on arm64 due to flush_dcache_page

Topics

Resources

License

Stars

Watchers

Forks

Contributors

Languages