Как STACKLEAK улучшает безопасность ядра Linux

в 14:00, , рубрики: hacking, linux kernel, security, Блог компании Positive Technologies, информационная безопасность, Разработка под Linux, ядро Linux

STACKLEAK — это функция безопасности ядра Linux, изначально разработанная создателями Grsecurity/PaX. Я решил довести STACKLEAK до официального ванильного ядра (Linux kernel mainline). В этой статье будет рассказано о внутреннем устройстве, свойствах данной функции безопасности и ее очень долгом непростом пути в mainline.

Как STACKLEAK улучшает безопасность ядра Linux - 1

STACKLEAK защищает от нескольких классов уязвимостей в ядре Linux, а именно:

  • сокращает полезную для атакующего информацию, которую могут выдать утечки из ядерного стека в пользовательское пространство;
  • блокирует некоторые атаки на неинициализированные переменные в стеке ядра;
  • предоставляет средства динамического обнаружения переполнения ядерного стека.

Данная функция безопасности отлично укладывается в концепцию проекта Kernel Self Protection Project (KSPP): безопасность — это больше, чем только исправление ошибок. Абсолютно все ошибки в коде исправить невозможно, и поэтому ядро Linux должно безопасно отрабатывать в ошибочных ситуациях, в том числе при попытках эксплуатации уязвимостей. Больше подробностей о KSPP доступно на wiki проекта.

STACKLEAK присутствует как PAX_MEMORY_STACKLEAK в grsecurity/PaX патче. Однако grsecurity/PaX патч перестал распространяться свободно с апреля 2017 года. Поэтому появление STACKLEAK в ванильном ядре было бы ценным для пользователей Linux с повышенными требованиями к информационной безопасности.

Порядок работы:

  • выделить STACKLEAK из grsecurity/PaX патча,
  • тщательно изучить код и сформировать патч,
  • отправить в LKML, получить обратную связь, улучшить, повторять заново до принятия в mainline.

На момент написания статьи (25.09.2018) была отправлена 15 версия серии патчей. Она содержит архитектурно независимую часть и код для x86_64 и x86_32. Поддержка STACKLEAK для arm64, разработанная Лорой Эббот (Laura Abbott) из Red Hat, уже успела попасть в ванильное ядро 4.19.

STACKLEAK: свойства безопасности

Очистка остаточной информации в стеке ядра

Данная мера сокращает полезную информацию, которую могут выдать некоторые утечки из ядерного стека в пользовательское пространство.

Пример утечки информации из стека ядра представлен на схеме 1.

Как STACKLEAK улучшает безопасность ядра Linux - 2

Схема 1.

Однако утечки такого типа становятся бесполезны, если в конце системного вызова использованная часть стека ядра заполняется фиксированным значением (схема 2).

Как STACKLEAK улучшает безопасность ядра Linux - 3

Схема 2.

Как следствие, STACKLEAK блокирует некоторые атаки на неинициализированные переменные в стеке ядра. Примеры таких уязвимостей: CVE-2017-17712, CVE-2010-2963. Описание методики эксплуатации уязвимости CVE-2010-2963 можем найти в статье Кейса Кука (Kees Cook).

Суть атаки на неинициализированную переменную в стеке ядра представлена на схеме 3.

Как STACKLEAK улучшает безопасность ядра Linux - 4

Схема 3.

STACKLEAK блокирует атаки такого типа, так как значение, которым заполняется ядерный стек в конце системного вызова, указывает на неиспользованную область в виртуальном адресном пространстве (схема 4).

Как STACKLEAK улучшает безопасность ядра Linux - 5

Схема 4.

При этом важным ограничением является то, что STACKLEAK не защищает от аналогичных атак, выполняемых за один системный вызов.

Обнаружение переполнения стека ядра «в глубину»

В ванильном ядре (Linux kernel mainline) STACKLEAK эффективен против переполнения стека «в глубину» (kernel stack depth overflow) только в сочетании с CONFIG_THREAD_INFO_IN_TASK и CONFIG_VMAP_STACK. Обе эти меры внедрены Энди Лутомирски (Andy Lutomirski).

