Обнаружение в коде дефекта «разыменование нулевого указателя»

Этой статьей мы открываем серию публикаций, посвященных обнаружению ошибок и уязвимостей в open-source проектах с помощью статического анализатора кода AppChecker. В рамках этой серии будут рассмотрены наиболее часто встречающиеся дефекты в программном коде, которые могут привести к серьезным уязвимостям. Сегодня мы остановимся на дефекте типа «разыменование нулевого указателя».



Разыменование нулевого указателя (CWE-476) представляет собой дефект, когда программа обращается по некорректному указателю к какому-то участку памяти. Такое обращение ведет к неопределенному поведению программы, что приводит в большинстве случаев к аварийному завершению программы.

Ниже приведен пример обращения по нулевому указателю. В данном случае, скорее всего, программа отработает без выдачи сообщений об ошибках.

#include <iostream>
class A {
        public:
            void bar() {
                std::cout << "Test!\n";
            }
};

int main() {
    A* a = 0;
    a->bar();
    return 0;
}

А теперь рассмотрим пример, в котором программа аварийно завершит свою работу. Пример очень похож на предыдущий, но с небольшим отличием.

#include <iostream>
class A {
        int x;
        public:
            void bar() {
                std::cout << x << "Test!\n";
            }
};

int main() {
    A* a = 0;
    a->bar();
    return 0;
}

Почему же в одном случае программа отработает нормально, а в другом нет? Дело в том, что во втором случае вызываемый метод обращается к одному из полей нулевого объекта, что приведет к считыванию информации из непредсказуемой области адресного пространства. В первом же случае в методе нет обращения к полям объекта, поэтому программа скорее всего завершится корректно.

Рассмотрим следующий фрагмент кода на C++:

if( !pColl )
   pColl->SetNextTxtFmtColl( *pDoc->GetTxtCollFromPool( nNxt ));

Нетрудно заметить, что если pColl == NULL, выполнится тело этого условного оператора. Однако в теле оператора происходит разыменование указателя pColl, что вероятно приведет к краху программы.

Обычно такие дефекты возникают из-за невнимательности разработчика. Чаще всего блоки такого типа применяются в коде для обработки ошибок. Для выявления таких дефектов можно применить различные методы статического анализа, например, сигнатурный анализа или symbolic execution. В первом случае пишется сигнатура, которая ищет в абстрактном синтаксическом дереве (AST) узел типа «условный оператор», в условии которого есть выражение вида! а, a==0 и пр., а в теле оператора есть обращение к этому объекту или разыменование этого указателя. После этого необходимо отфильтровать ложные срабатывания, например, перед разыменованием этой переменной может присвоиться значение:

if(!a) {
  a = new A();
  a->bar();
}

Выражение в условии может быть нетривиальным.

Во втором случае во время работы анализатор «следит», какие значения могут иметь переменные. После обработки условия if (!a) анализатор понимает, что в теле условного оператора переменная a равна нулю. Соответственно, ее разыменование можно считать ошибкой.

Приведенный фрагмент кода взят из популярного свободного пакета офисных приложений Apache OpenOffice версии 4.1.2. Дефект в коде был обнаружен при помощи статического анализатора программного кода AppChecker. Разработчики были уведомлены об этом дефекте, и выпустили патч, в котором этот дефект был исправлен ).

Рассмотрим аналогичный дефект, обнаруженный в Oracle MySQL Server 5.7.10:

bool sp_check_name(LEX_STRING *ident)
{
  if (!ident || !ident->str || !ident->str[0] ||
      ident->str[ident->length-1] == ' ')
  {
    my_error(ER_SP_WRONG_NAME, MYF(0), ident->str);
    return true;
  }
..
}

В этом примере если ident равен 0, то условие будет истинным и выполнится строка:

my_error(ER_SP_WRONG_NAME, MYF(0), ident->str);

что приведет к разыменованию нулевого указателя. По всей видимости разработчик в процессе написания этого фрагмента кода, в котором ловятся ошибки, просто не учел, что такая ситуация может возникнуть. Правильным решением было бы сделать отдельный обработчик ошибок в случае, когда ident=0.

Нетрудно догадаться, что разыменование нулевого указателя – это дефект, не зависящий от языка программирования. Предыдущие два примера демонстрировали код на языке C++, однако с помощью статического анализатора AppChecker можно находить подобные проблемы в проектах на языках Java и PHP. Приведем соответствующие примеры.

Рассмотрим фрагмент кода системы управления и централизации информации о строительстве BIM Server версии bimserver 1.4.0-FINAL-2015-11-04, написанной на языке Java:

if (requestUri.equals("") || requestUri.equals("/") || requestUri == null) {
     requestUri = "/index.html";
}

В данном примере сначала идет обращение к переменной requestUri и только после этого происходит проверка на нулевой указатель. Для того чтобы избежать этого дефекта, достаточно было просто поменять очередность выполнения этих действий.

Теперь рассмотрим фрагмент кода популярной коллекции веб-приложений phabricator, написанной на php:

if (!$device) {
    throw new Exception(
      pht(
        'Invalid device name ("%s"). There is no device with this name.',
        $device->getName()));
}

В данном случае условие выполняется только если $device = NULL, однако затем происходит обращение к $device->getName(), что приведет к fatal error.

Подобные дефекты могут оставаться незамеченными очень долго, но в какой-то момент условие выполнится, что приведет к краху программы. Несмотря на простоту и кажущуюся банальность такого рода дефектов, они встречаются достаточно часто, как в open-source, так и в коммерческих проектах.

Update:

Ссылка на бесплатную версию AppChecker: https://file.cnpo.ru/index.php/s/o1cLkNrUX4plHMV
Эшелон
44,00
Компания
Поделиться публикацией

