编程 CVE-2026-31431 Copy Fail 深度技术剖析:Linux 内核 AF_ALG + splice() 提权漏洞从原理到实战修复

2026-07-28 13:17:02 +0800 CST views 7

CVE-2026-31431 "Copy Fail" 深度技术剖析:Linux 内核 AF_ALG + splice() 提权漏洞从原理到实战修复

前言

2026 年 4 月底,韩国安全研究团队 Theori 公开了一个编号为 CVE-2026-31431、代号为 "Copy Fail" 的 Linux 内核本地权限提升漏洞。该漏洞评级 CVSS 3.1 7.8(高危),影响范围横跨 Linux 内核 4.14 至 6.18.21 / 6.19.11,几乎覆盖了 2017 年以后所有主流 Linux 发行版。

这不是一个需要复杂竞争条件的漏洞。攻击者只需本地普通用户权限,无需任何特殊配置,利用 AF_ALG 加密套接字splice() 零拷贝系统调用的组合,就能在数秒内将任意普通用户提升为 root。整个攻击过程干净利落,不需要 ROP 链、JIT spraying 或堆风水——这是近十年来 Linux 内核提权漏洞中最优雅、也最危险的一类。

本文将从漏洞根因的代码层面出发,深入剖析 AF_ALG 的设计缺陷、splice() 的页缓存交互机制,以及两者如何共同构成可控的页缓存越界写,再给出多发行版的完整检测、复现与修复方案,并探讨容器逃逸的特殊场景。


一、背景:AF_ALG 加密套接字是什么

1.1 用户态到内核的加密桥梁

Linux 内核从 2.6.38 开始引入 AF_ALG 套接字族(最早在 2.6.38 中作为 "ashmem" 类接口出现,实际加密支持在后续版本逐步完善),目的是让用户态程序能够直接调用内核的密码学子系统,而无需加载内核模块或使用 /dev/crypto 设备。

AF_ALG 的工作模型非常直观:

// 用户态代码示例(伪代码)
int sock = socket(AF_ALG, SOCK_SEQPACKET, 0);

// 绑定到特定算法(例如 aes-gcm)
struct sockaddr_alg sa = {
    .salg_family = AF_ALG,
    .salg_type = "aead",          // 带认证加密
    .salg_name = "gcm(aes)"       // 算法名称
};
bind(sock, (struct sockaddr *)&sa, sizeof(sa));

// 设置密钥
setsockopt(sock, SOL_ALG, ALG_SET_KEY, key, keylen);

// 创建加密上下文(类似打开文件句柄)
int op_fd = accept(sock, NULL, NULL);

// 发送明文,接收密文(全程在内核完成)
send(op_fd, plaintext, plen, 0);
recv(op_fd, ciphertext, clen, 0);

这套接口的好处是:用户态代码可以用熟悉的 socket/read/write 语义操作内核加密,同时享受内核加密子系统的所有实现(C 代码复用、硬件加速、密钥管理统一)。

1.2 AEAD 模式与 in-place 优化

AF_ALG 支持多种加密模式,其中 AEAD(Authenticated Encryption with Associated Data) 模式最为常见,典型代表是 AES-GCM 和 ChaCha20-Poly1305。

AEAD 的一个核心特点是:加密和认证是同时完成的,并且通常要求密文和明文可以共享同一块内存(即 in-place 加密)。这对性能很重要——不需要为输出单独分配一块 buffer。

2017 年,内核开发者提交了一个优化 commit,目的是减少 AEAD 操作中的内存复制:如果输入和输出 buffer 重叠(或同一块内存),直接原地处理密文,而不是先写到临时 buffer 再复制回来

这个优化节省了内存分配和复制开销,却引入了一个微妙而致命的逻辑缺陷——CVE-2026-31431 的种子就此埋下。


二、漏洞根因:从加密失败到页缓存写入

2.1 splice() 与页缓存的零拷贝机制