Простейший вариант эксплуатации данного типа уязвимостей отражен на схеме 5.

Как STACKLEAK улучшает безопасность ядра Linux - 6

Схема 5.

Перезапись определенных полей в структуре thread_info на дне ядерного стека позволяет повысить привилегии процесса. Однако при включении опции CONFIG_THREAD_INFO_IN_TASK данная структура выносится из ядерного стека, что устраняет описанный способ эксплуатации уязвимости.

Более продвинутый вариант данной атаки состоит в том, чтобы с помощью выхода за границу стека переписать данные в соседнем регионе памяти. Подробнее о данном подходе:

Атака такого типа отражена на схеме 6.

Как STACKLEAK улучшает безопасность ядра Linux - 7

Схема 6.

Защитой в данном случае служит CONFIG_VMAP_STACK. При включении данной опции рядом с ядерным стеком помещается специальная страница памяти (guard page), доступ к которой приводит к исключению (схема 7).

Как STACKLEAK улучшает безопасность ядра Linux - 8

Схема 7.

Наконец, самым интересным вариантом переполнения стека в глубину является атака типа Stack Clash. Идею еще в 2005 году выдвинул Гаэль Дэлалю (Gael Delalleau).

В 2017 году ее переосмыслили исследователи из компании Qualys, назвав данную технику Stack Clash. Дело в том, что существует способ перепрыгнуть guard page и перезаписать данные из соседнего региона памяти (схема 8). Это делается с помощью массива переменной длинны (VLA, variable length array), размер которого контролирует атакующий.

Как STACKLEAK улучшает безопасность ядра Linux - 9

Схема 8.

Больше информации о STACKLEAK и Stack Clash содержится в блоге grsecurity.

Как STACKLEAK защищает от Stack Clash в ядерном стеке? Перед каждым вызовом alloca() выполняется проверка на переполнение стека в глубину. Вот соответствующий код из 14 версии серии патчей:

void __used stackleak_check_alloca(unsigned long size)
{
       unsigned long sp = (unsigned long)&sp;
       struct stack_info stack_info = {0};
       unsigned long visit_mask = 0;
       unsigned long stack_left;

       BUG_ON(get_stack_info(&sp, current, &stack_info, &visit_mask));

       stack_left = sp - (unsigned long)stack_info.begin;

       if (size >= stack_left) {
               /*
                * Kernel stack depth overflow is detected, let's report that.
                * If CONFIG_VMAP_STACK is enabled, we can safely use BUG().
                * If CONFIG_VMAP_STACK is disabled, BUG() handling can corrupt
                * the neighbour memory. CONFIG_SCHED_STACK_END_CHECK calls
                * panic() in a similar situation, so let's do the same if that
                * option is on. Otherwise just use BUG() and hope for the best.
                */
#if !defined(CONFIG_VMAP_STACK) && defined(CONFIG_SCHED_STACK_END_CHECK)
               panic("alloca() over the kernel stack boundaryn");
#else
               BUG();
#endif
       }
}

Однако данный функционал был исключен из 15 версии. Это было сделано в первую очередь из-за спорного запрета Линуса Торвальдса использовать BUG_ON() в патчах по безопасности ядра Linux.

Кроме того, 9-я версия серии патчей привела к дискуссии, в результате которой было решено устранить все массивы переменной длинны из mainline-ядра. В эту работу включилось около 15 разработчиков, и она скоро будет закончена.

Влияние STACKLEAK на производительность

Привожу результаты тестирования производительности на x86_64. Оборудование: Intel Core i7-4770, 16 GB RAM.

Тест №1, привлекательный: сборка ядра Linux на одном процессорном ядре

    # time make
    Результат на 4.18:
        real 12m14.124s
        user 11m17.565s
        sys 1m6.943s
    Результат на 4.18+stackleak:
        real 12m20.335s (+0.85%)
        user 11m23.283s
        sys 1m8.221s

