Запустите ESLint на JavaScript-файле с уязвимостью SQL-инъекции, и он пройдёт проверку без ошибок. Запустите CodeQL на том же коде, и он выявит инъекцию с указанием точного файла, строки и пути потока данных. Оба инструмента являются инструментами статического анализа. Оба проверяют один и тот же исходный код, не запуская его. Разница в результатах заключается не в конфигурации правил или поддержке языка, а в принципиальном различии в глубине анализа. ESLint проверяет одну функцию за раз и сообщает о том, что видит в пределах этой области. CodeQL отслеживает данные по вызовам функций, по модулям и по всему графу вызовов, пока не достигнет чего-то опасного.
Различие между внутрипроцедурным и межпроцедурным анализом является наиболее важной технической концепцией для понимания того, почему разные инструменты статического анализа обнаруживают разные вещи. Оно объясняет, почему линтер может дать коду положительную оценку, в то время как более сложный инструмент обнаруживает критические уязвимости безопасности в том же коде. Оно объясняет, почему инструменты статического анализа безопасности (SAST) дороги в эксплуатации. Оно объясняет, почему частота ложноотрицательных результатов у «быстрых» инструментов структурно выше, чем у «медленных». И это концепция, которую корпоративные команды должны понимать, прежде чем оценивать любую цепочку инструментов статического анализа.
Смотрите полную карту звонков, а не только один файл.
SMART TS XL Отслеживает потоки данных в COBOL, Java, Python и любом другом языке программирования, который вы используете.
ПодробнееЧто такое внутрипроцедурный анализ?
Внутрипроцедурный анализ изучает то, что происходит внутри отдельной функции, метода или процедуры, не рассматривая то, что происходит, когда эта функция вызывает другие функции или вызывается ими. Граница анализа — это граница функции. Когда внутрипроцедурный инструмент достигает вызова функции, он останавливается, не отслеживает вызов до вызываемой функции и не знает, что вызываемая функция делает с полученными данными.
Классическими примерами внутрипроцедурного анализа являются проверка синтаксиса и базовый поток данных внутри одной функции:
питон
def process_input(user_data):
# Intraprocedural analysis sees: user_data used here
query = "SELECT * FROM users WHERE id = " + user_data # flagged: string concat in SQL
return query
Простое правило, которое выявляет конкатенацию строк в SQL-запросах, работает внутри процедуры, поскольку опасный шаблон виден в пределах одной функции. Но немного изменим структуру кода:
питон
def get_user_id(request):
return request.args.get("id") # untrusted input
def build_query(user_id):
return "SELECT * FROM users WHERE id = " + user_id # looks safe in isolation
def handle_request(request):
uid = get_user_id(request)
query = build_query(uid) # vulnerability spans three functions
db.execute(query)
Теперь источник искаженных данных (request.args.get("id")) выполняет иную функцию, чем опасная раковина (db.execute(query)Ни одна отдельная функция не содержит достаточно информации для того, чтобы инструмент, используемый в рамках процедуры, смог обнаружить уязвимость. get_user_id Выглядит нормально, просто считывает данные из запроса. build_query Выглядит нормально, программа принимает параметр и формирует строку. handle_request Выглядит нормально, вызывает функции и выполняет запрос. SQL-инъекция реальна, её можно использовать, и она невидима для любого инструмента, который не выходит за пределы функциональных блоков.
В чём преимущества интраоперационного анализа?
Инструменты для внутрипроцедурного анализа работают быстро. Очень быстро. Поскольку они анализируют каждую функцию независимо, их производительность масштабируется линейно с размером кодовой базы: анализ кодовой базы объемом в миллион строк обходится пропорционально так же, как и анализ кодовой базы объемом в десять тысяч строк. Эта скорость делает их идеальными для:
- Предварительные хуки которые выполняются менее чем за секунду
- Встроенная обратная связь IDE пока разработчик печатает
- Контрольные точки качества CI/CD при каждом запросе на слияние
- Соблюдение стиля и общепринятых норм (именование, форматирование, показатели сложности)
- Неправильное использование локальных переменных (неиспользуемые переменные, неопределенные ссылки, очевидные ошибки типов)
- Цикломатическая сложность и длина методачисто внутрифункциональные показатели
К инструментам этой категории относятся ESLint, Pylint, Clippy (Rust), golangci-lint, Checkstyle и большинство линтеров. Они необходимы и ценны для решения тех задач, которые могут выявлять. Их ограничение носит структурный характер, а не является недостатком качества.
Что такое межпроцедурный анализ?
Межпроцедурный (анализ всей программы) анализ, как правило, является наиболее мощным и ресурсоемким. Межпроцедурный анализ дополняет графы потока управления ребрами вызовов и возвратов функций из графа вызовов. Анализ может позволить избежать многократного повторного обращения к одним и тем же функциям за счет создания сводных данных о функциях.
В то время как внутрипроцедурный анализ останавливается на границах функций, межпроцедурный анализ их пересекает. Он строит граф вызовов, представляющий, какие функции вызывают какие другие, а затем отслеживает поток данных, поток управления или загрязнение по ребрам этого графа. Когда межпроцедурный инструмент встречает вызов функции, он следует за вызовом до вызываемой функции или использует предварительно вычисленное описание поведения вызываемой функции и продолжает анализ на другой стороне.
Три приведенные выше функции SQL-инъекций — это простой пример. Реальный межпроцедурный анализ обрабатывает:
- Многошаговый поток данных: ненадежный входной сигнал, проходящий через пять функций, прежде чем достичь опасного приемника.
- Погрешность возвращаемого значенияФункция возвращает некорректные данные; вызывающие функции используют это небезопасное возвращаемое значение в небезопасных операциях.
- Повреждение, чувствительное к полю: ненадежные данные, хранящиеся в поле объекта и извлекаемые позже в другой функции.
- Условное распространение: потоки заражения распространяются только по определенным путям выполнения, в зависимости от условий времени выполнения, которые анализ должен моделировать статически.
Ява
// Interprocedural vulnerability: three-hop taint flow
public class UserController {
public String handleRequest(HttpServletRequest request) {
String userId = request.getParameter("id"); // source: untrusted input
User user = userService.getUser(userId); // taint flows through call
return generateReport(user); // taint reaches sink
}
}
public class UserService {
public User getUser(String id) {
// id is tainted -- passed to repository
return userRepository.findByRawId(id); // taint propagates
}
}
public class UserRepository {
public User findByRawId(String id) {
String query = "SELECT * FROM users WHERE id = " + id; // sink: SQL injection
return db.execute(query);
}
}
Инструмент для проведения внутрипроцедурных исследований. UserRepository.findByRawId В отрыве от контекста рассматривается параметр. id Это может быть объединено в SQL-запрос. Выглядит потенциально небезопасно, но инструмент не может определить, где именно. id Если речь идёт о данных, полученных из внутреннего кода, это может быть доверенное, проверенное значение. Поэтому внутрипроцедурный инструмент либо помечает его как возможную проблему (выдавая ложные срабатывания на каждом параметризованном, но структурированном запросе в кодовой базе), либо игнорирует его (выдавая ложные отрицательные результаты при обнаружении реальных уязвимостей). Межпроцедурный анализ загрязнения данных обнаруживает, если недоверенные данные проходят через функции к конфиденциальным операциям. Он отслеживает данные из request.getParameter("id") через userService.getUser(), Через userRepository.findByRawId()до db.execute(query), что позволяет получить точный результат с указанием точного пути распространения загрязнения, без ложноположительных и ложноотрицательных результатов.
Три уровня анализа: практический спектр
Инструменты статического анализа, используемые в реальных условиях, занимают различные позиции в спектре от чисто внутрипроцедурных до полностью межпроцедурных. Понимание того, где находится каждый инструмент, объясняет как то, что он обнаруживает, так и то, что он упускает.
| Уровень анализа | Объем | Что оно видит | Чего в нём не хватает | Инструменты |
|---|---|---|---|---|
| Лексический / синтаксический | Отдельные токены и синтаксис | Форматирование, именование, базовый синтаксис | Всё семантическое | Prettier, rustfmt, gofmt |
| Интраоперационный | Одиночная функция | Локальный поток данных, сложность, поток управления внутри функции. | Уязвимости между различными функциями, архитектурные проблемы | ESLint, Pylint, Clippy, golangci-lint |
| Поверхностный межпроцедурный | Один уровень глубины вызова | Прямые отношения вызывающего абонента | Глубокие многошаговые потоки | Некоторые правила Semgrep, основные правила SAST. |
| Полное межпроцедурное вмешательство | Полный обход графа вызовов | Многошаговые потоки заражения, контекстно-зависимый анализ | Полиморфизм во время выполнения (частичный), рефлексивные вызовы | CodeQL, Facebook Infer, SMART TS XL |
| Целостная программа / семантическая | Полная семантика программы | Формально доказуемые свойства | Масштабируемость на очень больших кодовых базах | Infer, Kani (Rust), инструменты формальной верификации |
Межпроцедурный анализ обладает относительно высоким потенциалом эффективности и относительно низким потенциалом производительности. Благодаря богатой программной модели он также менее подвержен ложным отрицательным и ложным положительным результатам и обладает относительно более высокой эффективностью. Межпроцедурный анализ не является оптимальным решением для сред CI/CD из-за ограничений производительности, что может снизить скорость разработки. Поэтому межпроцедурный анализ часто предоставляется в виде выделенной службы, работающей на серверных фермах или с использованием специализированных инструментов.
Как работает межпроцедурный анализ: три ключевых подхода
1. Анализ на основе сводных данных
Анализ позволяет избежать многократного повторного обращения к одним и тем же функциям, создавая сводки функций (иногда называемые моделями). Сводка описывает поведение функции в компактной форме: получив этот набор входных данных с этими свойствами, функция выдает этот набор выходных данных с этими свойствами. Когда вызывающая сторона вызывает функцию, анализ создает экземпляр сводки, а не анализирует тело функции с нуля.
Анализ на основе сводных данных делает межпроцедурный анализ осуществимым в больших масштабах. Без сводных данных анализ функции, вызывающей библиотеку, потребовал бы рекурсивного анализа всей библиотеки, включая каждую вызываемую ею функцию. С использованием сводных данных функции библиотеки предварительно анализируются один раз, и их сводные данные используются повторно при каждом вызове.
2. Подход с использованием строк вызова (контекстно-зависимый анализ)
Контекстно-зависимый анализ позволяет различать различные места вызова одной и той же функции. Если sanitize(input) Если в одном месте используются достоверные данные, а в другом — недостоверные, то контекстно-зависимый анализ дает разные результаты для каждого места вызова, вместо того чтобы консервативно предполагать наихудший сценарий для всех. Это значительно снижает количество ложных срабатываний по сравнению с контекстно-независимым анализом.
В рамках подхода IFDS (Interprocedural, Finite, Distributive, Subset) это формализуется как задача достижимости графа. В рамках IFDS поток заражений описывается дистрибутивными функциями над конечной областью фактов, а межпроцедурное распространение сводится к достижимости графа. Такая формулировка делает анализ IFDS точным и алгоритмически эффективным; такие инструменты, как FlowDroid (для Android) и несколько запросов CodeQL, используют IFDS внутри себя.
3. Построение графа вызовов
Для любого межпроцедурного анализа необходим граф вызовов: ориентированный граф, где узлы представляют функции, а ребра — вызовы. Построение самого графа вызовов включает в себя принятие непростых решений:
Статическая диспетчеризация (Невиртуальные вызовы в Java, вызовы функций в C, вызовы на уровне модулей Python): легко моделируются, f() призывы f.
Динамическая диспетчеризация (виртуальные методы Java, вызовы с использованием утиной типизации в Python): требует анализа иерархии классов (CHA) или более точных алгоритмов построения графа вызовов (RTA, VTA) для определения того, какие реализации метода могут быть фактически вызваны в данном месте вызова.
Рефлексивные вызовы и указатели на функции : самые сложные случаи. Инструменты должны либо завышать приблизительные значения (предполагать, что может быть вызвана любая соответствующая функция, что приводит к ложным срабатываниям), либо занижать приблизительные значения (пропускать некоторые пути вызовов, что приводит к ложным отрицательным результатам).
Точность графа вызовов напрямую определяет точность межпроцедурного анализа. Граф вызовов, содержащий слишком много ребер (избыточное приближение), приводит к ложным срабатываниям; граф, в котором отсутствуют ребра, приводит к ложным отрицательным результатам.
Что обнаруживает межпроцедурный анализ, чего не может обнаружить внутрипроцедурный?
Уязвимости в системе безопасности (анализ загрязнений)
Для надежного обнаружения большинства уязвимостей из списка OWASP Top 10 требуется межпроцедурный анализ загрязнения данных:
- SQL-инъекцияНенадежные входные данные проходят через множество функций для выполнения запроса к базе данных.
- XSSНенадежные потоки HTML проходят через функции рендеринга в контекст вывода.
- Командная инъекция: данные, контролируемые пользователем, достигают
exec()orsystem()посредством промежуточных функций - Прохождение путиПути к файлам, полученные из пользовательского ввода, соответствуют операциям файловой системы.
- ССРФURL-адреса, управляемые пользователем, передаются на HTTP-запросы клиенту.
питон
# Intraprocedural tool: sees no issue in any individual function
# Interprocedural tool: traces the full taint path and reports SSRF
def get_target_url(request):
return request.json().get("url") # source: attacker-controlled
def fetch_resource(url):
return requests.get(url) # sink: SSRF
def api_endpoint(request):
target = get_target_url(request)
data = fetch_resource(target) # full path: source -> sink
return data
Проблемы с нулевыми указателями и безопасностью памяти
Facebook Infer — это канонический межпроцедурный инструмент для анализа безопасности нулевых значений. Он использует биабдукцию, форму межпроцедурного рассуждения, для обнаружения предусловий и постусловий функций, а затем проверяет, удовлетворяют ли вызывающие функции этим предусловиям. Разыменование нулевого значения, требующее знания того, что функция может возвращать null при определенных условиях, невидимо для внутрипроцедурных инструментов.
Ява
// Intraprocedural tool: no issue in either function
// Infer: finds that processResult() can receive null from fetchData()
String fetchData(boolean condition) {
if (condition) return getData();
return null; // can return null
}
void processResult(String result) {
System.out.println(result.length()); // NPE if result is null
}
void run() {
String data = fetchData(false); // passes false: fetchData returns null
processResult(data); // null dereference
}
Функция «использование после освобождения» и утечки ресурсов
В языках C и C++ ошибки типа «использование памяти после освобождения» часто связаны с выделением памяти в одной функции и освобождением её в другой, при этом третья функция сохраняет ссылку на неё. Анализ отдельных функций не может обнаружить этот класс ошибок. Межпроцедурный анализ проверяет целостность потока управления: он гарантирует, что вызовы функций следуют ожидаемым шаблонам, чтобы предотвратить эксплойты.
Нарушения архитектурной взаимосвязи и зависимостей
Помимо безопасности и корректности, межпроцедурный анализ применяется и к архитектурному анализу: какие компоненты вызывают какие, какие зависимости нарушают определенные границы уровней и какие изменения будут распространяться по графу вызовов. Именно в этом применении межпроцедурного анализа и работают такие инструменты, как [название инструмента]. SMART TS XL Применяется к корпоративным кодовым базам, отслеживая, как изменение одной функции распространяется по всему графу вызовов, чтобы определить истинный масштаб воздействия.
Когда вам необходим межпроцедурный анализ
Не каждому проекту необходим полный межпроцедурный анализ. Решение зависит от того, что именно вы пытаетесь найти и какова цена упущения этой информации.
Межпроцедурный анализ необходим в следующих случаях:
- Безопасность — первостепенная задача, и вам необходимо надежно выявлять уязвимости внедрения кода, XSS или SSRF.
- Вы анализируете устаревший или корпоративный код, в котором бизнес-логика охватывает множество функций и модулей.
- Перед рефакторингом необходимо провести анализ влияния изменений, чтобы понять, что именно повлияет на работу той или иной функции.
- Вы проводите анализ соответствия требованиям или аудит, требующий демонстрации тщательности.
- Вы переносите или модернизируете кодовую базу и вам необходимо понимать зависимости между компонентами.
Внутрипроцедурный анализ достаточен в следующих случаях:
- Цель состоит в обеспечении соблюдения стиля, соответствия общепринятым нормам и выявлении очевидных местных ошибок.
- Вам необходима быстрая обратная связь, интегрированная в цикл разработки (предварительные коммиты, IDE, CI для каждого запроса на слияние).
- Код достаточно мал, чтобы разработчики знали все взаимосвязи между вызовами функций.
- Безопасность обеспечивается на другом уровне (проверенная структура, система типов), и риск межфункциональных уязвимостей низок.
Практический ответ для большинства команд: использовать оба подхода. Инструменты для внутрипроцедурного анализа (ESLint, Pylint, Clippy) запускаются при каждом коммите для быстрой обратной связи. Инструменты для межпроцедурного анализа (CodeQL, Infer, Semgrep с правилами проверки меток) запускаются при запросах на слияние или по ночному расписанию для тщательного анализа. Эти два уровня дополняют друг друга, а не конкурируют.
Инструменты межпроцедурного анализа: что каждый из них охватывает.
| Инструмент | Подход к анализу | Языки | Основной вариант использования |
|---|---|---|---|
| КодQL | Полномасштабная межпроцедурная процедура на основе IFDS. | C/C++, Java, JS/TS, Python, Go, Ruby | Обнаружение уязвимостей безопасности |
| Facebook Infer | Би-абдукция, межпроцедурная | Java, C, C++, Objective-C | Нулевая безопасность, утечки памяти, условия гонки |
| Semgrep (режим проверки на загрязнение) | Поверхностное межпроцедурное загрязнение | 30 + языки | Сопоставление шаблонов безопасности с потоком данных |
| Код Сныка | Межпроцедурный подход на основе машинного обучения | Java, JS/TS, Python, C# | Безопасность с удобной для разработчиков обратной связью |
| Полипространство | Абстрактная интерпретация | C, C++, Ада | Встроенные системы, критически важные для безопасности |
| Frama-С | Полное межпроцедурное + формальное | C | Официальная проверка, сертификация безопасности |
| SMART TS XL | Межъязыковой, межсистемный | COBOL, JCL, Java, Python, RPG, SQL | Анализ влияния на предприятие, сопоставление зависимостей |
Межпроцедурный анализ в условиях языковых различий
Стандартные межпроцедурные инструменты работают в рамках одного языка. Запрос CodeQL к кодовой базе Java отслеживает загрязнение данных через вызовы функций Java, но останавливается, когда код Java вызывает программу COBOL или считывает данные из набора данных, записанного пакетным заданием JCL. Именно на этом этапе инструменты анализа корпоративного масштаба сталкиваются с проблемой, которую инструменты, работающие на одном языке, не могут решить.
SMART TS XLАвтора статический анализ кода Выполняет анализ зависимостей в межпроцедурном стиле одновременно для всех языков программирования в корпоративной среде. Когда служба Java вызывает программу COBOL, которая считывает данные из таблицы DB2, заполняемой другим заданием JCL, SMART TS XL отслеживает эту цепочку и представляет ее в виде единой модели зависимостей. анализ воздействия Затем эта функция использует граф вызовов между языками программирования, чтобы ответить на вопрос: если эта программа на COBOL изменится, какие службы Java, какие задания JCL и какие конечные потребители будут затронуты?
Это межпроцедурная проблема, рассматриваемая в архитектурном, а не функциональном масштабе, тот же принцип (следовать по графу зависимостей через границы, а не останавливаться на них) применяется ко всей корпоративной системе, а не к отдельным программам. Как показано в контексте визуализации кода и отображения зависимостей , обеспечение видимости всей структуры зависимостей является необходимым условием для любого анализа, который претендует на понимание того, на что повлияет изменение.
Для организаций, осуществляющих модернизация наследияграф вызовов, который SMART TS XL Многоязыковая архитектура выполняет ту же функцию, что и межпроцедурный граф вызовов внутри одного языка: это карта того, как всё взаимосвязано, и без неё любой анализ воздействия, риска или последовательности миграции будет основан на предположениях, а не на доказательствах.
Компромисс между ложноположительными и ложноотрицательными результатами
Причина, по которой межпроцедурный анализ не используется повсеместно, заключается не в том, что никто об этом не подумал, а в том, что точность и производительность находятся в противоречии. Полный контекстно-зависимый межпроцедурный анализ кода объемом в миллион строк может занять часы или дни. Тот же самый код, проанализированный ESLint, занимает секунды. Эта разница в производительности носит структурный характер: межпроцедурный анализ развивается с усложнением графа вызовов, а не только с размером кода.
Преимуществом инвестиций является заметно более низкий уровень ложноположительных и ложноотрицательных результатов:
- Ложноположительный (инструменты, утверждающие, что что-то не так, когда это не так): внутрипроцедурные инструменты выдают гораздо больше ложных срабатываний, потому что они не могут определить, действительно ли подозрительный шаблон опасен, не зная контекста от вызывающих сторон. Внутрипроцедурный инструмент, который видит SQL-запрос с объединенной строкой, не может определить, поступила ли эта строка от пользователя или из доверенного внутреннего источника.
- Ложные негативы (инструменты, упускающие реальные уязвимости): внутрипроцедурные инструменты выдают гораздо больше ложных отрицательных результатов, поскольку они не могут обнаружить уязвимости, выходящие за рамки функциональных границ, а это, с точки зрения анализа безопасности, составляет большинство реальных уязвимостей.
Умение выбирать, когда использовать каждый уровень анализа, и комбинировать их в многоуровневой цепочке инструментов — вот что отличает проектирование безопасности от формального соблюдения требований. Знание того, что ваш линтер выдает чистые результаты, мало что говорит о вашем уровне безопасности. Знание того, что ваш межпроцедурный анализ заражения отследил каждый путь от внешних источников ввода до чувствительных приемников в вашем приложении и не обнаружил непроверенных потоков, говорит о гораздо большем.