在说漏洞之前,必须先理解 splice() 系统调用的工作原理。

splice() 是 Linux 提供的零拷贝数据传输接口,签名如下:

ssize_t splice(int fd_in,  loff_t *off_in,
               int fd_out, loff_t *off_out,
               size_t len, unsigned int flags);

它的作用是将数据从一个文件描述符(fd_in)直接移动到另一个文件描述符(fd_out),整个过程不经过用户态内存。内核通过在两个 fd 对应的内核缓冲区间建立"管道缓冲区"(pipe buffer)的引用来完成传输。

在处理普通文件时,splice() 操作的是页缓存(Page Cache)——Linux 文件系统缓存的核心数据结构。当你对一个文件执行 read/write 时,实际操作的是页缓存中的页,而不是磁盘。splice() 从文件页缓存读数据时,并不直接修改原始文件的页缓存,而是创建一个对原页的引用(零拷贝),然后将引用放入管道缓冲区,供 fd_out 端消费。

但如果 fd_out 恰好是同一个文件的页缓存呢?例如,我们 splice() 一个普通文件,fd_in 是该文件自身,fd_out 是通过某个系统调用获得的新 fd——这时,数据的来源和去向都指向同一文件的页缓存。

2.2 AF_ALG + splice() 的致命组合

问题出在 algif_aead 驱动(AF_ALG 的 AEAD 实现)的 recvmsg() 处理路径上。以下是极度简化的漏洞原理图:

正常 AEAD 解密流程(send plaintext → recv ciphertext):
  用户空间 plaintext → [AF_ALG send] → 内核加密 → [AF_ALG recv] → 用户空间 ciphertext

漏洞利用流程(send plaintext → splice → 文件页缓存):
  用户空间 plaintext → [AF_ALG send]
        ↓
    内核加密处理(in-place 优化生效)
        ↓
    加密操作中途失败(AEAD 认证失败)
        ↓
    内核错误处理路径中:释放输出 buffer 的"发送方引用"
        ↓
    splice() 此时正在等待写入文件页缓存
        ↓
    页缓存中的原始文件页被部分覆盖(4 字节可控越界写入)

更具体地说:

  1. 攻击者通过 splice() 将一个普通文件(假设是 /usr/bin/passwd)的 fd 与 AF_ALG 套接字连接,建立起从加密输出到文件页缓存的数据流。
  2. AEAD 操作启动send() 传入精心构造的明文(含认证标签),内核在 in-place 模式下开始处理。
  3. 认证失败:AEAD 的核心安全性来自认证标签——如果密文被篡改,解密时认证会失败。在正常路径下,认证失败会导致操作中止,不产生任何输出。
  4. 但是:in-place 优化路径中存在一个错误——即使 AEAD 认证最终失败,已经被解密到输出 buffer 的数据(在 in-place 场景下即文件的页缓存页)不会被回滚。内核认为"这次操作失败了,不用管输出了",但实际上已经写入了 4 字节。
  5. splice() 看到有数据可读,就把它写入目标文件的页缓存。写入位置完全可预测(基于 splice 的 offset 参数和页对齐计算),写入内容完全可控制(精心构造的 4 字节明文)。

2.3 为什么是 4 字节

AES-GCM 等 AEAD 算法的认证标签(Authentication Tag)长度为 16 字节。在处理 in-place 解密时,内核会先将密文写入输出区域(与明文重叠),然后验证标签。如果验证失败,内核已经写了多少就"泄漏"了多少——答案是 4 字节

这与 AEAD 的块对齐和 crypto_aead_decrypt() 的实现有关:认证检查失败后,之前写入的 4 字节已经落入了页缓存,无法回滚。这 4 字节足以改写 setuid 程序的关键跳转地址或 GOT(全局偏移表)条目。

2.4 漏洞代码定位

官方修复补丁 commit 为 a664bf3d603dc3bdcf9ae47cc21c0daec706d7a5,位于 crypto/algif_aead.c 中的 recvmsg 处理函数。核心问题是:

