Один BPF-объект, два верификатора, разные вердикты: разбираемся, кто прав

4 мин чтения 0
Один BPF-объект, два верификатора, разные вердикты: разбираемся, кто прав

PREVAIL принял BPF-программу как безопасную. Штатный верификатор ядра Linux ту же самую программу отклонил. Кода не было в статье про уязвимости. Ошибки в логике верификатора тоже не нашлось. Разбираемся, почему отказ верификатора — не синоним бага и как отличить ложный вердикт от реальной проблемы.

Суть события

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

Но если один и тот же объектный файл прогнать через два разных верификатора, картина меняется. PREVAIL (внешний статический анализатор) принял объект correlated_branch.o. Верификатор ядра Linux тот же самый объект отклонил. Программа одна, условия одни.

Никакого бага в коде не было — и именно в этом главный урок.

Детали

Программа ConvergedBranch из набора ebpf-samples читает Ethernet-заголовок сетевого пакета. Перед чтением функция check_packet убеждается, что данных хватает: если размер пакета недостаточен, она возвращает нулевой указатель. Если проверка проходит — возвращается указатель на начало пакета, после чего выполняется проверка и чтение поля.

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

Верификатор ядра проверку границ видел: получил ограничение на размер доступной области (не меньше 14 байт). Однако это ограничение не связалось с указателем r7, который используется при последующем чтении. Верификатор остановился на отказе: invalid access to packet, off=12 size=2.

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

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

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

Контекст

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

Цена ошибки в обратную сторону тоже реальна: если разработчик видит отказ верификатора и решает, что это ложное срабатывание, не разобравшись, — в продакшен может уйти код с неочевидной уязвимостью.

Для фрилансеров и удалённых разработчиков, которые разворачивают eBPF-фильтры на серверах клиентов, это означает дополнительный этап отладки: прежде чем править «очевидный» баг, стоит посмотреть в лог верификатора по состоянию регистров — запись вида R7 pkt(off=0,r=0) говорит больше, чем сам текст отказа. А сравнение вердиктов PREVAIL и ядра может уберечь от часов напрасной переработки.

Что дальше

Если верификатор отклонил программу — не паникуйте и не правиль сразу. Оцените, вынесена ли проверка границ в отдельную функцию. Попробуйте перенести её в место использования указателя: if (ptr + N > data_end) return 0;. Проверьте вердикт PREVAIL. Если оба верификатора дали один вердикт — скорее всего, проблема в коде. Если разошлись — возможно, вы имеете дело с ограничением анализа, а не с уязвимостью.

Для команд, которые массово деплоят eBPF-фильтры, имеет смысл добавить в CI-пайплайн прогон через внешний верификатор в дополнение к ядерному — это даёт второе мнение и помогает приоритизировать, над каким отказом стоит разбираться в первую очередь.

Материал был полезен?
AI-инструменты без границ

Получите доступ к нужному AI-сервису уже сегодня

ChatGPT, Claude, Gemini, MiniMax и агенты для кода — предложения проверенных продавцов по доступным ценам. Оплата российскими картами, через СБП или криптовалютой.

Проверенные продавцы Быстрая выдача Доступные цены
Прокрутить вверх