Теперь Pvs-Studio Еще Лучше Знает, Что Это За Зверь – Strlen



Теперь PVS-Studio еще лучше знает, что это за зверь – strlen

Как-то так несправедливо, что в наших заметках мы почти не уделяем внимания совершенствованию внутренних механизмов анализатора, в отличие от новой диагностики.

Итак, для разнообразия, давайте взглянем на новое полезное усовершенствование в анализе потоков данных.



Все началось с твита от JetBrains CLion IDE

На днях я увидел в Твиттере пост от JetBrains о новых возможностях статического анализатора, встроенного в CLion.

Теперь PVS-Studio еще лучше знает, что это за зверь – strlen

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

И что есть смысл попробовать PVS-Studio в качестве плагина для CLion, чтобы найти еще больше ошибок.



Теперь PVS-Studio еще лучше знает, что это за зверь – strlen

Ну, я с ними переписывался чуть поприятнее:

После всего этого я подумал.

Но они великолепны! Улучшенный анализ потока данных и рассказать миру.

Чем мы хуже? Ведь внутри анализатора мы тоже постоянно что-то совершенствуем, в том числе и тот самый механизм анализа потока данных.

И сейчас я пишу эту заметку.



Что интересного в Data Flow?

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

Анализатор в некоторых случаях путался со значениями беззнаковых переменных, если происходило переполнение.

Проблема была в примерно таком коде:

  
  
  
  
   

bool foo() { unsigned N = 2; for (unsigned i = 0; i < N; ++i) { bool stop = (i - 1 == N); if (stop) return true; } return false; }

Анализатор не смог понять, что переменная останавливаться всегда присваивается значение ЛОЖЬ .

Почему ЛОЖЬ ? Давайте быстро посчитаем:

  • диапазон значений переменной я = [0; 1] ;
  • возможные значения выражения я-1 = [0; 0] U [UINT_MAX; UINT_MAX] ;
  • значение переменной N, равное двум, не входит в набор {0, UINT_MAX};
  • условие всегда ложно.

Примечание.

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

Теперь мы научили PVS-Studio корректно работать с такими выражениями и выдавать соответствующие предупреждения.

Интересно, что это изменение привело к каскаду других улучшений.

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

Борьба с ними привела к новым улучшениям и обучению анализатора лучше понимать, как и почему функционируют такие функции, как стрлен .

Сейчас мы на практике покажем, о каких улучшениях идет речь.

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

После внесения доработок мы смогли обнаружить в коде интересную ошибку функции Assemble.

int Assemble(unsigned char *output, int addr, char *str) { output[0] = output[1] = output[2] = 0; char astr[128],ins[4]; if ((!strlen(str)) || (strlen(str) > 0x127)) return 1; strcpy(astr,str); .

}

Вы видите ошибку? Честно говоря, мы не сразу это заметили и подумали, что что-то сломали.

И когда мы поняли, в чем дело, мы еще раз восхитились тем, насколько может быть полезен статический анализ.

Предупреждение PVS-Studio: V512 Вызов функции strcpy приведет к переполнению буфера astr. asm.cpp 21 Все еще не видите ошибку? Давайте внимательно посмотрим на код. Для начала удалим все неактуальное:

int Assemble(char *str) { char astr[128]; if ((!strlen(str)) || (strlen(str) > 0x127)) return 1; strcpy(astr,str); .

}

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

Копирование не следует производить, если строка пуста или содержит более 127 символов (не считая терминального нуля).

Пока все логично и правильно? На первый взгляд да.

Но что это?! Что это за константа? 0x127 ?! Это вовсе не 127. Совсем не 127 :) Константа указывается в шестнадцатеричном формате.

Если перевести его в десятичную форму, то получится 295. Таким образом, написанный код эквивалентен следующему:

int Assemble(char *str) { char astr[128]; if ((!strlen(str)) || (strlen(str) > 295)) return 1; strcpy(astr,str); .

}

Как видите, проверка никак не защищает от переполнения буфера, и анализатор корректно предупреждает о проблеме.

Раньше анализатор не мог найти ошибку, не понимая, что две функции стрлен работайте с одной строкой.

И эта строка не меняется между двумя вызовами стрлен .

С точки зрения программиста все это очевидно, но анализатор нужно научить во всем этом разбираться :).

Теперь PVS-Studio из выражения выводит, что длина строки ул.

лежит в диапазоне [1.295], а это значит, что массив может выйти за границы, если попытаться скопировать его в буфер астр .



Теперь PVS-Studio еще лучше знает, что это за зверь – strlen



Новые испытания

Описанная ошибка присутствует и в текущей версии кодовой базы проекта FCEUX. Но мы его не найдем, так как код изменился и теперь длина строки хранится в переменной.

Это нарушает связь между строкой и ее длиной.

К сожалению, о новой версии кода анализатор пока молчит:

int Assemble(unsigned char *output, int addr, char *str) { output[0] = output[1] = output[2] = 0; char astr[128],ins[4]; int len = strlen(str); if ((!len) || (len > 0x127)) return 1; strcpy(astr,str); .

}

Человеку этот код может показаться еще более простым, но с точки зрения статического анализа сложно уследить за значениями.

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

.

Кроме того, вам необходимо внимательно следить за тем, когда эта связь нарушается при изменении содержимого строки или переменной.

Лен .

Анализатор PVS-Studio пока этого сделать не может. Но вы же видите, куда нам можно и нужно развиваться! Со временем мы научимся находить ошибки в этом новом коде.

Кстати, читатель может задаться вопросом, почему мы анализируем старый код проекта и не обновляем его регулярно? Это просто.

Если мы обновим проекты, мы не сможем проводить регрессионное тестирование.

Не будет ясно, произошли ли изменения в работе анализатора из-за изменений в коде самого анализатора или из-за изменений в коде проверяемого проекта.

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

А чтобы протестировать анализатор на современном коде, написанном на C++14, C++17 и т.д., мы постепенно пополняем базу новыми проектами.

Например, сравнительно недавно мы добавлен коллекция библиотек C++ только для заголовков ( потрясающе-hpp ).



Заключение

Разработка механизмов анализа потоков данных интересна и полезна.

А если вам интересно узнать больше о работе статических анализаторов кода в целом, то предлагаем вашему вниманию следующие наши публикации:

  1. Анализатор кода неправильный, да здравствует анализатор
  2. Ложные срабатывания в PVS-Studio: насколько глубока кроличья нора
  3. Технологии, используемые в анализаторе кода PVS-Studio для поиска ошибок и потенциальных уязвимостей
  4. Использование машинного обучения в статическом анализе исходного кода программ
я приглашаю тебя скачать Анализатор PVS-Studio и проверяйте свои проекты.

Если вы хотите поделиться этой статьей с англоязычной аудиторией, воспользуйтесь ссылкой для перевода: Андрей Карпов.

PVS-Studio узнает, что такое strlen .

Теги: #информационная безопасность #C++ #c #статический анализ кода #pvs-studio #статический анализатор кода #strlen #fceux

Вместе с данным постом часто просматривают: