CVE-2026-31431 Copy Fail:Linux 内核本地提权漏洞深度分析与修复
|
wanlinwang
|
11 min read
严重程度:CVSS 7.8 (High)
漏洞别名:Copy Fail
影响范围:2017 年至今几乎所有主流 Linux 发行版
公开披露:2026-04-29
内核补丁:commit a664bf3d603d
一、交互式漏洞利用流程演示
以下是一个 7 步交互式可视化,展示从普通用户到 root 的完整攻击链。
二、漏洞技术原理
根本原因
2017 年,algif_aead.c 引入了 in-place 优化(commit 72548b093ee3):
1
2
3
4
5
6
7
| // 优化前(安全):src 和 dst 分离
req->src = TX_SGL; // 含 Page Cache 页(只读路径)
req->dst = RX_SGL; // 用户缓冲区(可写)
// 优化后(有漏洞):src = dst
req->src = req->dst = 合并的 SGL;
// Page Cache 页通过 sg_chain() 链入可写的 dst!
|
authencesn 的越界写
1
2
3
| // 第3步:写 seqno_lo 到 dst[assoclen + cryptlen]
// 这个位置正好是 Tag 区域 = 现在是 Page Cache 页!
scatterwalk_map_and_copy(tmp+1, dst, assoclen+cryptlen, 4, 1);
|
攻击者控制三个维度:
| 控制项 |
方法 |
| 哪个文件 |
任何普通用户可读的 setuid 文件 |
| 哪个偏移 |
通过 assoclen + splice offset 精确定位 |
| 写入什么值 |
AAD[4:8](seqno_lo)完全由攻击者构造 |
三、修复方法
数据来源:Ubuntu 官方、CERT-EU、CloudLinux、Tenable(2026-04-30)
3.1 确认是否受影响
1
2
3
4
5
6
7
| # 查看内核版本
uname -r
# 确认模块状态(built-in 或 loadable)
modinfo algif_aead | grep filename
# 输出 "(builtin)" → RHEL 系,需用 grubby 方案
# 输出文件路径 → Debian 系,rmmod 有效
|
3.2 立即临时缓解
方案 A:禁用模块(Ubuntu / Debian 系)
1
2
3
| echo "install algif_aead /bin/false" | sudo tee /etc/modprobe.d/disable-algif.conf
sudo rmmod algif_aead 2>/dev/null || true
sudo update-initramfs -u
|
验证:
1
2
| sudo modprobe algif_aead # 应该报错
lsof | grep AF_ALG # 应该无输出
|
方案 B:grubby 内核参数(RHEL / AlmaLinux / Rocky 系)
⚠️ RHEL 系 algif_aead 为内置模块,modprobe.d 无效,必须用此方案。
1
2
3
4
| sudo grubby --update-kernel=ALL --args="initcall_blacklist=algif_aead_init"
sudo reboot
# 重启后验证
sudo grubby --info=ALL | grep initcall_blacklist
|
方案 C:eBPF / BPF LSM 热拦截 AF_ALG socket(Rocky Linux 8.10 上实测)
适用于 BPF LSM 已启用的 RHEL / Rocky / Alma 系内核。该方案不杀进程,而是在 socket_create 阶段直接返回 EPERM,阻止非 root 用户创建 AF_ALG socket。攻击链第一步失败,后续 bind()、splice()、recv() 都不会发生。
确认当前内核支持 BPF LSM:
1
2
3
4
| cat /sys/kernel/security/lsm 2>/dev/null || true
grep -E 'CONFIG_BPF_LSM|CONFIG_BPF_SYSCALL|CONFIG_BPF_JIT|CONFIG_DEBUG_INFO_BTF' \
/boot/config-$(uname -r) 2>/dev/null || true
|
如果输出中包含:
1
2
3
4
| capability,yama,selinux,bpf
CONFIG_BPF_LSM=y
CONFIG_BPF_SYSCALL=y
CONFIG_DEBUG_INFO_BTF=y
|
则可以使用 BPF LSM 方案。
安装编译依赖:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| # 如果当前 repo 没有 PowerTools,可先补充 Rocky 8 PowerTools repo
cat > /etc/yum.repos.d/Rocky-PowerTools-Aliyun.repo <<'EOF'
[PowerTools]
name=Rocky Linux $releasever - PowerTools - Aliyun
baseurl=https://mirrors.aliyun.com/rockylinux/$releasever/PowerTools/$basearch/os/
enabled=1
gpgcheck=1
countme=0
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-rockyofficial
EOF
dnf clean all
dnf makecache
dnf install -y clang llvm gcc make bpftool libbpf libbpf-devel \
elfutils-libelf-devel zlib-devel kernel-headers
|
生成 vmlinux.h:
1
2
3
| mkdir -p /root/block-afalg
cd /root/block-afalg
bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
|
编写 BPF LSM 程序:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| cat > block_afalg.bpf.c <<'EOF'
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
char LICENSE[] SEC("license") = "GPL";
#define AF_ALG 38
#define EPERM 1
SEC("lsm/socket_create")
int BPF_PROG(block_afalg_socket_create,
int family,
int type,
int protocol,
int kern,
int ret)
{
__u64 uid_gid;
__u32 uid;
char comm[16];
if (ret != 0)
return ret;
uid_gid = bpf_get_current_uid_gid();
uid = (__u32)uid_gid;
if (family == AF_ALG && uid != 0) {
bpf_get_current_comm(&comm, sizeof(comm));
bpf_printk("deny AF_ALG socket: uid=%d comm=%s\n", uid, comm);
return -EPERM;
}
return 0;
}
EOF
|
编写用户态 loader:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
| cat > block_afalg_loader.c <<'EOF'
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/resource.h>
#include <bpf/libbpf.h>
static volatile sig_atomic_t exiting = 0;
static void sig_handler(int sig)
{
exiting = 1;
}
int main(int argc, char **argv)
{
struct bpf_object *obj = NULL;
struct bpf_program *prog;
struct bpf_link *links[32];
int link_count = 0;
int err;
struct rlimit rlim = {
.rlim_cur = RLIM_INFINITY,
.rlim_max = RLIM_INFINITY,
};
const char *obj_path = "/usr/local/libexec/block_afalg.bpf.o";
if (argc > 1)
obj_path = argv[1];
signal(SIGINT, sig_handler);
signal(SIGTERM, sig_handler);
setrlimit(RLIMIT_MEMLOCK, &rlim);
obj = bpf_object__open_file(obj_path, NULL);
if (!obj) {
fprintf(stderr, "failed to open BPF object: %s\n", obj_path);
return 1;
}
err = bpf_object__load(obj);
if (err) {
fprintf(stderr, "failed to load BPF object: %d\n", err);
bpf_object__close(obj);
return 1;
}
bpf_object__for_each_program(prog, obj) {
struct bpf_link *link = bpf_program__attach(prog);
if (!link) {
fprintf(stderr, "failed to attach BPF program\n");
bpf_object__close(obj);
return 1;
}
if (link_count < 32)
links[link_count++] = link;
}
printf("block-afalg BPF LSM loaded: non-root AF_ALG socket returns EPERM.\n");
while (!exiting)
sleep(1);
while (link_count > 0)
bpf_link__destroy(links[--link_count]);
bpf_object__close(obj);
return 0;
}
EOF
|
编译并安装:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| clang -O2 -g -target bpf -D__TARGET_ARCH_x86 \
-I/usr/include \
-c block_afalg.bpf.c \
-o block_afalg.bpf.o
gcc -O2 -g block_afalg_loader.c \
-I/usr/include \
-L/usr/lib64 \
-o block-afalg \
-lbpf -lelf -lz
mkdir -p /usr/local/libexec
install -m 0600 block_afalg.bpf.o /usr/local/libexec/block_afalg.bpf.o
install -m 0750 block-afalg /usr/local/sbin/block-afalg
|
手工加载测试:
1
| /usr/local/sbin/block-afalg /usr/local/libexec/block_afalg.bpf.o
|
另开普通用户终端验证:
1
2
3
4
5
6
| python3 - <<'PY'
import socket
print("before")
s = socket.socket(38, socket.SOCK_SEQPACKET, 0)
print("after", s)
PY
|
预期结果:
1
2
| before
PermissionError: [Errno 1] Operation not permitted
|
root 用户仍然允许创建 AF_ALG socket:
1
2
3
4
5
6
| sudo python3 - <<'PY'
import socket
print("before")
s = socket.socket(38, socket.SOCK_SEQPACKET, 0)
print("after", s)
PY
|
普通 TCP socket 不受影响:
1
2
3
4
5
| python3 - <<'PY'
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
print("AF_INET ok")
PY
|
配置为 systemd 服务:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| cat > /etc/systemd/system/block-afalg.service <<'EOF'
[Unit]
Description=Block non-root AF_ALG socket creation using BPF LSM
After=multi-user.target
[Service]
Type=simple
ExecStart=/usr/local/sbin/block-afalg /usr/local/libexec/block_afalg.bpf.o
Restart=always
RestartSec=2
User=root
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now block-afalg.service
systemctl status block-afalg.service
|
查看 BPF 拦截日志:
1
| cat /sys/kernel/debug/tracing/trace_pipe
|
回滚:
1
2
3
4
5
6
| systemctl stop block-afalg.service
systemctl disable block-afalg.service
rm -f /etc/systemd/system/block-afalg.service
rm -f /usr/local/sbin/block-afalg
rm -f /usr/local/libexec/block_afalg.bpf.o
systemctl daemon-reload
|
该方案的本质是:
1
2
3
4
5
| 非 root 用户调用 socket(AF_ALG, ...)
→ BPF LSM socket_create hook
→ family == AF_ALG && uid != 0
→ return -EPERM
→ socket fd 不创建
|
3.3 安装修复补丁
Ubuntu / Debian
1
2
3
| sudo apt update && sudo apt upgrade -y linux-image-$(uname -r)
sudo reboot
uname -r
|
RHEL / AlmaLinux 8
1
2
3
| sudo dnf clean all && sudo dnf update kernel* -y && sudo reboot
sudo dnf config-manager --disable almalinux-testing
sudo dnf remove $(dnf repoquery --installonly --latest-limit=-1 -q)
|
RHEL / AlmaLinux 9
1
2
3
4
| sudo dnf install -y https://repo.almalinux.org/almalinux/9/extras/x86_64/os/Packages/almalinux-release-testing-9-1.el9.noarch.rpm
sudo dnf update kernel -y && sudo reboot
sudo dnf config-manager --disable almalinux-testing
sudo dnf remove $(dnf repoquery --installonly --latest-limit=-1 -q)
|
RHEL / AlmaLinux 10
1
2
3
4
| sudo dnf install -y https://repo.almalinux.org/almalinux/10/extras/x86_64/os/Packages/almalinux-release-testing-10-1.el10.x86_64.rpm
sudo dnf update kernel -y && sudo reboot
sudo dnf config-manager --disable almalinux-testing
sudo dnf remove $(dnf repoquery --installonly --latest-limit=-1 -q)
|
Amazon Linux 2023
1
| sudo yum update kernel -y && sudo reboot
|
SUSE / openSUSE
1
| sudo zypper update kernel-default -y && sudo reboot
|
3.4 打完补丁后撤销临时缓解
1
2
3
4
5
6
7
| # Debian 系
sudo rm /etc/modprobe.d/disable-algif.conf && sudo update-initramfs -u
# RHEL 系
sudo grubby --update-kernel=ALL --remove-args="initcall_blacklist=algif_aead_init"
sudo systemctl disable block-afalg.service
sudo reboot
|
3.5 Kubernetes / 容器环境
⚠️ 修复必须在宿主机上操作,不能只在容器内。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
| # patch-copy-fail.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: patch-copy-fail-cve
namespace: default
spec:
selector:
matchLabels:
app: patch-copy-fail-cve
updateStrategy:
type: RollingUpdate
template:
metadata:
labels:
app: patch-copy-fail-cve
spec:
hostPID: true
priorityClassName: system-node-critical
volumes:
- name: root-mount
hostPath:
path: /
type: Directory
initContainers:
- name: patch-copy-fail-cve
image: busybox:1.36.1
command: ["/bin/sh", "-c"]
args:
- |
tee /host/etc/modprobe.d/disable-algif-aead.conf <<<'install algif_aead /bin/false'
chroot /host rmmod algif_aead 2>/dev/null || true
chroot /host update-initramfs -u
securityContext:
privileged: true
runAsUser: 0
volumeMounts:
- name: root-mount
mountPath: /host
containers:
- name: pause
image: registry.k8s.io/pause:3.10.1
|
1
| kubectl apply -f patch-copy-fail.yaml
|
四、各方案对比
| 方案 |
需要重启 |
效果 |
适用场景 |
rmmod algif_aead |
否 |
立即生效,重启失效 |
Debian 系,模块可卸载 |
modprobe.d 禁用 |
需更新 initramfs |
持久有效 |
Debian 系 |
grubby 内核参数 |
需要 |
持久有效 |
RHEL 系(built-in 内核) |
| eBPF / BPF LSM 拦截 AF_ALG |
否 |
即时拒绝非 root 创建 AF_ALG socket |
BPF LSM 可用的 RHEL / Rocky / Alma 系 |
| 升级内核(推荐) |
需要 |
根本修复 |
所有发行版 |
| KernelCare livepatch |
不需要 |
根本修复 |
订阅用户 |
五、影响范围说明
禁用 algif_aead 不影响:dm-crypt/LUKS、kTLS、IPsec/XFRM、OpenSSL/GnuTLS/NSS(默认配置)、SSH。
检查当前是否有程序依赖:
1
2
| lsof | grep AF_ALG
# 无输出 → 可安全禁用
|
六、参考资料
Support the Creator
If you found this article helpful, consider supporting.