Статический поиск ошибок повторной блокировки семафора
А. Е. Бородин а1ехеу.ЬогосИп@1А'ргаА\ ги ИСП РАН, 109004, Россия, г. Москва, ул. А. Солженицына, дом 25
Аннотация. В статье описывается алгоритм статического поиска ошибки повторной блокировки семафоров, позволяющий выдавать предупреждения с низким уровнем ложных срабатываний. Поиск ошибок рассмотрен для абстрактной библиотеки, включающей функции блокировки семафора, разблокировки семафора и условной блокировки семафора. Определено множество регулярных языков, моделирующих блокировки и разблокировки при конкретном исполнении программы. Определён домен, аппроксимирующий множество регулярных языков. Алгоритм реализован в терминах анализа потока данных. При анализе элементы домена используются в качестве свойств потока данных. Алгоритм описан для программы с одним семафором и без алиасов. В этом случае каждое выданное предупреждение должно соответствовать ошибке при конкретном выполнении. Алгоритм реализован в системе статического анализа Буасе, разрабатываемой в Институте системного программирования Российской академии наук. Буасе осуществляет анализ алиасов и сопоставление формальных и фактических параметров при вызове функции. Благодаря этому можно применять алгоритм поиска повторных блокировок для программы, содержащей только один семафор, а вся остальная работа будет выполнена системой Буасе. Алгоритм поиска повтороных блокировок реализованный в Буасе может выдавать некоторое количество ложных срабатываний, т. к. Буасе выполняет неконсервативный анализ.
Ключевые слова: статический анализ; семафор; ошибка повтороной блокировки; анализ потока данных; регулярные языки.
1. Введение
Одной из ошибок использования примитивов синхронизации семафоров является повторная блокировка семафора, которая может привести к тупиковой ситуации при выполнении приложения. Конкретное поведение зависит от операционной системы и используемых библиотек. Семафор - это примитив синхронизации, который может находиться в двух состояниях: открытом и закрытом. С помощью функции блокировки семафор из открытого состояния переводится в закрытое состояние. Блокировка семафора, уже находящегося в закрытом состоянии, может вызвать проблемы
в зависимости от операционной системы, типа семафора и настроек. Например, в операционных системах, поддерживающих стандарт POSIX, семафоры могут иметь следующие типы [1]:
PTHREADMUTEXNORMAL — нет контроля повторного захвата тем же потоком;
PTHREAD MUTEX RECURSIVE — повторные захваты тем же потоком допустимы, ведётся счётчик таких захватов;
PTHREAD MUTEX ERRORCHECK — повторные захваты тем же потоком вызывают немедленную ошибку.
Таким образом повторная блокировка семафора может привести к тупиковой ситуации для PTHREADMUTEXNORMAL или к ошибке времени выполнения для PTHREAD MUTEX ERRORCHEC.
В данной работе рассматривается алгоритм поиска ошибок повторной блокировки с помощью статического анализа исходного текста программы. Поиск ошибок будет рассмотрен для абстрактной библиотеки, функции которой показаны на рис. 1.
struct MUTEX;
//переводит семафор в закрытое состояние, void lock(MUTEX*);
//переводит семафор в открытое состояние, void unlock(MUTEX*);
//если семафор находится в открытом состоянии, //переводит семафор в закрытое состояние и в случае успеха возвращает 0. int trylock(MUTEX*);
Рис. 1. Абстрактная библиотека работы с семафорами.
При использовании на конкретной платформе эти функции могут быть легко заменены на соответствующие аналоги.
2. Модель повторной блокировки
Рассмотрим процедуры1 lock и unlock абстрактной библиотеки работы с семафорами. Процедура trylock будет описана ниже.
1 Функции языка Си здесь будут называться процедурами, чтобы избежать
неоднозначностей с математическими функциями.
Процедура lock закрывает семафор, если семафор уже был закрыт, то произойдёт ошибка повторной блокировки. Процедура unlock переводит семафор в открытое состояние. Таким образом семафор может находиться в трёх состояниях: открытом, закрытом и ошибочном. Переходы между состояниями происходят только при вызове процедур lock и unlock. Вызов любой другой библиотечной процедуры не меняет состояния семафора. Начальным состоянием является открытое состояние.
Для программы, содержащей один семафор, рассмотрим конкретное выполнение (трассу) некоторой процедуры.
Обозначим I - множество инструкций процедуры. Обозначим L = {u, 1}. Определим функцию IL: I —> L*, сопоставляющую каждой инструкции последовательность меток из L следующим образом: IL возвращает [1] для инструкции вызова процедуры lock; [и] для инструкции вызова unlock и [] для всех остальных инструкций.
Пусть Т множество конечных и бесконечных трасс (последовательностей элементов I). Для t 6 Т, |t| - обозначает длину трассы, для бесконечных трасс |t| = со. t| 6l (для k-1 < |t|) - к-й элемент трассы t. ti к - префикс трассы, содержащий к элементов.
Введём функцию S: Т —> L*, отображающую трассы в последовательности меток, такую что
SCtj .л) = ILft)
S(ti,.k) = S(ti,.k-i) • IL(tk), где k-1 < |t|. Состоянием шага k трассы t будем называть S(ti..k).
Будем говорить, что трасса содержит нелокальную повторную блокировку, если для некоторой инструкции вызова процедуры входящей в трассу, сегмент трассы, соответствующий исполнению этой инструкции, отображается функцией S на последовательность меток, содержащую подстроку 11. Пусть Т* - множество трасс, не содержащих нелокальные повторные блокировки. Рассмотрим множество префиксов трасс Р £ т*, заканчивающихся в некоторой точке программы. Накапливающей семантикой [2] данной точки будем называть множество (S(t) 11 6 Р}. Для выполнения анализа будем приближать накапливающую семантику ребёр графа потока управления регулярными языками. Определим множество регулярных языков В ={е, с, зс, е}, где е = L*11L*, е = {е}, с = L*l, к = 1L*. Пусть С получено замыканием В относительно операции пересечения множеств, дополненное элементом Т = L* (Т можно рассматривать как пересечение пустого набора множеств). Множество D определяется как замыкание С относительно операции объединения. Тогда (D,c) составляет полную решетку.
При анализе элементы D будут использоваться в качестве свойств потока данных. Будем говорить, что элемент d£D корректен для точки программы, если накапливающая семантика данной точки является подмножеством d. Интуитивно элемент е обозначает множество трасс выполнения программ, содержащих повторную блокировку; элемент с - множество трасс, заканчивающихся блокировкой; элемент зс - множество трасс, начинающихся с блокировки; е - трассы без блокировок и разблокировок, Т - все возможные трассы.
Примеры описываемых множеств последовательностей: ulul £ о; lui £ (зс По); {ulul, s}£(o U s).
Определение. Бинарный оператор х: DxD->D будем называть приближенной конкатенацией, если конкатенация любых элементов di, d2 из D является подмножеством dlxd2.
Определим бинарный оператор DxD->D следующими правилами:
• Пусть с, с' £ С.
• с°0 = 0°С = 0.
• е°с = с° е = с.
• Если с£зс, с'Ф е, то с°с' = зсПс'.
• Если с'£с, то с°с' = сПс.
• Если с£с и с'Етс, то с°с' = е.
• Иначе, если с£е то с°с' = с'°с =е.
• Иначе, с°с' = Т.
• Если d, d'£ D, где d=ciu...ucm, d'=ci'u...ucn', то d°d' = Ufe - сj), где 0<i<m, 0<j<n.
Лемма 1. Оператор ° является приближённой конкатенацией. Доказательство. 1) с • 0 = 0 £ с°0. 0 • С = 0 £ 0°С
2) с • е = с £ с°е. е • с = с £ е°с.
3) Пусть с£зс, тогда с • с' £ зс • с' (т.к. x£y=>x-z£y-z) зс ■ с' = 1L* • с' £ зс. Если также зс • с'£с', то зс • с' £зсПс'. Докажем, что зс • с'£с'.
Если с' = Т, то зс • с' = зс • Т £ L* £ Т. Иначе с'= bj П ...П Ьш Ь^ е.
Если для любого i верно зс • b¡E Ьъ то зс • с'Е зс • b¡ £ b¡, следовательно зс • с' £ bi П...П bn=c'. Осталось доказать, что зс • Ь,£ Ьъ где b, £ ¡ а. 71. с ¡ : зс • зс = IL* • IL* £ IL* = зс;
к • с = 1Ь* • Ь*1 С Ь*1 = с; ж ■ е = 1Ь* • Ь*11Ь* с 1Ь*11Ь* с Ь*11Ь* = е.
4) Если с'£о. то с°с' = аПс доказывается аналогично 3.
5) Если с£с и с'Ел, тогда с • с' £ а • к = Ь*11Ь* = е.
6) Иначе, если с£е, то с • с' £ с' • е £ т • е = Ь*Ь*11Ь* £ Ь*11Ь* = е
7) Иначе, с°с' = Т. По определению Т верно, что для любого с1бО верно с1 £ Т, следовательно с • с' £Т.п
Результат применения оператора ° для элементов множества В приведён в табл. 1, элементы С могут быть представлены как пересечение элементов В, а элементы Б как объединение элементов С.
Табл.1. Операция конкатенации для элементов В.
л a е е
71 л яПа яПе а
a е а е л
е е еПа е е
е л а е е
3. Выполнение анализа
Выполним анализ потока данных, сопоставив рёбрам графа потока управления элементы домена D. Элемент, сопоставленный ребру выхода из процедуры назовём аннотацией. Введём функцию А, возвращающую аннотацию для каждой процедуры. Будем считать, что A(lock) = тсПа. A(unlock) = Т. Определим передаточные функции Р^ D —> D для инструкции i программы:
1. Для инструкции i вызова процедуры р, где А(р) = d2, Pi(di) = di ° d2.
2. Для остальных инструкций Pi(d) = d.
Оператор сбора u:DxD-»D будет объединением множеств.
Для шага к трассы t находим инструкцию i = tl:. конкретная семантика с =
S(ti к), абстрактная семантика d = OUT! 6D. В соответствии с определением
оператора сбора и передаточной функцией, а также леммой 1, с 6 d.
Если анализ потока данных сопоставляет некоторому ребру состояние е, то
при конкретном выполнении программы в данном месте произойдёт
повторная блокировка.
Доказательство: Достаточно доказать, что S(ti..к)^ => S(t] |: i )6d, ]. где к - шаг трассы для инструкции I, d, = P(d,_i). Если i - инструкция вызова процедуры, то передаточная функция определена через оператор для которого утверждение верно согласно лемме 1.
Для остальных инструкций передаточная функция является тождественной функцией, поэтому d1+i = d, и S(ti k) = S(ti,.k+1).
Т.е. если на некотором шаге к состояние равно е, то S(tj к) 6 е = L*11L*, что означает ситуацию с повторной блокировкой. □
Как видно из определения передаточной функции Р и оператора сбора и, если в программе возможна повторная блокировка, то алгоритм не обязательно найдёт её. Контрпример приведён на рис.2. Аннотация для процедуры foo A(foo) = тсПсие. Аннотация для lock - тсПа. Поэтому после повторного вызова lock состояние Р = лПсие • тс Па = 71ПаПси71Па=71Па. Таким образом ошибка повторной блокировки не будет найдена. Причина подобной реализации в том, что неизвестно точно о взаимосвязи глобальной переменной glob и параметра flag. Если флаги взаимно исключают друг друга, то мы получим ложное срабатывание. Так как целью работы было создание алгоритма, выдающего предупреждения с низким уровнем ошибок, то мы предпочитаем не выдавать предупреждения для подобных ситуаций. Альтернативный подход может не использовать приближение е £ т и выдавать предупреждения о повторной блокировке в случае условных блокировок. Но так как в существующих библиотеках часто используются функции, которые в зависимости от параметра вызывают блокировку, либо разблокировку семафора, то такой подход приведёт к ложным срабатываниям для всех мест использования подобных функций. Также можно использовать разрешитель булевых формул для анализа взаимосвязей между переменными программы, но в этом случае возрастает сложность и увеличивается время анализа.
void foo(MUTEX* mutex) {
if(glob) {lock(mutex);}
}
void func(int flag, MUTEX* mutex) { if(flag) {foo(mutex);} lock(mutex);
}
Рис. 2. Потенциальная ошибка.
Если программа содержит несколько семафоров, то алгоритм работает с каждым семафором отдельно.
4. Реализация для trylock
Функция trylock тестирует состояние семафора и если он находится в закрытом состоянии, производит блокировку. Таким образом двойной вызов trylock не приводит к ошибке повторной блокировки.
Если семафор находится в открытом состоянии, то после выполнения trylock, он будет в закрытом состоянии. Если же семафор находится в закрытом
состоянии, то функция trylock ничего не будет делать. Т.е. после вызова trylock семафор будет всегда находится в закрытом состоянии. Формально, если d£D - состояние перед выполнением trylock, а а - аннотация trylock, то если d = L*l, то d • а = L*1 • а = L*l, т.е. а эквивалентно е; если d = L*, то d • а = L* • а = L*l, т.е. а эквивалентно с.
Таким образом для эмуляции поведения trylock, можно принять что A(trylock) = с. Мы говорим об эмуляции, т.к. A(trylock) = с означает, что все трассы проходящие через функцию trylock заканчиваются блокировкой строго внутри этой функции, что некорректно.
5. Реализация
Описанный выше алгоритм был реализован в системе статического анализа Svace, разрабатываемой в ИСП РАН.
Svace осуществляет анализ алиасов и сопоставление формальных и фактических параметров при вызове функции. Благодаря этому можно разрабатывать алгоритм поиска повторных блокировок для программы, содержащей только один семафор, а вся остальная работа будет выполнена средой Svace.
Система Svace выполняет неконсервативный анализ, допуская ложные срабатывания. Поэтому алгоритм поиска повторных блокировок реализованный в Svace может выдавать ложные срабатывания. Подробнее система Svace описана в [3], [4], [5].
Для тестирования алгоритма был создан набор тестов, моделирующий различные варианты использования блокировок. Для тестов алгоритм выдал предупреждения о повторной блокировке для всех ожидаемых ситуаций. Пример теста приведён на рис. 3, ожидается что предупреждение о повторной блокировке будет выдано для строки 12.
1: void mutex_unlock(MUTEX*lock) {
2: unlock(lock);
3:}
4:void lock_section(MUTEX *lock) { 5: mutex_unlock(lock);
6: lock(lock); //svace: not_emitted DOUBLE_LOCK 7:}
8:void test(MUTEX* mut) { 9: lock(mut); 10: if(flag) { 11: lock_section(mut);
12: lock(mut); //svace: emitted DOUBLE_LOCK
13: } 14:}
Рис. 3. Пример теста для проверки алгоритма.
Также был произведён анализ 33 проектов с открытым исходным кодом (включая ядро операционной системы линукс (linux-kernel-3.10.9), binutils-2.20.1, libxml2-2.7.6, nss-3.12.9+ckbi-1.82, pulseaudio-0.9.22). Было выдано одно сообщение о двойной блокировке для pulseaudio-0.9.22, фрагмент кода приведён на рис. 4. Процедура pamutexlock блокирует семафор e->mutex на строке 1068. После чего в цикле вызывается процедура pa_memexport_process_release на строке 1070, которая на строке 1087 вызывает процедуру pa mutex lock и повторно блокирует семафор. В данном случае сообщение является ложным, т. к. семафор был создан с флагом PTHREADMUTEXRECURSIVE, позволяющим повторную блокировку. Анализатор выдал сообщение, т. к. не учитывает способ создания семафора. Таким образом с точки зрения анализатора предупреждение является истинным.
1065: void pa_memexport_free(pa_memexport *е) {
1066: pa_assert(e);
1067:
1068: pa_mutex_lock(e->mutex); 1069: while (e->used_slots)
1070: pa_memexport_process_release(e, (uint32_t) (e->used_slots - e->slots));
1071: pa_mutex_unlock(e->mutex); 1072:
1073: pa_mutex_lock(e->pool->mutex);
1074: PA_LLIST_REMOVE(pa_memexport, e->pool->exports, e);
1075: pa_mutex_unlock(e->pool->mutex);
1076:
1077: pa_mutex_free(e->mutex);
1078: pa_xfree(e); }
1082: int pa_memexport_process_release(pa_memexport *e, uint32_t id) {
1083: pa_memblock *Ь;послать
1084:
1085: pa_assert(e); 110
Труды ИСП РАН, том 26, вып. 3, 2014 г.. 1086:
1087: pa_mutex_lock(e->mutex); 1088:
1089: if(id >= e->n_init) 1090: goto fail;
Рис. 4. Фрагмент кода для выданного сообщения.
6. Заключение
Был разработан и реализован алгоритм поиска двойных блокировок семафоров. Алгоритм разработан таким образом, чтобы выдавать как можно меньше ложных срабатываний. Для реализации использовалась система статического анализа Svace. Результаты тестирования показали, что алгоритм может быть полезен, но выдаётся слишком мало срабатываний. На наш взгляд причина заключается в большом количестве взаимосвязанных условных выражений, которые используются вместе с семафорами. Текущая реализация не выдаёт предупреждения для условных блокировок семафоров. Поэтому в планах по улучшению анализа - добавление в систему Svace разрешителя булевых формул, после чего алгоритм сможет выдавать предупреждения для более сложных ситуаций. При этом не потребуется менять сам алгоритм поиска двойных блокировок, а только инфраструктуру Svace.
Список литературы
[1]. POSIX. 1-2008, Technical Corrigendum 1, XSH/TC1-2008/0464 [121]
[2]. Cousot, P., & Cousot, R. (1992). Abstract interpretation and application to logic programs. The Journal of Logic Programming, 13(2), 103-179.
[3]. B.C. Несов. Автоматическое обнаружение дефектов при помощи межпроцедурного статического анализа исходного кода. Материалы XI Международной конференции «РусКрипто'2009».
[4]. А. Аветисян, А. Белеванцев, А. Бородин, В. Несов. Использование статического анализа для поиска уязвимостей и критических ошибок в исходном коде программ. Труды ИСП РАН, 2011, том 21.
[5]. Иванников, В. П., Белеванцев, А. А., Бородин, А. Е., Игнатьев, В. Н., Журихин, Д. М., Аветисян А.И., Леонов, М. И. Статический анализатор Svace для поиска дефектов в исходном коде программ. Труды Института системного программирования РАН, 2014, 26(1), с. 231-250, doi: 10.15514/ISPRAS-2014-26(1)-7.
Static detection of error of double locking of
mutex
Alexey Borodin <alexey. [email protected] > ISP RAS, 25 Alexander Solzhenitsyn Str., Moscow, 109004, Russian Federation
Abstract. This paper describes algorithm for static search for error of double locking of mutex. The algorithm allows emitting warnings with low level of false positives. We considered finding errors for abstract library containing functions of mutex lock, unlock and conditional locking. We defined a set of regular languages, which models locks and unlocks during concrete execution of a program. We have defined a domain approximated set of regular languages. The algorithm is implemented in terms of data flow analysis. In the analysis elements of the domain are used as the data flow properties. The algorithm is described for a program that has only one mutex and does not contain any aliases. In that case every emitted warning will correspond to a real error which may occur during program execution. The algorithm is implemented in system of static analysis Svace developed in Institute for System Programming of the Russian Academy of Sciences. Svace performs alias analysis and matching of formal and actual function parameters. This makes it possible to apply the algorithm to search for double locking of a program containing only one mutex, and the rest of the work will be executed by Svace. The search algorithm of the double lock implemented in Svace can emit some number of false positives because Svace performs nonconservative analysis.
Keywords: static analysis; mutex; error of double locking; data-flow analysis; regular languages.
References
[1]. POSIX. 1-2008, Technical Corrigendum 1, XSH/TC1-2008/0464 [121]
[2]. Cousot, P., & Cousot, R. (1992). Abstract interpretation and application to logic programs. The Journal of Logic Programming, 13(2), 103-179.
[3]. Nesov V. S. Automatically Finding Bugs in Open Source Programs. Proceedings of the Third International Workshop on Foundations and Techniques for Open Source Software Certification (OpenCert 2009).
[4]. Avetisyan A.I., Belevantsev A.A., Borodin A.E., Nesov V. S. Ispol'zovanie staticheskogo analiza dlya poiska uyazvimostej i kriticheskikh oshibok v iskhodnom kode programm [Using static analysis to find vulnerabilities and critical errors in the source code of programs], Trudy ISP RAN [The Proceedings of ISP RAS], 2011, vol 21 (in Russian).
[5]. Ivannikov V.P., Belevantsev A.A., Borodin A.E., Ignatyev V.N., Zhurikhin D.M., Avetisyan A.I., Leonov M.I. Staticheskij analizator Svace dlya poiska defektov v iskhodnom kode programm [Svace: static analyzer for detecting of defects in program source code], Trudy ISP RAN [The Proceedings of ISP RAS], 2014, vol 26(1), pp. 231-250 (inRussian), doi: 10.15514/ISPRAS-2014-26(l)-7.