Контакты

Метод статической проверки кода. Анализ унаследованного кода, когда исходный код утрачен: делать или не делать? Комбинирование последовательности действий различных видов анализа в среде разработки

Введение

Стандартные возможности программных продуктов и различных систем управления недостаточны для большинства заказчиков. Системы управления веб-сайтами (например, WordPress, Joomla или Bitrix), бухгалтерские программы, системы управления клиентами (CRM), предприятием и производством (например, 1С и SAP) предоставляют широкие возможности по расширению функциональности и адаптации под потребности конкретных заказчиков. Такие возможности реализуются с помощью сторонних модулей, выполненных на заказ, или кастомизации существующих. Эти модули являются программным кодом, написанным на одном из встроенных языков программирования, взаимодействующим с системой и реализующим необходимые заказчикам функциональные возможности.

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

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

Существует методология защищенной разработки, предусматривающая встраивание процессов аудита и контроля кода на этапе создания программного продукта - SDL (Security Development Lifecycle, защищенный жизненный цикл разработки). Однако применить эту методологию может только разработчик программного обеспечения, если говорить о заказчиках, то SDL для них неприменим, так как процесс подразумевает перестройку алгоритмов создания кода и использовать его при приемке уже поздно. Кроме того, многие разработки затрагивают небольшую часть уже существующего кода, и в этом случае SDL также неприменим.

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

Классификация анализаторов исходного кода

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

  • Первая группа включает в себя анализаторы кода веб-приложений и средства по предотвращению эксплуатации уязвимостей веб-сайтов.
  • Вторая группа - анализаторы встраиваемого кода, позволяющие обнаружить проблемные места в исходных текстах модулей, предназначенных для расширения функциональности корпоративных и производственных систем. К таким модулям относятся программы для линейки продуктов 1С, расширения CRM-систем, систем управления предприятием и систем SAP.
  • Последняя группа предназначена для анализа исходного кода на различных языках программирования, не относящихся к бизнес-приложениям и веб-приложениям. Такие анализаторы предназначены для заказчиков и разработчиков программного обеспечения. В том числе данная группа анализаторов применяется для использования методологии защищенной разработки программных продуктов. Анализаторы статического кода находят проблемы и потенциально уязвимые места в исходных кодах и выдают рекомендации для их устранения.

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

Анализаторы могут содержать различные механизмы анализа, но наиболее распространенным и универсальным является статический анализ исходного кода - SAST (Static Application Security Testing), также существуют методы динамического анализа - DAST (Dynamic Application Security Testing), выполняющие проверки кода при его исполнении, и различные гибридные варианты, совмещающие разные типы анализов. Динамический анализ является самостоятельным методом проверки, который может расширять возможности статического анализа или применяться самостоятельно в тех случаях, когда доступ к исходным текстам отсутствует. В данном обзоре рассматриваются только статические анализаторы.

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

Принципы работы анализаторов исходного кода

Общие принципы работы схожи для всех классов анализаторов: и анализаторов исходного кода веб-приложений, и анализаторов встраиваемого кода. Отличие между этими типами продуктов - только в возможности определить особенности выполнения и взаимодействия кода с внешним миром, что отражается в базах уязвимостей анализаторов. Большая часть анализаторов, представленных на рынке, выполняет функции обоих классов, одинаково хорошо проверяя как встраиваемый в бизнес-приложения код, так и код веб-приложений.

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

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

Рисунок 1. Алгоритм работы анализатора исходных кодов

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

  • Собственная база уязвимостей и ошибок программирования - у каждого разработчика анализаторов исходных кодов есть свои отделы аналитики и исследований, которые готовят специализированные базы для анализа исходных текстов программ. Качество собственной базы - один из ключевых критериев, влияющий на общее качество работы продукта. Кроме того, собственная база должна быть динамической и постоянно обновляемой - новые векторы атак и эксплуатации уязвимостей, а также изменения в языках программирования и методах разработки требуют от разработчиков анализаторов выполнять постоянные обновления базы для сохранения высокого качества проверки. Продукты со статической необновляемой базой чаще всего проигрывают в сравнительных тестах.
  • Государственные базы ошибок программирования - существует ряд государственных баз уязвимостей, составлением и поддержкой которых занимаются регуляторы разных стран. К примеру, в США используется база CWE - Common Weakness Enumeration, обслуживанием которой занимается организация MITRE, поддерживаемая в том числе Министерством обороны США. В России пока отсутствует аналогичная база, но ФСТЭК России в будущем планирует дополнить свои базы уязвимостей и угроз базой по ошибкам программирования. Анализаторы уязвимостей реализуют поддержку базы CWE, встраивая ее в собственную базу уязвимостей или используя как отдельный механизм проверки.
  • Требования стандартов и рекомендации по защищенному программированию - существует как ряд государственных и отраслевых стандартов, описывающих требования к безопасной разработке приложений, так и ряд рекомендаций и «лучших практик» от мировых экспертов в области разработки и защиты программного обеспечения. Данные документы напрямую не описывают ошибки программирования, в отличие от CWE, но содержат перечень методов, которые могут быть преобразованы для использования в статическом анализаторе исходного кода.

От того, какие базы используются в анализаторе, напрямую зависит качество проведения анализа, количество ложных срабатываний и пропущенных ошибок. Кроме того, анализ на соответствие требованиям регуляторов позволяет облегчить и упросить процедуру внешнего аудита инфраструктуры и информационной системы в том случае, если требования являются обязательными. К примеру, требования PCI DSS обязательны для веб-приложений и встраиваемого кода, работающего с платежной информацией по банковским картам, при этом проведение внешнего аудита по выполнению PCI DSS осуществляется в том числе с анализом применяемых программных продуктов.

Мировой рынок

На мировом рынке представлено множество различных анализаторов - как от известных вендоров в области безопасности, так и нишевых игроков, занимающихся только данным классом продуктов. Аналитический центр Gartner ведет классификацию и оценку анализаторов исходных кодов уже более пяти лет, при этом до 2011 года Gartner выделял отдельно статические анализаторы, о которых идет речь в данной статье, позднее объединив их в более высокий класс - средства проверки защищенности приложений (Application Security Testing).

В магическом квадранте Gartner в 2015 году лидерами рынка проверки защищенности являются компании HP, Veracode и IBM. При этом Veracode - единственная из компаний-лидеров, у которой отсутствует анализатор как программный продукт, а функциональность предоставляется только как услуга в облаке компании Veracode. Остальные компании-лидеры предлагают либо исключительно продукты, выполняющие проверки на компьютерах пользователей, либо возможность выбора между продуктом и облачной услугой. Лидерами мирового рынка в течение последних пяти лет остаются компании HP и IBM, обзор их продуктов приведен ниже. Наиболее близок к лидирующим позициям продукт компании Checkmarx, специализирующейся только на данном классе средств, поэтому он также включен в обзор.

Рисунок 2. Магический квадрант аналитиков Gartner по игрокам рынка анализа защищенности приложений в августе 2015 года

По данным отчета аналитиков ReportsnReports , в США объем рынка анализаторов исходных кодов в 2014 году составил $2,5 млрд, к 2019 году прогнозируется двукратный рост до $5 млрд с ежегодным ростом на 14,9%. Более 50% организаций, опрошенных в ходе составления отчета, планируют выделение и увеличение бюджетов на анализ исходного кода при заказной разработке, и только 3% негативно высказались о применении данных продуктов.

Большое число продуктов, находящихся в области претендентов (challengers), подтверждает популярность данного класса продуктов и стремительное развитие отрасли. За последние пять лет общее число производителей в этом квадранте увеличилось почти в три раза, а по сравнению с отчетом за 2014 год добавилось три продукта.

Российский рынок

Российский рынок анализаторов исходных текстов достаточно молод - первые публичные продукты начали появляться на рынке менее пяти лет назад. При этом рынок сформировался из двух направлений - с одной стороны, компании, разрабатывающие продукты для проведения испытаний по выявлению недекларированных возможностей в лабораториях ФСТЭК, ФСБ и Минобороны РФ; с другой стороны - компании, занимающиеся различными областями безопасности и решившие добавить в свое портфолио новый класс продуктов.

Наиболее заметные игроки нового рынка - компании Positive Technologies, InfoWatch, а также Solar Security. Positive Technologies долгое время специализировались на поиске и анализе уязвимостей; в их портфолио есть продукт MaxPatrol - один из лидеров отечественного рынка по внешнему контролю защищенности, поэтому неудивительно, что в компании решили заняться и внутренним анализом и разрабатывать собственный анализатор исходных кодов. Компания InfoWatch развивалась как разработчик DLP-систем, со временем превратившись в группу компаний, находящуюся в поисках новых рыночных ниш. В 2012 году в состав InfoWatch вошла компания Appercut, добавив в портфель InfoWatch средство анализа исходного кода. Инвестиции и опыт InfoWatch позволили быстро развить продукт до высокого уровня. Solar Security официально представили свой продукт Solar inCode только в конце октября 2015 года, но уже на момент выхода имели четыре официальных внедрения в России.

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

Еще одним нишевым игроком российского рынка является Digital Security - консалтинговая компания в области информационной безопасности. Имея большой опыт проведения аудитов и внедрений ERP-систем, она нащупала незанятую нишу и взялась за разработку продукта для анализа безопасности ERP-систем, в числе прочих функций содержащего механизмы анализа исходных кодов для встраиваемых программ.

Краткий обзор анализаторов

Первое средство анализа исходного кода в нашем обзоре - продукт компании Fortify, с 2010 года принадлежащей Hewlett-Packard. В линейке HP Fortify присутствуют различные продукты для анализа программных кодов: есть и SaaS-сервис Fortify On-Demand, предполагающий загрузку исходного кода в облако HP, и полноценное приложение HP Fortify Static Code Analyzer, устанавливаемое в инфраструктуре заказчика.