// 漏洞逻辑(简化)
ssize_t algif_aead_recvmsg(struct socket *sock, struct msghdr *msg, size_t size, int flags)
{
    // ...
    /* 如果是 in-place 操作(输入输出 buffer 相同)且 AEAD 认证最终失败,
       内核会跳过错误返回,但此时输出 buffer 已经被写入了部分数据。
       这个写入正是页缓存中的目标文件页。 */
    err = crypto_aead_decrypt(&ctx->tfm, ...);
    if (err) {
        /* 错误处理:仅返回错误码,未清理已写入的输出区域 */
        return err;  // ← 这里丢失了刚刚写入的 4 字节
    }
    // ...
}

修复方式是:即使 AEAD 操作失败,也显式清零或撤销已经被写入的输出区域,或者在 in-place 场景下禁用就地处理优化。


三、利用路径:从普通用户到 root

3.1 攻击前提

  • 本地普通用户账户
  • 系统运行受影响内核版本(4.14+,且未打补丁)
  • algif_aead 内核模块已加载(大多数默认加载,可通过 modprobe 触发)

3.2 setuid 提权的标准路径

攻击的核心思路是:覆写一个 setuid-root 程序(如 /usr/bin/passwd/bin/su)的 GOT 条目或函数指针,将其指向攻击者控制的内存区域,执行 shellcode 获取 root shell。

具体步骤如下:

步骤 1:定位目标 setuid 程序(如 /usr/bin/passwd)
        读取其 GOT 表,找到一个可覆写的条目(例如 atexit、__free_hook 等)

步骤 2:通过 splice() 建立 AF_ALG → 文件页缓存的数据流
        - open("/usr/bin/passwd", O_RDONLY)          // fd_in
        - open("/usr/bin/passwd", O_RDWR)             // fd_out,指向同一文件
        - 通过一系列 ioctl + splice() 将 AF_ALG 的输出导入目标文件的页缓存

步骤 3:构造恶意输入
        - 精心构造 4 字节内容,覆盖 GOT 中目标函数的地址
        - 将这 4 字节嵌入 AEAD 明文中,使得 in-place 解密时恰好将这 4 字节写入 GOT

步骤 4:触发 splice() 并触发解密
        - sendmsg() 发送构造的 AEAD 数据
        - splice() 管道同时接收输出并写入文件页缓存
        - AEAD 认证失败,但 4 字节已写入

步骤 5:执行 setuid 程序
        - 覆写后的 GOT 导致控制流转移到攻击者地址
        - 执行提权 shellcode:setuid(0) + exec("/bin/sh")
        - 获得 root shell

3.3 容器逃逸的特殊场景

如果攻击者运行在容器内(非特权容器)

容器共享宿主机的内核,这意味着容器内的用户实际上就是宿主机上的普通用户。通过上述步骤,容器内用户可以直接逃逸到宿主机 root。

一个典型场景:

  • 攻击者在 K8s Pod 中获得初始 shell(非 root)
  • Pod 内核为宿主机内核(如果未做内核隔离)
  • 通过 CVE-2026-31431 提权到 root
  • 通过 cgroup ns / pid ns 逃逸到宿主机文件系统

这也解释了为什么容器安全加固中内核隔离(使用 gVisor、轻量级 VM 等)是重要的一环。


四、影响范围与版本矩阵

4.1 内核版本影响

内核版本范围是否受影响说明
< 4.14✅ 安全漏洞代码尚未引入
4.14 ~ 6.18.21❌ 受影响全部受影响
6.19.0 ~ 6.19.11❌ 受影响同上,独立版本线
≥ 6.18.22✅ 已修复补丁 a664bf3d603d 已合入
≥ 6.19.12✅ 已修复同上
Linux 7.0+✅ 安全新版本架构已重构

4.2 主流发行版状态

发行版状态修复版本
Ubuntu 18.04/20.04/22.04/24.04❌ 全部受影响USN-6768-1 已发布
Debian 11/12❌ 全部受影响DSA-6238-1 已发布
RHEL 8/9/10❌ 全部受影响RHSA-2026:2864/2865/2866 已发布
Rocky Linux 8/9/10❌ 全部受影响跟随 RHEL 补丁
AlmaLinux 8/9/10❌ 全部受影响跟随 RHEL 补丁
CentOS 8❌ 全部受影响已 EOL,无官方补丁,需临时缓解
CentOS 7(默认内核)✅ 安全默认内核 3.10,不含 algif_aead;手动升级内核者需核实
CentOS 6(默认内核)✅ 安全默认内核 2.6,不含 algif_aead
OpenEuler 22.03/24.03❌ 全部受影响官方已发布修复
Kylin V10/V15❌ 全部受影响官方已发布修复
Deepin 20/23/25❌ 全部受影响官方已发布修复
Deepin 15 及以下❌ 全部受影响已 EOL,需手动编译内核或升级
统信 UOS V20/V25❌ 全部受影响官方已发布修复

五、检测:如何判断系统是否受影响

5.1 检查内核版本

uname -r

对比上表判断是否在漏洞版本范围内。

5.2 检查 algif_aead 模块状态

# 检查模块是否加载
lsmod | grep algif_aead

# 检查模块是否存在(即使未加载,也可能被攻击者主动加载)
modprobe -c | grep algif_aead
find /lib/modules/$(uname -r) -name "algif_aead*" 2>/dev/null

5.3 自动化检测脚本

#!/bin/bash
# cve-2026-31431-check.sh — CVE-2026-31431 "Copy Fail" 检测脚本
set -euo pipefail

echo "========================================="
echo "CVE-2026-31431 Copy Fail 检测报告"
echo "检测时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo "========================================="

# 1. 内核版本
KVER=$(uname -r)
echo -e "\n[1] 内核版本: $KVER"

# 2. 版本判断
if [[ "$KVER" =~ ^3\. ]] || [[ "$KVER" =~ ^2\. ]]; then
    echo "    ✅ 内核版本 (< 4.0) 不受影响"
    exit 0
fi

# 解析主版本号
MAJOR=$(echo "$KVER" | cut -d. -f1)
MINOR=$(echo "$KVER" | cut -d. -f2)
PATCH=$(echo "$KVER" | cut -d. -f3 | cut -d'-' -f1)

is_vulnerable_version() {
    local major=$1 minor=$2 patch=$3
    # 4.14+ 到 6.18.21 / 6.19.11 均为漏洞版本
    if (( major > 4 )) || \
       (( major == 4 && minor >= 14 )); then
        if (( major < 6 )) || \
           (( major == 6 && minor < 18 )) || \
           (( major == 6 && minor == 18 && patch <= 21 )); then
            return 0
        fi
        if (( major == 6 && minor == 19 && patch <= 11 )); then
            return 0
        fi
    fi
    return 1
}

if is_vulnerable_version "$MAJOR" "$MINOR" "$PATCH"; then
    echo "    ❌ 内核版本在漏洞范围内 (4.14 ~ 6.18.21 / 6.19.11)"
    VULNERABLE=1
else
    echo "    ✅ 内核版本已修复或不受影响"
    VULNERABLE=0
fi

# 3. 检查 algif_aead 模块
echo -e "\n[2] algif_aead 模块状态:"
if lsmod 2>/dev/null | grep -q algif_aead; then
    echo "    ⚠️  algif_aead 模块已加载 (攻击面激活)"
    MODULE_LOADED=1
else
    echo "    ℹ️  algif_aead 模块未加载 (但模块文件可能存在)"
    MODULE_LOADED=0
fi

# 4. 检查是否存在缓解配置
echo -e "\n[3] 缓解措施检查:"
if [[ -f /etc/modprobe.d/disable-algif-aead.conf ]]; then
    CONTENT=$(cat /etc/modprobe.d/disable-algif-aead.conf)
    if grep -q "install algif_aead /bin/false" "$_"; then
        echo "    ✅ algif_aead 已通过 modprobe 配置禁用"
        MITIGATED=1
    else
        echo "    ⚠️  存在配置文件但内容未知: $CONTENT"
        MITIGATED=0
    fi
else
    echo "    ❌ 未发现缓解配置 /etc/modprobe.d/disable-algif-aead.conf"
    MITIGATED=0
fi

# 5. 综合结论
echo -e "\n========================================="
if [[ "$VULNERABLE" == "1" && "$MODULE_LOADED" == "1" && "$MITIGATED" == "0" ]]; then
    echo "🚨 结论: 系统存在 CVE-2026-31431 漏洞风险!"
    echo "   建议立即执行修复或缓解措施。"
    exit 1
elif [[ "$VULNERABLE" == "1" && "$MITIGATED" == "1" ]]; then
    echo "⚠️  结论: 内核有漏洞但已应用临时缓解措施"
    echo "   缓解措施已生效,但建议尽快升级内核。"
    exit 2
elif [[ "$VULNERABLE" == "1" ]]; then
    echo "⚠️  结论: 内核存在漏洞风险"
    echo "   algif_aead 模块未加载,请确认是否需要加载。"
    echo "   建议部署缓解措施并计划内核升级。"
    exit 2
else
    echo "✅ 结论: 系统暂不受 CVE-2026-31431 影响"
    exit 0
fi

六、修复方案:临时缓解 + 永久升级

6.1 临时缓解(立即生效,无需重启——但治标不治本)

临时缓解的思路是完全禁用 algif_aead 内核模块,阻断攻击路径:

# 1. 创建禁用配置(永久生效,重启后仍有效)
cat << 'EOF' | sudo tee /etc/modprobe.d/disable-algif-aead.conf > /dev/null
install algif_aead /bin/false
EOF

# 2. 立即卸载已加载的模块(如有)
sudo rmmod algif_aead 2>/dev/null && echo "algif_aead 已卸载" || echo "模块未加载"

# 3. 验证缓解
lsmod | grep algif_aead && echo "⚠️  模块仍在加载" || echo "✅ 缓解生效"

注意:禁用 algif_aead 不会影响系统的正常使用。algif_aead 主要用于用户态应用程序调用内核加密服务(如 openssl 的 engine 接口),大多数服务器应用并不依赖 AF_ALG 直接调用方式。

6.2 永久修复:内核升级

Ubuntu / Debian

sudo apt update
sudo apt install linux-image-amd64 linux-headers-amd64 -y
sudo update-grub
sudo reboot

验证:

uname -r
# 应显示 6.18.22+ 或 6.19.12+ 或 7.0+

apt changelog linux-image-$(uname -r) | grep CVE-2026-31431

RHEL / Rocky Linux / AlmaLinux

sudo dnf clean all && sudo dnf makecache
sudo dnf update kernel -y
sudo reboot

验证:

uname -r
rpm -qa --changelog kernel | grep CVE-2026-31431

CentOS 8(已 EOL,无官方补丁)

# 临时缓解(首选)
sudo sh -c 'echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif-aead.conf'
sudo rmmod algif_aead 2>/dev/null

# 长期方案:迁移到 Rocky Linux 或 AlmaLinux

Kylin / OpenEuler

sudo yum clean all && sudo yum makecache
sudo yum update kernel -y
sudo reboot

6.3 一键修复脚本(适配多发行版)

#!/bin/bash
# cve-2026-31431-fix.sh — CVE-2026-31431 自动修复脚本
# 用法: curl -fsSL https://your-mirror/fix.sh | sudo bash
set -euo pipefail

echo "[*] CVE-2026-31431 修复脚本"

# 检测发行版
if [[ -f /etc/os-release ]]; then
    source /etc/os-release
    DISTRO=$ID
