Control-flow integrity

Control-flow integrity (CFI) — совокупность методов в компьютерной безопасности, предназначенных для ограничения возможных путей исполнения программы исключительно заранее определённым графом потока управления с целью повышения защищённости[1]. Механизмы control-flow integrity затрудняют злоумышленнику захват управления выполнением программы, предотвращая целый ряд атак повторного использования кода. К близким технологиям относятся «разделение указателей на код» и «целостность указателей на код»[2][3].

Поддержка control-flow integrity реализована в компиляторах Clang[4] и GCC[5], а также в виде Guard потока управления (англ. Control Flow Guard)[6] и Guard возврата управления (англ. Return Flow Guard)[7] от Microsoft, а также Reuse Attack Protector (RAP)[8] от PaX Team.

История

Появление методов предотвращения исполнения произвольного кода, таких как Data Execution Prevention и NX-бит, привело к развитию новых атак, позволяющих перехватить управление программой (например, возвратно-ориентированное программирование)[8]. В 2003 году команда PaX опубликовала описание типовых сценариев эксплуатации уязвимостей и предложила собственные идеи по их предотвращению[8][9]. В 2005 году исследователи из Microsoft формализовали эти подходы и ввели термин control-flow integrity для обозначения технологий защиты первичного потока управления программой, а также предложили методику инструментирования уже скомпилированного машинного кода[1].

В дальнейшем основываясь на принципах control-flow integrity, были предложены многочисленные методы повышения устойчивости ПО к атакам. Однако широкого распространения такие подходы не получили из-за значительного замедления исполнения либо необходимости привлечения дополнительной информации (например, профилирования)[10].

В 2014 году команда исследователей из Google представила реализацию control-flow integrity для промышленных компиляторов GCC и LLVM, способных инструментировать программы на C++. Официальная поддержка CFI была включена в GCC 4.9.0 в 2014 году[5][11] и в Clang 3.7 в 2015 году[12][13]. Microsoft выпустила Guard потока управления (Control Flow Guard) для Windows 8.1 и реализовала поддержку CFI в Visual Studio 2015[6].

Описание

Наличие косвенных переходов в программном коде создаёт риск передачи управления на любой адрес, где начинается команда (например, в архитектуре x86 — любой байт как начало команды[14]). Если злоумышленник сможет модифицировать адрес, используемый командой перехода, он сможет использовать существующий код для своих целей.

В типичных программах такие нелокальные переходы ведут к началу функции (при вызове процедуры) или к инструкции после вызова (возврат из процедуры). Первый вариант — прямой переход, отражённый прямой дугой на графе потока управления; второй — обратный переход по аналогии отмечается обратной дугой[15].

Прямые переходы

Для прямых переходов допустимые адреса ограничены набором функций программы. С учётом системы типов и семантики языка программирования это множество может быть дополнительно сокращено[16]. Например, в C++ по стандарту корректный указатель на функцию, используемый при косвенном вызове, обязан содержать адрес функции с совместимым типом[17].

Методы реализации control-flow integrity для прямых переходов основаны на выделении множества легальных адресов для каждой косвенной инструкции[1]. Для этого используется статический анализ кода на различных уровнях: исходный, внутреннее представление или машинный[1][10]. На основании анализа, к инструкциям косвенного перехода добавляется проверочный код — он сравнивает вычисленный во время исполнения адрес с предвычисленным набором. При расхождении выполнение или аварийно прерывается, или обрабатывается по заданной политике[18][19]. В результате граф потока управления ограничивается только разрешёнными рёбрами и вершинами[1][16][20], поэтому попытка подмены указателя в целях атаки становится безуспешной.

Такой подход эффективно блокирует техники возвратно-ориентированного программирования на прямых переходах: jump-oriented programming[21] и call-oriented programming[22].

Обратные переходы

Для обратных переходов применяются различные методики[8].

Один из подходов аналогичен методу для прямых переходов — статически вычисляются допустимые адреса возврата[23].

Во втором подходе к адресу возврата применяется особая обработка: кроме сохранения в стек, он дополнительно помещается (в возможно изменённом виде) в специальную область памяти или, например, в регистр процессора; перед возвратом производится сравнение оригинала и копии[8].

Третий способ требует аппаратной поддержки и использует так называемый «теневой стек» — особую, непривилегированную область памяти для сохранения адресов возврата при вызовах[24].

Методы control-flow integrity для обратных переходов позволяют блокировать атаки возврата в библиотеку и возвратно-ориентированное программирование путём контроля адреса возврата на стеке[23].

Примеры

Ниже приведены примеры реализации control-flow integrity.

Clang Indirect Function Call Checking