Комментарии 92

    +11
    Разжевывание совсем уж для детей.
      0
      Рассмотренные проекты явно не детьми разрабатываются, а ошибки такие допускаются…
        +10
        Но не потому что не знают, что это такое, а по невнимательности.

        В том числе поэтому языки, не имеющие NULL, рулят)
          –6
          так поэтому мы разрабатываем наше решение AppChecker. На нашем сайте можно заполнить форму и получить лайт версию.
            +1
            Никогда не понимал до конца вот это вот «в языке есть NULL, поэтому это плохо» — NULL это просто ноль. Или какое-то другое значение. Почему если есть возможность присводить какое-то значение в какую-то переменную, то сразу язык от этого портится?
              +3
              NULL — это не просто ноль, а нулевой указатель. И плохо это как раз тем, что к нему можно случайно обратиться и программа случайно упадёт (или не упадёт, что ещё хуже, потому что ошибку никто не заметит, а потом она вылезет лет через двадцать на ближайшей к вам атомной электростанции :). И эти обращения в мейнстримовых языках не проверяются во время компиляции, поэтому над ними приходится городить костыли в виде этих самых статических анализаторов
                0
                С тем же успехом можно разыменовать любой другой указатель (например мой любимый — 5). Указатель это число, некоторый адрес в памяти. Он может принимать очень много значений и очень немногие из них будут соответствовать расположенным в этой памяти объектам нужного типа. NULL это только одно из некорректных значений. Просто оно очень удобно для обозначения «на самом деле тут ничего нет». Все равно, это лучше, чем если бы был указатель на объект, который уже удален, но зато не NULL.
                  0
                  разыменовать любой другой указатель

                  Ответ почти аналогичный: в том числе поэтому языки с указателями это плохо)
                    0
                    Интересно, что в статически типизированных языках без использования unsafe-кода нельзя в ссылочную переменную записать недействительное значение указателя, кроме нулевого.
                    Чтобы обойти статические проверки компилятора, даже придумали для нуля ключевое слово null.
                    Интересно, можно ли было обойтись без null.
                      +6
                      обойтись без null

                      let obj: Option<MyClass> = get_some_object_if_exists();
                      match obj {
                        Some(x) => { println!("Имя объекта: {}", x.getName()) },
                        None => { println!("Объекта нету") }
                      }

                      Без явной проверки на наличие объекта обратиться к нему в Rust никак нельзя, obj.getName() не скомпилируется, только или match, или if let, или obj.unwrap().getName(), если есть уверенность, что объект действительно есть (а если его таки не окажется, то на unwrap программа упадёт с внятным сообщением об ошибке, а не с неопределённым поведением как в C/C++)


                      А просто let obj: MyClass = чтототам будет содержать рабочий объект гарантированно и в таком случае будет можно obj.getName()

                        –3

                        Смотрите шире: просто не надо писать на С++ то, что можно писать не на С++. Я Rust не знаю, но приведённый пример вызывает уныние: это что, при каждом обращении к свойству объекта необходимо вручную писать код, что делать, если объекта нет? Извините, но это просто слишком. Если писать код не для спутника или ядерной ракеты, то это просто безумный расход труда на перестраховки.

                          0

                          А если бы это был C++?


                          MyClass* obj = get_some_object_if_exists();
                          if (obj != nullptr) {
                              std::cout << "Имя объекта: " << obj->getName() << '\n';
                          } else {
                              std::cout << "Объекта нету\n";
                          }
                          free_some_object(obj);

                          это что, при каждом обращении к свойству объекта необходимо вручную писать код, что делать, если объекта нет? Извините, но это просто слишком. Если писать код не для спутника или ядерной ракеты, то это просто безумный расход труда на перестраховки.
                            +3

                            Зачем при каждом-то


                            // fn get_some_object_if_exists() -> Option<MyClass> { ... }
                            
                            let obj: MyClass = match get_some_object_if_exists() {
                              Some(x) => { x },
                              None => { println!("Объекта нету"); return; }
                            };
                            // Здесь объект уже абсолютно точно есть
                            println!("{}", obj.getName());
                            println!("{}", obj.getDate());
                            println!("{}", obj.getStatus());
                            println!("{}", obj.getЧтоТоТам());
                            // ...

                            Или, как вариант


                            if let Some(obj) = get_some_object_if_exists() {
                              println!("{}", obj.getName());
                              println!("{}", obj.getЧтоТоТам());
                              // ...
                            } else {
                              println!("Объекта нету");
                            }

                            Капитан Очевидность замечает, что если объекта нет, то и обращаться к нему нельзя. Причём вы ДОЛЖНЫ проверить, есть объект или нет, вообще на ЛЮБОМ языке программирования, ОСОБЕННО на C/C++ :)

                    0
                    Так ведь наоборот всё, гораздо лучше, если приложение падает там где должно, разве нет?

                    Простейший пример: функция загрузки какого-либо объекта из файла, если файла нет или он повреждён — возвращается NULL. Естественная абстракция.

                    Без NULL необходимо вводить специальный экземпляр «невалидного объекта», на который после вызова функции следует проверять возвращённое значение. Проверка такая же, только теперь уже анализатор под это не напишешь. Более того, приложение начнёт работать как ни в чём не бывало, если эту проверку пропустить. В итоге этот-то баг как раз и просочится на электростанцию и вылезет через 20 лет, а не будет обнаружен на тестировании.

                    Разве не так всё?
                      +1

                      Так с NULL оно может или падать, или не падать — как повезёт. На тестировании не упадёт, а на атомной станции через 20 лет сложится такая сильно специфическая ситуация, что оно возьмёт да упадёт, кто знает. Или наоборот: на тестировании будет падать и на это будет расчёт (хотя зачем так вообще делать?), а на станции возьмёт и не упадёт.


                      Языки, в которых эту самую проверку пропустить нельзя, рулят) Специальный экземпляр «невалидного объекта» должен быть таким, чтобы его нельзя было не проверить. Тот пример на Rust, что я кинул выше, подходит и для этого случая с файлом. Анализ там проходит прямо в процессе компиляции и никаких проблем нет и быть не может)


                      Кстати, за это я не люблю активно пиарившийся тут Go: там проверку пропустить можно (if err != nil, вот это вот всё), и это считается нормой. В Rust пропустить проверку нельзя — не скомпилируется.

                      0
                      NULL — это не просто ноль, а нулевой указатель.
                      А разве не nullptr, как теперь рекомендуется?
                        0
                        Может и nullptr, в контексте данной ветки всё равно не суть)
                        Хотя гугл намекает, что в C такого нет
                      0
                      Потому что NULL это ноль не в абстрактной целочисленной переменной, а в переменной, которая трактуется как указатель на объект (или на на ту же целочисленную переменную).

                      Но принята «конвенция», что в памяти по адресу ноль не может быть объекта/переменной.

                      Именно поэтому разыменование нулевого указателя (или обращение с полям/методам переменной ссылочного типа) приводит в лучшем случае Null Reference/Pointer Exception, в худшем — к Undefined Behaviour.
                        0
                        Интересно, никогда раньше не слышал о такой «конвенции». Гугл ничего явного не подсказал, или я что-то не то искал. Зато я узнал, что в Линуксе нулевая страница адресного пространства просто всегда явно маппится без прав доступа, поэтому происходит segfault.

                        В «особых случаях» нулевой адрес является вполне себе валидной штукой — примеры опять же можно найти в гугле.

                        Под рукой нет Кернигана-Ритчи, но насколько я помню, разыменование указателя, который не содержит адрес объекта того типа, на который ссылается этот указатель, это Undefined Behavior. Ни о каком особом статусе нулевого адреса я не помню. Может просто невнимательно читал.

                        Все эти фокусы с указателями, разыменовываниями и прочим могут выглядеть как игра с огнем или чем-то более опасным, но если просто оперировать памятью, структурами и массивами, то оно все выглядит вполне естественным. Выделил память, передал в функцию адрес этой памяти, записал по нужному смещению нужное значение. Никакой магии, все на виду.
                          0
                          А вот нашел C99. Там все написано:

                          An integer constant expression with the value 0, or such an expression cast to type
                          void *, is called a null pointer constant. If a null pointer constant is converted to a pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal to a pointer to any object or function.


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

                            Не совсем так. В стандарте нет требования, чтобы нулевой указатель указывал на нулевой адрес. На нестандартных архитектурах или компиляторах он может указывать, к примеру, на 0xffffffff.

                              –3
                              В стандарте есть требование, что константа 0 это нулевой указатель. Про адрес тут речи нет. Кроме того, есть требование, что любые два нулевых указателя равны. Значит никаких других нулевых указателей быть не должно. 0xffffffff это не нулевой указатель. Просто особый невалидный.
                                +1

                                В стандарте нет требования, чтобы нулевые указатели в понимании разных компиляторов совпадали. Если на некоторой платформе все нулевые указатели равны 0xffffffff — это не будет нарушением стандарта.

                                  0
                                  Если при этом выполняется условие «константа 0 является нулевым указателем» — вопросов нет. Но выглядит это странно.
                                    0

                                    Так в станарте не сказано, что битовое представление константы 0 и нулевого указателя должно совпадать. Это в коде вы пишите


                                    int xxx  = 0
                                    void* ptr = (void*)xxx;

                                    а машина выполняет


                                    if xxx == 0 {
                                      ptr = 0xFFFFFFFF;
                                    }

                                    Никаких противоречий, хоть и непривычно.

                                      0

                                      Нет, не так. (void*)xxx будет указателем на нулевой адрес. К нулевому указателю приводится только константа 0.

                                        0

                                        Ну в общем да. Изначально я писал пример с приводом (void*)0, но потом мне показалось нелогичным, что в "точке зрения компилятора" используется какой-то необъявленный xxx и я решил его явно указать. Исправлять потом было поздно :). Но логика, я думаю, понятна

                              0
                              Верно, особый статус, т.к. понятие указателя несет определенную семантику, в отличие от «просто» числа
                              И выше вам верно отметили, что NULL это необязательно 0, может быть и "-1" (все биты единичные)
                              Даже в WinAPI, функции, возвращающие хендлы (те же указатели), в качестве невалидного указателя, через одну возвращают то 0, то -1.
                          0

                          Сложность возрастает. Нужно писать больше if для одного и того же, потому что внезапно значение вместо юзернейм пришёл нул. Особенную пикантность добавляет то, что в типизированных языках обычно нет разделение на тип который может принимать нул и тип который не может. Приходится пилить костыли, как это сделано в восьмой яве — Optional. Но эти костыли на то и костыли что спасают только от части проблем.

                          0
                          -Рассмотренные проекты явно не детьми разрабатываются, а ошибки такие допускаются…
                          -Но не потому что не знают, что это такое, а по невнимательности.

                          Причина не имеет значения, ключевое слово «допускаются». А главное, попробуйте потом объяснить, что «я просто просмотрел» персоналу ближайшей атомной электростанции :)
                          С утверждением о языках, не имеющих NULL,  категорически согласен :)
                        +6
                        Кстати, предлагаю свою статью, для тек хочет пофилософствовать о более интересном и экзотическом случае. Ведь иногда бывает, что разыменование вроде как есть и вроде как его нет :)
                        +5
                        Поддерживаемые спецификации языков программирования

                        ANSI С/С90/С99/С11;
                        C++98/C++2003/С++11/С++14;
                        Java 6/7 Language Specification;
                        PHP 4/5;
                        С# 5.0.

                        В общем, кроме Си/C++ никакие языки толком не поддерживаются.

                          +6
                          С# 5.0

                          Абсолютно не актуально, ибо на дворе уже 2017 с C# 7.0
                            +3

                            Также как Java 8 и PHP 7 (если уже новых версий не выпустили, я там не слежу особо). Собственно, потому я и выделил эти версии.

                              +3
                              Java 8 — уже поддерживаем, новые PHP и C# — в процессе доработки.
                                +2

                                Я цитировал страницу продукта по вашей же ссылке.

                              –8
                              Можно подумать PVS-Studio с первых дней поддерживал C# и все новомодные фишки языков. Пройдет чуть больше времени, разработчики подтянут свой проект, вот тогда и посмотрим.
                                +4

                                Но они и такой поддержки не заявляли. А тут поддержка как бы есть — но на самом деле ее нет.


                                Кстати, а при чем тут вообще PVS-Studio?

                                  –2
                                  Потому, что наш уважаемый kishchenko — сотрудник Сипровер.
                                  Который, конечно же не оставил без внимания этот нюанс.
                                    +4
                                    «Долой PVS-Studio! Да здравствует конкуренция на Хабре.»
                                      +3
                                      Процитировав Ваш комментарий, я хотел сказать, что Вы же вроде за конкуренцию на Хабре…
                                  –1
                                  unsafe убрали?
                                +5
                                Кстати, интересный пример двойственности мира. Описывая подобные ошибки в наших статьях, мы обычно используем для кода вида:

                                if( !pColl )
                                   pColl->SetNextTxtFmtColl( *pDoc->GetTxtCollFromPool( nNxt ));
                                


                                Термин опечатка. Ведь в показанных примерах явно «дрогнула рука». Впрочем, это не значит, что это не серьезная ошибка. И обнаруживаем мы таких ошибок действительно много (примеры найденных ошибок в открытых проектах: V522, V595, V713). А двойственность в том, что эту ошибку можно назвать и уязвимостью. Думается мне, рано или поздно мы выпустим PVS-Studio для поиска уязвимостей, вообще ничего в нём не меняя, кроме названия диагностик и документации. :)
                                  +2
                                  Долой PVS-Studio! Да здравствует конкуренция на Хабре.
                                    +6

                                    Чему вы так радуетесь? Вы их прайс-то видели?

                                      –4
                                      А вы прайс PVS-Studio видели? Нет? Вот и я о том же…
                                        +1
                                        Прайс? Сейчас можно скачать лайт-версию (без ограничений по времени использования) бесплатно: https://file.cnpo.ru/index.php/s/o1cLkNrUX4plHMV
                                          +1

                                          И чем лайт отличается от фулл?

                                            +1
                                            Лайт-версия — это полнофункциональный AppChecker с зафиксированной базой правил поиска дефектов на август 2016 года
                                            –1
                                            Кстати, если кто-нибудь из уважаемых критиков испытает продукт в деле и сделает обзор, подарим наш фирменный настенный календарь)
                                              +2
                                              Для windows опишите подробнее по шагам как проверить .sln
                                              который корректно собирается в Visual C++
                                                +1
                                                У нас есть инструкция по конфигурированию проектов C/C++. Её можно скачать прямо из AppChecker — при создании проекта перейдите по ссылке «Перейти на страницу загрузки утилит и инструкции по конфигурированию» -> «Настройка парсера C/C++», там же можно скачать и утилиту для конфигурирования.




                                                Шаги такие:
                                                • запустить утилиту CppConfMonitor.exe
                                                • пересобрать проект в Visual Studio
                                                • остановить утилиту, введя «s» в ее консоли
                                                • утилита сформирует архив, который нужно загрузить в AppChecker
                                              +1

                                              Объясняю при чем тут прайс. После выхода версии PVS-Studio под Linux а также появления бесплатной лиценции со своеобразными, но все же выполнимыми, условиями, у нас осталась всего одна причина ненавидеть PVS-Studio — их секретный прайс.


                                              И когда человек пришел сюда и написал в комментариях: "Долой PVS-Studio! Да здравствует конкуренция на Хабре." — мне и стало интересно, видел ли он ваш прайс. Я вот, к слову, его не нашел.

                                                –3
                                                То то мы и наблюдаем как их бесплатной версией активно пользуются на гитхабе
                                                Если серьезно, то это больше плевок в лицо, нежели нормальная бесплатная версия.
                                                  +7
                                                  Если правильно пользоваться поиском на гитхабе, то процент активного использования PVS-Studio Free резко возрастает.
                                                    +2
                                                    Некоторые настройки поиска при переходе по ссылке не сохраняются, поэтому руками нужно переключить фильтр с «Best Match» на «Recently indexed», либо выбрать язык (C#/C++) из списка.

                                                    Результат: 2 351 упоминание в коде.
                                                      0

                                                      Простите, но где это выбирать и переключать?

                                                        +2

                                                        UPD: там не "настройки не сохраняются", а нельзя выполнить поиск по всем открытым репозиториям по прямой ссылке. Надо обязательно либо вводить запрос вручную, либо после перехода по ссылке поменять любую настройку. И еще надо быть залогиненым — анонимам искать по всем репозиториям нельзя.

                                                        0

                                                        (удалено)

                                                    +1
                                                    У вас есть лайт версия под linux?
                                                      –2
                                                      Лайт-версия AppChecker только под Windows, коммерческую можем собрать и под Linux, если такая потребность будет у наших заказчиков
                                                        0
                                                        Есть и под Linux (Ubuntu 14.04), так же есть и в виде ВМ (внутри которой тоже убунта).
                                                          0
                                                          Именно лайт версия? А ее можно как-то скачать?
                                                  +7

                                                  А почему AppChecker предлагает загружать архив с исходниками? А как же интеграция с системой контроля версий, CI? На дворе 2017 год!


                                                  Ещё и написали о "патенте", коим оказалось всего лишь свидетельство о регистрации ПО.

                                                    +1
                                                    Интеграция с СУВ (git, subversion, mercurial) уже есть. Поддержка CI (Jenkins, TeamCity) также есть, но в альфа-версии, релиз планируем в феврале. Так же имеется консольный клиент, с помощью него можно в автоматическом режиме производить анализ и встраивать его в свою систему сборки.

                                                    Ряд алгоритмов, используемых в AppChecker, покрывается следующим патентом:
                                                    https://npo-echelon.ru/upload/iblock/7d0/7d01385cccfddcb4eb4c925db3fc3b7a.png
                                                      0
                                                      Для оборонки вроде были характерны нейтральные названия вроде средмаша или вообще маскировка типа тракторного завода или вагоностроительного.
                                                    +1
                                                    ЗАО «НПО „Эшелон“» Ничего себе! Название — прямо привет из оборонки 70-х. Ну, разве что «ЗАО» несколько выбивается из ассоциативного ряда.

                                                    Рад за отечественную разработку, честно.
                                                      0

                                                      В современных реалиях ЗАО только дополняет старый термин ;-)


                                                      ЗЫ только, вроде, с 2014 года классификация поменялась ЗАО -> АО, ОАО — ПАО.

                                                      +7
                                                      Результат разработки AppChecker довольно предсказуем.
                                                      Компания Эшелон специализируется на проведении работ в системах сертификации ФСТЭК, МО, ФСБ. Для проведения этих работ много лет назад разработано средство анализа АК-ВС, осуществляющее ряд исследований по руководящему документу ФСТЭК по контролю наличия недекларированных возможностей. Те, кто с этим средством имел несчастье соприкасаться, знаком с его качеством реализации и функционирования.
                                                      В прошлом году был утверждён ГОСТ, разработанный компанией Эшелон, определяющий процесс безопасной разработки ПО. ГОСТ будет обязателен для применения компаниями, которые работают на рынке сертифицируемых решений. Это светлое будущее различных систем сертификации, в котором декларируется необходимость поиска программных дефектов (уязвимостей). Вот для этого и разрабатывается AppChecker. Ни разработчикам, ни потребителям, ни исследователям, ни экспертам не интересно качество реализации и функционирования средства анализа, всем нужна заветная бумажка — сертификат. Есть бумажка — ПО надёжно и безопасно.
                                                        –4
                                                        Александр, хорошая попытка вскрыть международный заговор строителей систем защиты информации!))

                                                        По поводу качества первой версии нашего продукта АК-ВС: качество – характеристика относительная и, если решили о ней поговорить, то давайте сравнивать с другими аналогичными решениями, а потом уже выплескивать эмоции. Продукт, как вам известно, не единственный на рынке, также никто не мешает вам разработать собственный инструмент анализа.

                                                        Правда, проводить тестирование лучше уже АК-ВС 2, который уже пару лет как представлен на рынке. Если у вас появятся конкретные предложения по улучшению нашего продукта, будем очень признательны.

                                                        Спасибо, что вспомнили ГОСТ Р 56939-2016. В его разработке приняло участие 105 компаний и было учтено более 300 замечаний (в том числе и от вашей). Нам о планах сделать его обязательным ничего неизвестно, но мы бы порадовались, так как вложили в ГОСТ массу интеллектуальных усилий: обратите внимание, что он не является переводом западного, а разработан специально под наши реалии. Если его читать внимательно, то можно заметить, что помимо статического анализа кода, реализованного в AppChecker, предусматривается и динамический анализ, и fuzzy-тестирование, и тестирование на проникновение и многие другие меры.

                                                        Александр, предлагаем вам, как эксперту скачать AppChecker и попробовать его в деле, и поделиться впечатлением на страницах Хабра, а мы вам подарим наш легендарный перекидной календарь!
                                                          +6
                                                          Подпишите инсталлятор и исполняемые модули AppChecker сертификатом
                                                          не солидно выглядит такое распространение софта от фирмы с профилем по ИБ.

                                                          Не эксперт, но заинтригован… что в вашем календаре легендарного — он очень старый… за какой год?

                                                            +2
                                                            npoechelon, нет никакого заговора, но есть безразличие и безответственность.
                                                            Вас оправдывает то, что аналогичные решения написаны такими же безразличными к результату разработчиками? Да и какие это решения, Аист-С? Все, кто работает в отрасли сертификации, прекрасно знает как работает АК-ВС и что большая удача найти хоть какие-то исходники, на которых он отработает без единого сбоя.

                                                            АК-ВС за последние годы стал стандартом де-факто в отрасли сертификации, новые лаборатории покупают у вас продукт, отправляют в ваш учебный центр своих сотрудников, которые после прохождения обучения начинают считают, что раз средство разрешено к применению ФСТЭКом, то это автоматически решает все проблемы. Старый добрый принцип — есть бумажка, значит всё защищено и надёжно. В итоге у среднего специалиста в отрасли сертификации долгие годы было убеждение, что надо просто запустить АК-ВС, скормить ему исходники, сгенерировать протоколы и отправить их для проведения экспертизы, где в их содержимое редко кто будет вчитываться на предмет корректности проведённых работы. Вот и вся работа, вся сертификация на НДВ.

                                                            АК-ВС 2 пару лет представлен на рынке, но при этом работает также некорректно (но чуть лучше, чем АК-ВС). При этом, по старой доброй традиции, это никому не мешает: ни вам — его продавать, ни регуляторам — его разрешать к применению, ни лабораториям — его непосредственно применять.

                                                            Первая редакция ГОСТ представляла собой РД НДВ в новой обёртке. Утверждённая редакция является просто учётом тех замечаний, о которых вы упомянули. Если я правильно помню (это легко уточнить), то те виды исследований, которые вы перечислили, появились именно по результатам устранения замечаний.

                                                            npoechelon, я неоднократно пробовал в деле и АК-ВС, и АК-ВС 2. Вы думаете, что у меня ещё осталась надежда, что у вашей компании есть мотивация делать качественные продукты? Единственная ваша цель — не допустить распространения в отрасли сертификации PVS-Studio и средств анализа путём формального выполнения требования использования статического анализа. В качестве вы не заинтересованы.
                                                              0
                                                              Уважаемый, asasasdd!

                                                              По вашему тексту можно изучать методы геббельсовской пропаганды. Здесь есть и явная ложь («появились именно по результатам устранения замечаний»), и попытки манипулировать («Все, кто работает… прекрасно знает», «Старый добрый принцип…»), и отсутствие конкретики («работает также некорректно, но чуть лучше»), и попытки сталкивать с третьей стороной («ваша цель — не допустить распространения в отрасли сертификации…»), ну и, конечно, перевод на параллельные темы и масса эмоций (в основном обиды). Короче, характерно выделяющийся профиль.

                                                              Так как дискуссия перестает быть конструктивной, этот ответ вам будет заключительным.

                                                              Теперь по пунктам.

                                                              Относительно ГОСТ Р 56939-2016, который вы связали с обсуждаемой темой.

                                                              По поводу исследований, которые с ваших слов появились после замечаний, вот первый вариант стандарта, в котором они все есть, так как были изначально: http://docs.cntd.ru/document/1200113247
                                                              Cмотреть нужно 13-16 стр. asasasdd, ЛГАТЬ – это очень плохо.

                                                              Ликбез. Разумеется, ГОСТ изначально не был копией РД НДВ и не задумывался таковым. Извините, ну уж даже не знать, что упоминаемый статический анализ в РД и в ГОСТ – это абсолютно разные понятия?! Да уж.
                                                              Идея РД НДВ в декомпозиционном подходе к разбору структуры текста программы (там вопросы безопасности косвенно возникают лишь тогда, когда речь идет о защите гостайны уровня аж СС!). Это поймет любой начинающий программист.
                                                              ГОСТ Р 56939-2016 – это (для справки) организационный стандарт, который в том числе включает в себя совокупность мер, которые может обоснованно принять разработчик ПО с целью повышения безопасности программного изделия в рамках именно его жизненного цикла! Здесь, пардон, преследуются другие цели: разработчики стремятся минимизировать риски появления ошибок безопасности, уязвимостей, угроз (связанных их наличием) и т.п. Иные и задачи, и предметная область, и методическая база.

                                                              Относительно АК-ВС, который вы подтянули к обсуждаемой теме:

                                                              Есть старый анекдот:
                                                              — Таки мне Каррузо не понравился: каатавит, гнусавит, в ноты не попадает…
                                                              — Вы были на концерте Карузо?
                                                              — Нет, мне дгуг по телефону напел.

                                                              asasasdd, вы легально используете АК-ВС 2.0 на своем месте работы, и у вас есть конкретные замечания по целевой работе этого продукта? Вы о них сообщили в техподдержку? Разработчики не отреагировали? Номера ваших обращений в техподдержку? Все, как в анекдоте.

                                                              Мы признательны всем за конструктивные конкретные замечания и предложения по улучшению продукта, чему и посвящен данный пост.
                                                          +6
                                                          Спасибо за еще один продукт синтаксического анализа! Я опробовал его, кратко поделюсь впечатлениями.
                                                          User Interface: красиво:) Запускаешь серверную часть, идешь в браузер — создаешь проект. И тут — неудобство:
                                                          Для С/C++ проектов требуется дополнительная конфигурация.
                                                          Я понимаю, почему это сделано, но запускать утилиту, которая мониторит системные вызовы — не всегда удобно.
                                                          На большом проекте, использующем Qt и сторонние библиотеки, утилита сдохла. bad_alloc и падение без каких-либо полезных результатов. Уже минус, т.к. проверять планировал именно его.
                                                          Открыт древний и большой проект, не использующий ничего и написанный на Си с классами:)
                                                          Продукт нашел всего 4 дефекта трех типов, которые, признаюсь, действительно выглядят странно:
                                                          • присвоение переменной самой себе
                                                            tmp = tmp;
                                                          • всегда истинное выражение
                                                            if ( arr[i]!='+' || arr[i]!=' ' )
                                                          • неконтролируемая рекурсия
                                                            ISuperStringStream& MySuperStringStream::operator<<(signed char v)
                                                            {
                                                            	return operator<<((char)v) ;
                                                            }


                                                          При этом параллельно работал CppCheck, который одних только ошибок нашел 37 штук, варнинги и прочие стилистические сообщения я отключил. При этом в коде есть вещи, которые явно указывают на ошибку:
                                                          void nps::MyClassEvent::getTextData(MyElement& el) const 
                                                          {
                                                          	el.doSmth();
                                                          	MyAttributes* attr = new MyAttributes() ;
                                                          	attr->insert("t0") ;
                                                          }

                                                          У меня есть подозрение, что AppChecker анализирует не все файлы и файл с проектом сформировался криво, но как это быстро проверить и исправить, я не знаю.
                                                          Возможно, я что-то делаю не так, однако для быстрой проверки CppCheck и прочие Clang Static Analyzer'ы лично мне подходят удобнее.
                                                          Я допускаю, что ваш продукт можно правильно настроить и он будет работать превосходно, но, например, ручное формирование и загрузка специально сохраненного файла — как-то это долго и лениво.

                                                            +7
                                                            Ради интереса, прошу ещё попробовать PVS-Studio. Выдам ключ на неделю (напишите мне на karpov [@] viva64.com), если демонстрационной версии покажется мало. И прошу написать о результатах.
                                                              +13
                                                              Попробовал и понял, что сравнение в пользу PVS-Studio.
                                                              Проверял я на standalone-приложении. Отпишу кратко впечатления от первого запуска. При первом запуске увидел отличную
                                                              картинку:

                                                              Проверил на двух машинах, проблема воспроизводится. Но на дальнейшую работоспособность никак не влияет, кажется.
                                                              Отличие от AppChecker'а — наличие удобного standalone-приложения: тоже приходится запускать мониторинг вызовов компилятора, но делается это из нормального графического интерфейса, а не из консольной утилиты. Результаты работа мониторинга никуда передавать не надо, они сразу отображаются в окне приложения PVS-Studio Standalone.

                                                              Проект с Qt и сторонними библиотеками вполне себе проверился. Ничего критичного там, к счастью, не нашлось, но пару членов класса мы инициализировать забываем:(
                                                              Древний проект на Си с классами тоже прошел сканирование: 120 ошибок высокой важности, 168 — средней, 870 — низкой. Примеры ошибок:
                                                              V570 The 'Parabola' variable is assigned to itself. SomeFileName.cpp 287
                                                               Parabola      =Parabola ;

                                                              V522 Dereferencing of the null pointer 'SetterGetter' might take place. OtherFileName.cpp 305
                                                              if ( SetterGetter==0 )
                                                              	{
                                                              		SetterGetter->set(false) ;
                                                              	}

                                                              При проверке старых проектов увидел странные дефекты:
                                                              V546 Member of a class is initialized by itself: 'd(d)'. qsharedpointer_impl.h 561
                                                              inline QWeakPointer(X *ptr) : d(ptr ? d->getAndRef(ptr) : 0), value(ptr)
                                                                  { }


                                                              V672 There is probably no need in creating the new 'stream' variable here. One of the function's arguments possesses the same name and this argument is a reference. Check lines: 8, 17. entityhelper.cpp 17
                                                              void PrintEntityParams(QDebug& stream, const paramsMap_t& parameters)  // 8 строка
                                                              {
                                                                  if (parameters.isEmpty())
                                                                  {
                                                                      return;
                                                                  }
                                                              
                                                                  if (!SingletonDataProvider::GetInstance().IsInitialized())
                                                                  {
                                                                      LOG_ERROR("SingletonDataProviderне инициализирован"); // 17 строка
                                                                      return;
                                                                  }
                                                                  const SingletonDataProviderне& storage = SingletonDataProviderне::GetInstance();
                                                                  stream << ". Параметры:";
                                                                  for (paramsMap_t::const_iterator it = parameters.constBegin(); it != parameters.constEnd(); ++it)
                                                                  {
                                                                      stream << storage.GetAttributeName(it.key()) << '='
                                                                          << SomeHelper::GetInstance().ToString(it.value()) << ';';
                                                                  }
                                                              }
                                                              Выяснилось, что макрос LOG_ERROR при разворачивании создает переменную с названием 'stream'. Если я правильно помню описание работы PVS-Studio на хабре, то с такими ложными срабатываниями можно бороться. В рамках быстрого ознакомления этого делать не стал, нет необходимости.
                                                              У PVS-Studio Standalone в контекстном меню найденных ошибок есть пункт «Add TODO comments for selected messages» — удобная вещь для накопления технического долга:)
                                                              Оказалось, что легко и даже приятно исключать директории из сканирования — в частности, ошибки в Qt я предпочел больше не искать.

                                                              Ну и на закуску я решил проверить анализаторы на
                                                              синтаксическом примере:
                                                              
                                                              bool CheckPointer(int value)
                                                              {
                                                                 int* intPtr(new int(value));
                                                                 std::cout << std::endl << *intPtr << std::endl;
                                                                 return true;
                                                              }
                                                              class A {
                                                                      int x;
                                                                      public:
                                                                          void bar() {
                                                                              std::cout << x << "Test!\n";
                                                                          }
                                                              };
                                                              
                                                              int main()
                                                              {
                                                                  CheckPointer(5);
                                                                  A* a;
                                                                  a->bar();
                                                              
                                                                  return 0;
                                                              }


                                                              PVS-Studio нашла две ошибки:
                                                              • V773 The function was exited without releasing the 'intPtr' pointer. A memory leak is possible. main.cpp 224
                                                              • V614 Uninitialized pointer 'a' used. main.cpp 238

                                                              AppChecker: В проекте не обнаружены потенциальные уязвимости и дефекты
                                                              CppCheck тоже говорит, что ошибок не найдено.

                                                              В целом — AppChecker'у есть куда расти как в части удобства, так и в части функциональности анализа. Пока что складывается ощущение, что сделать хотели много (и отчеты, и сравнение результатов сканирования), а получилось — не очень. Но я уверен, что разработчики компании «Эшелон» быстро исправятся и смогут составить достойную конкуренцию на рынке статических анализаторов и в статьях хабра!
                                                              И еще пару слов: наша небольшая команда разработчиков использует обязательное ревью всего кода, часть кода покрыта тестами, флаги компилятора включены по максимуму. И это все равно не избавляет нас от ошибок в коде — при запуске я был неприятно удивлен количеством срабатываний анализатора. Я думаю, что и синтаксический анализ не сможет полностью избавить от ошибок, но уменьшить их количество он сможет наверняка. Поэтому — рекомендую.
                                                                +1
                                                                Большое спасибо за эксперимент и подробный комментарий. Попробуем понять, что там не так с Settings.xml. По этому вопросу, возможно, кто-то из моих коллег напишет Вам личное письмо и уточнит детали.

                                                                Касательно ложного срабатывания. В PVS-Studio есть множество механизмов для их подавления. В данном случае, лучше всего написать комментарий:

                                                                //-V:LOG_ERROR:V672

                                                                Комментарий рационально написать рядом с объявлением макроса. Тогда комментарий будет воздействовать на весь код, где используется этот макрос. Есть и другие способы. Подробности можно посмотреть в документации.

                                                                Ещё раз спасибо.

                                                                P.S. Да, если кому-то интересны графики и т.п., напоминаю, что PVS-Studio умеет интегрироваться с SonarQube.

                                                                  +1

                                                                  Андрей, а у вас нет какого-нибудь не очень большого забагованного проекта для экспериментов? Хочу на выходных попробовать сравнить PVS-Studio, AppChecker, Clang, CppCheck и может быть что-нибудь ещё доступное.

                                                                    +2
                                                                    Задача не простая. По опыту знаю, что бывает непросто найти небольшой интересный проект, который в добавок одновременно адекватно проверится несколькими анализаторами. То кто-то отвалится, то кто-то подведёт и из-за какого-то макроса выдаст слишком много ложных срабатываний. Эти ложные срабатывания можно убрать, но они портят картину, если хочется указать количество ложных срабатываний. Не анализатор плох, а не повезло. А другому не повезёт на другом проекте. :)

                                                                    На вскидку, предлагаю взять проект WinMerge. Но не потому, что он очень забагован :). Просто проект небольшой и должен относительно легко проверяться (правда не знаю, что делать с Clang). Мы про него уже пару раз писали (в 2010 и 2012 году). Но только надо брать старую версию проекта (скажем конца 2009 года), когда до него ещё не добрался ни PVS-Studio, ни Cppcheck.

                                                                    Что ещё… Вот есть такой проект Torque2D. Я его пару месяцев назад быстренько проверил, увидел, что есть ошибки, но не добрался до полноценной проверки и написания статьи. Можно на нём попробовать.
                                                                      +1
                                                                        +2
                                                                        Интересно, а там есть где-нибудь ошибки вида 1, 2, 3? Почему-то в синтетических тестах и коллекциях их обычно нет. Видимо считаются, что такие ошибки не делают. А они есть. :)
                                                                    0

                                                                    Странно, что у вас cppcheck ничего не нашёл.


                                                                    $ cppcheck bugs.cpp
                                                                    [bugs.cpp:21]: (error) Uninitialized variable: a

                                                                    Clang так же остался недоволен:


                                                                    $ g++ -Wall -Wextra -pedantic -Werror -O3 -c bugs.cpp
                                                                    bugs.cpp:21:5: error: variable 'a' is uninitialized when used here [-Werror,-Wuninitialized]
                                                                        a->bar();
                                                                        ^
                                                                    bugs.cpp:20:9: note: initialize the variable 'a' to silence this warning
                                                                        A* a;
                                                                            ^
                                                                             = NULL
                                                                    1 error generated.
                                                                      +3
                                                                      Вы правы, CppCheck всё нашел. Было уже поздно и я проверял файл, в котором этих ошибок еще не было:( В этом плюс AppChecker'а и PVS-Studio: там что ты компилируешь, то и проверяешь.
                                                                      Тем не менее, AppChecker ничего подозрительного не нашел в этом коде.
                                                                      0
                                                                      Да, Вы правы, использовать standalone-приложение конечно приятнее. Но для этого необходимо, чтобы оно было на той же машине, на которой производится сборка проекта, а это не всегда удобно и даже не всегда реально — из-за различий операционных систем и даже архитектур процессора. Особенно, если аудит кода проводит не разработчик, а специалист по QA, ИБ и т.п.
                                                                        +2

                                                                        Вот только PVS-Studio умеет запускаться по-разному, а AppChecker — только через одно место.

                                                                          +5
                                                                          PVS-Studio тоже через одно место умеет!

                                                                          (не сдержался)
                                                                          +4
                                                                          А как аудит кода проводят вышеозначенные специалисты?
                                                                          Насколько я понимаю, им тоже придется запустить сборку проекта, чтобы гарантировать соответствие исходников и бинарных файлов. Или там другие продукты используются?
                                                                            0

                                                                            По идее же, если отчёт был получен с сервера CI, то будет известен коммит, для которого выполнялся анализ. Этот коммит аналитик у себя и зачекаутит.

                                                                              0
                                                                              Поддержка CI (Jenkins, TeamCity) также есть, но в альфа-версии

                                                                              Я не заметил этот комментарий от разработчиков, а сейчас AppChecker на такое не очень способен (по крайней мере, доступная для скачивания версия).
                                                                      0
                                                                      Спасибо за предоставленный отзыв! Как с вами можно напрямую связаться, чтобы получить более подробную информацию для разрешения возникших сложностей?

                                                                      Относительно результатов тестов, то похоже действительно проанализировались не все исходники, поскольку те дефекты, которые нашел pvs и о которых Вы пишете в своем комментарии, AppChecker также обнаруживает)

                                                                      В частности:
                                                                      V570 The 'Parabola' variable is assigned to itself. SomeFileName.cpp 287
                                                                      Parabola      =Parabola ;
                                                                      

                                                                      Это тот же тип дефекта, что и нашелся у Вас:
                                                                      присвоение переменной самой себе
                                                                      tmp = tmp;
                                                                      

                                                                      Относительно V522 Dereferencing of the null pointer 'SetterGetter' might take place. OtherFileName.cpp 305
                                                                      if ( SetterGetter==0 )
                                                                      	{
                                                                      		SetterGetter->set(false) ;
                                                                      	}
                                                                      


                                                                      об этом типе дефекта собственно сама наша статья) было бы подозрительно, если бы мы писали о таком дефекте и при этом его не обнаруживали)
                                                                        +2
                                                                        Да, я сам удивился, что дефект не найден — и поэтому решил сделать специальный пример. Удалось ли проверить этот тест из комментария выше?
                                                                        Я работаю из под QtCreator, pro-файл имеет
                                                                        следующий вид:
                                                                        TEMPLATE = app
                                                                        CONFIG += console
                                                                        CONFIG -= app_bundle
                                                                        CONFIG -= qt
                                                                        
                                                                        SOURCES += main.cpp
                                                                        

                                                                        Собирается MinGw, версия Qt: 4.6.2
                                                                        И CppCheck, и PVS-Studio на нем отработали достойно, а AppChecker почему-то ничего не нашел. Хотя в логах разбор cpp-файла присутствовал.
                                                                        Спрашиваю это потому, что остальные проекты собираются схожим образом, и без примера нет смысла говорить о проверке реального кода.

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

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