else
    echo "[!] 无法识别发行版,请手动处理"
    exit 1
fi

echo "[*] 检测到发行版: $DISTRO ($NAME)"

# ============ 步骤 1: 临时缓解(所有发行版通用)============
echo "[*] 部署临时缓解措施..."
MODPROBE_CONF="/etc/modprobe.d/disable-algif-aead.conf"
if [[ -f "$MODPROBE_CONF" ]]; then
    if grep -q "install algif_aead /bin/false" "$MODPROBE_CONF"; then
        echo "[+] 缓解措施已存在,跳过"
    else
        echo "install algif_aead /bin/false" | sudo tee "$MODPROBE_CONF" > /dev/null
    fi
else
    echo "install algif_aead /bin/false" | sudo tee "$MODPROBE_CONF" > /dev/null
fi
sudo rmmod algif_aead 2>/dev/null || true
echo "[+] 临时缓解已部署"

# ============ 步骤 2: 内核升级(按发行版)============
case "$DISTRO" in
    ubuntu)
        echo "[*] Ubuntu 系统,执行内核升级..."
        sudo apt update -qq
        sudo apt install -y --only-upgrade linux-image-generic linux-headers-generic
        echo "[+] 内核升级已安排,请手动执行: sudo reboot"
        ;;
    debian)
        echo "[*] Debian 系统,执行内核升级..."
        sudo apt update -qq
        sudo apt install -y linux-image-amd64 linux-headers-amd64
        echo "[+] 内核升级已安排,请手动执行: sudo reboot"
        ;;
    rhel|rhel|rocky|almalinux)
        echo "[*] RHEL 系系统,执行内核升级..."
        sudo dnf clean all && sudo dnf makecache -qq
        sudo dnf update -y kernel
        echo "[+] 内核升级已完成,请手动执行: sudo reboot"
        ;;
    centos)
        MAJOR_VERSION=$(grep -oP '(?<=VERSION_ID=)\d+' /etc/os-release)
        if [[ "$MAJOR_VERSION" == "8" ]]; then
            echo "[!] CentOS 8 已 EOL,仅部署了临时缓解"
            echo "[!] 建议迁移至 Rocky Linux 或 AlmaLinux"
        else
            echo "[*] CentOS $MAJOR_VERSION,执行内核升级..."
            sudo dnf clean all && sudo dnf makecache -qq
            sudo dnf update -y kernel
            echo "[+] 内核升级已安排,请手动执行: sudo reboot"
        fi
        ;;
    fedora)
        echo "[*] Fedora 系统,执行内核升级..."
        sudo dnf clean all && sudo dnf makecache -qq
        sudo dnf update -y kernel
        echo "[+] 内核升级已完成,请手动执行: sudo reboot"
        ;;
    opensuse|suse)
        echo "[*] openSUSE 系统..."
        sudo zypper patch -y kernel
        echo "[+] 请手动 reboot"
        ;;
    *)
        echo "[!] 未知发行版 $DISTRO,仅完成临时缓解"
        echo "[!] 请手动查找 ${DISTRO} 的内核升级方案"
        ;;
esac

echo ""
echo "[✓] 所有可执行步骤已完成"
echo "[!] 重要: 请执行 'sudo reboot' 重启以启用新内核"

七、生产环境修复的注意事项

7.1 升级前必做清单

在生产服务器上升级内核是高风险操作,建议遵循以下流程:

1. 快照 / 备份(云服务器创建快照,物理机备份关键数据)
2. 测试环境验证
   - 克隆生产环境到测试机
   - 执行同样的内核升级
   - 运行至少 30 分钟冒烟测试(应用健康检查、监控告警验证)
3. 低峰期操作
   - 确认监控告警就位
   - 通知相关方
   - 滚动更新而非同时重启所有机器
4. 升级后验证
   - uname -r 确认版本
   - rpm -qa --changelog | grep CVE-2026-31431 验证补丁
   - 重启前:运行业务关键路径测试
   - 重启后:检查系统日志、应用日志、监控指标

