Copy Fail: 732 Bytes to Root on Every Major Linux Distribution

So to clarify, only Alpine systems where the admin has elected to install packages containing setuid binaries can exploit the setuid-to-root path.

Which makes me wonder (stepping back from just Alpine, but still including it) …

I’ve read that the exploit can overwrite any in-memory file? If so, I assume there other binaries that are not setuid root, but have access to resources that subsets of users shouldn’t have (due to group read/write or other security boundaries). To me that means that the attack surface is larger and more subtle than the flashiness of setuid exploitation. sshd. anything called by cron as root. libpam. Anything that calls a world-readable binary (?)

Claimed exploit detection (eBPF / Falco):

2 Likes

eBPF MENTIONED

Seriously though, that is clever. I don’t know enough about what uses AF_ALG to know if their “expected processes” list is comprehensive, but it’s a solid start.

Falco is awesome.

1 Like

I think it’s worth noting that yes, this mechanism can be used to write to any file. The SUID component is what grants privesc, since the SUIDed binary is being overwritten with a no-checks shell invocation.

But any file can be a target, and until the page cache pages holding the corrupted data are dropped/refreshed, any read() to the file will produce the corrupted version.

Here I’ve compiled a simple “Hello, World!” C program and hexdumped its top. Then I run the exploit, which I’ve retargeted to this file.

But until I forcibly drop the page cache, the file as the system sees it as the corrupted version.

That said, I believe the kernel will write back (drop) dirty pages in 30 seconds by default, controlled by dirty_expire_centisecs?

6 Likes

Added to linpeas

1 Like

Just A) pick something that’s regularly read, B) do something to regularly read it, or C) do something to lock it in memory (which unprivileged users can do)

2 Likes

Rootsecdev has a nice scanner/non-minifed exploit here. Yeah it’s probably vibe-coded, but the script output has details on why it fails, which is helpful.

1 Like

CERN has released two proposed mitigations. One requires a reboot & the other does not. So far, testing on Rocky 8 & 9 confirms the 1st Mitigation that requires a reboot is working. ComputerSecurity / mitigations / CVE-2026-31431 · GitLab

2 Likes

Tenable FAQ post:

1 Like

Ah this is a great explainer on the fixes:

If the module is loaded dynamically (CONFIG_CRYPTO_USER_API_AEAD=m):

echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif.conf
rmmod algif_aead 2>/dev/null || true

If the module is compiled into the kernel (CONFIG_CRYPTO_USER_API_AEAD=y), which is the case on some enterprise kernels, the above will not work. Contributors on the oss-security mailing list have reported that adding the following to the kernel boot parameters and rebooting blocks the exploit:

initcall_blacklist=algif_aead_init

The Register coverage

I need to amend this! Per the writeup:

The kernel never marks the corrupted page dirty for writeback, so the file on disk remains unchanged and ordinary on-disk checksum comparisons miss the modification.

So it isn’t 30 seconds, but basically memory pressure and reboots that would drop the page. So corrupted pages could last a lot longer than 30 seconds.

1 Like

Exactly, I believe RHEL bakes it in referencing Will Dormann: "While this vulnerability seems to be discovered u…" - Infosec Exchange

Their thread also mentions some possible IoCs

If you’re curious about IOCs for copyfail, look in syslog for:
NET: Registered PF_ALG protocol family
for attempts to exploit copyfail on systems that use the vulnerable code as a module. For systems that have the vulnerable code compiled into the kernel, like RHEL, you’ll see this line on every boot.
And at least for this particular flavor of exploit, a wall-clock nearby:
process 'su' launched '/bin/sh with NULL argv: empty string added`
is an indication of successful exploitation.

But it’s worth noting that the “process launched” stuff is merely what the ITW PoC will leave behind. More clever exploitation may not be as obvious.

1 Like

Specifics on your testbed, please? I tried it on some of the nodes of my test network and was not able to get it to work. Was running as an unprivileged user (nobody) and the only way I was able to get su to run was after supplying the current root password. It might be because I set everything up for sudo use instead of su. I made a few edits in those tests to try other suid binaries in /usr/bin but to no avail. Maybe I’m just using it wrong, I don’t know.

I also have thoughts about both the obfuscation of the Python and the “curl | bash” workflow. That gives me the opposite of warm fuzzies.

That might explain it. All my stuff’s running the kernel v6.19.x series.

Useful, but not “not vulnerable kernel version” aware.

Not trying to knock you, just reporting what happens on a not-vulnerable box.

See the rootsecdev link above for a more comprehensive tester and exploit.

1 Like

Thanks for the analysis, I saw your post on mastodon and was unable to figure out why it wasn’t working on alpine. The original write-up wasn’t as clear too.

Just wanted to pop in and say thank you all for the analysis in this thread. I’m not too deeply into kernel things, but you all have certainly expanded my knowledge here the past day. :purple_heart:

4 Likes

Debian has been patched with kernel 6.12.85-1.

5 Likes