Всем привет! Мы представляем Департамент анализа безопасности Дирекции базовой операционной системы «Группы Астра» – производителя российской защищенной операционной системы Astra Linux и ряда других защищенных программных продуктов. В своей работе мы непрерывно автоматизируем процессы, связанные с идентификацией, документированием и исправлением уязвимостей исходного кода, в том числе, с использованием новейших достижений в области машинного обучения и искусственного интеллекта. В этой статье мы расскажем о результатах нашего исследования в области практического применения больших языковых моделей для помощи специалистам в обработке данных от статических анализаторов.
1. Введение
Статический анализ широко используется в разработке программного обеспечения для автоматизации выявления уязвимостей. Однако до 80% срабатываний, выдаваемых статическими анализаторами являются ложными, что не позволяет полностью полагаться на статический анализ в отрасли и таким образом требует трудоемкой ручной верификации срабатываний статических анализаторов. Эта проблема представляется особенно острой при разработке такого продукта, как защищенная операционная система Astra Linux, содержащего множество пакетов и миллионы строк кода, подлежащих анализу, и необходимости выпуска частых и своевременных обновлений.
В существующих научных исследованиях предлагается выполнять верификацию срабатываний статических анализаторов с использованием методов машинного обучения, что позволяет значительно снизить стоимость верификации срабатываний, а именно, больших языковых моделей (Large Language Models, LLMs) [1], [2], [3], равно как и методов машинного обучения, не связанных с LLM [4]. Эти и другие подобные исследования, показывающие многообещающие результаты, как правило, проводятся на эталонных наборах данных, а работ, основанных на данных из реальной практики, не так много. Мы решили провести аналогичное исследование на данных из реальной практики, а именно, пакетах операционной системы Astra Linux, и выяснить, применим ли метод, объединяющий статический анализ и большие языковые модели в условиях реального мира.
Важно отметить, что условия реального мира, с которыми мы столкнулись в процессе работы, заметно отличаются от тех условий, которые явно или неявно подразумеваются в большинстве академических статей. А именно, можно выделить следующие ограничения:
1) Запрет на передачу данных вовне внутренней компьютерной сети организации.
2) Относительно скромное аппаратное обеспечение, доступное на месте.
3) Использование готовых, хорошо поддерживаемых, «коробочных» решений, требующих минимальных усилий на настройку.
Ограничение 1 обусловлено требованиями безопасности и конфиденциальности данных. Конкретно в нашем случае это означает, что нам нельзя пользоваться открыто доступными большими языковыми моделями, такими как HuggingFace или ChatGPT, и даже разворачивать наши собственные LLM-модели на «облачных» сервисах, таких как AWS или Google Cloud Platform. Таким образом, в нашем случае неприменимы подходы Ли и др. [2] или Мохаджер и др. [3], выстроившие свои решения на основе сервиса ChatGPT.
Ограничение 2 обусловлено естественным желанием исключить чрезмерные бюджетные траты. Мы проводили эксперименты, используя вычислительные мощности единственной «скромной» видеокарты потребительского класса, в то время как у других исследователей в распоряжении было либо значительно более дорогостоящее оборудование [1], либо возможность использовать облачные вычисления [2], [3].
Ограничение 3 обусловлено тем, что зачастую предприятия и организации не располагают собственными возможностями, требующимися для разработки и/или поддержки внутренних полноценных инструментов машинного обучения производственного уровня.
Отметим, что Ограничения 1–3 довольно широко распространены в отрасли разработки программного обеспечения. Таким образом, первый вопрос настоящей статьи следующий:
Возможно ли использовать модели машинного обучения (ML-модели) для верификации срабатываний статических анализаторов в условиях практического применения в реальном мире с учетом Ограничений 1–3 с удовлетворительными результатами?
Естественно, в том, что касается отнесения результатов к «удовлетворительным» имеется определенное пространство для спора. Для целей данной работы мы используем очень мягкое определение «удовлетворительного», полагая, что процедура является удовлетворительной, если она превосходит полностью случайную процедуру.
Представляется разумным полагать, что стопроцентную точность в верификация срабатываний можно достичь, если срабатывания будут проверяться вручную экспертами в безопасности программного обеспечения высокого класса, что весьма затратно. Противоположная крайность, т.е. когда мы полностью полагаемся на машинное обучение, может дать недостаточно точные результаты, что, несмотря на свою дешевизну, может оказаться неприемлемым с точки зрения применимости для нужд организации. Наилучшим решением представляется комбинация ручной верификации и верификации, основанной на машинном обучении, что может дать разумные компромисс между стоимостью и качеством. Таким образом, возникает вопрос, существует ли способ разделить обработку срабатываний между ручной обработкой и обработкой методами машинного обучения и существует ли наилучшая стратегия подобного разделения. В качестве естественного компромисса сокращения доли ручного труда без потери общего качества представляется частичная повторная ручная верификация. Мы задаемся вопросом, есть ли эффективная стратегия для выбора срабатываний для ручной верификации.
Таким образом, второй вопрос настоящий статьи следующий:
Существует ли эффективная стратегия выборочной повторной ручной верификации результатов, полученных с помощью ML-модели?
2. Подход
2.1. Конвейер выявления уязвимостей
Общая схема рассматриваемого конвейера выявления уязвимостей следующая (см. Рис. 1). На первом шаге исходный код на C или C++ сканируется одним или несколькими статическими анализаторами. Результатом статического анализа выступает множество срабатываний, каждое из которых указывает на определенную строку в коде, подозрительную с точки зрения анализатора. Далее каждое срабатывание подается в ML-модель, которая отвечает на вопрос, действительно ли тот код, на который указывает срабатывание, содержит уязвимость. Ответ ML-модели представляет собой числовое значение от 0 до 1, отражающее правдоподобие того, что код действительно содержит уязвимость. Некоторые результаты далее могут быть направлены на повторную ручную верификацию.
2.2. Модель и тюнинг модели
Несмотря на то что, формально говоря, мы не ограничены выбором конкретной модели машинного обучения, на практике важным требованием оказалась способность модели принимать большие фрагменты кода. Это требование связано с тем, что функции, взятые из реального исходного кода операционной системы Astra Linux, могут содержать 100 и более строк, тогда как функции из эталонных наборов данных, на которых по большей части вычисляются метрики качества в научных публикациях, редко когда содержат более десятка строк. Это ограничение не позволяет нам применять LLM-модели для наших практических задач непосредственно, как это делается в [1], [2], [3] и других публикациях, ввиду ограниченности контекста ввода у LLM-моделей. Поэтому изначально мы рассматривали применение ML-моделей, не связанных с LLM, такие какDevign [5], Codebert [6] и CodeT5 [7]. Тем не менее, для наших целей оказалась наиболее предпочтительной модель DefectHunter [8], в которой LLM-модель используется, но лишь как источник признаков для другой ML-модели, а не как основной классификатор. Такая архитектура позволяет этой модели принимать большие фрагменты кода, а также запускаться, после некоторой настройки, на достаточно скромном аппаратном обеспечении.
Мы используем следующие параметры для обучения модели DefectHunter:
1) Используем L2-регуляризацию для предотвращения переобучения модели;
2) Используем метод Class Weights для предотвращения проблем, связанных с дисбалансом в наборе данных (в нашем комбинированном наборе данных намного больше отрицательных значений, чем положительных);
3) Устанавливаем значение параметра “precision policy” равным mixed_float16, чтобы противостоять ограничениям по VRAM;
4) В качестве фрагмента кода для передачи модели машинного обучения мы берем всю функцию, содержащую строку кода, отмеченную статическим анализом как уязвимая;
5) Мы проводим тюнинг гиперпараметров для следующих значений: скорость обучения (learning rate), доля отсева (dropout rate), размер партии (batch size), количество эпох (number of epochs) и фактор регуляризации L2 (L2 regularization factor). Мы проводим тюнинг скорости обучения с экспоненциальным затуханием (exponential learning rate decay), используя параметры: начальная скорость обучения (initial learning rate) – 10-5, скорость затухания (decay rate) – 0.9 и число шагов затухания (decay steps) – 1000. Мы обнаружили, что другие гиперпараметры имеют следующие оптимальные значения при наших условиях: доля отсева – 0.3, размер партии – 64, количество эпох – 50 и фактор регуляризации L2 – 0.03;
6) Пороговое значение для модели классификации мы полагаем равным 0.5.
Для обучения модели использовалась одна видеокарта Nvidia GeForce RTX 4070 Ti с 24 гигабайтами VRAM.
2.3. Наборы данных для обучения и тестирования
Мы обучаем модель DefectHunter на комбинации из следующих наборов данных: FFmpeg, QEMU, CWE-476, CWE-754 и CWE-758 [9], суммарно содержащих 27000 фрагментов кода, наряду с нашим собственным набором данных, размеченных нашими сотрудниками и содержащей 6900 фрагментов кода. Разделение данных следующее: 60% для обучающего набора, 20% для валидационного набора и 20% для тестового набора.
В качестве тестового набора данных использовались данные верификации срабатываний статических анализаторов срабатываний для 30 распространенных пакетов Linux, таких как keepalived, postgresql-11, php8.1, apt, openssl. Общее число срабатываний в тестовом наборе данных составило 2012, из которых 1094 было размечено нашими специалистами как содержащие уязвимость.
2.4. Моделирование повторной ручной верификации
Как было отмечено во введении, одной из наших целей является исследование качества работы частичной повторной ручной верификации результатов ML-модели. Первый вопрос – это какие результаты должны быть выбраны для повторной ручной верификации для достижения наибольшей общей точности. Если сделать интуитивное предположение, что чем ближе выходная вероятность модели к 0 или 1, тем меньше составляет шанс того, что модель выдаст ошибочный результат, естественным будет выбрать для повторной ручной верификации те результаты, вероятности которых лежат ближе к значению 0.5, например, те, которые лежат в промежутке c ≤ p ≤ 1 – c, где c – наперед заданное действительное число между 0 и 0.5. Альтернативой этому подходу будет полностью случайный выбор результатов, с которым мы и будем сравнивать предложенную нами процедуру.
Таким образом, мы моделируем процесс повторной ручной верификации, выбрав точку отсечения c, 0 ≤ c ≤ 0.5, и подставляя истинные результаты вместо результатов ML-модели с вероятностями, удовлетворяющими c ≤ p ≤ 1 – c. Далее это позволяет нам вычислить процентную долю результатов, проходящих повторную ручную верификацию и метрику качества работы всей процедуры. Затем мы вычисляем ту же самую метрику качества для полностью случайного выбора при одинаковом числе результатов, направляемых на повторную ручную верификацию, и сравниваем одно с другим.
3. Результаты
Обучение модели на нашем наборе данных с указанными параметрами занимает 24 часа. В этом разделе мы приведем полученные метрики качества работы модели.
Отметим, что вследствие природы нашего конвейера выявления уязвимостей (см. раздел 2.1), никакие истинно-отрицательные или ложно-отрицательные значения не достигают ML-модели, так как срабатывания статических анализаторов в априори “положительные” (как истинно-положительные, так и ложно-положительные), а в модель не передается никаких данных, кроме срабатываний статических анализаторов. В таких условиях метрика полнота (Recall) всегда равна 1 и единственная показательная метрика – это точность (Precision), при этом метрика Accuracy всегда равна метрике Precision. Дополнение тестового набора данных строками кода, не содержащихся в срабатываниях статического анализатора – предмет будущих исследований.
Таблица 1.
|
Тип срабатывания |
Количество |
Точность |
|
OVERFLOW_UNDER_CHECK NO_UNLOCK.STRICT TAINTED_ARRAY_INDEX UNCHECKED_FUNC_RES.STAT NULL_AFTER_DEREF DEREF_AFTER_NULL.EX.COND PROC_USE.VULNERABLE.GETENV BUFFER_UNDERFLOW DOUBLE_LOCK INTEGER_OVERFLOW WRONG_ARGUMENTS_ORDER NONTERMINATED_STRING.STYLE INVARIANT_RESULT.OP_ZERO BUFFER_OVERLAP NONTERMINATED_STRING DANGLING_POINTER.STAT FREE_NONHEAP_MEMORY |
11 28 12 11 14 10 31 15 112 12 19 63 35 7 23 9 11 |
90.91% 82.14% 75.00% 72.73% 71.43% 70.00% 67.74% 66.67% 61.61% 58.33% 57.89% 57.14% 57.14% 57.14% 56.52% 55.56% 54.55% |
|
Всего: |
423 |
63.59% |
Таблица 1 показывает точность для ряда типов срабатывания статического анализатора Svace; в таблицу были включены только те типы, срабатываний, значение точности для которых составило 50% или лучше. Сопоставляя наши результаты с результатами из таблицы III [1], мы обнаружили лишь два совпадающих типа, а именно DEREF_OF_NULL и BAD_COPY_PASTE, при этом наша модель дает для указанных типов точность 39.28% и 40.00%, соответственно, что отличается от значений 68% и 92% в [1], Таблица III (столбец «LLM Precision»). Оказалось, что из 2012 записей в нашем наборе данных только 423 принадлежат к классам срабатываний, точность для которых превышает случайное угадывание.
Естественно, наш метод допускает улучшения, в первую очередь, можно применять более точечных подход при формировании фрагмента кода, подаваемого на вход ML-модели. В настоящее время мы отправляем целую функцию, содержащую срабатывание, что, конечно же, может приводить к большому количеству ложноположительных результатов из-за возможного наличия других ошибок в данном фрагменте кода; отметим, что подход Игнатьева [1] отличается (они берут несколько строк кода, окружающих срабатывание), хоть и также не стойкий к подобного рода ситуациям. Другое очевидное возможное направление улучшений — расширение входной информации, такой как определения функций и иерархия классов.
Так что, отвечая на первый поставленный во введении вопрос, мы приходим к выводу, что использовать модели машинного обучения для верификации срабатываний статического анализатора в реальных условиях возможно, но удовлетворительные результаты показывают только некоторые типы срабатываний.
Прежде чем ответить на второй поставленный во введении вопрос, проверим, насколько справедливо наше интуитивное предположение из раздела 2.4. Таблица 2 и соответствующий ей график на Рис. 2 показывают точность нашего метода при разных значениях вероятностей, выдаваемых моделью: из нашего предположения должно следовать, что значение точности будет выше у диапазонов вероятностей, которые ближе расположены к 0 или 1. Мы используем данные для типов срабатываний из таблицы 1.
Таблица 2.
|
Диапазон вероятностей |
Количество |
Точность(Precision) |
|
0.0000000 .. 0.0000425 0.0000425 .. 0.0000430 0.0000430 .. 0.0000440 0.0000440 .. 0.0000600 0.0000600 .. 0.0050000 0.0050000 .. 0.1000000 0.1000000 .. 0.3000000 0.3000000 .. 0.5000000 0.5000000 .. 0.7000000 0.7000000 .. 0.9000000 0.9000000 .. 0.9991000 0.9991000 .. 0.9993500 0.9993500 .. 0.9993700 0.9993700 .. 1.0000000 |
23 38 44 45 36 13 6 6 9 37 38 45 39 44 |
56.52% 57.89% 68.18% 73.33% 69.44% 61.54% 66.67% 33.33% 44.44% 64.86% 47.36% 64.44% 76.92% 61.36% |
|
Всего: |
423 |
63.59% |
Таблица 2 не более чем частично поддерживает наше интуитивное предположение из раздела 2.4 о том, что модель работает точнее всего для выходных вероятностей, значения которых ближе к 0 or 1: хотя точность на концах отрезка [0, 1] действительно выше, чем в середине, строгой зависимости качества работы модели от близости к 0 или 1 не наблюдается.
Таблица 3 и соответствующий ей график на Рис. 3 показывают точность моделирования процедуры повторной ручной верификации, описанной в разделе 2.4, (Колонка «Точность») в сравнении с точностью полностью случайной повторной верификации с тем же количеством повторно верифицируемых результатов ML-модели (колонка «Точность (Случайное)»). Для обеспечения устойчивости результатов в таблице, процедура полностью случайного выбора для повторной верификации проводилась 1000 раз, затем результаты усреднялись. Колонка «Прирост точность» отражает процентное значение улучшения, которое дает использование детерминистической повторной ручной верификации по сравнению со случайной повторной верификацией (это отношение значений в колонках «Точность (Случайное)» и «Точность», умноженное на 100%).
Таблица 3.
|
Проверено вручную |
Проверено вручную % |
Порог |
Точность |
Точность (Случайное) |
Прирост точности |
|
0 22 43 63 84 106 129 148 171 191 212 233 253 273 295 318 337 362 384 400 423 |
0.00% 5.20% 10.17% 14.89% 19.86% 25.06% 30.50% 34.99% 40.43% 45.15% 50.12% 55.08% 59.81% 64.54% 69.74% 75.18% 79.67% 85.58% 90.78% 94.56% 100.00% |
0.5000000 0.2500000 0.1900000 0.0800000 0.0100000 0.0014000 0.0007500 0.0006700 0.0006480 0.0006360 0.0006284 0.0006250 0.0002000 0.0000600 0.0000450 0.0000440 0.0000435 0.0000430 0.0000426 0.0000425 0.0000420 |
63.59% 65.96% 67.61% 69.98% 72.34% 73.76% 76.83% 78.72% 79.91% 80.85% 82.27% 84.40% 86.05% 87.71% 89.60% 90.54% 91.72% 93.85% 95.98% 97.64% 100.00% |
63.59% 65.48% 67.28% 68.99% 70.81% 72.70% 74.71% 76.32% 78.32% 80.04% 81.85% 83.64% 85.35% 87.07% 88.97% 90.95% 92.59% 94.76% 96.66% 98.03% 100.00% |
0.00% +0.73% +0.50% +1.43% +2.16% +1.46% +2.85% +3.15% +2.03% +1.02% +0.52% +0.90% +0.82% +0.73% +0.71% −0.45% −0.93% −0.95% −0.71% −0.41% 0.00% |
Таким образом, отвечая на второй поставленный во введении вопрос, мы предложили стратегию выборочной повторной ручной верификации результатов модели машинного обучения, которая, в большинстве практических случаев, показывает большую точность, чем повторной верификация на случайно выбранных данных.
4. Список литературы
[1]. Ignatyev V.N., Shimchik N.V., Panov D.D., Mitrofanov A.A. Large language models in source code static analysis. 2024 Ivannikov Memorial Workshop (IVMEM), Velikiy Novgorod, Russian Federation, 2024, pp. 28-35, doi: 10.1109/IVMEM63006.2024.10659715.
[2]. Li H., Hao Y., Zhai Y., and Qian Z.. 2024. Enhancing Static Analysis for Practical Bug Detection: An LLM-Integrated Approach. Proc. ACM Program. Lang. 8, OOPSLA1, Article 111 (April 2024), 26 pages.
[3]. Mohajer M.M., Aleithan R., Harzevili N.S., Wei M., Belle A.B., Pham H.V., Wang. S. Skipanalyzer: An embodied agent for code analysis with large language models, arXiv preprint arXiv:2310.18532, 2023.
[4]. Tsiazhkorob U.V., Ignatyev V.N. Classification of Static Analyzer Warnings using Machine Learning Methods, 2024 Ivannikov Memorial Workshop (IVMEM), Velikiy Novgorod, Russian Federation, 2024, pp. 69-74, doi: 10.1109/IVMEM63006.2024.10659704.
[5]. Zhou Y., Liu S., Siow J., Du X., Liu Y. Devign: Effective vulnerability identification by learning comprehensive program semantics via graph neural networks. Advances in neural information processing systems, vol. 32, 2019. https://proceedings.neurips.cc/paper_files/paper/2019/file/49265d2447bc3bbfe9e76306ce40a31f-Paper.pdf.
[6]. Feng Z., Guo D., Tang D., Duan N., Feng X., Gong M., Shou L., Qin B., Liu T., Jiang D., Zhou M. Codebert: A pre-trained model for programming and natural languages. ArXiv, abs/2002.08155, 2020 (online). https://arxiv.org/abs/2002.08155.
[7]. Wang Y., Wang W., Joty S., Hoi S.C.H. CodeT5: Identifier-aware unified pre-trained encoder-decoder models for code understanding and generation. In Proceedings of the 2021 Conference on Empirical Methods in Natural Language Processing, 2021, pp. 8696–8708.
[8]. Wang J., Huang Z., Liu H., Yang N., Xiao Y. DefectHunter: A novel LLM-driven boosted-conformer-based code vulnerability detection mechanism. ArXiv, abs/2309.15324, 2023 (online). https://arxiv.org/abs/2309.15324.
[9]. Nist software assurance reference dataset, https://samate.nist.gov/SARD/.
ссылка на оригинал статьи https://habr.com/ru/articles/1028622/