7.2 内核升级不回滚的风险

内核升级不像应用重启可以快速回滚。如果新内核导致问题,回滚可能需要数小时。以下场景可能导致升级失败:

  • 驱动不兼容(GPU 驱动、存储控制器驱动、自定义内核模块)
  • 安全加固工具与新内核的兼容性(如某些 HIDS 驱动)
  • 特定硬件需要外置固件加载

建议:在所有关键服务器上,至少保留一个旧内核版本作为 fallback:

# 查看已安装的内核版本
ls /boot/vmlinuz-*
# 保留至少一个旧版本以便紧急回滚
# 设置 GRUB_DEFAULT 为旧版本即可回滚
sudo grub2-set-default "Previous Kernel Version Name"
sudo reboot

7.3 云原生环境特别提示

Kubernetes 集群中,单节点内核升级意味着该节点会被标记为 NotReady,K8s 会自动驱逐 Pod:

# 建议使用 kubectl drain 配合
kubectl drain <node-name> --ignore-daemonsets --delete-emptydir-data --force
# 执行内核升级和重启
# 重启后 uncordon
kubectl uncordon <node-name>

使用 KubeSphereRancher 等管理平台时,平台通常提供了节点升级的编排功能。


八、容器层面的深层防护

即使内核未修复,以下容器安全措施也能显著降低风险:

8.1 使用安全加固的容器运行时

gVisor(Google):每个容器使用独立的用户态内核(runsc),完全隔离宿主机内核与容器内核的 syscalls。gVisor 的 syscall 过滤层天然阻断了 AF_ALG 相关攻击面。

# Pod 配置中使用 gVisor runtime
securityContext:
  runtimeClassName: gvisor

Kata Containers:每个容器运行在轻量级 VM 中,有独立 Linux 内核。CVE-2026-31431 只能影响 Kata 管理的 VM 内核,不影响宿主机。

8.2 Seccomp + Syscall 过滤

通过 seccomp 阻止容器内使用 splice()sendmsg() + AF_ALG 相关的 syscalls:

{
  "defaultAction": "SCMP_ACT_ERRNO",
  "syscalls": [
    {
      "names": ["splice", "sendmsg", "recvmsg"],
      "action": "SCMP_ACT_ERRNO",
      "errnoRetRet": 1
    },
    {
      "names": ["socket"],
      "action": "SCMP_ACT_ERRNO",
      "args": [
        {
          "index": 0,
          "op": "SCMP_CMP_EQ",
          "value": 38  // AF_ALG
        }
      ]
    }
  ]
}

8.3 AppArmor / SELinux 策略

在 AppArmor 中限制 algif_aead 模块加载:

# /etc/apparmor.d/local/usr.sbin.dhcpd(或在主策略中追加)
# 禁止加载特定内核模块(需要宿主内核支持)
deny module algif_aead

九、漏洞演进:为什么"优雅"的优化反而最危险

9.1 内核安全的核心矛盾

CVE-2026-31431 是一个典型的性能优化引入的安全漏洞,它深刻揭示了内核开发中一个永恒的矛盾:

  • 性能派:内存复制是昂贵的,in-place 优化可以显著提升吞吐量
  • 安全派:在加密操作的错误路径中,任何残留的副作用都是潜在的攻击面

AF_ALG 的 in-place 优化本身没有问题——在内存操作的语义下它是正确的。问题在于,当 in-place 优化与页缓存(文件系统层)以及文件描述符重定向(splice)相遇时,产生了一个跨越多个内核子系统的副作用链,而这个副作用在错误路径中没有被妥善清理。

9.2 密码学实现的安全边界

大多数密码学库(OpenSSL、libsodium、BoringSSL)在用户态层面有严格的密钥隔离错误处理:解密失败时,输出 buffer 要么被完全清零,要么被标记为不可用。