HP Fortify Static Code Analyzer поддерживает большое число языков программирования и платформ, включая веб-приложения, написанные на PHP, Python, Java/JSP, ASP.Net и JavaScript, и встраиваемый код на языках ABAP (SAP), Action Script и VBScript.

Рисунок 3. Интерфейс HP Fortify Static Code Analyzer

Из особенностей продукта стоит выделить наличие в HP Fortify Static Code Analyzer поддержки интеграции с различными системами управления разработкой и отслеживания ошибок. Если разработчик программного кода предоставляет заказчику доступ к прямой передаче сообщений об ошибках в Bugzilla, HP Quality Center или Microsoft TFS, анализатор может автоматически создавать сообщения об ошибках в этих системах без необходимости ручных действий.

Работа продукта основана на собственных базах знаний HP Fortify, сформированных адаптацией базы CWE. В продукте реализован анализ на выполнение требований DISA STIG, FISMA, PCI DSS и рекомендаций OWASP.

Из недостатков HP Fortify Static Code Analyzer следует отметить отсутствие локализации продукта для российского рынка - интерфейс и отчеты на английском языке, отсутствие материалов и документации на продукт на русском языке, не поддерживается анализ встраиваемого кода для 1С и других отечественных продуктов enterprise-уровня.

Преимущества HP Fortify Static Code Analyzer:

  • известный бренд, высокое качество решения;
  • большой перечень анализируемых языков программирования и поддерживаемых сред разработки;
  • наличие возможности интеграции с системами управления разработкой и другими продуктами HP Fortify;
  • поддержка международных стандартов, рекомендаций и «лучших практик».

Checkmarx CxSAST - средство американо-израильской компании Сheckmarx, специализирующейся на разработке анализаторов исходных кодов. Данный продукт предназначен в первую очередь для анализа обычного программного обеспечения, но за счет поддержки языков программирования PHP, Python, JavaScript, Perl и Ruby отлично подходит для анализа веб-приложений. Checkmarx CxSAST это универсальный анализатор, не имеющий ярко выраженной специфики и поэтому подходящий для применения на любых этапах жизненного цикла программного продукта - от разработки до применения.

Рисунок 4. Интерфейс Checkmarx CxSAST

В Checkmarx CxSAST реализована поддержка базы ошибок программного кода CWE, поддерживаются проверки на соответствие рекомендациям OWASP и SANS 25, стандартам PCI DSS, HIPAA, MISRA, FISMA и BSIMM. Все обнаруженные Checkmarx CxSAST проблемы разделяются по степени риска - от незначительного до критического. Из особенностей продукта - наличие функций по визуализации кода с построением блок-схем маршрутов выполнения и рекомендациями по исправлению проблем с привязкой к графической схеме.

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

Преимущества Checkmarx CxSAST:

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

Еще один продукт от известного вендора - анализатор исходных кодов IBM Security AppScan Source. Линейка AppScan включает множество продуктов, связанных с безопасной разработкой программного обеспечения, но для применения у заказчиков программного кода остальные продукты не подойдут, так как обладают большим количеством излишнего функционала. IBM Security AppScan Source, как и Checkmarx CxSAST, в первую очередь предназначен для организаций-разработчиков, при этом поддерживает даже меньшее число языков веб-разработки - только PHP, Perl и JavaScript. Языки программирования для встраиваемого в бизнес-приложения кода не поддерживаются.

Рисунок 5. Интерфейс IBM Security AppScan Source

IBM Security AppScan Source тесно интегрируется с платформой для разработки IBM Rational, поэтому продукт чаще всего используется на этапе разработки и тестирования программных продуктов и не очень хорошо подходит для выполнения приемки или проверки разработанного на заказ приложения.

Особенностью IBM Security AppScan Source является разве что поддержка анализа программ для IBM Worklight - платформы для мобильных бизнес-приложений. Перечень поддерживаемых стандартов и требований скуден - PCI DSS и рекомендации DISA и OWASP, база уязвимостей сопоставляет найденные проблемы с CWE.

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

AppChecker от отечественной компании ЗАО «НПО Эшелон» - решение, появившееся на рынке совсем недавно. Первая версия продукта вышла всего год назад, но при этом следует учитывать опыт компании «Эшелон» в анализе программного кода. «НПО Эшелон» является испытательной лабораторией ФСТЭК, ФСБ и Министерства обороны РФ и имеет большой опыт в области проведения статического и динамического анализа исходных текстов программ.

Рисунок 6. Интерфейс «Эшелон» AppChecker

AppChecker предназначен для анализа разнообразного программного обеспечения и веб-приложений, написанных на языках PHP, Java и C/C++. Полностью поддерживает классификацию уязвимостей CWE и учитывает рекомендации OWASP, CERT и NISP. Продукт можно использовать для выполнения аудита на соответствие требованиям PCI DSS и стандарта Банка России ИББС-2.6-2014.

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

Преимущества:

  • наличие возможности проведения аудита по отечественным требованиям и PCI DSS;
  • учет влияния особенностей языков программирования за счет гибкой конфигурации анализируемых проектов;
  • низкая стоимость.

PT Application Inspector - продукт российского разработчика Positive Technologies, отличающийся своим подходом к решению проблемы анализа исходного кода. PT Application Inspector нацелен в первую очередь на поиск уязвимостей в коде, а не на выявление общих программных ошибок.

В отличие от всех остальных продуктов в данном обзоре, PT Application Inspector обладает не только возможностью составления отчета и демонстрации уязвимых мест, но и способностью автоматически создавать эксплоиты для отдельных категорий и видов уязвимостей - небольшие исполняемые модули, эксплуатирующие найденные уязвимости. С помощью созданных эксплоитов можно на практике проверять опасность найденных уязвимостей, а также контролировать разработчика, проверив работу эксплоита после декларированного закрытия уязвимости.

Рисунок 7. Интерфейс PT Application Inspector

PT Application Inspector поддерживает как языки разработки веб-приложений (PHP, JavaScript), так и встраиваемый код для бизнес-приложений - SAP ABAP, SAP Java, Oracle EBS Java, Oracle EBS PL/SQL. Также продукт PT Application Inspector поддерживает визуализацию маршрутов выполнения программ.

PT Application Inspector является универсальным решением как для разработчиков, так и для заказчиков, эксплуатирующих разработанные на заказ веб-приложения и встраиваемые модули для бизнес-приложений. База уязвимостей и ошибок в программном коде содержит собственные наработки компании Positive Technologies, базу CWE и WASC (база уязвимостей веб-консорциума, аналог CWE для веб-приложений).

Использование PT Application Inspector позволяет выполнить требования стандартов PCI DSS, СТО БР ИББС, а также 17 приказа ФСТЭК и требования по отсутствию недекларированных возможностей (актуально при сертификации кода).

Преимущества:

  • поддержка анализа веб-приложений и большого набора систем разработки для бизнес-приложений;
  • отечественный, локализованный продукт;
  • широкий набор поддерживаемых государственных стандартов;
  • использование базы уязвимостей веб-приложений WASC и классификатора CWE;
  • возможность визуализации программного кода и поиска программных закладок.

InfoWatch Appercut разработан российской компанией InfoWatch. Основное отличие данного продукта от всех остальных в этой подборке - специализация на предоставлении сервиса для заказчиков бизнес-приложений.

InfoWatch Appercut поддерживает практически все языки программирования, на которых создаются веб-приложения (JavaScript, Python, PHP, Ruby) и встраиваемые модули для бизнес-предложений - 1С, ABAP, X++ (ERP Microsoft Axapta), Java, Lotus Script. InfoWatch Appercut обладает способностью подстраиваться под специфику конкретного приложения и уникальность бизнес-процессов каждой компании.

Рисунок 8. Интерфейс InfoWatch Appercut

InfoWatch Appercut поддерживает многие требования по эффективному и безопасному программированию, включая общие требования PCI DSS и HIPPA, рекомендации и «лучшие практики» CERT и OWAST, а также рекомендации производителей платформ бизнес-процессов - 1С, SAP, Oracle, Microsoft.

Преимущества:

  • отечественный, локализованный продукт, сертифицированный ФСТЭК России;
  • единственный продукт, поддерживающий все популярные в России бизнес-платформы, включая 1С, SAP, Oracle EBS, IBM Collaboration Solutions (Lotus) и Microsoft Axapta;
  • быстрый сканер, выполняющий проверки за считанные секунды и способный проверять только измененный код и фрагменты кода.

Digital Security ERPScan - специализированный продукт для анализа и мониторинга защищенности бизнес-систем, построенных на продуктах SAP, первая версия выпущена в 2010 году. В состав ERPScan входит помимо модуля анализа конфигураций, уязвимостей и контроля доступа (SOD) модуль оценки безопасности исходного кода, реализующий функции поиска закладок, критичных вызовов, уязвимостей и ошибок программирования в коде на языках программирования ABAP и Java. При этом продукт учитывает специфику платформы SAP, проводит корреляцию обнаруженных уязвимостей в коде с настройками конфигурации и правами доступа и выполняет анализ лучше, чем неспециализированные продукты, работающие с теми же языками программирования.

Рисунок 9. Интерфейс Digital Security ERPScan