Тест №2, непривлекательный:

    # hackbench -s 4096 -l 2000 -g 15 -f 25 -P
    Средний результат на 4.18: 9.08 сек
    Средний результат на 4.18+stackleak: 9.47 сек (+4.3%)

Таким образом влияние STACKLEAK на производительность системы зависит от типа нагрузки. В частности, большое количество коротких системных вызовов повышает накладные расходы. Т.о. необходимо оценивать производительность STACKLEAK для планируемой нагрузки перед промышленной эксплуатацией.

Внутреннее устройство STACKLEAK

STACKLEAK состоит из:

  • Кода, очищающего стек ядра в конце системного вызова (изначально был написан на ассемблере),
  • GCC плагина для инструментации кода ядра на этапе компиляции.

Очистка стека ядра выполняется в функции stackleak_erase(). Данная функция отрабатывает перед возвращением в пользовательское пространство после системного вызова. В использованную часть стека thread’а записывается STACKLEAK_POISON (-0xBEEF). На начальную точку очистки указывает переменная lowest_stack, постоянно обновляемая в stackleak_track_stack().

Стадии работы stackleak_erase() отражены на схемах 9 и 10.

Как STACKLEAK улучшает безопасность ядра Linux - 10

Схема 9.

Как STACKLEAK улучшает безопасность ядра Linux - 11

Схема 10.

Т.о. stackleak_erase() очищает только использованную часть ядерного стека. Именно поэтому STACKLEAK такой быстрый. А если на x86_64 очищать все 16 кБ стека ядра в конце каждого системного вызова, hackbench показывает падение производительности 40%.

Инструментация кода ядра на этапе компиляции выполняется в STACKLEAK GCC плагине.

GCC плагины — это загружаемые модули для компилятора GCC, специфичные для проекта. Они регистрируют новые проходы с помощью GCC Pass Manager, предоставляя обратные вызовы (callbacks) для данных проходов.

Итак, для полноценной работы STACKLEAK в код функций с большим стековым кадром (stack frame) вставляются вызовы stackleak_track_stack(). Также перед каждой alloca() вставляется вызов уже упомянутой stackleak_check_alloca(), а после — вызов stackleak_track_stack().

Как уже было сказано, в 15 версии серии патчей из GCC-плагина была исключена вставка вызовов stackleak_check_alloca().

Путь в Linux kernel mainline

Путь STACKLEAK в mainline очень долгий и непростой (схема 11).

Как STACKLEAK улучшает безопасность ядра Linux - 12

Схема 11. Ход работ по внедрению STACKLEAK в Linux kernel mainline.

В апреле 2017 года создатели grsecurity закрыли свои патчи для сообщества, начав распространять их только на коммерческой основе. В мае 2017 года я принял решение взяться за задачу внедрения STACKLEAK в ванильное ядро. Так начался путь длиной более года. Компания Positive Technologies, в которой я работаю, дает мне возможность заниматься этой задачей некоторую часть моего рабочего времени. Но в основном я трачу на нее «свободное» время.

С прошлого мая моя серия патчей прошла многократное ревью, претерпела значительные изменения, дважды была раскритикована Линусом Торвальдсом. Мне хотелось оставить всю эту затею уже много раз. Но в определенный момент появилось твердое желание все же дойти до конца. На момент написания статьи (25.09.2018) 15 версия серии патчей находится в ветке linux-next, соответствует всем озвученным требованиям Линуса и готова к merge-window ядра 4.20 / 5.0.

Месяц назад я сделал доклад о данной работе на Linux Security Summit. Привожу ссылки на слайды и видео:

Заключение

STACKLEAK — очень полезная функция безопасности ядра Linux, блокирующая эксплуатацию сразу несколько типов уязвимостей. Помимо этого изначальный автор PaX Team смог сделать ее быстрой и красивой в инженерном плане. Поэтому появление STACKLEAK в ванильном ядре было бы ценным для пользователей Linux с повышенными требованиями к информационной безопасности. Более того, работа в данном направлении привлекает внимание сообщества разработчиков Linux к средствам самозащиты ядра.

Автор: a13xp0p0v

Источник

* - обязательные к заполнению поля