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





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.



Схема 1.

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



Схема 2.

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

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



Схема 3.

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



Схема 4.

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

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


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

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



Схема 5.

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

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


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



Схема 6.

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



Схема 7.

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

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



Схема 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 boundary\n");
#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.



Схема 9.



Схема 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).



Схема 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 к средствам самозащиты ядра.

Комментарии (13)


  1. ReklatsMasters
    27.09.2018 19:31

    Почитал я немного вашу переписку с линусом про внедрение патча. Сложно не сгореть после слов


    … I fart in your general direction and call your mother a hamster

    А это, оказывается, цитата из Монти Пайтона.


    1. a13xp0p0v Автор
      27.09.2018 22:49

      Ладно, проехали.
      Он все-таки извинился:
      lwn.net/Articles/764901

      В любом случае, все его замечания по коду я устранил.
      Кейс Кук снова пошлет pull request в грядущий релиз.


  1. kITerE
    27.09.2018 20:38
    +2

    Поздравляю с результатами! Это шаги в правильном направлении.


  1. staticmain
    27.09.2018 21:02

    Dammit, the whole f*cking point of this patch-set is to clear the
    stack used. It is *not* supposed to do anything else. If the process
    runs out of stack, that's caught by the vmalloc'ed stack.

    And if you don't have vmalloc'ed stack, then clearly you don't care.

    I refuse to take this kind of code that does stupid things, and then
    *because* it does those initial stupid things it does even more stupid
    things to correct for it.

    I hated the thing to begin with, told people that there are better
    approaches that don't have the downsides, got ignored, and then I'm
    pushed crap.

    No.

    Linus

    Ваше начинание может и хорошо, но то, что вы пишете в переписке с человеком, который ответственен за то, что он принимает в ядро (которое, к слову, крутится на миллионах компьютеров) это, извините, пц.

    Вам указали на ошибки, вам сказали что код ужасен, вы прекрасно осведомлены, что Линус не стесняется в выражениях.

    Нет, вы будете кричать о том, как Линус не прав, будете на конференции рассказывать как у вас пригорело от того, какой Линус плохой. Да блин, у вас на слайдах написано, что после первых замечаний вы откодестайлили код, убрали магические переменные, прекратили делать oops в kernel space, откомментировали код и проч. Вы что? Вы послали разработчикам ядра патч, который:
    * Содержит магические константы
    * Валит ядро
    * Недокументирован

    Да вас еще на входе должно было отстрелить. То есть вам настолько было все равно, что вы даже не почитали правила отправки патчей в ядро.
    После первого раза Линус вам сказал, что подход, который вы используете неверен, что он против такого подхода, даже чинить дальше бессмысленно, потому что сам базовый подход некуда не годен.
    Но нет, вы покрасили стены и послали его второй раз без изменения базовой концепции. Получили ожог седалища. А теперь виноват Линус.


    1. a13xp0p0v Автор
      27.09.2018 23:10
      +2

      Добрый вечер, товарищ эксперт.


      Ваше начинание может и хорошо, но то, что вы пишете в переписке с человеком, который ответственен за то, что он принимает в ядро (которое, к слову, крутится на миллионах компьютеров) это, извините, пц.

      Спасибо большое, я уже 6 лет разрабатываю ядро и знаю, кто такой Линус.
      А что же именно такого я ему написал?


      Вам указали на ошибки, вам сказали что код ужасен, вы прекрасно осведомлены, что Линус не стесняется в выражениях.

      Все недостатки, на которые мне указали Линус Торвальдс, Инго Молнар, Кейс Кук, Марк Рутланд, Питер Зийлстра и еще ряд людей, я устранил в течение полутора лет, пока работал над STACKLEAK. В этом и заключается итеративная работа в LKML (аналогично с другими открытыми проектами).


      Нет, вы будете кричать о том, как Линус не прав, будете на конференции

      Ах, так это я кричу? Да я предельно корректен, уважаемый.


      рассказывать как у вас пригорело от того, какой Линус плохой. Да блин, у вас на слайдах написано, что после первых замечаний вы откодестайлили код, убрали магические переменные, прекратили делать oops в kernel space, откомментировали код и проч.

      Вы рассуждаете, хотя не смотрели патчи. Вы даже не видели changelog, гарантирую.
      Конкретные претензии Линуса к 9 версии были в том, что код очистки стека написан на ассемблере. А во время pull-request 14 версии он поменял правила игры и запретил BUG_ON() во всех патчах по kernel hardening. Вы не видите реакции Кейса Кука?


      Вы что? Вы послали разработчикам ядра патч, который:
      • Содержит магические константы
      • Валит ядро
      • Недокументирован

      Дорогой эксперт, всё, что вы перечислили, содержится в изначальном коде grsecurity. Смотрите лучше патчи, а потом комментируйте.


      Да вас еще на входе должно было отстрелить. То есть вам настолько было все равно, что вы даже не почитали правила отправки патчей в ядро.
      После первого раза Линус вам сказал, что подход, который вы используете неверен, что он против такого подхода, даже чинить дальше бессмысленно, потому что сам базовый подход некуда не годен.

      А я и еще очень много людей считают, что изначальный подход по очистке остаточной информации в ядерном стеке очень даже хорош. Что дальше?


      Но нет, вы покрасили стены и послали его второй раз без изменения базовой концепции. Получили ожог седалища. А теперь виноват Линус.

      Попытка номер 3. Посмотрите код, если можете. А потом давайте авторитетную оценку. Лучше не сюда, а в LKML, пожалуйста.


      1. staticmain
        28.09.2018 11:53

        Добрый вечер, товарищ эксперт.


        Не удивительно, что у вас burnt после общения с Линусом, если вы при малейшей критике уходите в режим «я больше всех знаю».

        я уже 6 лет разрабатываю ядро и знаю, кто такой Линус

        Но видимо его прямолинейность вам была неизвестна, раз уж вы на конференции жаловались людям, что он «even swearing».

        Все недостатки, на которые мне указали Линус Торвальдс, Инго Молнар, Кейс Кук, Марк Рутланд, Питер Зийлстра и еще ряд людей, я устранил в течение полутора лет, пока работал над STACKLEAK.


        Проблема не в том, за сколько вы их исправили, а то что вы тратили время разработчиков из рассылки (в т.ч. Линуса) на вообще никак не соответствующий даже просто правилам приличия код (magic numbers, нет комментариев, валит ядро).А после этого пошли жаловаться на презентации что у вас Burnt.

        Дорогой эксперт, всё, что вы перечислили, содержится в изначальном коде grsecurity. Смотрите лучше патчи, а потом комментируйте.


        А вы думали можно просто взять изначальный код grsecurity и просто в наглую без изменений послать его в ядро?

        А я и еще очень много людей считают, что изначальный подход по очистке остаточной информации в ядерном стеке очень даже хорош. Что дальше?


        Вот только ни вы ни «еще очень много людей» не распоряжаетесь пуллом патчей в ядро.


        1. a13xp0p0v Автор
          28.09.2018 13:04
          +1

          Вижу, что патчи или хотя бы список изменений по версиям вы решили не смотреть.
          Ну ладно.


          Проблема не в том, за сколько вы их исправили, а то что вы тратили время разработчиков из рассылки (в т.ч. Линуса) на вообще никак не соответствующий даже просто правилам приличия код (magic numbers, нет комментариев, валит ядро).А после этого пошли жаловаться на презентации что у вас Burnt.

          То, что вы пишете, ложно. Зачем-то вы выдумываете.


          В разработке ПО есть принцип RERO (Release early, release often). Следуя ему первые версии STACKLEAK я посылал с тэгом RFC узкому кругу людей. А в cover letter были обозначены TODO пункты. Так делают все, кто работает над крупными задачами в ядре Linux.


          По мере того, как серия патчей становилась все более качественной, я понемногу расширял список получателей в LKML.


          Линус впервые увидел STACKLEAK на 9 версии патч-серии (в марте). По мнению Кейса Кука (мэйнтейнер gcc-плагинов) и некоторых разработчиков x86 этот код уже вполне соответствовал критериям приема в mainline. Линус, кстати, отдельно выразил недовольство тем, что STACKLEAK посылался слишком узкому кругу разработчиков. Но основным раздражающим фактором для него была очистка стека, написанная на ассемблере (около 100 строк). Мне стоило большого труда в 10 версии переписать этот ассемблер на C так, чтобы компилятор выдавал бинарь, который очень похож на рукописный ассемблер. Однако это дало возможность Лоре Эббот в дальнейшем без проблем портировать STACKLEAK на arm64.


          Теперь по поводу BUG_ON(). Это assertion в ядре Linux, который вызывается в экстренных случаях, например когда мы обнаружили порчу памяти либо невосстановимую ошибку во внутренних структурах ядра. Если в командной строке ядра не выставлен panic_on_warn, BUG_ON() в системном вызове приводит к остановке пользовательского процесса, от которого в данный момент работало ядро.


          В случае STACKLEAK BUG_ON() вызывался при порче lowest_stack и при блокировке атаки Stack Clash. Это правильно, потому что ядро должно прерывать порчу памяти или эксплойт как можно скорее.


          Но у Линуса и KSPP давнишний спор по поводу BUG_ON(). Я знаю, что Кейс Кук снова будет обсуждать с Линусом этот вопрос и искать компромиссы, когда тот вернется из своего отпуска.


          Одним из путей, кстати, может быть введение SECURITY_BUG_ON(), который дает oops при включении определенной config-опции ядра. Это было бы аналогично существующей опции CONFIG_BUG_ON_DATA_CORRUPTION.


          А на счет "жалоб", как вы это назвали, — в докладе я обозначил только факты, без оценок.
          Быть несогласным — это нормально. Приводить технические аргументы — это прекрасно. Именно поэтому ядро Linux и его сообщество живет — мы спорим и находим компромиссы.


  1. powerman
    27.09.2018 21:06

    Вот бы ещё всё остальное из PaX и GrSecurity получить на текущих ядрах… minipli остановился на 4.9.74, ниасилив слияние с патчами Meltdown/Spectre. :( Персональных лицензий GrSec не продают. Сидеть вечно на 4.9.74 тоже не вариант. Всё плохо, в общем.


    1. a13xp0p0v Автор
      27.09.2018 23:15

      Да, Meltdown сильно повлиял на PAX_UDEREF и PAX_KERNEXEC.
      А введение retpoline сильно повлияло на PAX_RAP.
      Сами авторы grsecurity потратили много сил на то, чтобы их адаптировать.
      В общем, патч grsecurity только растет в размерах по словам Брэда Спенглера.


  1. amarao
    28.09.2018 15:00

    А что сказали на это grsercurity-товарищи? Особенно в контексте, «какое было, какое стало».


    1. a13xp0p0v Автор
      28.09.2018 17:28
      +2

      А что сказали на это grsercurity-товарищи? Особенно в контексте, «какое было, какое стало».

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


      Так вот LWN выпустил статью про STACKLEAK после Linux Security Summit: https://lwn.net/Articles/764325/
      После этого Брэд сразу атаковал, я ответил и понеслась: https://lwn.net/Articles/764685/


      Он громко ушел с LWN некоторое время назад, поэтому полемика получилась странная: он пишет в твиттере и своем блоге, я отвечаю в LWN.
      В любом случае моя цель была вытянуть из него побольше информации. Мне кажется, это в целом получилось.


      1. amarao
        28.09.2018 17:48

        Спасибо, весело.


  1. anatolymik
    29.09.2018 15:19
    -2

    !