Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
block-copyfail
7 changes: 5 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
FROM registry.fedoraproject.org/fedora:latest AS builder
FROM registry.fedoraproject.org/fedora:43 AS builder

# hadolint ignore=DL3041
RUN dnf install -y \
clang llvm bpftool \
libbpf-devel elfutils-libelf-devel zlib-devel \
Expand All @@ -8,10 +9,12 @@ RUN dnf install -y \

WORKDIR /build
COPY block_copyfail.bpf.c block_copyfail.h block_copyfail.c Makefile ./
RUN make
RUN make block-copyfail

# hadolint ignore=DL3007
FROM registry.access.redhat.com/ubi9/ubi-minimal:latest

# hadolint ignore=DL3041
RUN microdnf install -y libbpf elfutils-libelf zlib && microdnf clean all

COPY --from=builder /build/block-copyfail /usr/local/bin/block-copyfail
Expand Down
9 changes: 8 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ LDFLAGS := $(shell pkg-config --libs libbpf 2>/dev/null || echo "-lbpf -lelf -lz

.PHONY: all clean

all: block-copyfail
all: podman-build

block_copyfail.bpf.o: block_copyfail.bpf.c block_copyfail.h
$(CLANG) $(BPF_CFLAGS) -c $< -o $@
Expand All @@ -24,5 +24,12 @@ block_copyfail.skel.h: block_copyfail.bpf.o
block-copyfail: block_copyfail.c block_copyfail.h block_copyfail.skel.h
$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)

podman-build:
podman build -t block-copyfail-builder .
podman create --name bcf-tmp --replace block-copyfail-builder
podman cp bcf-tmp:/usr/local/bin/block-copyfail .
podman rm bcf-tmp
@echo "Copied to ./block-copyfail"

clean:
rm -f block_copyfail.bpf.o block_copyfail.skel.h block-copyfail
48 changes: 24 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ oc debug node/<any-node> -- chroot /host cat /sys/kernel/security/lsm
# Must contain "bpf"

# 2. Deploy the namespace and grant privileged SCC
oc apply -f daemonset.yaml
oc apply -f configs/daemonset

# 3. DaemonSet pods will start automatically on all nodes

Expand All @@ -32,6 +32,16 @@ oc logs -n block-copyfail -l app=block-copyfail
No reboots. No node drains. No pod restarts. Protection is immediate and
covers all processes on all nodes (100% coverage).

### MachineSet Quick Start (requires reboot)

```bash
oc apply -f configs/machine-config
```

See also - https://access.redhat.com/solutions/7141979, [[YAML](configs/machine-config-kb/machineconfig-disable-algif.yaml)]

Machine config solution(s) will cause all nodes to reboot due to machine config pool changes (adding kernel arguments)

## Table of Contents