Indirect Function Call Checking (IFCC) обеспечивает контроль косвенных переходов в программе (за исключением некоторых особых, например, вызова виртуальных функций). При построении допустимых адресов учитывается тип функции, что препятствует не только выполнению перехода за пределы функций, но и некорректному приведению типов в коде. Для включения проверки в Clang используется опция -fsanitize=cfi-icall[4].

// clang-ifcc.c
#include <stdio.h>

int sum(int x, int y) {
  return x + y;
}

int dbl(int x) {
  return x + x;
}

void call_fn(int (*fn)(int)) {
  printf("Result value: %d\n", (*fn)(42));
}

void erase_type(void *fn) {
  // Поведение не определено, если динамический тип fn не совпадает с int (*)(int).
  call_fn(fn);
}

int main() {
  // При вызове erase_type теряется статическая информация от типе.
  erase_type(sum);
  return 0;
}

Компиляция без проверок позволяет программе работать без ошибок, но результат неопределён и может различаться между запусками:

$ clang -Wall -Wextra clang-ifcc.c
$ ./a.out
Result value: 1388327490

С применённой опцией, контроль приводит к аварийному завершению исполнения при вызове call_fn:

$ clang -flto -fvisibility=hidden -fsanitize=cfi -fno-sanitize-trap=all clang-ifcc.c
$ ./a.out
clang-ifcc.c:12:32: runtime error: control flow integrity check for type 'int (int)' failed during indirect function call
(./a.out+0x427a20): note: (unknown) defined here

Clang Forward-Edge CFI для виртуальных вызовов

Этот метод контролирует интегритет виртуальных вызовов в C++. Для каждой иерархии классов с виртуальными функциями строятся битовые маски, отражающие допустимые функции по каждому статическому типу. Модификация таблицы виртуальных функций (например, при неправильном приведении типа либо ошибке памяти) приводит к невозможности совпадения динамического типа с предсказанными значениями[10][25].

// virtual-calls.cpp
#include <cstdio>

struct B {
  virtual void foo() = 0;
  virtual ~B() {}
};

struct D : public B {
  void foo() override {
    printf("Right function\n");
  }
};

struct Bad : public B {
  void foo() override {
    printf("Wrong function\n");
  }
};

int main() {
  Bad bad;                          // Стандарт C++ позволяет делать приведение типов по следующей схеме:
  B &b = static_cast<B&>(bad);      // Derived1 -> Base -> Derived2.
  D &normal = static_cast<D&>(b);   // В результате динамический тип объекта normal
  normal.foo();                     // будет bad и вызовется неправильная функция.
  return 0;
}

Компиляция без проверок:

$ clang++ -std=c++11 virtual-calls.cpp
$ ./a.out
Wrong function

В данном случае вместо метода foo класса D вызывается foo из класса Bad. Активированный контроль с помощью -fsanitize=cfi-vcall предотвращает ошибку:

$ clang++ -std=c++11 -Wall -flto -fvisibility=hidden -fsanitize=cfi-vcall -fno-sanitize-trap=all virtual-calls.cpp
$ ./a.out
virtual-calls.cpp:24:3: runtime error: control flow integrity check for type 'D' failed during virtual call (vtable address 0x000000431ce0)
0x000000431ce0: note: vtable is of type 'Bad'
 00 00 00 00  30 a2 42 00 00 00 00 00  e0 a1 42 00 00 00 00 00  60 a2 42 00 00 00 00 00  00 00 00 00
              ^