Из дополнительных функций ERPScan можно отметить возможность автоматической генерации исправлений для обнаруженных уязвимостей а также генерацию сигнатур для возможных атак и выгрузку этих сигнатур в системы обнаружения и предотвращений вторжений (в партнерстве с CISCO). Кроме того в системе присутствуют механизмы оценки производительности встраиваемого кода, что является критичным для бизнес-приложений, так как медленная работа дополнительных модулей может серьезно отразиться на бизнес-процессах в организации. Система также поддерживает анализ в соответствии со специфичными рекомендациями по анализу кода бизнес-приложений, такими, как EAS-SEC и BIZEC а также общими рекомендациями PCI DSS и OWASP.

Преимущества:

  • глубокая специализация на одной платформе бизнес-приложений с корреляцией анализа с настройками конфигурации и правами доступа;
  • тесты производительности встраиваемого кода;
  • автоматическое создание исправлений к найденным уязвимостям и виртуальных патчей;
  • поиск уязвимостей нулевого дня.

Solar inCode - инструмент статического анализа кода, предназначенный для выявления уязвимостей информационной безопасности и недекларированных возможностей в исходных текстах программного обеспечения. Основной отличительной чертой продукта является возможность восстанавливать исходный код приложений из рабочего файла с использованием технологии декомпиляции (обратной инженерии).

Solar inCode позволяет проводить анализ исходного кода, написанного на языках программирования Java, Scala, Java for Android, PHP и Objective C. В отличие от большинства конкурентов, в перечне поддерживаемых языков программирования присутствуют средства разработки для мобильных платформ Android и iOS.

Рисунок 10. Интерфейс

В случаях, когда исходный код не доступен, Solar inCode позволяет осуществить анализ готовых приложений, эта функциональность поддерживает веб-приложения и мобильные приложения. В частности, для мобильных приложений достаточно просто скопировать в сканер ссылку на приложение из Google Play или Apple Store, приложение будет автоматически загружено, декомпилировано и проверено.

Использование Solar inCode позволяет выполнить требования стандартов PCI DSS, СТО БР ИББС, а также 17 приказа ФСТЭК и требования по отсутствию недекларированных возможностей (актуально при сертификации кода).

Преимущества:

  • Поддержка анализа приложений для мобильных устройств под управлением Android и iOS;
  • поддерживает анализ веб-приложений и мобильных приложений без использования исходных текстов программ;
  • выдает результаты анализа в формате конкретных рекомендаций по устранению уязвимостей;
  • формирует детальные рекомендации по настройке средств защиты: SIEM, WAF, FW, NGFW;
  • легко интегрируется в процесс безопасной разработки ПО за счет поддержки работы с репозиториями исходных текстов.

Выводы

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

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

В данном обзоре явным лидером среди иностранных продуктов по поддержке языков программирования и качеству сканирования является решение HP Fortify Static Code Analyzer. Также хорошим продуктом является Checkmarx CxSAST, но он способен анализировать только обычные приложения и веб-приложения, поддержка встраиваемых модулей для бизнес-приложений в продукте отсутствует. Решение IBM Security AppScan Source на фоне конкурентов выглядит блекло и не отличается ни функциональностью, ни качеством проверок. Впрочем, этот продукт не предназначен для бизнес-пользователей и направлен на использование в компаниях-разработчиках, где он может показывать большую эффективность, чем конкуренты.

Среди российских продуктов сложно выделить однозначного лидера, рынок представляют три основных продукта – InfoWatch Appercut, PT Application Inspector и Solar inCode. При этом данные продукты существенно различаются технологически и предназначены для разных целевых аудиторий - первый поддерживает больше платформ бизнес-приложений и отличается большим быстродействием за счет поиска уязвимостей исключительно статическими методами анализа. Второй - сочетает в себе статический и динамический анализ, а также их комбинацию, что одновременно с улучшением качества сканирования приводит к увеличению времени проверки исходного кода. Третий же направлен на решение проблем бизнес-пользователей и специалистов по информационной безопасности, а также позволяет проверять приложения без доступа к исходному коду.

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

Digital Security ERPScan является отличным продуктом для решения узкоспециализированной задачи анализа бизнес-приложений для платформы SAP. Сконцентрировавшись только на этом рынке, компания Digital Security разработала уникальный по своей функциональности продукт, который не только проводит анализ исходного кода, но и учитывает всю специфику платформы SAP, конкретных настроек конфигурации и прав доступа бизнес-приложений, а также обладает возможностью автоматического создания исправлений к обнаруженным уязвимостям.


Аннотация

Статический анализ - это способ проверки исходного кода программы на корректность. Процесс статического анализа состоит из трех этапов. Сначала анализируемый код разбивается на лексемы - константы, идентификаторы, и т. д. Эта операция выполняется лексером. Затем лексемы передаются синтаксическому анализатору, который выстраивает по этим лексемам дерево кода. Наконец, проводится статический анализ построенного дерева. В данной обзорной статье приведено описание трех методов статического анализа: анализ с обходом дерева кода, анализ потока данных и анализ потока данных с выбором путей.

Введение

Тестирование является важной частью процесса разработки приложений. Существует множество различных видов тестирования, в том числе и два вида, касающиеся программного кода: статический анализ и динамический анализ.

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

Статический анализ позволяет проверять исходный код программы до ее выполнения. В частности, любой компилятор проводит статический анализ при компиляции. Однако, в больших реальных проектах зачастую возникает необходимость проверить весь код на предмет соответствия некоторым дополнительным требованиям. Эти требования могут быть весьма разнообразны, начиная от правил именования переменных и заканчивая мобильностью (например, код должен благополучно выполняться на платформах х86 и х64). Наиболее распространенными требованиями являются:

  • Надежность - меньшее количество ошибок в тестируемой программе.
  • Удобство сопровождения - более понятный код, который легко изменять и усовершенствовать.
  • Мобильность - гибкость тестируемой программы при запуске на различных платформах.
  • Удобочитаемость - сокращение времени, необходимого для понимания кода.

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

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

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

Процесс анализа

Процесс статического анализа состоит из двух основных шагов: создания дерева кода (также называемого ) и анализа этого дерева.

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

В общем случае дерево строится только для анализируемого фрагмента кода (например, для какой-то конкретной функции). Для того чтобы создать дерево код обрабатывается сначала , а затем .

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

Рассмотрим примерный алгоритм определения типа лексемы.

Если первый символ лексемы является цифрой, лексема считается числом, если этот символ является знаком "минус", то это - отрицательное число. Если лексема является числом, она может быть числом целым или дробным. Если в числе содержится буква E, определяющая экспоненциальное представление, или десятичная точка, число считается дробным, в противном случае - целым. Заметим, что при этом может возникнуть лексическая ошибка - если в анализируемом исходном коде содержится лексема "4xyz", лексер сочтет ее целым числом 4. Это породит синтаксическую ошибку, которую сможет выявить синтаксический анализатор. Однако подобные ошибки могут обнаруживаться и лексером.

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

Наконец, если лексема не является строкой, она должна быть идентификатором, зарезервированным словом, или зарезервированным символом. Если лексема не подходит и под эти категории, возникает лексическая ошибка. Лексер не будет обрабатывать эту ошибку самостоятельно - он только сообщит синтаксическому анализатору, что обнаружена лексема неизвестного типа. Обработкой этой ошибки займется синтаксический анализатор.

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

В то время как лексер понимает лишь синтаксис языка, синтаксический анализатор также распознает и контекст. Например, объявим функцию на языке Си:

Int Func(){return 0;}

Лексер обработает эту строку и разобьет ее на лексемы как показано в таблице 1:

Таблица 1 - Лексемы строки "int Func(){return 0};".

Строка будет распознана как 8 корректных лексем, и эти лексемы будут переданы синтаксическому анализатору.

Этот анализатор просмотрит контекст и выяснит, что данный набор лексем является объявлением функции, которая не принимает никаких параметров, возвращает целое число, и это число всегда равно 0.

Синтаксический анализатор выяснит это, когда создаст дерево кода из лексем, предоставленных лексером, и проанализирует это дерево. Если лексемы и построенное из них дерево будут сочтены правильными - это дерево будет использовано при статическом анализе. В противном случае синтаксический анализатор выдаст сообщение об ошибке.

Однако процесс построения дерева кода не сводится к простому представлению лексем в виде дерева. Рассмотрим этот процесс подробнее.

Дерево кода

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

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

При разработке вершин дерева в первую очередь обычно определяется уровень модульности. Иными словами, определяется, будут ли все конструкции языка представлены вершинами одного типа, различаемыми по значениям. В качестве примера рассмотрим представление бинарных арифметических операций. Один вариант - использовать для всех бинарных операций одинаковые вершины, одним из атрибутов которых будет тип операции, например, "+". Другой вариант - использовать для разных операций вершины различного типа. В объектно-ориентированном языке это могут быть классы вроде AddBinary, SubstractBinary, MultipleBinary, и т. п., наследуемые от абстрактного базового класса Binary.

В качестве примера разберем два выражения: 1 + 2 * 3 + 4 * 5 и 1+ 2 * (3 + 4) * 5 (см. рисунок 1).

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

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

Методы статического анализа

Существует множество различных методов , в частности, анализ с , анализ потока данных, анализ потока данных с выбором пути и т. д. Конкретные реализации этих методов различны в разных анализаторах. Тем не менее, статические анализаторы для различных языков программирования могут использовать один и тот же базовый код (инфраструктуру). Эти инфраструктуры содержат набор основных алгоритмов, которые могут использоваться в разных анализаторах кода вне зависимости от конкретных задач и анализируемого языка. Набор поддерживаемых методов и конкретная реализация этих методов, опять же, будет зависеть от конкретной инфраструктуры. Например, инфраструктура может позволять легко создавать анализатор, использующий обход дерева кода, но не поддерживать анализ потока данных .

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

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

Анализ потока данных можно описать как процесс сбора информации об использовании, определении и зависимостях данных в анализируемой программе. При анализе потока данных используется граф потока команд, генерируемый на основе дерева кода. Этот граф представляет все возможные пути выполнения данной программы: вершины обозначают "прямолинейные", без каких бы то ни было переходов, фрагменты кода, а ребра - возможную передачу управления между этими фрагментами. Поскольку анализ выполняется без запуска проверяемой программы, точно определить результат ее выполнения невозможно. Иными словами, невозможно выяснить, по какому именно пути будет передаваться управление. Поэтому алгоритмы анализа потока данных аппроксимируют возможное поведение, например, рассматривая обе ветви оператора if-then-else, или выполняя с определенной точностью тело цикла while. Ограничение точности существует всегда, поскольку уравнения потока данных записываются для некоторого набора переменных, и количество этих переменных должно быть ограничено, поскольку мы рассматриваем лишь программы с конечным набором операторов. Следовательно, для количества неизвестных всегда существует некий верхний предел, дающий ограничение точности. С точки зрения графа потока команд при статическом анализе все возможные пути выполнения программы считаются действительными. Из-за этого допущения при анализе потока данных можно получать лишь приблизительные решения для ограниченного набора задач .

Описанный выше алгоритм анализа потока данных не различает путей, поскольку все возможные пути, вне зависимости от того реальны они, или нет, будут ли они выполняться часто, или редко, все равно приводят к решению. На практике, однако, выполняется лишь малая часть потенциально возможных путей. Более того, самый часто выполняемый код, как правило, составляет еще меньшее подмножество всех возможных путей. Логично сократить анализируемый граф потока команд и уменьшить таким образом объем вычислений, анализируя лишь некоторое подмножество возможных путей. Анализ с выбором путей проводится по сокращенному графу потока команд, в котором нет невозможных путей и путей, не содержащих "опасного" кода. Критерии выбора путей различны в различных анализаторах. Например, анализатор может рассматривать лишь пути, содержащие объявления динамических массивов, считая такие объявления "опасными" согласно настройкам анализатора.

Заключение

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

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

Библиографический список

  • Dirk Giesen Philosophy and practical implementation of static analyzer tools . -Electronic data. -Dirk Giesen, cop. 1998.
  • James Alan Farrell Compiler Basics . -Electronic data. -James Alan Farrell, cop 1995. -Access mode: http://www.cs.man.ac.uk/~pjj/farrell/compmain.html
  • Joel Jones Abstract syntax tree implementation idioms . -Proceedings of the 10th Conference on Pattern Languages of Programs 2003, cop 2003.
  • Ciera Nicole Christopher Evaluating Static Analysis Frameworks .- Ciera Nicole, cop. 2006.
  • Leon Moonen A Generic Architecture for Data Flow Analysis to Support Reverse Engineering . - Proceedings of the 2nd International Workshop on the Theory and Practice of Algebraic Specifications, cop. 1997.

При статическом анализе (static analysis) можно обнаружить много разнообразных дефектов и слабых мест исходного кода даже до того, как код будет готов для запуска. С другой стороны, динамический анализ (runtime analysis), или анализ во время выполнения, происходит на работающем программном обеспечении и обнаруживает проблемы по мере их возникновения, обычно используя сложные инструментальные средства. Кто-то может возразить, что одна форма анализа предваряет другую, но разработчики могут комбинировать оба способа для ускорения процессов разработки и тестирования, а также для повышения качества выдаваемого продукта.

В данной статье вначале рассматривается метод статического анализа. С его помощью можно предотвратить проблемы до их проникновения в состав основного программного кода и гарантировать, что новый код соответствует стандарту. Используя разные техники анализа, например, проверку абстрактного синтаксического дерева (abstract syntax tree, AST) и анализ кодовых путей, инструменты статического анализа могут выявить скрытые уязвимости, логические ошибки, дефекты реализации и другие проблемы. Это может происходить как на этапе разработки на каждом рабочем месте, так и во время компоновки системы. Далее в статье исследуется метод динамического анализа, который можно использовать на этапе разработки модулей и системной интеграции и который позволяет выявить проблемы, пропущенные при статическом анализе. При динамическом анализе не только обнаруживаются ошибки, связанные с указателями и другими некорректностями, но и есть возможность также оптимизировать использование циклов ЦПУ, ОЗУ, флеш-памяти и других ресурсов.

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

Объединяя лучшее из двух миров

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

На этапе статического анализа обнаруживаются и описываются области исходного кода со слабыми местами, включая скрытые уязвимости, логические ошибки, дефекты реализации, некорректности при выполнении параллельных операций, редко возникающие граничные условия и многие другие проблемы. Например, инструменты статического анализа Klocwork Insight выполняют глубокий анализ исходного кода на синтаксическом и семантическом уровнях. Эти инструментальные средства выполняют также сложный межпроцедурный анализ управляющих потоков и потоков данных и используют усовершенствованную технику отсекания ложных путей, оценивают значения, которые будут принимать переменные и моделируют потенциальное поведение программы во время исполнения.

Разработчики могут использовать инструменты статического анализа в любое время на этапе разработки, даже когда написаны лишь фрагменты проекта. Однако чем более завершенным является код, тем лучше. При статическом анализе могут просматриваться все потенциальные пути исполнения кода – при обычном тестировании такое происходит редко, если только в проекте не требуется обеспечения 100%-го покрытия кода. Например, при статическом анализе можно обнаружить программные ошибки, связанные с краевыми условиями или ошибками путей, не тестируемыми во время разработки.

Поскольку во время статического анализа делается попытка предсказать поведение программы, основанное на модели исходного кода, то иногда обнаруживается "ошибка", которой фактически не существует – это так называемое "ложное срабатывание" (false positive). Многие современные средства статического анализа реализуют улучшенную технику, чтобы избежать подобной проблемы и выполнить исключительно точный анализ.

Статический анализ: аргументы "за" Статический анализ: аргументы "против"
Используется уже на ранних этапах жизненного цикла программного обеспечения, прежде чем код готов для исполнения и до начала тестирования.

Могут анализироваться существующие базы кодов, которые уже прошли тестирование.

Средства могут быть интегрированы в среду разработки в качестве части компонента, используемого при "ночных сборках" (nightly builds) и как часть инструментального набора рабочего места разработчика.

Низкие стоимостные затраты: нет необходимости создавать тестовые программы или фиктивные модули (stubs); разработчики могут запускать свои собственные виды анализа.

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

Ненулевая вероятность "ложного срабатывания".

Таблица 1 - Аргументы "за" и "против" статического анализа.

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

Во многих случаях в инструменте динамического анализа производится модификация исходного или бинарного кода приложения, чтобы установить ловушки, или процедуры-перехватчики (hooks), для проведения инструментальных измерений. C помощью этих ловушек можно обнаружить программные ошибки на этапе выполнения, проанализировать использование памяти, покрытие кода и проверить другие условия. Инструменты динамического анализа могут генерировать точную информацию о состоянии стека, что позволяет отладчикам отыскать причину ошибки. Поэтому, когда инструменты динамического анализа находят ошибку, то, скорее всего, это настоящая ошибка, которую программист может быстро идентифицировать и исправить. Следует заметить, что для создания ошибочной ситуации на этапе выполнения должны существовать точно необходимые условия, при которых проявляется программная ошибка. Соответственно, разработчики должны создать некоторый контрольный пример для реализации конкретного сценария.

Динамический анализ: аргументы "за" Динамический анализ: аргументы "против"
Редко возникают "ложные срабатывания" – высокая продуктивность по нахождению ошибок

Для отслеживания причины ошибки может быть произведена полная трассировка стека и среды исполнения.

Захватываются ошибки в контексте работающей системы, как в реальных условиях, так и в режиме имитационного моделирования.

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

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

Таблица 2 - Аргументы "за" и "против" динамического анализа.

Раннее обнаружение ошибок для уменьшения затрат на разработку

Чем раньше обнаруживается программная ошибка, тем быстрее и дешевле ее можно исправить. Поэтому инструменты статического и динамического анализа представляют реальную ценность, обеспечивая поиск ошибок на ранних этапах жизненного цикла программного обеспечения. Различные исследования промышленных продуктов показывают, что коррекция проблемы на этапе тестирования системы (для подтверждения качества ее работы, QA) или уже после поставки системы оказывается на несколько порядков более дорогостоящей, чем устранение тех же проблем на этапе разработки программного обеспечения. Многие организации имеют свои оценочные показатели относительно затрат на устранение дефектов. На рис. 1 приведены данные по обсуждаемой проблеме, взятые из часто цитируемой книги Каперса Джонса (Capers Jones) "Applied Software Measurement" ("Прикладные измерения программного обеспечения") .

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

Статический анализ

Статический анализ используется в практике программных разработок почти столь же долго, как существует сама разработка программного обеспечения. В своем первоначальном виде анализ сводился к контролю соответствия стандартам стиля программирования (lint). Разработчики пользовались этим непосредственно на своем рабочем месте. Когда дело доходило до обнаружения программных ошибок, то в ранних инструментах статического анализа основное внимание обращалось на то, что лежит на поверхности: стиль программирования и часто возникающие синтаксические ошибки. Например, даже самые простейшие инструменты статического анализа смогут обнаружить ошибку такого рода:

Int foo(int x, int* ptr) { if(x & 1); { *ptr = x; return; } ... }

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

Ранние инструменты анализа обращали внимание, главным образом, на синтаксические ошибки. Поэтому, хотя при этом и можно было обнаружить серьезные ошибки, все же большинство обнаруживаемых проблем оказывались относительно тривиальными. Кроме того, для инструментов предоставлялся достаточно небольшой кодовый контекст, чтобы можно было ожидать точных результатов. Это происходило потому, что работа проводилась в течение типичного цикла компиляции/компоновки при разработке, а то, что делал разработчик, представляло собой лишь маленькую частицу кода большой программной системы. Такой недостаток приводил к тому, что в инструменты анализа закладывались оценки и гипотезы относительно того, что может происходить за пределами "песочницы" разработчика. А это, в свою очередь, приводило к генерации повышенного объема отчетов с "ложными срабатываниями".

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

Для выполнения таких изощренных форм анализа в инструментах статического анализа имеют дело с двумя основными типами проверок кода:

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

Абстрактные синтаксические деревья

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

Очень просто построить программу контроля, которая проверяет соблюдение стандартов относительно соглашения по присваиванию имен и ограничений по функциональным вызовам, например, контроль небезопасных библиотек. Цель выполнения проверок по AST обычно состоит в получении каких-либо логических выводов на основании кода как такового без использования знаний о поведении кода в процессе исполнения.

Многие инструменты предлагают проверки на основе AST для множества языков, включая инструменты с открытым исходным кодом, например, PMD для Java. Некоторые инструменты используют грамматику Х-путей или производную от Х-путей грамматику, чтобы определять условия, которые представляют интерес для программ контроля. Другие инструменты предоставляют расширенные механизмы, дающие возможность пользователям создавать свои собственные программы проверки на основе AST. Такой тип проверки относительно просто проводить, и многие организации создают новые программы проверки такого типа, чтобы проверить соблюдение корпоративных стандартов написания кода или рекомендованных по отрасли лучших методов организации работ.

Анализ кодовых путей

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

If(x & 1) ptr = NULL; *ptr = 1;

Поверхностное рассмотрение фрагмента приводит к очевидному выводу, что переменная ptr может принимать значение NULL, если переменная x – нечетна, и это условие при разыменовании неизбежно приведет к обращению к нулевой странице. Тем не менее, при создании программы проверки на основе AST найти такую программную ошибку весьма проблематично. Рассмотрим дерево AST (в упрощенном виде для ясности), которое было бы создано для приведенного выше фрагмента кода:

Statement Block If-statement Check-Expression Binary-operator & x 1 True-Branch Expression-statement Assignment-operator = ptr 0 Expression-statement Assignment-operator = Dereference-pointer - ptr 1 В подобных случаях никакой поиск по дереву или простое перечисление узлов не смогут обнаружить в разумно обобщенной форме предпринимаемую попытку (по меньшей мере, иногда недопустимую) разыменования указателя ptr. Соответственно, инструмент анализа не может просто проводить поиск по синтаксической модели. Необходимо также анализировать жизненный цикл объектов данных по мере их появления и использования внутри управляющей логики в процессе исполнения.

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

  • Не был ли вновь созданный объект освобожден прежде, чем из области видимости были удалены все обращающиеся к нему ссылки?
  • Проводилась ли для некоторого объекта данных проверка разрешенного диапазона значений, прежде чем объект передается функции ОС?
  • Проверялась ли строка символов на наличие в ней специальных символов перед передачей этой строки в качестве SQL-запроса?
  • Не приведет ли операция копирования к переполнению буфера?
  • Безопасно ли в данное время вызывать эту функцию?

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

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

Последовательность действий при выполнении статического анализа

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

Уникальный в своем роде продукт Klocwork Insight позволяет проводить анализ кода, создаваемого на рабочем месте конкретного разработчика, при этом избегать проблем, связанных с неточностью диагностики, что обычно свойственно инструментам подобного рода. Компания Klocwork предоставляет возможность проведения связного анализа кода на рабочем месте (Connected Desktop Analysis), когда выполняется анализ кода разработчика с учетом понимания всех системных зависимостей. Это приводит к проведению локального анализа, который является в такой же мере точным и мощным, как и централизованный системный анализ, но все это выполняется до полной сборки кода.

С точки зрения перспектив относительно последовательности действий при анализе, данная способность позволяет разработчику провести точный и высококачественный статический анализ на самом раннем этапе жизненного цикла разработки. Инструмент Klockwork Insight выдает в интегрированную среду разработчика (IDE) или в командную строку сообщения по всем проблемам по мере написания разработчиком кода и периодического выполнения им операций компиляции/компоновки. Выдача таких сообщений и отчетов происходит до выполнения динамического анализа и до того, как все разработчики сведут свои коды воедино.

Рис. 2 - Последовательность выполнения статического анализа.

Технология динамического анализа

Для обнаружения программных ошибок в инструментах динамического анализа часто производится вставка небольших фрагментов кода либо непосредственно в исходный текст программы (вставка в исходный код), либо в исполняемый код (вставка в объектный код). В таких кодовых сегментах выполняется "санитарная проверка" состояния программы и выдается отчет об ошибках, если обнаруживается что-то некорректное или неработоспособное. В таких инструментах могут быть задействованы и другие функции, например, отслеживание распределения памяти и ее использования во времени.

Технология динамического анализа включает в себя:

  • Размещение вставок в исходный код на этапе препроцессорной обработки – в исходный текст приложения до начала компиляции вставляется специальный фрагмент кода для обнаружения ошибок. При таком подходе не требуется детального знания среды исполнения, в результате чего такой метод пользуется популярностью среди инструментов тестирования и анализа встраиваемых систем. В качестве примера такого инструмента можно привести продукт IBM Rational Test RealTime.
  • Размещение вставок в объектный код – для такого инструмента динамического анализа необходимо обладать достаточными знаниями относительно среды исполнения, чтобы иметь возможность вставлять код непосредственно в исполняемые файлы и библиотеки. При этом подходе не нужно иметь доступа к исходному тексту программы или делать перекомпоновку приложения. В качестве примера такого инструмента можно привести продукт IBM Rational Purify.
  • Вставка кода во время компиляции – разработчик использует специальные ключи (опции) компилятора для внедрения в исходный код. Используется способность компилятора обнаруживать ошибки. Например, в компиляторе GNU C/C++ 4.x используется технология Mudflap для выявления проблем, касающихся операций с указателями.
  • Специализированные библиотеки этапа исполнения – для обнаружения ошибок в передаваемых параметрах разработчик использует отладочные версии системных библиотек. Дурной славой пользуются функции типа strcpy() из-за возможности появления нулевых или ошибочных указателей во время исполнения. При использовании отладочных версий библиотек такие "плохие" параметры обнаруживаются. Данная технология не требует перекомпоновки приложения и в меньшей степени влияет на производительность, чем полноценное использование вставок в исходный/объектный код. Данная технология используется в инструменте анализа ОЗУ в QNX® Momentics® IDE.

В данной статье мы рассмотрим технологии, используемые в инструментах разработчика QNX Momentics, особенно обращая внимание на GCC Mudflap и специализированные библиотеки этапа исполнения.

GNU C/C++ Mudflap: внедрение в исходный код на этапе компиляции

В инструментальном средстве Mudflap, присутствующем в версии 4.х компилятора GNU C/C++ (GCC), используется внедрение в исходный код во время компиляции. При этом во время исполнения происходит проверка конструкций, потенциально несущих в себе возможность появления ошибок. Основное внимание в средстве Mudflap обращается на операции с указателями, поскольку они являются источником многих ошибок на этапе выполнения для программ, написанных на языках C и C++.

С подключением средства Mudflap в работе компилятора GCC появляется еще один проход, когда вставляется проверочный код для операций с указателями. Вставляемый код обычно выполняет проверку на достоверность значений передаваемых указателей. Неправильные значения указателей приведут к выдаче компилятором GCC сообщений на стандартное устройство выдачи ошибок консоли (stderr). Средство Mudflap для контроля за указателями не просто проверяет указатели на нулевое значение: в его базе данных хранятся адреса памяти для действительных объектов и свойства объектов, например, местоположение в исходном коде, метка даты/времени, обратная трассировка стека при выделении и освобождении памяти. Такая база данных позволяет быстро получить необходимые данные при анализе операций доступа к памяти в исходном коде программы.

В библиотечных функциях, подобных strcpy(), не выполняется проверка передаваемых параметров. Такие функции не проверяются и средством Mudflap. Тем не менее, в Mudflap можно создать символьную оболочку (symbol wrapper) для статически подключаемых библиотек или вставку для динамических библиотек. При такой технологии между приложением и библиотекой создается дополнительный слой, что дает возможность провести проверку достоверности параметров и выдать сообщение о проявлении отклонений. В средстве Mudflap используется эвристический алгоритм, основанный на знании границ памяти, используемых приложением (динамическая память, стек, сегменты кода и данных и т.д.), чтобы определить правильность значений возвращаемых указателей.

Используя ключи командной строки компилятора GCC, разработчик может подключить возможности Mudflap для вставки кодовых фрагментов и управления поведением, например, управление нарушениями (границ, значений), проведение дополнительных проверок и настроек, подключение эвристических методов и самодиагностики. Например, комбинация ключей -fmudflap устанавливает конфигурацию Mudflap по умолчанию. Сообщения компилятора о выявленных средством Mudflap нарушениях выдаются на выходную консоль (stderr) или в командную строку. В подробном выводе предоставляется информация о нарушении и о задействованных при этом переменных, функциях, а также о местонахождении кода. Эта информация может автоматически импортироваться в среду IDE, где происходит ее визуализация, и выполняется трассировка стека. Пользуясь этими данными, разработчик может быстро перейти к соответствующему месту исходного кода программы.

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

