PVS-Studio: поиск дефектов безопасности

Андрей Карпов
Статей: 328



Анализатор PVS-Studio всегда умел искать множество различных дефектов безопасности (потенциальных уязвимостей) в коде программ. Однако, исторически сложилось, что мы позиционировали PVS-Studio как инструмент для поиска ошибок. Сейчас наблюдается мода на поиск в коде именно уязвимостей, а не ошибок, хотя на самом деле это одно и тоже. Что же, значит пришло время провести ребрендинг нашего статического анализатора PVS-Studio. Начнём мы с Common Weakness Enumeration (CWE). В этой статье приводится таблица, сопоставляющая диагностические предупреждения PVS-Studio с классификатором. Таблица будет постепенно пополняться и изменяться, но уже сейчас с её помощью мы сможем писать статьи, посвященные обнаруженным дефектам безопасности в том или ином проекте. Думаем, это привлечёт к нашему инструменту больше внимания специалистов, занимающихся безопасностью программного обеспечения.

Picture 2

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')

Таблица N1. Первый черновой вариант таблицы соответствий CWE и диагностик PVS-Studio.

Теперь мы сможем писать в статьях о проверке проектов, какие мы нашли потенциальные уязвимости в том или ином проекте. Раз многие хвалятся, что их анализаторы выявляют дефекты безопасности, то и мы затронем эту тему в своих статьях.

Демонстрация

Давайте посмотрим, как приведенную выше таблицу нам можно использовать при написании статей. Проанализируем проект и посмотрим на диагностические сообщения 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. Таким образом, приватные данные не будут затерты и останутся в памяти.

Хочу обратить внимание, что это не абстрактное теоретически возможное поведение компилятора. Компиляторы действительно так делают, чтобы ускорить наши программы. Подробности:

Пример 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 или воспользоваться формой обратной связи.



Используйте PVS-Studio для поиска ошибок в C, C++ и C# коде

Предлагаем попробовать проверить код вашего проекта с помощью анализатора кода PVS-Studio. Одна найденная в нём ошибка скажет вам о пользе методологии статического анализа кода больше, чем десяток статей.

goto PVS-Studio;


А ты совершаешь ошибки в коде?

Проверь с помощью
PVS-Studio

Статический анализ
кода для C, C++ и C#

goto PVS-Studio;
Мы используем cookie-файлы для анализа событий на нашем веб-сайте, что позволяет улучшить наш контент и сделать взаимодействие с пользователем более удобным. Продолжая просмотр страниц нашего веб-сайта, вы принимаете условия использования этих файлов. Узнайте подробнее о cookie-файлах и политике конфиденциальности или скройте это уведомление, нажав на кнопку. Подробнее →
Больше не показывать