Linux内核漏洞分析之CVE-2025-39682
字数 2582
更新时间 2026-06-01 22:33:26

Linux内核漏洞CVE-2025-39682教学文档

一、漏洞概述

项目 内容
漏洞编号 CVE-2025-39682
影响版本 Linux Kernel < v6.17
涉及模块 Linux Kernel - TLS / tls_record_content_type
漏洞类型 Use-After-Free (UAF)
利用效果 本地提权

该漏洞本质上是另一个漏洞CVE-2024-58239的未修复完全版本。CVE-2024-58239的补丁(commit fdfbaec5923d9359698cbb286bc0deadbb717504)忽略了当非Data类型的record长度为0时的场景,导致copied值为0时出现逻辑短路,绕过了后续的control != TLS_RECORD_TYPE_DATA检查。


二、环境搭建

2.1 复现环境

  • 模拟器: QEMU
  • 内核版本: Linux Kernel v6.16
  • 环境附件: bzImage

2.2 复现步骤

  1. 使用QEMU启动已patch的内核
  2. 将PoC编译后传输到虚拟机中(或编译后重新打包文件系统)
  3. 运行PoC即可触发KASAN报告

注意:作者测试时基于一个已修复版本进行patch,其中6.12.67版本已修复此漏洞。


三、漏洞原理

漏洞本质是Use-After-Free (UAF)

当kTLS使用零拷贝(Zero Copy, ZC) 时,内核不会额外分配一个新的SKB(Socket Buffer),而是直接复用接收队列上原有的SKB。解密完成后,该SKB会被直接释放。然而,在同一解密流中,当Record的类型不一致时,kTLS的处理逻辑会将SKB放入rx_list中等待下一次接收再读取。这导致已释放的内存仍然保留额外的引用在rx_list中,从而引发UAF。


四、漏洞分析

4.1 关键代码段分析

4.1.1 Record类型不一致时的处理

tls_sw_recvmsg函数中存在以下逻辑:

err = tls_record_content_type(msg, tls_msg(darg.skb), &control);
if (err <= 0) {
    DEBUG_NET_WARN_ON_ONCE(darg.zc);
    tls_rx_rec_done(ctx);
put_on_rx_list_err:
    __skb_queue_tail(&ctx->rx_list, darg.skb);
    goto recv_end;
}

该代码的作用:当处理多个Record时,如果当前Record的类型与之前不一致,则将当前Record对应的SKB存入rx_list中。

4.1.2 rx_list优先读取逻辑

tls_sw_recvmsg函数开头存在以下逻辑:当rx_list中有数据时优先读取,如果读取的长度已经满足需求,则直接跳转到end。

结合上述两段代码,整体流程是:当Record类型不一致时,将其存放至rx_list供下次读取。

4.1.3 DEBUG提示的含义

代码中的DEBUG_NET_WARN_ON_ONCE(darg.zc)提示了一个重要信息:如果设置了darg.zc(即启用零拷贝),理论上不应走到这个路径。但实际情况并非如此。

4.2 解密流程中的引用关系

4.2.1 SKB加载流程

在TLS解密之前,调用链如下:

tls_rx_rec_wait -> tls_strp_msg_load -> tls_strp_load_anchor_with_queue

最终将接收队列中的一个SKB加载到strp->anchor这条引用中。

4.2.2 解密缓冲区处理

进行TLS解密时的调用链:

tls_rx_one_record -> tls_decrypt_sw -> tls_decrypt_sg

tls_decrypt_sg函数中,对解密缓冲区的处理有三个选择:

  • 非零拷贝路径:内核会分配新的clear_skb作为载体,在函数返回前将引用传递给darg->skb
  • 零拷贝路径:返回的是strp->anchor这条引用

4.2.3 UAF触发点

tls_rx_rec_done的功能是释放strp->anchor这条引用。然而,在内存释放之后,又通过以下代码将引用附加到rx_list中:

err = tls_record_content_type(msg, tls_msg(darg.skb), &control);
if (err <= 0) {
    DEBUG_NET_WARN_ON_ONCE(darg.zc);
    tls_rx_rec_done(ctx);           // 释放 strp->anchor
put_on_rx_list_err:
    __skb_queue_tail(&ctx->rx_list, darg.skb);  // darg.skb == strp->anchor,已释放
    goto recv_end;
}

这就形成了典型的Use-After-Free。


五、PoC分析

5.1 触发条件

触发漏洞的核心在于使两个Record的类型不一致,并且后续的Record走零拷贝路径。

关键判断逻辑在tls_record_content_type函数中:

static int tls_record_content_type(struct msghdr *msg, struct tls_msg *tlm,
                                   u8 *control)
{
    int err;
    if (!*control) {
        *control = tlm->control;
        if (!*control)
            return -EBADMSG;
        err = put_cmsg(msg, SOL_TLS, TLS_GET_RECORD_TYPE,
                       sizeof(*control), control);
        if (*control != TLS_RECORD_TYPE_DATA) {
            if (err || msg->msg_flags & MSG_CTRUNC)
                return -EIO;
        }
    } else if (*control != tlm->control) {
        return 0;   // 类型不一致时返回0
    }
    return 1;
}

另外,在tls_sw_recvmsg中还有以下逻辑:

if (control != TLS_RECORD_TYPE_DATA)
    break;

当Record类型不是DATA时,不再继续处理。

5.2 触发步骤(需三个Record)

第一步:发送两个Record后进行第一次recv

  1. 处理第一个Data类型的Record,初始化controlTLS_RECORD_TYPE_DATA
  2. 处理第二个其他类型的Record,触发类型不一致判断,将对应的SKB加入rx_list

第二步:发送第三个Record,以零拷贝方式进行recv

  1. rx_list中有SKB,优先处理该SKB
  2. 处理完毕后,control被设置为0x16(非Data类型)
  3. 读取长度不够,继续读取第三个Record
  4. 触发类型不一致判断,将第三个Record对应的SKB加入rx_list
  5. 由于零拷贝的原因,该SKB内存已被释放,造成UAF

5.3 为什么需要三个Record

关键在于control变量的赋值逻辑:

  • 如果control未被赋值,则进行赋值
  • 赋值后,后续Record与之比较

control != TLS_RECORD_TYPE_DATA这条检查会导致非Data类型的Record不被继续处理。因此需要三个Record来绕过这一限制,使得第三次recv时能成功触发UAF。


六、利用思路

6.1 总体方向

SKB的frags数组中如果包含Page,可以尝试转化为Page UAF

6.2 Data-only利用方式

获得Page UAF后,最简单的Data-only利用方式是:

  1. 堆喷filp结构体
  2. 篡改/etc/passwd文件实现权限提升

七、修复方案

7.1 官方补丁

修复该漏洞的补丁提交为:

commit 62708b9452f8eb77513115b17c4f8d1a22ebf843

7.2 修复原理

补丁修复了CVE-2024-58239补丁中遗留的问题——当非Data类型的Record长度为0时,copied值为0导致的逻辑短路,从而绕过了control != TLS_RECORD_TYPE_DATA这条检查。

7.3 升级建议

建议将Linux内核升级至v6.17及以上版本,或至少升级至包含该补丁的稳定版本(如6.12.67+)。

相似文章
相似文章
 全屏