Доклад Дениса Ефремова (ИСП РАН) о разработке корректного программного обеспечения с применением одного из видов статического анализа кода на PHDays VII.
In on Perfect, Hack-Proof Code (wired) • Kaspersky Launches ‘Unhackable’ OS (guidingtech) • Unhackable kernel could keep all computers safe from cyberattack (newscientist) • Is This Security-Focused Linux Kernel Really UnHackable? (thehackernews) • Hack-resilient • Error-free code • Yale develops world's first hacker-resistant operating system (ibtimes) Немного заголовков
A1) • Common Criteria (EAL7) • DO-178C/DO-333 "Formal Methods Supplement to DO-178C and DO-278A” • IEC 61508 (SIL4) • ФСТЭК России ГОСТ Р ИСО/МЭК 15408 «Требованиях безопасности информации к операционным системам» профили защиты операционных систем общего назначения (типа «А») Стандарты
нему требованиям; • Дедуктивная верификация – представление корректности программы как набора математических утверждений, называемых условиями верификации, выполнение которых проверяется автоматическими или интерактивными доказателями теорем; • Спецификация - набор требований и параметров, которым удовлетворяет некоторый объект (представлена в виде мат. модели, тестовых наборов, формальной спецификации) Верификация
программном обеспечении, использующемся при верификации, не произошло ошибок • Компьютер функционирует таким образом, как мы думаем об этом (rowhammer) • Нижележащий слой ПО (например, ОС, прошивка сетевой карты, микрокод процессора) функционирует в рамках нашего представления о том, что он должен делать и что не должен делать (и ещё не содержит ошибок) • Пользователь компьютера, если он есть, специально не «пакостит» • Выполнены предположения о входных данных программы, о начальном состоянии • … На что опираться vs. Что вы будете с этого иметь (1)
с требованиями, к нему предъявляемыми, на всех входных данных, начальных состояниях, при любом поведении окружения * ** • * В предположении что все предположения выполнены • ** И не осталось предположений, которых мы не занесли в списочек На что опираться vs. Что вы будете с этого иметь (2)
− 1 ⇒ ≤ [ + 1] • Функция возвращает всегда положительное значение \ > 0 • Функция может менять порядок элементов в массиве, но не его содержимое 0_0 Примеры требований по функциональности
− 1 ⇒ ≤ [ + 1] • Функция возвращает всегда положительное значение \ > 0 • Функция может менять порядок элементов в массиве, но не его содержимое 0_0 • Если в дереве присутствует искомый элемент, то функциях его обязательно найдёт O_O Примеры требований по функциональности
− 1 ⇒ ≤ [ + 1] • Функция возвращает всегда положительное значение \ > 0 • Функция может менять порядок элементов в массиве, но не его содержимое 0_0 • Если в дереве присутствует искомый элемент, то функциях его обязательно найдёт O_O • Программа не держит в памяти секретные данные дольше, чем это требуется для их обработки @_@ • … Примеры требований по функциональности
функции на языке Си по её коду? (1) • Она существует и написана на языке Си; • Это чистая функция; • Она вычисляет среднее между двумя целыми числами; • При определённых условиях возможно целочисленное переполнение.
функции на языке Си по её коду? (2) • Возможно ли целочисленное переполнение в том контексте, где функция вызывается? • Считать ли возможное целочисленное переполнение ошибкой?
функции на языке Си по её коду? (3) • Контекст: функция двоичного поиска; • Индексы l и h неотрицательны, l не превосходит h; • Возможна ошибка выхода за границу массива при целочисленном переполнении.
понятие ошибки (целочисленное переполнение): _: → {⊤, ⊥} _ ≡ _ ≤ ≤ _ • Формализовать код программы: функция , которая возвращает результат (, ) в соответствии со своим программным кодом если завершается и завершается без ошибки, иначе возвращается специальное значение
функции корректен? (2) • Формализовать понятие ошибки (целочисленное переполнение): _: → {⊤, ⊥} _ ≡ _ ≤ ≤ _ • Формализовать код программы: функция , которая возвращает результат (, ) в соответствии со своим программным кодом если завершается и завершается без ошибки, иначе возвращается специальное значение • Доказать полную корректность: ∀, , ⇒ , ≠ && , , ,
функции корректен? (3) function to_int bint : int function of_int int : bint predicate in_bounds (n:int) = -2147483648 <= n && n <= 2147483647 constant a, b, o1, o2: bint axiom H0: a >= of_int 0 && b >= of_int 0 && b >= a
функции корректен? (3) function to_int bint : int function of_int int : bint predicate in_bounds (n:int) = -2147483648 <= n && n <= 2147483647 constant a, b, o1, o2: bint axiom H0: a >= of_int 0 && b >= of_int 0 && b >= a axiom H1: to_int o1 = 2 axiom H2: to_int o2 = (to_int a + to_int b)
функции корректен? (3) function to_int bint : int function of_int int : bint predicate in_bounds (n:int) = -2147483648 <= n && n <= 2147483647 constant a, b, o1, o2: bint axiom H0: a >= of_int 0 && b >= of_int 0 && b >= a axiom H1: to_int o1 = 2 axiom H2: to_int o2 = (to_int a + to_int b) goal avr_safety: in_bounds 2 ->
функции корректен? (3) function to_int bint : int function of_int int : bint predicate in_bounds (n:int) = -2147483648 <= n && n <= 2147483647 constant a, b, o1, o2: bint axiom H0: a >= of_int 0 && b >= of_int 0 && b >= a axiom H1: to_int o1 = 2 axiom H2: to_int o2 = (to_int a + to_int b) goal avr_safety: in_bounds 2 -> in_bounds(to_int a + to_int b) ->
функции корректен? (3) function to_int bint : int function of_int int : bint predicate in_bounds (n:int) = -2147483648 <= n && n <= 2147483647 constant a, b, o1, o2: bint axiom H0: a >= of_int 0 && b >= of_int 0 && b >= a axiom H1: to_int o1 = 2 axiom H2: to_int o2 = (to_int a + to_int b) goal avr_safety: in_bounds 2 -> in_bounds(to_int a + to_int b) -> not to_int o1 = 0 ->
функции корректен? (3) function to_int bint : int function of_int int : bint predicate in_bounds (n:int) = -2147483648 <= n && n <= 2147483647 constant a, b, o1, o2: bint axiom H0: a >= of_int 0 && b >= of_int 0 && b >= a axiom H1: to_int o1 = 2 axiom H2: to_int o2 = (to_int a + to_int b) goal avr_safety: in_bounds 2 -> in_bounds(to_int a + to_int b) -> not to_int o1 = 0 -> in_bounds(div (to_int o2) (to_int o1))
CIL with annotations С program with ACSL annotations Jessie program (with annotations built-in) Jessie translator Why3 support Jessie2 CIL visitors (rewriters) Frama-C Jessie plugin
чего не может? • Формальные спецификации • Можно ли разработать формальные спецификации до стадии написания кода? • Что в них должно быть отображено? • Что инструмент (его модели и теоретическая основа) позволяет в них отобразить? • Насколько полными/точными/непротиворечивыми должны быть спецификации? • Код и спецификации • Можно ли дважды ошибиться и при этом доказать, что всё корректно? Где может встречаться ошибка?
верификации • Памяти, целочисленной арифметики, битовой арифметики… • Чем сложнее модель, тем детальнее она отражает действительность • Чем сложнее модель, тем более сложными становятся формулы условий верификации • Чем сложнее формулы, тем хуже на них работают автоматические доказатели логических формул • Аналогия QEMU⟺BOCHS Ошибки, которые «ловятся» в коде (1)
сдвиг типизированного указателя • Выход за границу массива • Целочисленное переполнение • Переполнение при операциях с плавающей запятой • Бесконечные циклы • … Ошибки, которые «ловятся» в коде (2)
0; long abs(int a) { return 4; } •Как мы пишем функциональные требования? unsigned abs(int a) return a >= 0 ? a :-((long)a); ensures \result == a || \result == -a; Ошибки, специфичные для спецификаций (полнота) (1)
0; long abs(int a) { return 4; } •Как мы пишем функциональные требования? unsigned abs(int a) return a >= 0 ? a :-((long)a); ensures \result == a || \result == -a; ensures \result == -a <==> a < 0; Ошибки, специфичные для спецификаций (полнота) (1)
== 2 && \valid(a+(0..n-1)); ensures \forall integer i, j; 0 <= i < j < n ==> a[i] <= a[j]; // отсортированность void sort(size_t n, int a[n]) { a[0] = 1; a[1] = 2; } • Как правильно их выразить? ... //сохранение всех элементов ensures \forall int *i; a <= i < a + n ==> Сount{Pre}(a, n, *i) == Сount{Post}(a, n, *i); void sort(size_t n,int a[n]){if(a[0]>a[1])swap(a,0,1);} Ошибки, специфичные для спецификаций (полнота) (2)
a == 2 • Изо лжи следует всё, что угодно requires 0 == 1; ensures \result == 0 && \result == 1 && \result == 2; int main(void) { int a = 1; return a / 0; } • Мертвый код void test(int a){ if (a > 0) if (a < 0) a/0; } Ошибки, специфичные для спецификаций (противоречия) (1)
Applications (VESSEDIA) • STANCE project • Programme Inter Carnot Fraunhofer from BMBF and ANR • Начало проекта - 2009 Пример ошибки в реальном проекте (1)
v); axiom CountSectionEmpty: \forall int *a, v, integer m, n; n <= m ==> Count(a, m, n, v) == 0; axiom CountSectionHit: \forall int *a, v, integer n, m; a[n] == v ==> Count(a,m,n+1,v)==Count(a,m,n,v)+1; Пример ошибки в реальном проекте (2)
v); axiom CountSectionEmpty: \forall int *a, v, integer m, n; n <= m ==> Count(a, m, n, v) == 0; axiom CountSectionHit: \forall int *a, v, integer n, m; a[n] == v ==> Count(a,m,n+1,v)==Count(a,m,n,v)+1; int a = 5; assert Count(&a+1,0,-1,5) == 0 && Count(&a+1,0,0,5) == 0; Пример ошибки в реальном проекте (2)
v); axiom CountSectionEmpty: \forall int *a, v, integer m, n; n <= m ==> Count(a, m, n, v) == 0; axiom CountSectionHit: \forall int *a, v, integer n, m; a[n] == v ==> Count(a,m,n+1,v)==Count(a,m,n,v)+1; int a = 5; assert Count(&a+1,0,-1,5) == 0 && Count(&a+1,0,0,5) == 0; assert Count(&a+1,0,0,5) == Count(&a + 1,0,-1,5)+1; Пример ошибки в реальном проекте (2)
v); axiom CountSectionEmpty: \forall int *a, v, integer m, n; n <= m ==> Count(a, m, n, v) == 0; axiom CountSectionHit: \forall int *a, v, integer n, m; a[n] == v ==> Count(a,m,n+1,v)==Count(a,m,n,v)+1; int a = 5; assert Count(&a+1,0,-1,5) == 0 && Count(&a+1,0,0,5) == 0; assert Count(&a+1,0,0,5) == Count(&a + 1,0,-1,5)+1; assert 0 == 1; Пример ошибки в реальном проекте (2)
Каждой строчке кода соответствует ~3-5 строчек спецификаций • Инструменты поддерживают не все конструкции языков программирования • Goto назад по коду • Switch с “проваливающимися” case • Цикломатическая сложность функций (< 15) • … • Применяется для проектов небольшого размера • обычно не более 10 тыс. строк Ограничения по применению дедуктивной верификации
как плюсы, так и минусы • Сложность применения • Что мы доказываем (безопасность(safety), функциональные требования) • Предположения, исходя из которых проводится верификация • Формальная верификация не гарантирует отсутствие всех ошибок • Формальная верификация не является заменой тестированию • При дедуктивной верификации существенна роль человека Резюме
свободной лицензией • http://linuxtesting.ru/astraver • Руководства и введение в инструменты на русском • http://astraver.linuxtesting.org/ • Спецификации для библиотечных функций ядра Linux • https://github.com/evdenis/verker/ Дополнительная информация