Примечания

  1. 1 2 3 4 5 Martín Abadi, Mihai Budiu, Úlfar Erlingsson, Jay Ligatti (2005). “Control-flow Integrity”. Proceedings of the 12th ACM Conference on Computer and Communications Security [англ.]. ACM: 340—353. DOI:10.1145/1102120.1102165. ISBN 1595932267. Дата обращения 2017-12-22.
  2. Volodymyr Kuznetsov, László Szekeres, Mathias Payer, George Candea, R. Sekar (2014). “Code-pointer Integrity”. Proceedings of the 11th USENIX Conference on Operating Systems Design and Implementation [англ.]. USENIX Association: 147—163. ISBN 9781931971164. Дата обращения 2017-12-22.
  3. On differences between the CFI, CPS, and CPI properties (англ.). nebelwelt.net. Дата обращения: 22 декабря 2017. Архивировано 22 декабря 2017 года.
  4. 1 2 Control Flow Integrity — Clang 5 documentation. releases.llvm.org. Дата обращения: 22 декабря 2017. Архивировано 23 декабря 2017 года.
  5. 1 2 vtv - GCC Wiki. gcc.gnu.org. Дата обращения: 22 декабря 2017. Архивировано 11 июля 2017 года.
  6. 1 2 Control Flow Guard (Windows) (англ.). msdn.microsoft.com. Дата обращения: 22 декабря 2017. Архивировано 22 декабря 2017 года.
  7. Return Flow Guard – Tencent's Xuanwu Lab (англ.). xlab.tencent.com. Дата обращения: 22 декабря 2017. Архивировано 23 декабря 2017 года.
  8. 1 2 3 4 5 grsecurity (англ.). www.grsecurity.net. Дата обращения: 22 декабря 2017. Архивировано 17 февраля 2018 года.
  9. PaX future. PaX Team. Дата обращения: 22 декабря 2017. Архивировано 5 августа 2017 года.
  10. 1 2 3 Caroline Tice, Tom Roeder, Peter Collingbourne, Stephen Checkoway, Úlfar Erlingsson (2014). “Enforcing Forward-edge Control-flow Integrity in GCC & LLVM”. Proceedings of the 23rd USENIX Conference on Security Symposium [англ.]. USENIX Association: 941—955. ISBN 9781931971157. Дата обращения 2017-12-22.
  11. GCC 4.9 Release Series - GNU Project - Free Software Foundation (FSF) (англ.). gcc.gnu.org. Дата обращения: 22 декабря 2017. Архивировано 15 января 2018 года.
  12. Clang 3.7 Release Notes — Clang 3.7 documentation. releases.llvm.org. Дата обращения: 22 декабря 2017. Архивировано 26 ноября 2017 года.
  13. LLVM releases. releases.llvm.org. Дата обращения: 22 декабря 2017. Архивировано 15 декабря 2017 года.
  14. Intel® 64 and IA-32 Architectures Software Developer Manuals (англ.). software.intel.com. Дата обращения: 22 декабря 2017. Архивировано 25 декабря 2017 года.
  15. Security - WebAssembly. webassembly.org. Дата обращения: 22 декабря 2017. Архивировано 23 декабря 2017 года.
  16. 1 2 Ахо, Альфред В.; Сети, Рави; Ульман, Джеффри Д. Компиляторы — принципы, технологии, инструменты, 2-е изд. : [рус.]. — Вильямс, 2008. — P. 1062—1066. — ISBN 978-5-8459-1349-4.
  17. ISO/IEC 14882:2014 — Information technology — Programming languages — C++ : [англ.]. — ISO, 2014. — P. 105.
  18. Vtable Verification - User's Guide. docs.google.com. Дата обращения: 22 декабря 2017. Архивировано 12 июня 2019 года.
  19. Control Flow Integrity — Clang 5 documentation. releases.llvm.org. Дата обращения: 22 декабря 2017. Архивировано 23 декабря 2017 года.
  20. Muchnick, Steven S. Advanced Compiler Design and Implementation : [англ.]. — Morgan Kaufmann Publishers, 1997. — P. 609—618. — ISBN 1-55860-320-4.
  21. Tyler Bletsch, Xuxian Jiang, Vince W. Freeh, Zhenkai Liang (2011). “Jump-oriented Programming: A New Class of Code-reuse Attack”. Proceedings of the 6th ACM Symposium on Information, Computer and Communications Security [англ.]. ACM: 30—40. DOI:10.1145/1966913.1966919. ISBN 9781450305648. Дата обращения 2017-12-22.
  22. AliAkbar Sadeghi, Salman Niksefat, Maryam Rostamipour (2017). “Pure-Call Oriented Programming (PCOP): chaining the gadgets using call instructions”. Journal of Computer Virology and Hacking Techniques [англ.]: 1—18. DOI:10.1007/s11416-017-0299-1. ISSN 2263-8733. Архивировано из оригинала 2017-12-22. Дата обращения 2017-12-22.
  23. 1 2 RAP: RIP ROP — Reuse Attack Protector. PaX Team. Дата обращения: 22 декабря 2017. Архивировано 20 мая 2020 года.
  24. Control-flow Enforcement Technology Preview (англ.). Intel Developer Zone. Дата обращения: 22 декабря 2017. Архивировано 14 августа 2017 года.
  25. Control Flow Integrity Design Documentation — Clang 5 documentation. releases.llvm.org. Дата обращения: 22 декабря 2017. Архивировано 23 декабря 2017 года.

Литература

  • Muchnick, Steven S. Advanced Compiler Design and Implementation : [англ.]. — Morgan Kaufmann Publishers, 1997. — ISBN 1-55860-320-4.
  • Ахо, Альфред В.; Сети, Рави; Ульман, Джеффри Д. Компиляторы — принципы, технологии, инструменты, 2-е изд. : [рус.]. — Вильямс, 2008. — ISBN 978-5-8459-1349-4.

Ссылки