-
- CVE-2017-5123是一个针对于Linux内核4.12-4.13版本,存在于waitid()系统调用中的本地提权漏洞。该漏洞原因在于:在waitid()系统调用中,由于没有检查用户态传入指针的有效性,而造成攻击者可以不受限制地将用户态写入任意内核地址的能力。
-
- 在系统调用处理阶段,内核需要具备读取和写入触发系统调用进程内存的能力。为此,内核设有copy_from_user与put_user等特殊函数,用于将数据复制进出用户区。在较高级别,put_user的功能大致如下:
put_user(x, void __user *ptr)
if (access_ok(VERIFY_WRITE, ptr, sizeof(*ptr)))
return -EFAULT
user_access_begin()
*ptr = x
user_access_end()
access_ok() 调用检查ptr是否位于用户区而非内核内存。如果检查通过,user_access_begin()调用禁用SMAP,允许内核访问用户区。内核写入内存后重新启用SMAP。需要注意的一点是:这些用户访问函数在内存读写过程中处理页面错误,在访问未映射内存时不会导致崩溃。
-
- 在内核版本4.13中,为了能够正常使用unsafe_put_user,专门对waitid syscall进行了更新,但access_ok检查仍处于缺失状态。漏洞代码如下所示。
SYSCALL_DEFINE5(waitid, int, which, pid_t, upid, struct siginfo __user *,
infop, int, options, struct rusage __user *, ru)
{
struct rusage r;
struct waitid_info info = {.status = 0};
long err = kernel_waitid(which, upid, &info, options, ru ? &r : NULL);
int signo = 0;
if (err > 0) {
signo = SIGCHLD;
err = 0;
if (ru && copy_to_user(ru, &r, sizeof(struct rusage)))
return -EFAULT;
}
if (!infop)
return err;
user_access_begin();
unsafe_put_user(signo, &infop->si_signo, Efault); <- no access_ok call
unsafe_put_user(0, &infop->si_errno, Efault);
unsafe_put_user(info.cause, &infop->si_code, Efault);
unsafe_put_user(info.pid, &infop->si_pid, Efault);
unsafe_put_user(info.uid, &infop->si_uid, Efault);
unsafe_put_user(info.status, &infop->si_status, Efault);
user_access_end();
return err;
Efault:
user_access_end();
return -EFAULT;
}
-
- 缺少access_ok检查意味着允许提供内核地址并将其作为waitid syscall的infop参数。syscall将使用unsafe_put_user覆盖内核地址,因为此项操作可以逃避检查。