Анализатор PVS-Studio всегда умел искать множество различных дефектов безопасности (потенциальных уязвимостей) в коде программ. Однако, исторически сложилось, что мы позиционировали PVS-Studio как инструмент для поиска ошибок. Сейчас наблюдается мода на поиск в коде именно уязвимостей, а не ошибок, хотя на самом деле это одно и тоже. Что же, значит пришло время провести ребрендинг нашего статического анализатора PVS-Studio. Начнём мы с Common Weakness Enumeration (CWE). В этой статье приводится таблица, сопоставляющая диагностические предупреждения PVS-Studio с классификатором. Таблица будет постепенно пополняться и изменяться, но уже сейчас с её помощью мы сможем писать статьи, посвященные обнаруженным дефектам безопасности в том или ином проекте. Думаем, это привлечёт к нашему инструменту больше внимания специалистов, занимающихся безопасностью программного обеспечения.
Common Weakness Enumeration (CWE)
Для начала давайте разберемся с терминологией. Для этого я процитирую фрагмент FAQ с сайта cwe.mitre.org.
A1. Что такое CWE? Что такое «дефект безопасности ПО»?
Общий перечень дефектов безопасности ПО (Common Weakness Enumeration, CWE) предназначен для разработчиков и специалистов по обеспечению безопасности ПО. Он представляет собой официальный реестр или словарь общих дефектов безопасности, которые могут проявиться в архитектуре, проектировании, коде или реализации ПО, и могут быть использованы злоумышленниками для получения несанкционированного доступа к системе. Данный перечень был разработан в качестве универсального формального языка для описания дефектов безопасности ПО, а также в качестве стандарта для измерения эффективности инструментов, выявляющих такие дефекты, и для распознавания, устранения и предотвращения этих дефектов.
Дефекты безопасности ПО — это дефекты, сбои, ошибки, уязвимости и прочие проблемы реализации, кода, проектирования или архитектуры ПО, которые могут сделать системы и сети уязвимыми к атакам злоумышленников, если их вовремя не исправить. К таким проблемам относятся: переполнения буферов, ошибки форматной строки и т.д.; проблемы структуры и оценки валидности данных; манипуляции со специальными элементами; ошибки каналов и путей; проблемы с обработчиками; ошибки пользовательского интерфейса; обход каталога и проблемы с распознаванием эквивалентности путей; ошибки аутентификации; ошибки управления ресурсами; недостаточный уровень проверки данных; проблемы оценки входящих данных и внедрение кода; проблемы предсказуемости и недостаточная «случайность» случайных чисел.
A2. В чем разница между уязвимостью и дефектом безопасности ПО?
Дефекты безопасности — это ошибки, которые могут спровоцировать уязвимости. Уязвимости, например, описанные в перечне общих уязвимостей и подверженностей воздействиям (Common Vulnerabilities and Exposures, CVE), — это ошибки программы, которые могут быть непосредственно использованы злоумышленником для получения доступа к системе или сети.
Соответствие между предупреждениями PVS-Studio и CWE
Нам хочется, чтобы анализатор PVS-Studio начали воспринимать не только как инструмент поиска ошибок, но и как инструмент, который помогает сократить количество уязвимостей в коде. Конечно, не каждый дефект безопасности, перечисленный в CWE, является уязвимостью. Можно ли использовать тот или иной дефект для атаки, зависит от множества факторов. Поэтому в дальнейшем мы будем писать, что анализатор PVS-Studio выявляет не уязвимости, а потенциальные уязвимости. Это будет более правильно.
Итак, представляю первый вариант таблицы соответствий. Таблица будет пополняться и уточняться, но даже первый вариант уже позволяет составить общее впечатление о возможностях анализатора.
CWE | PVS-Studio | CWE Description |
CWE-14 | V597 | Compiler Removal of Code to Clear Buffers |
CWE-121 | V755 | Stack-based Buffer Overflow |
CWE-122 | V755 | Heap-based Buffer Overflow |
CWE-123 | V575 | Write-what-where Condition |
CWE-129 | V557, V781, V3106 | Improper Validation of Array Index |
CWE-131 | V514, V531, V568, V620, V627, V635, V641, V651, V687, V706, V727 | Incorrect Calculation of Buffer Size |
CWE-134 | V576, V618, V3025 | Use of Externally-Controlled Format String |
CWE-135 | V518, V635 | Incorrect Calculation of Multi-Byte String Length |
CWE-188 | V557, V3106 | Reliance on Data/Memory Layout |
CWE-195 | V569 | Signed to Unsigned Conversion Error |
CWE-197 | V642 | Numeric Truncation Error |
CWE-36 | V631, V3039 | Absolute Path Traversal |
CWE-369 | V609, V3064 | Divide By Zero |
CWE-401 | V701, V773 | Improper Release of Memory Before Removing Last Reference ('Memory Leak') |
CWE-404 | V611, V773 | Improper Resource Shutdown or Release |
CWE-415 | V586 | Double Free |
CWE-416 | V774 | Use after free |
CWE-457 | V573, V614, V670, V3070, V3128 | Use of Uninitialized Variable |
CWE-462 | V766, V3058 | Duplicate Key in Associative List (Alist) |
CWE-467 | V511, V512, V568 | Use of sizeof() on a Pointer Type |
CWE-468 | V613, V620, V643 | Incorrect Pointer Scaling |
CWE-476 | V522, V595, V664, V757, V769, V3019, V3042, V3080, V3095, V3105, V3125 | NULL Pointer Dereference |
CWE-478 | V577, V719, V622, V3002 | Missing Default Case in Switch Statement |
CWE-481 | V559, V3055 | Assigning instead of comparing |
CWE-482 | V607 | Comparing instead of Assigning |
CWE-483 | V640, V3043 | Incorrect Block Delimitation |
CWE-561 | V551, V695, V734, V776, V779, V3021 | Dead Code |
CWE-562 | V558 | Return of Stack Variable Address |
CWE-563 | V519, V603, V751, V763, V3061, V3065, V3077, V3117 | Assignment to Variable without Use ('Unused Variable') |
CWE-570 | V501, V547, V560, V654, V3022, V3063 | Expression is Always False |
CWE-571 | V501, V547, V560, V617, V654, V694, V3022, V3063 | Expression is Always True |
CWE-587 | V566 | Assignment of a Fixed Address to a Pointer |
CWE-588 | V641 | Attempt to Access Child of a Non-structure Pointer |
CWE-674 | V3110 | Uncontrolled Recursion |
CWE-690 | V522, V3080 | Unchecked Return Value to NULL Pointer Dereference |
CWE-762 | V611 | Mismatched Memory Management Routines |
CWE-805 | V512, V594, V3106 | Buffer Access with Incorrect Length Value |
CWE-806 | V512 | Buffer Access Using Size of Source Buffer |
CWE-843 | V641 | Access of Resource Using Incompatible Type ('Type Confusion') |
Теперь мы сможем писать в статьях о проверке проектов, какие мы нашли потенциальные уязвимости в том или ином проекте. Раз многие хвалятся, что их анализаторы выявляют дефекты безопасности, то и мы затронем эту тему в своих статьях.
Демонстрация
Давайте посмотрим, как приведенную выше таблицу нам можно использовать при написании статей. Проанализируем проект и посмотрим на диагностические сообщения PVS-Studio с точки зрения дефектов безопасности.
Конечно, далеко не каждый проект стоит изучать с точки зрения уязвимости. Поэтому давайте возьмем такой серьезный проект, как Apache HTTP Server.
Итак, проверяем Apache HTTP Server с помощью PVS-Studio и видим, что баги лезут из всех щелей. Стоп! Теперь это не баги, а дефекты безопасности! Намного солидней говорить про потенциальные уязвимости, чем про опечатки и ошибки.
Сразу скажу, что в этот раз мы не будем анализировать проект целиком, так как перед нами стоит задача только показать использование таблицы на практике. Остановимся на трех предупреждениях.
Пример N1
#define myConnConfig(c) (SSLConnRec *)ap_get_module_config(c->conn_config, &ssl_module)
....
int ssl_callback_alpn_select(SSL *ssl,
const unsigned char **out, unsigned char *outlen,
const unsigned char *in, unsigned int inlen,
void *arg)
{
conn_rec *c = (conn_rec*)SSL_get_app_data(ssl);
SSLConnRec *sslconn = myConnConfig(c);
apr_array_header_t *client_protos;
const char *proposed;
size_t len;
int i;
/* If the connection object is not available,
* then there's nothing for us to do. */
if (c == NULL) {
return SSL_TLSEXT_ERR_OK;
}
....
}
Анализатор PVS-Studio выдаёт предупреждение: V595 The 'c' pointer was utilized before it was verified against nullptr. Check lines: 2340, 2348. ssl_engine_kernel.c 2340
С точки зрения дефектов безопасности это: CWE-476 (NULL Pointer Dereference)
Суть ошибки. Выделим две наиболее важные сточки кода:
SSLConnRec *sslconn = myConnConfig(c);
if (c == NULL) {
Проверка (c == NULL) говорит нам, что указатель может быть нулевым. Однако, он уже разыменовывался внутри макроса myConnConfig:
#define myConnConfig(c) (SSLConnRec *)ap_get_module_config(c->conn_config, &ssl_module)
Таким образом, код никак не защищён от разыменовывания нулевого указателя.
Пример N2
int get_password(struct passwd_ctx *ctx)
{
char buf[MAX_STRING_LEN + 1];
....
memset(buf, '\0', sizeof(buf));
return 0;
err_too_long:
....
}
Анализатор PVS-Studio выдаёт предупреждение: V597 The compiler could delete the 'memset' function call, which is used to flush 'buf' buffer. The memset_s() function should be used to erase the private data. passwd_common.c 165
С точки зрения дефектов безопасности это: CWE-14 (Compiler Removal of Code to Clear Buffers)
Суть ошибки. При компиляции кода в режиме оптимизации, компилятор удалит вызов функции memset, так как с точки зрения компилятора этот вызов лишний. После заполнения нулями буфера, созданного на стеке, этот буфер более никак не используется. Значит, заполнять буфер нулями — это пустая трата времени и следует удалить вызов функции memset. Таким образом, приватные данные не будут затерты и останутся в памяти.
Хочу обратить внимание, что это не абстрактное теоретически возможное поведение компилятора. Компиляторы действительно так делают, чтобы ускорить наши программы. Подробности:
- Перезаписывать память — зачем?
- V597. The compiler could delete the 'memset' function call, which is used to flush 'Foo' buffer.
Пример N3
static int is_quoted_pair(const char *s)
{
int res = -1;
int c;
if (((s + 1) != NULL) && (*s == '\\')) {
c = (int) *(s + 1);
if (apr_isascii(c)) {
res = 1;
}
}
return (res);
}
Анализатор PVS-Studio выдаёт предупреждение: V694 The condition ((s + 1) != ((void *) 0)) is only false if there is pointer overflow which is undefined behaviour anyway. mod_mime.c 531
С точки зрения дефектов безопасности это: CWE-571 (Expression is Always True)
Суть ошибки. Условие ((s + 1) != NULL) всегда истинно. Ложным оно может стать только при переполнении указателя. Переполнение указателя приводит к неопределённому поведению программы, поэтому про такой случай говорить вообще смысла нет. Можно считать, что условие всегда истинно, о чем и сообщил нам анализатор.
Мы не авторы кода и точно не знаем, как должен выглядеть код, но, скорее всего, он должен быть таким:
if ((*(s + 1) != '\0') && (*s == '\\')) {
Заключение
Ура, анализатор PVS-Studio может использоваться для выявления потенциальных уязвимостей кода!
Предлагаем всем желающим подробнее познакомиться с анализатором кода PVS-Studio и попробовать демонстрационную версию анализатора на собственных проектах. Страница продукта: PVS-Studio.
По всем техническим вопросам и вопросам лицензирования просим писать нам на почту support [@] viva64.com или воспользоваться формой обратной связи.
Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Andrey Karpov, Phillip Khandeliants. PVS-Studio: searching software weaknesses
Комментарии (17)
Andrey2008
16.03.2017 23:12Почему полезно использовать PVS-Studio? Потому, что он находит неинициализированные переменные в LLVM! Use of Uninitialized Variable (CWE-457).
EvgeniyRyzhkov
16.03.2017 23:43+1Разве в Clang нет соответсвующей диагностики?
Andrey2008
17.03.2017 10:01Не знаю, не смотрел. Одну из двух. Или они это не осиливают. Или сами собой не пользуются. :)
В целом же ничего удивительно. Задача PVS-Studio в том и состоит, чтобы быть лучше компиляторов в плане поиска дефектов.Andrey2008
17.03.2017 10:05Мы и в плане C# сильны. Очередное доброе дело: EntityFramework. PVS-Studio: fixed vulnerability CWE-670 (Always-Incorrect Control Flow Implementation)
lany
18.03.2017 07:55+3В первом случае ошибка в юнит-тесте, который проверял меньше, чем следовало. Во втором — ошибка, которая ведёт к снижению производительности через увеличение коллизий хэшкодов, но на корректность не влияет. Доброе дело, да, но вряд ли это прямо fixed vulnerability :-) А уж рекламку вставлять в юнит-тест совсем как-то некрасиво, на мой взгляд. Хватило бы и commit-message.
Andrey2008
Тишина. Тогда сам комментарий оставлю. :)
Ещё примеры поиска уязвимостей в коде FreeBSD:
alan008
Андрей, я не C-разработчик, но не могли бы вы пояснить, чтобы злоумышленнику использовать любую из этих т.н. "уязвимостей", что нужно сделать? А то вы сейчас тут приучите народ после каждой строчки кода по 100 проверок добавлять, дабы не породить некую "дыру" в программе.
Andrey2008
Данные ошибки ещё не подтвержденные уязвимости (CVE), а только потенциальные уязвимости. Можно их использовать или нет — это сложная исследовательская задача, интересная не нам пользователям, а злоумышленникам. Нам же пользователям/разработчикам интересно побыстрее исправить эти ошибки от греха подальше.
Про проверки. Да, где-то их может не хватать. Но, например, всё перечисленное здесь возникло не из-за отсутствия проверок, а из-за ошибок в коде.
Что нужно делать? Это целый комплекс мер, который тянет на книгу. Но точно могу сказать, одна из мер, это использование статических анализаторов кода, таких как PVS-Studio. :)
alan008
Андрей, спасибо за ответ. Безусловно, это полезный функционал. Могу предложить вам реализовать вам еще один тип ошибок — предупреждения о неэффективном коде. Иногда одна и так команда может быть записана несколько по-разному и в некоторых случаях приводит к лишним вычислениям, инвариантным фрагментам в циклах (например, когда какая-то команда внутри цикла выполняется, но результаты ее выполнения никак не используются или это просто один и тот же вызов). Было бы полезно получать предупреждения о таких потенциально неэффективных местах.
Также могло бы быть полезным выявление дублирования кода в разных модулях.
Andrey2008
Такие предупреждения уже есть, хотя их пока немного. Мы называем их микро-оптимизации.
alan008
Андрей, понимаю вашу специфику (в большей степени поиск ошибок в низкоуровневом коде относительно высокого качества), но может вам замахнуться на всякие Python, Ruby, Java, PHP, Objective C — вот где непаханое поле багов и тормозов :-)
Конечно, понятно, что это будет уже совсем другой продукт. Может и не стоит так распыляться.
Andrey2008
Пока мы считаем, что не готовы идти дальше. Если пойдём, то скорее всего это будет Java.
alan008
Да, это логично. Enterprise все-таки. Переход к языкам с динамической типизацией может потребовать пересмотра всех подходов к анализу.
Основная проблема добавления анализа другого языка — найти экспертов, которые знают, какие там бывают проблемы. Потому что надо сначала самому нарваться несколько раз на ошибку/проблему, чтобы понять, что её стОит выявлять. Желаю удачи в полезном деле! Кстати в качестве вашей рекламы не пробовали получить реальный отзыв от какого-то вашего клиента (не важно платного или бесплатно). Т.е. чтобы не вы сами, а ваши пользователи описали, какой профит они получили от PVS-Studio?
EvgeniyRyzhkov
Пробовали. Вот, например, про Unreal Engine компании Epic Games.
alan008
Ну не надо лукавить, здесь, я так понял, вы сами проверили, сами пофиксили и попросили их отписаться.
Было бы интереснее услышать тех, кто добровольно сам пользуется и сам фиксит (не команда PVS). Но кто же захочет писать про свои баги. Хотя..
EvgeniyRyzhkov
Очень мало кто готов публично писать о исправлении собственных ошибок. Причина — а вдруг наши клиенты подумают, что у нас в программах есть ошибки!
Это смешно звучит для программистов, но для директоров это вот так.
SvyatoslavMC
Взгляните на статью Aurelien Aptel, он сам проверял и сам провил проект Samba. Статья из его блога: http://emacsdump.blogspot.ru/2016/03/running-pvs-studio-on-samba.html