
authencesn's Dirty Scratch Pad: Why a 4-Byte Write in the Crypto Stack Keeps Showing Up Uninvited
What this bug actually does
The short version is bad: an unprivileged local user can turn a crypto API into a controlled 4-byte write into the page cache of a readable file.
That matters because the page cache is what the kernel actually reads for read(), mmap(), and execve() when the file is hot. The file on disk stays clean, but the in-memory copy does not. If the target is a setuid binary, the attacker can corrupt the code path the kernel will execute.
The bug has a real name in the stack: authencesn, the AEAD wrapper used for IPsec Extended Sequence Number support. The exploit path needs three ingredients to line up:
AF_ALGaccess from userspacesplice()so file-backed pages enter the crypto scatterlistauthencesndoing a scratch write past the legitimate output boundary
That intersection is what makes the issue practical, portable, and more annoying than a one-off kernel crash.
Why AF_ALG and splice() become dangerous together
AF_ALG exposes kernel crypto to userspace. On its own, that is not the problem. The trouble starts when the input data comes from splice() instead of a normal copy.
The page cache is the real target
splice() can move file-backed pages into a pipe and then into another file descriptor without copying the bytes into fresh user memory. In this case, those pages still point at the page cache entry for the file.
So when the crypto layer later references that scatterlist, it is not operating on a private buffer. It is holding references to the same physical pages that back the file on disk.
That means a write into the wrong part of the scatterlist becomes a write into the file's cached page.
In-place AEAD turns a read path into a write path
The risky part is the in-place decrypt path in algif_aead.c. The kernel copies the AAD and ciphertext into the destination buffer, but the tag pages remain chained into the same scatterlist via sg_chain(). Then req->src and req->dst point at the same combined chain.
That design assumes every AEAD implementation stays inside the destination region it is supposed to use.
authencesn does not.
Where authencesn crosses the boundary
authencesn exists to support ESN handling in IPsec. It rearranges the sequence number bytes while preparing the authentication input. Historically, that rearrangement used the destination buffer as scratch space.
The scratch-write design in the ESN path
The relevant behavior is straightforward:
- read the first 8 bytes of AAD
- temporarily shuffle the high sequence number into place
- write the low sequence number back near the end of the AEAD output
That last write is the problem. It lands at dst[assoclen + cryptlen], which is exactly where the in-place AEAD path has chained the tag pages.
So the kernel writes 4 bytes into the page cache page referenced by the tag region.
The key detail is that the code treats that location like expendable scratch, even if the operation later fails authentication. The HMAC check fails, recvmsg() returns an error, and the overwrite still sticks in memory.
Why the 4-byte overwrite persists after failure
This is not a race. It is not timing-dependent. The write happens before the failure is returned.
That means the attacker gets a deterministic primitive:
- choose the file
- choose the offset
- choose the 4-byte value
- trigger the write
- let the auth failure happen afterward
The failure is what makes the API look harmless. The write is already done.
What makes the primitive practical
This was not interesting because it was theoretical. It was interesting because it was stable enough to use.
Deterministic overwrite target selection
By adjusting the splice offset, length, and assoclen, the attacker can line up the 4-byte write with a specific offset inside a target file's page cache. That is enough for code corruption if the target is a binary with reachable execution flow.
In the reported case, /usr/bin/su was the practical target because it is widely present and setuid-root on the tested systems.
Cross-distro behavior and why it is portable
The exploit did not depend on weird offsets or distro-specific heap behavior. The same logic worked on the tested Ubuntu, Amazon Linux, RHEL, and SUSE systems because the bug sits at the kernel API boundary, not in a distro patch stack.
That is what makes this class worse than a brittle local exploit. If the kernel behavior is the same, the payload can stay the same.
Why on-disk checks miss the corruption
The kernel never needs to mark the page dirty in the usual writeback path. So an integrity check that compares the file on disk to a trusted package hash can still say “clean” while the live page cache is already altered.
That is the core trap:
| Check | What it sees | What it misses |
|---|---|---|
| Package hash | on-disk file | page cache corruption |
| File checksum | stored bytes | live kernel memory |
| AV on disk | persisted payload | runtime-only overwrite |
How to reason about impact safely
The cleanest impact statement is also the most useful one: this is a local privilege escalation primitive that can become broader in shared-kernel environments.
Local privilege escalation from a readable setuid binary
If an attacker can corrupt the page cache of a setuid-root binary before execution, the binary can run attacker-controlled code as root.
That is the end of the story for host compromise. No race, no persistence trick, just a corrupted execution image in the page cache.
Shared page cache means container boundaries do not help
Containers do not get their own kernel page cache. If the host kernel is vulnerable, a process in one container can still affect host-visible file pages that are shared in the same kernel instance.
That is why this is more than “just” a local bug. The blast radius includes containerized workloads and node-level trust boundaries.
Timeline of the kernel changes that made this possible
The bug is the product of three individually reasonable changes colliding later.
authencesn added for IPsec ESN support
authencesn entered the kernel in 2011 to support IPsec ESP with Extended Sequence Numbers. At that point, it was a narrow internal implementation detail.
AF_ALG gains AEAD and later in-place operation
AF_ALG added AEAD support in 2015. Later, the in-place optimization landed in 2017, which made decryption reuse the same scatterlist for input and output and chain tag pages into the writable destination path.
That optimization was the missing condition. Without it, the scratch write would have landed in the user buffer, not the page cache.
What defenders should verify
Kernel version and package exposure
Check whether your kernel includes the fixed path or the vulnerable in-place logic. Vendors shipped fixes after the issue was disclosed, and the affected range spans multiple major distributions.
Also check whether your environment exposes readable setuid binaries that are stable targets. The bug only matters when there is something valuable to corrupt.
Workload audit for AF_ALG use
Look for applications using AF_ALG sockets directly or indirectly. Most teams do not need userspace access to kernel crypto in the first place.
A quick audit should focus on:
- code that opens
AF_ALG - services that use
splice()with file-backed descriptors - containers that run tooling with broad filesystem read access
Detection limits and practical monitoring
Monitoring on-disk file integrity will not catch this. You need to watch for:
- suspicious
AF_ALGsocket creation - unusual
splice()chains involving readable binaries - repeated failed decrypt operations against the same target file
Even then, the signal is weak. The practical answer is patching, not hoping to spot the overwrite after the fact.
Mitigation and remediation
Apply vendor fixes or disable the risky path
The real fix is to remove the in-place behavior that allows page cache pages to become writable through the AEAD path. If your vendor has shipped the kernel update, apply it.
If you cannot patch immediately, reduce exposure by disabling or restricting the attack surface around AF_ALG and by limiting who can read sensitive setuid binaries.
Reduce exposure inside containers and nodes
Treat this as a node issue, not a container issue. Hardening steps should include:
- patched host kernels everywhere
- minimal setuid binaries
- reduced access to crypto sockets where feasible
- tight workload isolation on shared nodes
Closing notes
This bug is a good example of how safe-looking optimizations become dangerous when they cross subsystem boundaries.
splice() gave the attacker page cache references. AF_ALG made those references part of a crypto scatterlist. authencesn used the destination buffer as scratch. None of those choices looked outrageous in isolation.
Together, they turned a 4-byte write into root.