内核层面的密码学实现(crypto API + AF_ALG)也应该遵循同样的原则:认证失败 = 整次操作视为无效,输出区域必须恢复到操作前的状态。但内核的 in-place 优化跳过了这一步,因为它假设"操作失败就返回错误码,用户态不会用这个结果"——然而 splice() 管道缓冲区已经在消费这些"无效"数据了

9.3 防御性设计原则

从这次漏洞中,我们提炼出几条对内核开发者有意义的原则:

  1. 错误路径必须原子化:加密操作在认证失败时,其所有副作用(包括对共享内存/页缓存的写入)必须完全回滚,不能留下任何残留。
  2. 跨子系统交互需要安全审查:当一个优化涉及多个内核子系统(crypto + VFS + pipe)时,必须在每个子系统边界做安全审计,而不仅仅是各自子系统的单元测试。
  3. 用户态接口的隐式副作用:任何让用户态能间接影响内核内部状态的接口(splice 就是这样的接口),都需要以"最小惊讶原则"设计——行为必须可预测,不能因为内核内部优化而产生意外结果。

十、总结与行动建议

10.1 关键结论

维度结论
漏洞性质本地提权,CVSS 7.8,高危
攻击复杂度低,无需竞争条件,普通用户即可触发
影响范围几乎所有主流 Linux 发行版,2017 年以后的内核
容器影响可逃逸到宿主机 root
修复难度临时缓解简单(禁用模块),永久修复需升级内核
暴露面只要 algif_aead 模块存在即有风险

10.2 立即行动(72 小时内)

  1. 运行检测脚本(见第五节),确认所有服务器的状态
  2. 部署临时缓解:在所有受影响服务器上执行 echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif-aead.conf && rmmod algif_aead
  3. 优先级排序:面向互联网的服务器和 K8s 工作节点优先处理
  4. 建立资产清单:记录所有受影响服务器的内核版本和发行版,为后续升级做准备

10.3 短期行动(1-2 周内)

  1. 测试环境验证:在测试环境验证内核升级对业务的影响
  2. 制定升级计划:按业务优先级分批升级内核
  3. 滚动更新:K8s 集群建议逐节点 drain + upgrade + uncordon

10.4 长期安全加固

  1. 启用内核自动安全更新(Ubuntu unattended-upgrades,RHEL automatic reboot-update)
  2. 引入容器隔离层(gVisor / Kata)降低单点内核漏洞的影响
  3. 内核安全审计流程:定期对关键内核子系统做变更安全 review
  4. 漏洞情报订阅:订阅 CVE 数据库、发行版安全通告,确保 P0/P1 漏洞在 24 小时内处置

参考资料

  • 原始漏洞披露:Theori Team, 2026-04-29
  • 官方修复补丁:https://git.kernel.org/stable/c/a664bf3d603dc3bdcf9ae47cc21e0daec706d7a5
  • NVD 详情:https://nvd.nist.gov/vuln/detail/CVE-2026-31431
  • CERT-EU 安全公告:https://cert.europa.eu/publications/security-advisories/2026-005/
  • Sysdig 技术分析:https://www.sysdig.com/blog/cve-2026-31431-copy-fail-linux-kernel-flaw-lets-local-users-gain-root-in-seconds

推荐文章

前端代码规范 - Commit 提交规范
2024-11-18 10:18:08 +0800 CST
Vue3中的事件处理方式有何变化?
2024-11-17 17:10:29 +0800 CST
回到上次阅读位置技术实践
2025-04-19 09:47:31 +0800 CST
Rust async/await 异步运行时
2024-11-18 19:04:17 +0800 CST
最全面的 `history` 命令指南
2024-11-18 21:32:45 +0800 CST
CSS Grid 和 Flexbox 的主要区别
2024-11-18 23:09:50 +0800 CST
Elasticsearch 文档操作
2024-11-18 12:36:01 +0800 CST
程序员茄子在线接单