
CVE-2017-13868: Утечка информации из неинициализированных данных кучи ядра в XNU.
Функция ctl_ctloutput в macOS High Sierra 10.13 игнорирует возвращаемое значение вызова sooptcopyin, что открывает окно состояния гонки для утечки неинициализированных данных кучи ядра в пользовательское пространство. ctl_ctloutput-leak — это proof-of-concept эксплойт, который пытается вызвать эту утечку информации. Для эксплуатации требуются права root.
Этот эксплойт подтверждён работоспособностью на macOS High Sierra 10.13.1 Beta 17B25c и iOS 10.1.1 14B100 (под mach_portal).
Вот релевантная часть ctl_ctloutput на macOS High Sierra 10.13:
if (sopt->sopt_valsize && sopt->sopt_val) {
MALLOC(data, void *, sopt->sopt_valsize, M_TEMP, // (a) data is allocated
M_WAITOK); // without M_ZERO.
if (data == NULL)
return (ENOMEM);
/*
* 4108337 - copy user data in case the
* kernel control needs it
*/
error = sooptcopyin(sopt, data, // (b) sooptcopyin() is
sopt->sopt_valsize, sopt->sopt_valsize); // called to fill the
} // buffer; the return
len = sopt->sopt_valsize; // value is ignored.
socket_unlock(so, 0);
error = (*kctl->getopt)(kctl->kctlref, kcb->unit, // (c) The getsockopt()
kcb->userdata, sopt->sopt_name, // implementation is
data, &len); // called to process
if (data != NULL && len > sopt->sopt_valsize) // the buffer.
panic_plain("ctl_ctloutput: ctl %s returned "
"len (%lu) > sopt_valsize (%lu)\n",
kcb->kctl->name, len,
sopt->sopt_valsize);
socket_lock(so, 0);
if (error == 0) {
if (data != NULL)
error = sooptcopyout(sopt, data, len); // (d) If (c) succeeded,
else // then the data buffer
sopt->sopt_valsize = len; // is copied out to
} // userspace.
Этот код делает следующее:
data в getsockopt без указания флага M_ZERO для обнуления выделенных байтов.getsockopt из пользовательского пространства с помощью sooptcopyin, заполняя только что выделенный буфер. Это копирование должно полностью перезаписать выделенные данные, поэтому флаг M_ZERO не требовался. Однако возвращаемое значение sooptcopyin не проверяется, что означает возможность сбоя копирования, оставляя в буфере неинициализированные данные. Копирование может завершиться неудачей, если, например, программа передала в getsockopt неотображённый адрес.getsockopt для этого сокета управления ядра. Эта реализация должна обработать входной буфер, возможно, изменить его и укоротить, и вернуть код результата. Но реализация может полагать, что переданный буфер уже инициализирован (поскольку теоретически он приходит из пользовательского пространства), и поэтому многие реализации вообще не изменяют буфер. Например, функция NECP necp_ctl_getopt просто возвращает 0, не обрабатывая буфер данных.Таким образом, указав неотображённый адрес данных в getsockopt, мы можем заставить выделить буфер кучи контролируемого размера, предотвратить инициализацию содержимого этого буфера, а затем вызвать sooptcopyout, который попытается записать этот буфер обратно по неотображённому адресу. Всё, что нам нужно для успешного копирования, — повторно отобразить этот адрес в промежутке между вызовами sooptcopyin и sooptcopyout. Если мы сможем это сделать, произойдёт утечка неинициализированных данных кучи ядра в пользовательское пространство.
Оказывается, эту гонку довольно легко выиграть. Во время тестов на моём Macbook Pro 2015 года среднее количество попыток для победы в гонке никогда не превышало 600, а медиана — 5. На iOS 10.1.1 на iPhone 7 гонка выигрывалась ещё легче, обычно за не более чем 2 попытки. (Тестирование проводилось без DEBUG, так как printfs сильно замедляют эксплойт.)
Для сборки выполните make. См. начало Makefile для различных опций сборки.
Запустите эксплойт, указав целевой размер утечки в командной строке:
$ sudo ./ctl_ctloutput-leak 128
000000: ef be ad de ef be ad de 00 00 00 00 00 00 00 00
000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000020: 00 00 00 00 00 00 00 00 01 00 00 00 40 80 00 00
000030: de 28 45 00 04 00 00 00 a0 ff 4a 26 80 ff ff ff
000040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000050: 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000070: 00 00 00 00 00 00 00 00 ef be ad de ef be ad de
Я сообщил об этой проблеме Apple 7 октября 2017 года. Ей был присвоен номер CVE-2017-13868. Apple исправила уязвимость в macOS 10.13.2 и iOS 11.2.
Код ctl_ctloutput-leak выпущен в общественное достояние. В порядке вежливости я прошу указывать меня как автора, если вы ссылаетесь на этот код или используете его.
getsockopt не возвращает ошибку, ctl_ctloutput вызывает sooptcopyout для копирования буфера данных обратно в пользовательское пространство.