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


    Анализатор 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')
    Таблица 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 или воспользоваться формой обратной связи.



    Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Andrey Karpov, Phillip Khandeliants. PVS-Studio: searching software weaknesses

    Прочитали статью и есть вопрос?
    Часто к нашим статьям задают одни и те же вопросы. Ответы на них мы собрали здесь: Ответы на вопросы читателей статей про PVS-Studio, версия 2015. Пожалуйста, ознакомьтесь со списком.
    PVS-Studio 204,10
    Ищем ошибки в C, C++ и C# на Windows и Linux
    Поделиться публикацией
    Комментарии 17
    • 0
      Тишина. Тогда сам комментарий оставлю. :)

      Ещё примеры поиска уязвимостей в коде FreeBSD:

      1. CWE-467
      2. CWE-570
      3. CWE-571
      4. CWE-571
      5. CWE-571
      6. CWE-476
      7. CWE-563
      8. CWE-563
      9. CWE-561
      10. CWE-561
      • 0

        Андрей, я не C-разработчик, но не могли бы вы пояснить, чтобы злоумышленнику использовать любую из этих т.н. "уязвимостей", что нужно сделать? А то вы сейчас тут приучите народ после каждой строчки кода по 100 проверок добавлять, дабы не породить некую "дыру" в программе.

        • 0
          Данные ошибки ещё не подтвержденные уязвимости (CVE), а только потенциальные уязвимости. Можно их использовать или нет — это сложная исследовательская задача, интересная не нам пользователям, а злоумышленникам. Нам же пользователям/разработчикам интересно побыстрее исправить эти ошибки от греха подальше.

          Про проверки. Да, где-то их может не хватать. Но, например, всё перечисленное здесь возникло не из-за отсутствия проверок, а из-за ошибок в коде.

          Что нужно делать? Это целый комплекс мер, который тянет на книгу. Но точно могу сказать, одна из мер, это использование статических анализаторов кода, таких как PVS-Studio. :)
          • 0

            Андрей, спасибо за ответ. Безусловно, это полезный функционал. Могу предложить вам реализовать вам еще один тип ошибок — предупреждения о неэффективном коде. Иногда одна и так команда может быть записана несколько по-разному и в некоторых случаях приводит к лишним вычислениям, инвариантным фрагментам в циклах (например, когда какая-то команда внутри цикла выполняется, но результаты ее выполнения никак не используются или это просто один и тот же вызов). Было бы полезно получать предупреждения о таких потенциально неэффективных местах.
            Также могло бы быть полезным выявление дублирования кода в разных модулях.

            • 0
              предупреждения о неэффективном коде

              Такие предупреждения уже есть, хотя их пока немного. Мы называем их микро-оптимизации.
              • 0

                Андрей, понимаю вашу специфику (в большей степени поиск ошибок в низкоуровневом коде относительно высокого качества), но может вам замахнуться на всякие Python, Ruby, Java, PHP, Objective C — вот где непаханое поле багов и тормозов :-)
                Конечно, понятно, что это будет уже совсем другой продукт. Может и не стоит так распыляться.

                • 0
                  Пока мы считаем, что не готовы идти дальше. Если пойдём, то скорее всего это будет Java.
                  • 0

                    Да, это логично. Enterprise все-таки. Переход к языкам с динамической типизацией может потребовать пересмотра всех подходов к анализу.
                    Основная проблема добавления анализа другого языка — найти экспертов, которые знают, какие там бывают проблемы. Потому что надо сначала самому нарваться несколько раз на ошибку/проблему, чтобы понять, что её стОит выявлять. Желаю удачи в полезном деле! Кстати в качестве вашей рекламы не пробовали получить реальный отзыв от какого-то вашего клиента (не важно платного или бесплатно). Т.е. чтобы не вы сами, а ваши пользователи описали, какой профит они получили от PVS-Studio?

                    • 0
                      Пробовали. Вот, например, про Unreal Engine компании Epic Games.
                      • 0

                        Ну не надо лукавить, здесь, я так понял, вы сами проверили, сами пофиксили и попросили их отписаться.


                        After publishing every new article about checking some project, people will ask: "Have you reported the bugs to the project authors?" And of course we always do! But this time, we've not only "reported the bugs to the authors" but fixed all those bugs ourselves.

                        Было бы интереснее услышать тех, кто добровольно сам пользуется и сам фиксит (не команда PVS). Но кто же захочет писать про свои баги. Хотя..

                        • 0
                          Очень мало кто готов публично писать о исправлении собственных ошибок. Причина — а вдруг наши клиенты подумают, что у нас в программах есть ошибки!

                          Это смешно звучит для программистов, но для директоров это вот так.
                          • 0
                            Взгляните на статью Aurelien Aptel, он сам проверял и сам провил проект Samba. Статья из его блога: http://emacsdump.blogspot.ru/2016/03/running-pvs-studio-on-samba.html
        • 0
          Почему полезно использовать PVS-Studio? Потому, что он находит неинициализированные переменные в LLVM! Use of Uninitialized Variable (CWE-457).
          • +1
            Разве в Clang нет соответсвующей диагностики?
            • 0
              Не знаю, не смотрел. Одну из двух. Или они это не осиливают. Или сами собой не пользуются. :)

              В целом же ничего удивительно. Задача PVS-Studio в том и состоит, чтобы быть лучше компиляторов в плане поиска дефектов.
              • 0
                Мы и в плане C# сильны. Очередное доброе дело: EntityFramework. PVS-Studio: fixed vulnerability CWE-670 (Always-Incorrect Control Flow Implementation)
                • +3

                  В первом случае ошибка в юнит-тесте, который проверял меньше, чем следовало. Во втором — ошибка, которая ведёт к снижению производительности через увеличение коллизий хэшкодов, но на корректность не влияет. Доброе дело, да, но вряд ли это прямо fixed vulnerability :-) А уж рекламку вставлять в юнит-тест совсем как-то некрасиво, на мой взгляд. Хватило бы и commit-message.

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое