Эта небольшая статья про то, как я улучшил SLUB-аллокатор в ядре Linux.

7 августа я выступил на хакерском фестивале SHA2017 (Still Hacking Anyway). Слайды и запись выступления:

В своем докладе я рассказал о уязвимости CVE-2017-2636 в ядре Linux и технике ее эксплуатации для локального повышения привилегий. Техника эксплуатации двойного освобождения памяти (double free) состоит в том, что сначала нужно превратить его в ошибку типа "использование после освобождения" (use-after-free). Обычно это достигается с помощью выделения блока памяти того же размера между двойным освобождением (отражено на схеме). Данная техника называется heap spraying.


diagram


Однако в случае CVE-2017-2636 освобождаются сразу 13 элементов, причем двойное освобождение происходит одним из первых. Поэтому вышеописанная техника оказывается непригодной. Но все-таки мне удалось привести это состояние системы к ошибке использования после освобождения. Я воспользовался наивным поведением SLUB, основного аллокатора ядра Linux.

Оказывается, SLUB-аллокатор не препятствует последовательному двойному освобождению одного и того же участка памяти. В отличие от него аллокатор libc выполняет проверку под названием fasttop, сравнительно дешевую в отношении производительности. Идея проста: сообщить об ошибке в случае совпадения адреса освобождаемого элемента с адресом последнего элемента в списке свободных. Это позволит обнаруживать хотя бы некоторые ошибки двойного освобождения памяти.

Я добавил аналогичную проверку в функцию set_freepointer() SLUB-аллокатора и отправил патч в список рассылки ядра Linux (LKML). Патч вызвал оживленное обсуждение.

В этой доработке мейнтейнерам SLUB не понравилось то, что:

  1. данная проверка выполняется на каждом добавлении элемента в список свободных (какие-то накладные расходы все же есть);
  2. данная проверка дублирует отладочный функционал slub_debug;
  3. в случае двойного освобождения памяти происходит паника ядра (предложили опускать повторное добавление элемента в список свободных).

Я привел такие аргументы:

  1. slub_debug, действительно, предотвращает двойное освобождение памяти, однако по умолчанию не используется дистрибутивами Linux;
  2. когда аллокатор зафиксировал двойное освобождение памяти, серьезная ошибка где-то в ядре Linux уже произошла. Поэтому не стоило бы доверять процессу, в рамках которого это случилось (он может быть эксплойтом).

В итоге при содействии Кейса Кука (Kees Cook) была достигнута договоренность включить предлагаемую проверку в опцию ядра CONFIG_SLAB_FREELIST_HARDENED. В скором времени патч должен попасть в основную ветку, Linux Kernel mainline.

Обновление

В конечном итоге данная проверка попала в ядро v4.14 как часть функционала CONFIG_SLAB_FREELIST_HARDENED. Больше подробностей доступно в обзоре от Кейса Кука.