При использовании средства Mudflap может возрасти время на компоновку, и может уменьшиться производительность во время исполнения. Данные, представленные в статье “Mudflap: Pointer Use Checking for C/C++” ("Средство Mudflap: проверка использования указателей для языков C/C++”) говорят о том, что с подключением Mudflap время компоновки возрастает в 3…5 раз, а программа начинает работать медленнее в 1.25 … 5 раз. Совершенно ясно, что разработчики критических по времени исполнения приложений должны пользоваться эти средством с осторожностью. Тем не менее Mudflap является мощным средством для выявления подверженных ошибкам и потенциально фатальных кодовых конструкций. Компания QNX планирует использовать средство Mudflap в будущих версиях своих инструментов динамического анализа.

Рис. 3 - Использование информации обратной трассировки, отображаемой в среде QNX Momentics IDE для поиска исходного кода, приведшего к ошибке.

Отладочные версии библиотек этапа исполнения

Наряду с использованием специальных отладочных вставок в библиотеки этапа исполнения, которые приводят к значительным дополнительным расходам памяти и времени на этапах компоновки и исполнения, разработчики могут использовать предварительно подготовленные (pre-instrumented) библиотеки этапа исполнения. В таких библиотеках вокруг функциональных вызовов добавляется определенный код, цель которого состоит в проверке достоверности входных параметров. Например, рассмотрим старую знакомую – функцию копирования строк:

strcpy(a,b);

В ней участвуют два параметра, оба являющиеся указателями на тип char : один для исходной строки (b ), а другой для строки-результата (a ). Несмотря на такую простоту, эта функция может являться источником множества ошибок:

  • если значение указателя a равно нулю или является недействительным, то копирование по этому адресу назначения приведет к ошибке запрета доступа к памяти;
  • если значение указателя b равно нулю или является недействительным, то чтение информации с этого адреса приведет к ошибке запрета доступа к памяти;
  • если в конце строки b пропущен завершающий строку символ "0", то в строку назначения будет скопировано большее число символов, чем ожидается;
  • если размер строки b больше размера памяти, выделенного под строку a , то по указанному адресу будет записано больше байт, чем предполагалось (типичный сценарий переполнения буфера).

В отладочной версии библиотеки производится проверка значений параметров ‘a ’ и ‘b ’. Проверяются также длины строк, чтобы убедиться в их совместимости. Если обнаруживается недействительный параметр, то выдается соответствующее аварийное сообщение. В среде QNX Momentics данные сообщения об ошибках импортируются из целевой системы и выводятся на экран. В среде QNX Momentics используется также технология слежениями за случаями выделения и освобождения памяти, что дает возможность выполнять глубокий анализ использования ОЗУ.

Отладочная версия библиотеки будет работать с любым приложением, в котором используются ее функции; не нужно вносить никаких дополнительных изменений в код. Более того, разработчик может добавить библиотеку во время запуска приложения. Тогда библиотека заменит соответствующие части полной стандартной библиотеки, устраняя необходимость использовать отладочную версию полной библиотеки. В среде QNX Momentics IDE разработчик может добавить такую библиотеку при запуске программы как элемент нормального интерактивного сеанса отладки. На рис. 4 показан пример того, как в среде QNX Momentics происходит обнаружение и выдача сообщений об ошибках работы с памятью.

В отладочных версиях библиотек предоставляется проверенный "неагрессивный" метод обнаружения ошибок при вызовах библиотечных функций. Такая технология идеальна для анализа ОЗУ и для других методов анализа, которые зависят от согласованных пар вызовов, например, malloc() и free(). Другими словами, данная технология может обнаруживать ошибки на этапе исполнения только для кодов с библиотечными вызовами. При этом не обнаруживаются многие типичные ошибки, такие как встроенный код разыменования ссылки (inline pointer dereferences) или некорректные арифметические операции с указателями. Обычно при отладке осуществляется контроль только некоторого подмножества системных вызовов. Подробнее с этим можно познакомиться в статье .

Рис. 4 - Анализ ОЗУ происходит путем расстановки ловушек в области вызовов API, связанных с обращением к памяти.

Последовательность действий при динамическом анализе

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

Как показано на рис. 5, динамический анализ не только позволяет обнаружить ошибки, но и помогает обратить внимание разработчика на подробности расходования памяти, циклов ЦПУ, дискового пространства и других ресурсов. Процесс анализа состоит из нескольких шагов, и хороший инструмент для динамического анализа предоставляет надежную поддержку каждого шага:

  1. Наблюдение – прежде всего, происходит захват ошибок среды выполнения, обнаружение мест утечки памяти и отображение всех результатов в среде IDE.
  2. Корректировка – далее разработчик имеет возможность провести трассировку каждой ошибки назад до нарушающей работу строки исходного текста. При хорошей интеграции в среду IDE каждая ошибка будет отображаться на экране. Разработчику нужно просто щелкнуть мышью на строке ошибки, и откроется фрагмент исходного кода со строкой, нарушающей работу. Во многих случаях разработчик может быстро устранить проблему, используя доступную трассировку стека и дополнительные инструменты работы с исходным кодом в среде IDE (средства просмотра вызовов функций, средства трассировки вызовов и т.д.).
  3. Профилирование – устранив обнаруженные ошибки и утечки памяти, разработчик может проанализировать использование ресурсов во времени, включая пиковые ситуации, среднюю загрузку и избыточное расходование ресурсов. В идеальном случае инструмент анализа выдаст визуальное представление долговременного использования ресурсов, позволяя немедленно идентифицировать всплески в распределении памяти и иные аномалии.
  4. Оптимизация – используя информацию этапа профилирования, разработчик может теперь провести "тонкий" анализ использования ресурсов программой. Среди прочего, подобная оптимизация может минимизировать случаи пикового использования ресурсов и их избыточное расходование, включая работу с ОЗУ и использование времени ЦПУ.

Рис. 5 - Типовая последовательность действий при динамическом анализе

Комбинирование последовательности действий различных видов анализа в среде разработки

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

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

Вот какой можно предложить пример совместного использования двух видов инструментов:

  1. В начале рабочего дня разработчик просматривает отчет о результатах ночной компоновки. В данный отчет включаются как собственно ошибки компоновки, так и результаты статического анализа, проведенного во время компоновки.
  2. В отчете по статическому анализу приводятся обнаруженные дефекты наряду с информацией, которая поможет в их устранении, в том числе ссылки на исходный код. Используя среду IDE, разработчик может пометить каждую ситуацию либо как действительно найденную ошибку, либо как "ложное срабатывание". После этого производится исправление фактически присутствующих ошибок.
  3. Разработчик локально, внутри среды IDE, сохраняет внесенные изменения вместе с любыми новыми фрагментами кода. Разработчик не передает эти изменения обратно в систему управления исходными текстами до тех пор, пока изменения не будут проанализированы и протестированы.
  4. Разработчик анализирует и корректирует новый код, используя инструмент статического анализа на локальном рабочем месте. Для того чтобы быть уверенным в качественном обнаружении ошибок и отсутствии "ложных срабатываний", в анализе используется расширенная информация на уровне системы. Эта информация берется из данных выполненного в ночное время процесса компоновки/анализа.
  5. После анализа и "чистки" любого нового кода разработчик встраивает код в локальный тестовый образ или исполняемый файл.
  6. Используя инструменты динамического анализа, разработчик запускает тесты для проверки внесенных изменений.
  7. С помощью среды IDE разработчик может быстро выявить и исправить ошибки, о которых сообщается через инструменты динамического анализа. Код считается окончательным и готовым к использованию, когда он прошел через статический анализ, блочное тестирование и динамический анализ.
  8. Разработчик передает изменения в систему управления исходными текстами; после этого измененный код участвует в процессе последующей ночной компоновки.

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

Роль архитектуры ОСРВ

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

Например, в микроядерной ОСРВ типа QNX Neutrino все приложения, драйверы устройств, файловые системы и сетевые стеки располагаются за пределами ядра в отдельных адресных пространствах. В результате все они оказываются изолированными от ядра и друг от друга. Такой подход обеспечивает наивысшую степень локализации сбоев: отказ одного из компонентов не приводит к краху системы в целом. Более того, оказывается, что можно легко локализовать ошибку, связанную с ОЗУ, или иную логическую ошибку с точностью до компонента, который эту ошибку вызвал.

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

Рис. 6 - В микроядерной ОС сбои в ОЗУ для драйверов, стеков протоколов и других службах не приведут к нарушению работы других процессов или ядра. Более того, ОС может мгновенно обнаружить неразрешенную попытку доступа к памяти и указать, со стороны какого кода предпринята эта попытка.

По сравнению с обычным ядром ОС микроядру свойственно необычайно малое среднее время восстановления после сбоя (Mean Time to Repair, MTTR). Рассмотрим, что происходит при сбое в работе драйвера устройства: ОС может завершить работу драйвера, восстановить используемые драйвером ресурсы и перезапустить драйвер. Обычно на это уходит несколько миллисекунд. В обычной монолитной операционной системе устройство должно быть перезагружено – этот процесс может занять от нескольких секунд до нескольких минут.

Заключительные замечания

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

Между тем, инструменты динамического анализа поддерживают этапы интеграции и тестирования, сообщая в среду разработки об ошибках (или потенциальных проблемах), возникающих при исполнении программы. Эти инструменты предоставляют также полную информацию по обратной трассировке до места возникновения ошибки. Используя эту информацию, разработчики могут выполнить "посмертную" отладку таинственного отказа программы или краха системы за значительно меньший интервал времени. При динамическом анализе через трассировку стека и переменных могут быть выявлены основополагающие причины проблемы – эта лучше, чем повсеместно использовать операторы “if (ptr != NULL)” для предотвращения и обхода аварийных ситуаций.

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

Библиография

  • Eigler, Frank Ch., “Mudflap: Pointer Use Checking for C/C++”, Proceedings of the GCC Developers Summit 2003, pg. 57-70. http://www.linux.org.uk/~ajh/gcc/gccsummit-2003-proceedings.pdf
  • “Heap Analysis: Making Memory Errors a Thing of the Past”, QNX Neutrino RTOS Programmer’s Guide. http://pegasus.ott.qnx.com/download/download/16853/neutrino_prog.pdf

О компании QNX Software Systems

QNX Software Systems является одной из дочерних компаний Harman International и ведущим глобальным поставщиком инновационных технологий для встраиваемых систем, включая связующее ПО, инструментальные средства разработки и операционные системы. ОСРВ QNX® Neutrino®, комплект разработчика QNX Momentics® и связующее ПО QNX Aviage®, основанные на модульной архитектуре, образуют самый надежный и масштабируемый программный комплекс для создания высокопроизводительных встраиваемых систем. Глобальные компании-лидеры, такие как Cisco, Daimler, General Electric, Lockheed Martin и Siemens, широко используют технологии QNX в сетевых маршрутизаторах, медицинской аппаратуре, телематических блоках автомобилей, системах безопасности и защиты, в промышленных роботах и других приложениях для ответственных и критически важных задач. Головной офис компании находится в г. Оттава (Канада), а дистрибьюторы продукции расположены в более чем 100 странах по всему миру.

О компании Klocwork

Продукты компании Klocwork предназначены для автоматизированного анализа статического кода, выявления и предотвращения дефектов программного обеспечения и проблем безопасности. Наша продукция предоставляет коллективам разработчиков инструментарий для выявления коренных причин недостатков качества и безопасности программного обеспечения, для отслеживания и предотвращения этих недостатков на протяжении всего процесса разработки. Запатентованная технология компании Klocwork была создана в 1996 г. и обеспечила высокий коэффициент окупаемости инвестиций (ROI) для более чем 80 клиентов, многие из которых входят в рейтинг крупнейших 500 компаний журнала Fortune и предлагают среды разработки программного обеспечения, пользующиеся наибольшим спросом в мире. Klocwork является частной компанией и имеет офисы в Берлингтоне, Сан Хосе, Чикаго, Далласе (США) и Оттаве (Канада).

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

Здесь надо сказать, что код, который восстановили, по текстовому представлению имеет мало общего с тем кодом, который был изначально написан программистом и скомпилирован в исполняемый файл. Восстановить точно бинарный файл, полученный от компилируемых языков программирования типа C/C++, Fortran, нельзя, так как это алгоритмически неформализованная задача. В процессе преобразования исходного кода, который написал программист, в программу, которую выполняет машина, компилятор выполняет необратимые преобразования.

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

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

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

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

Задача дизассемблирования обычно решается в полуавтоматическом режиме, то есть специалист делает восстановление вручную при помощи интерактивных инструментов, например, интерактивным дизассемблером IdaPro , radare или другим инструментом. Дальше также в полуавтоматическом режиме выполняется декомпиляция. В качестве инструментального средства декомпиляции в помощь специалисту используют HexRays , SmartDecompiler или другой декомпилятор, который подходит для решения данной задачи декомпиляции.

Восстановление исходного текстового представления программы из byte-кода можно сделать достаточно точным. Для интерпретируемых языков типа Java или языков семейства.NET, трансляция которых выполняется в byte-код, задача декомпиляции решается по-другому. Этот вопрос мы в данной статье не рассматриваем.

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

Из практики работы с унаследованными программами

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

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

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

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

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

Во-первых, найденные уязвимости надо уметь не только находить, но и объяснять. Если уязвимость была найдена в программе на языке высокого уровня, аналитик или инструментальное средство анализа кода показывают в ней, какие фрагменты кода содержат те или иные недостатки, наличие которых стало причиной появления уязвимости. Что делать, если исходного кода нет? Как показать, какой код стал причиной появления уязвимости?

Декомпилятор восстанавливает код, который «замусорен» артефактами восстановления, и делать отображение выявленной уязвимости на такой код бесполезно, все равно ничего не понятно. Более того, восстановленный код плохо структурирован и поэтому плохо поддается инструментальным средствам анализа кода. Объяснять уязвимость в терминах бинарной программы тоже сложно, ведь тот, для кого делается объяснение, должен хорошо разбираться в бинарном представлении программ.

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

Несмотря на все особенности и сложности проведения статического анализа бинарных программ по требованиям ИБ, ситуаций, когда такой анализ выполнять нужно, много. Если исходного кода по каким-то причинам нет, а бинарная программа выполняет функционал, критичный по требованиям ИБ, ее надо проверять. Если уязвимости обнаружены, такое приложение надо оправлять на доработку, если это возможно, либо делать для него дополнительную «оболочку», которая позволит контролировать движение чувствительной информации.

Когда уязвимость спряталась в бинарном файле

Если код, который выполняет программа, имеет высокий уровень критичности, даже при наличии исходного текста программы на языке высокого уровня, полезно сделать аудит бинарного файла. Это поможет исключить особенности, которые может привнести компилятор, выполняя оптимизирующие преобразования. Так, в сентябре 2017 года широко обсуждали оптимизационное преобразование, выполненное компилятором Clang. Его результатом стал вызов функции , которая никогда не должна вызываться.

#include typedef int (*Function)(); static Function Do; static int EraseAll() { return system("rm -rf /"); } void NeverCalled() { Do = EraseAll; } int main() { return Do(); }

В результате оптимизационных преобразований компилятором будет получен вот такой ассемблерный код. Пример был скомпилирован под ОС Linux X86 c флагом -O2.

Text .globl NeverCalled .align 16, 0x90 .type NeverCalled,@function NeverCalled: # @NeverCalled retl .Lfunc_end0: .size NeverCalled, .Lfunc_end0-NeverCalled .globl main .align 16, 0x90 .type main,@function main: # @main subl $12, %esp movl $.L.str, (%esp) calll system addl $12, %esp retl .Lfunc_end1: .size main, .Lfunc_end1-main .type .L.str,@object # @.str .section .rodata.str1.1,"aMS",@progbits,1 .L.str: .asciz "rm -rf /" .size .L.str, 9

В исходном коде есть undefined behavior . Функция NeverCalled() вызывается из-за оптимизационных преобразований, которые выполняет компилятор. В процессе оптимизации он скорее всего выполняет анализ аллиасов , и в результате функция Do() получает адрес функции NeverCalled(). А так как в методе main() вызывается функция Do(), которая не определена, что и есть неопределенное стандартом поведение (undefined behavior), получается такой результат: вызывается функция EraseAll(), которая выполняет команду «rm -rf /».

Следующий пример: в результате оптимизационных преобразований компилятора мы лишились проверки указателя на NULL перед его разыменованием.

#include void Checker(int *P) { int deadVar = *P; if (P == 0) return; *P = 8; }

Так как в строке 3 выполняется разыменование указателя, компилятор предполагает, что указатель ненулевой. Дальше строка 4 была удалена в результате выполнения оптимизации «удаление недостижимого кода» , так как сравнение считается избыточным, а после и строка 3 была удалена компилятором в результате оптимизации «удаление мертвого кода» (dead code elimination). Остается только строка 5. Ассемблерный код, полученный в результате компиляции gcc 7.3 под ОС Linux x86 с флагом -O2, приведен ниже.

Text .p2align 4,15 .globl _Z7CheckerPi .type _Z7CheckerPi, @function _Z7CheckerPi: movl 4(%esp), %eax movl $8, (%eax) ret

Приведенные выше примеры работы оптимизации компилятора – результат наличия в коде undefined behavior UB. Однако это вполне нормальный код, который большинство программистов примут за безопасный. Сегодня программисты уделяют время исключению неопределенного поведения в программе, тогда как еще 10 лет назад не обращали на это внимания. В результате унаследованный код может содержать уязвимости, связанные с наличием UB.

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

Аннотация

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

Введение

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

Программное обеспечение часто содержит разнообразные уязвимости из-за ошибок в коде программ. Ошибки, допущенные при разработке программ, в некоторых ситуациях приводят к сбою программы, а следовательно, нарушается нормальная работа программы: при этом часто возникает изменение и порча данных, останов программы или даже системы. Большинство уязвимостей связано с неправильной обработкой данных, получаемых извне, или недостаточно строгой их проверкой.

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

Классификация уязвимостей защиты

Когда требование корректной работы программы на всех возможных входных данных нарушается, становится возможным появление так называемых уязвимостей защиты (security vulnerability). Уязвимости защиты могут приводить к тому, что одна программа может использоваться для преодоления ограничений защиты всей системы в целом.

Классификация уязвимостей защиты в зависимости от программных ошибок:

  1. Переполнение буфера (buffer overflow). Эта уязвимость возникает из-за отсутствия контроля за выходом за пределы массива в памяти во время выполнения программы. Когда слишком большой пакет данных переполняет буфер ограниченного размера, содержимое посторонних ячеек памяти перезаписывается, и происходит сбой и аварийный выход из программы. По месту расположения буфера в памяти процесса различают переполнения буфера в стеке (stack buffer overflow), куче (heap buffer overflow) и области статических данных (bss buffer overflow).
  2. Уязвимости (tainted input vulnerability). Уязвимости могут возникать в случаях, когда вводимые пользователем данные без достаточного контроля передаются интерпретатору некоторого внешнего языка (обычно это язык Unix shell или SQL). В этом случае пользователь может таким образом задать входные данные, что запущенный интерпретатор выполнит совсем не ту команду, которая предполагалась авторами уязвимой программы.
  3. Ошибки форматных строк (format string vulnerability). Данный тип уязвимостей защиты является подклассом уязвимости. Он возникает из-за недостаточного контроля параметров при использовании функций форматного ввода-вывода printf, fprintf, scanf, и т. д. стандартной библиотеки языка Си. Эти функции принимают в качестве одного из параметров символьную строку, задающую формат ввода или вывода последующих аргументов функции. Если пользователь сам может задать вид форматирования, то эта уязвимость может возникнуть в результате неудачного применения функций форматирования строк.
  4. Уязвимости как следствие ошибок синхронизации (race conditions). Проблемы, связанные с многозадачностью, приводят к ситуациям, называемым: программа, не рассчитанная на выполнение в многозадачной среде, может считать, что, например, используемые ею при работе файлы не может изменить другая программа. Как следствие, злоумышленник, вовремя подменяющий содержимое этих рабочих файлов, может навязать программе выполнение определенных действий.

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

Обзор существующих анализаторов

Для обнаружения уязвимостей защиты в программах применяют следующие инструментальные средства:

  • Динамические отладчики. Инструменты, которые позволяют производить отладку программы в процессе её исполнения.
  • Статические анализаторы (статические отладчики). Инструменты, которые используют информацию, накопленную в ходе статического анализа программы.

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

В данной статье предложен обзор нескольких существующих статических анализаторов. Рассмотрим подробнее каждый из них.

1. BOON

Инструмент , который на основе глубокого семантического анализа автоматизирует процесс сканирования исходных текстов на Си в поисках уязвимых мест, способных приводить к переполнению буфера. Он выявляет возможные дефекты, предполагая, что некоторые значения являются частью неявного типа с конкретным размером буфера.

2. CQual

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

3. MOPS

(MOdel checking Programs for Security) - инструмент для поиска уязвимостей в защите в программах на Си. Его назначение: динамическая корректировка, обеспечивающая соответствие программы на Си статической модели. MOPS использует модель аудита программного обеспечения, которая призвана помочь выяснить, соответствует ли программа набору правил, определенному для создания безопасных программ.

4. ITS4, RATS, PScan, Flawfinder

Для поиска ошибок переполнения буфера и ошибок форматных строк используют следующие статические анализаторы:

  1. . Простой инструмент, который статически просматривает исходный Си/Си++-код для обнаружения потенциальных уязвимостей защиты. Он отмечает вызовы потенциально опасных функций, таких, например, как strcpy/memcpy, и выполняет поверхностный семантический анализ, пытаясь оценить, насколько опасен такой код, а так же дает советы по его улучшению.
  2. . Утилита RATS (Rough Auditing Tool for Security) обрабатывает код, написанный на Си/Си++, а также может обработать еще и скрипты на Perl, PHP и Python. RATS просматривает исходный текст, находя потенциально опасные обращения к функциям. Цель этого инструмента - не окончательно найти ошибки, а обеспечить обоснованные выводы, опираясь на которые специалист сможет вручную выполнять проверку кода. RATS использует сочетание проверок надежности защиты от семантических проверок в ITS4 до глубокого семантического анализа в поисках дефектов, способных привести к переполнению буфера, полученных из MOPS.
  3. . Сканирует исходные тексты на Си в поисках потенциально некорректного использования функций, аналогичных printf, и выявляет уязвимые места в строках формата.
  4. . Как и RATS, это статический сканер исходных текстов программ, написанных на Си/Си++. Выполняет поиск функций, которые чаще всего используются некорректно, присваивает им коэффициенты риска (опираясь на такую информацию, как передаваемые параметры) и составляет список потенциально уязвимых мест, упорядочивая их по степени риска.

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

5. Bunch

Средство анализа и визуализации программ на Си, которое строит граф зависимостей, помогающий аудитору разобраться в модульной структуре программы.

6. UNO

Простой анализатор исходного кода. Он был разработан для нахождения таких ошибок, как неинициализированные переменные, нулевые указатели и выход за пределы массива. UNO позволяет выполнять несложный анализ потока управления и потоков данных, осуществлять как внутри- так и межпроцедурный анализ, специфицировать свойства пользователя. Но данный инструмент не доработан для анализа реальных приложений, не поддерживает многие стандартные библиотеки и на данном этапе разработки не позволяет анализировать сколь-нибудь серьёзные программы.

7. FlexeLint (PC-Lint)

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

В конце работы выдаются сообщения нескольких основных типов:

  • Возможен нулевой указатель;
  • Проблемы с выделением памяти (например, нет free() после malloc());
  • Проблемный поток управления (например, недостижимый код);
  • Возможно переполнение буфера, арифметическое переполнение;
  • Предупреждения о плохом и потенциально опасном стиле кода.

8. Viva64

Инструмент , который помогает специалисту отслеживать в исходном коде Си/Си++-программ потенциально опасные фрагменты, связанные с переходом от 32-битных систем к 64-битным. Viva64 встраивается в среду Microsoft Visual Studio 2005/2008, что способствует удобной работе с этим инструментом. Анализатор помогает писать корректный и оптимизированный код для 64-битных систем.

9. Parasoft C++ Test

Специализированный инструмент для Windows, позволяющий автоматизировать анализ качества кода Си++. Пакет C++Test анализирует проект и генерирует код, предназначенный для проверки содержащихся в проекте компонентов. Пакет C++Test делает очень важную работу по анализу классов C++. После того как проект загружен, необходимо настроить методы тестирования. Программное обеспечение изучает каждый аргумент метода и возвращает типы соответствующих значений. Для данных простых типов подставляются значения аргументов по умолчанию; можно определить тестовые данные для определенных пользователем типов и классов. Можно переопределить аргументы C++Test, используемые по умолчанию, и выдать значения, полученные в результате тестирования. Особого внимания заслуживает способность C++Test тестировать незавершенный код. Программное обеспечение генерирует код-заглушку для любого метода и функции, которые еще не существуют. Поддерживается имитация внешних устройств и входных данных, задаваемых пользователем. И та и другая функции допускают возможность повторного тестирования. После определения тестовых параметров для всех методов пакет C++Test готов к запуску исполняемого кода. Пакет генерирует тестовый код, вызывая для его подготовки компилятор Visual C++. Возможно формирование тестов на уровне метода, класса, файла и проекта.

10. Coverity

Инструменты используются для выявления и исправления дефектов безопасности и качества в приложениях критического назначения. Технология компании Coverity устраняет барьеры в написании и внедрении сложного ПО посредством автоматизации поиска и устранения критических программных ошибок и недостатков безопасности во время процесса разработки. Инструмент компании Coverity способен с минимальной положительной погрешностью обрабатывать десятки миллионов строк кода, обеспечивая 100-процентное покрытие трассы.

11. KlocWork K7

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

12. Frama-C

Открытый, интегрированный набор инструментов для анализа исходного кода на языке Си. Набор включает ACSL (ANSI/ISO C Specification Language) - специальный язык, позволяющий подробно описывать спецификации функций Си, например, указать диапазон допустимых входных значений функции и диапазон нормальных выходных значений.

Этот инструментарий помогает производить такие действия:

  • Осуществлять формальную проверку кода;
  • Искать потенциальные ошибки исполнения;
  • Произвести аудит или рецензирование кода;
  • Проводить реверс-инжиниринг кода для улучшения понимания структуры;
  • Генерировать формальную документацию.

13. CodeSurfer

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

  • Анализ указателей;
  • Различные анализы потока данных (использование и определение переменных, зависимость данных, построение графа вызовов);
  • Скриптовый язык.

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

14. FxCop

Предоставляет средства автоматической проверки.NET-сборок на предмет соответствия правилам Microsoft .NET Framework Design Guidelines. Откомпилированный код проверяется с помощью механизмов рефлексии, парсинга MSIL и анализа графа вызовов. В результате FxCop способен обнаружить более 200 недочетов (или ошибок) в следующих областях:

  • Архитектура библиотеки;
  • Локализация;
  • Правила именования;
  • Производительность;
  • Безопасность.

FxCop предусматривает возможность создания собственных правил с помощью специального SDK. FxCop может работать как в графическом интерфейсе, так и в командной строке.

15. JavaChecker

Это статический анализатор Java програм, основанный на технологии TermWare.

Это средство позволяет выявлять дефекты кода, такие как:

  • небрежная обработка исключений (пустые catch-блоки, генерирование исключений общего вида и.т.п.);
  • сокрытие имен (например, когда имя члена класса совпадает с именем формального параметра метода);
  • нарушения стиля (вы можете задавать стиль программирования с помощью набора регулярных выражений);
  • нарушения стандартных контрактов использования (например, когда переопределен метод equals, но не hashCode);
  • нарушения синхронизации (например, когда доступ к синхронизированной переменной находится вне synchronized блока).

Набором проверок можно управлять, используя управляющие комментарии.

Вызов JavaChecker можно осуществлять из ANT скрипта.

16. Simian

Анализатор подобия, который ищет повторяющийся синтаксис в нескольких файлах одновременно. Программа понимает синтаксис различных языков программирования, включая C#, T-SQL, JavaScript и Visual BasicR, а также может искать повторяющиеся фрагменты в текстовых файлах. Множество возможностей настройки позволяет точно настраивать правила поиска дублирующегося кода. Например, параметр порога (threshold) определяет, какое количество повторяющихся строк кода считать дубликатом.

Simian - это небольшой инструмент, разработанный для эффективного поиска повторений кода. У него отсутствует графический интерфейс, но его можно запустить из командной строки или обратиться к нему программно. Результаты выводятся в текстовом виде и могут быть представлены в одном из встроенных форматов (например, XML). Хотя скудный интерфейс и ограниченные возможности вывода результатов Simian требуют некоторого обучения, он помогает сохранить целостность и эффективность продукта. Simian подходит для поиска повторяющегося кода как в больших, так и в маленьких проектах.

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

Вывод

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



Понравилась статья? Поделитесь ей