1. [How the Exploit Works](#how-the-exploit-works)
Expand Down Expand Up @@ -78,7 +88,7 @@ oc -n cve-2026-31431-test logs -l app=cve-2026-31431-test

**On a vulnerable cluster** you will see:

```
```output
=== CVE-2026-31431 Vulnerability Test ===
Target: /usr/bin/su

Expand Down Expand Up @@ -122,33 +132,23 @@ oc debug node/<any-node> -- chroot /host cat /sys/kernel/security/lsm

Expected output includes `bpf`:

```
```output
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
```
[Machine Config](configs/machine-config/machineconfig-enable-bpf-lsm.yaml)

### Step 1: Create the namespace, grant the SCC, and deploy

Create a new `block-copyfail` namespace, grant SCC, and deploy the DaemonSet by applying [the `daemonset.yaml` manifest](daemonset.yaml).
Create a new `block-copyfail` namespace, grant SCC, and deploy the DaemonSet by applying [the `daemonset.yaml` manifest](configs/daemonset/ds-block-copyfail.yaml).
The privileged SCC must be granted before the DaemonSet pods are created,
otherwise pod creation will fail with SCC validation errors.

```bash
oc apply -f daemonset.yaml
oc apply -f configs/daemonset
```

### Step 2: Wait for pods to start on all nodes
Expand All @@ -159,7 +159,7 @@ oc get pods -n block-copyfail -o wide

Expected: one pod per node, all `Running`:

```
```output
NAME READY STATUS AGE NODE
block-copyfail-2jhzf 1/1 Running 34s ci-...-master-2
block-copyfail-4dfq7 1/1 Running 34s ci-...-master-1
Expand All @@ -177,7 +177,7 @@ oc logs -n block-copyfail -l app=block-copyfail

Expected:

```
```output
block-copyfail: blocker active — all AF_ALG AEAD binds blocked
```

Expand All @@ -189,7 +189,7 @@ Re-run the same exploit test from the [Confirming Vulnerability](#confirming-vul

**After deploying the BPF LSM DaemonSet**, the output will be:

```
```output
=== CVE-2026-31431 Vulnerability Test ===
Target: /usr/bin/su

Expand All @@ -206,7 +206,7 @@ The DaemonSet logs will show the blocked attempt:
oc logs -n block-copyfail -l app=block-copyfail
```

```
```output
block-copyfail: blocker active — all AF_ALG AEAD binds blocked
block-copyfail: BLOCKED pid=16777 comm=python3 time=2026-05-01 16:37:23
```
Expand Down Expand Up @@ -241,7 +241,7 @@ for t, n in tests:

Expected output:

```
```output
BLOCKED aead/gcm(aes) -- [Errno 1] Operation not permitted
BLOCKED aead/ccm(aes) -- [Errno 1] Operation not permitted
BLOCKED aead/rfc4106(gcm(aes)) -- [Errno 1] Operation not permitted
Expand All @@ -258,9 +258,9 @@ This confirms the BPF LSM blocks all AEAD binds while leaving other AF_ALG types

The BPF LSM blocker source is in `block-copyfail/`:

```
```output
block-copyfail/
block_copyfail.bpf.c # BPF kernel program (LSM hook)
block_copyfail.bpf.c # BPF kernel program (LSM hook)
block_copyfail.c # Userspace loader (libbpf skeleton)
block_copyfail.h # Shared event struct
Makefile # Build pipeline
Expand All @@ -287,7 +287,7 @@ for compilation, UBI 9 minimal for the runtime image (~122 MB).
Deleting the DaemonSet immediately removes the mitigation on all nodes:

```bash
oc delete -f daemonset.yaml
oc delete -f configs/daemonset
# or
oc delete namespace block-copyfail
```
Expand Down
12 changes: 12 additions & 0 deletions configs/daemonset/01-namespace.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
apiVersion: v1
kind: Namespace
metadata:
annotations:
kubernetes.io/description: Block / Detect CopyFail attempts on Openshift
openshift.io/display-name: Block CopyFail
name: block-copyfail
labels:
pod-security.kubernetes.io/enforce: privileged
pod-security.kubernetes.io/audit: privileged
pod-security.kubernetes.io/warn: privileged
14 changes: 14 additions & 0 deletions configs/daemonset/02-rbac.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: system:openshift:scc:privileged
namespace: block-copyfail
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:openshift:scc:privileged
subjects:
- kind: ServiceAccount
name: default
namespace: block-copyfail
38 changes: 38 additions & 0 deletions configs/daemonset/bc-block-copyfail.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
apiVersion: image.openshift.io/v1
kind: ImageStream
metadata:
name: block-copyfail
namespace: block-copyfail
spec:
tags:
- name: latest
---
apiVersion: build.openshift.io/v1
kind: BuildConfig
metadata:
labels:
app: block-copyfail
app.kubernetes.io/component: block-copyfail
app.kubernetes.io/instance: block-copyfail
app.kubernetes.io/name: block-copyfail
app.kubernetes.io/part-of: block-copyfail
name: block-copyfail
spec:
output:
to:
kind: ImageStreamTag
name: block-copyfail:latest
runPolicy: SerialLatestOnly
source:
contextDir: /
git:
uri: https://github.com/openshift/block-copyfail
ref: main
type: Git
strategy:
dockerStrategy:
dockerfilePath: Dockerfile
type: Docker
triggers:
- type: ConfigChange
63 changes: 63 additions & 0 deletions configs/daemonset/ds-block-copyfail.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
annotations:
image.openshift.io/triggers: '[{"from":{"kind":"ImageStreamTag","name":"block-copyfail:latest","namespace":"block-copyfail"},"fieldPath":"spec.template.spec.containers[?(@.name==\"blocker\")].image","paused":false}]'
name: block-copyfail
namespace: block-copyfail
labels:
app: block-copyfail
spec:
selector:
matchLabels:
app: block-copyfail
template:
metadata:
labels:
app: block-copyfail
spec:
priorityClassName: system-node-critical
tolerations:
- operator: Exists
containers:
- name: blocker
image: block-copyfail:latest
# replace the image above if you want to use the container image from public repos
# image: quay.io/openshift/block-copyfail:latest
command:
- /bin/sh
- -c
- |
/usr/local/bin/block-copyfail
sleep infinity
securityContext:
privileged: true
capabilities:
add:
- BPF
drop:
- ALL
volumeMounts:
- name: bpf
mountPath: /sys/fs/bpf
- name: btf
mountPath: /sys/kernel/btf/vmlinux
readOnly: true
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
cpu: 100m
memory: 64Mi
volumes:
- name: bpf
hostPath:
path: /sys/fs/bpf
type: DirectoryOrCreate
- name: btf
hostPath:
path: /sys/kernel/btf/vmlinux
type: File
terminationGracePeriodSeconds: 5
24 changes: 24 additions & 0 deletions configs/machine-config-kb/machineconfig-disable-algif.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
metadata:
annotations:
solution: https://access.redhat.com/solutions/7141979
labels:
machineconfiguration.openshift.io/role: master
name: 99-disable-algif-builtin-master
spec:
kernelArguments:
- initcall_blacklist=algif_aead_init
---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
metadata:
annotations:
solution: https://access.redhat.com/solutions/7141979
labels:
machineconfiguration.openshift.io/role: worker
name: 99-disable-algif-builtin-worker
spec:
kernelArguments:
- initcall_blacklist=algif_aead_init
20 changes: 20 additions & 0 deletions configs/machine-config/machineconfig-enable-bpf-lsm.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
metadata:
labels:
machineconfiguration.openshift.io/role: master
name: 99-enable-bpf-lsm-master
spec:
kernelArguments:
- lsm=lockdown,capability,selinux,bpf
---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
metadata:
labels:
machineconfiguration.openshift.io/role: worker
name: 99-enable-bpf-lsm-worker
spec:
kernelArguments:
- lsm=lockdown,capability,selinux,bpf
Loading