BPF program rejected by verifier — Ошибки BPF верификатора в Linux
Архитектура ошибки и симптомы сбоя
Ошибка «BPF: program rejected by verifier (infinite loop / memory access / R1 invalid mem access)» генерируется встроенным в ядро статическим анализатором безопасности eBPF Verifier при выполнении системного вызова bpf(BPF_PROG_LOAD, ...). Верификатор выполняет доказательство безопасности (Directed Acyclic Graph analysis) и блокирует загрузку программы, если обнаруживает риск бесконечного цикла, выход за границы указателя (Out-of-Bounds Memory Access), разыменование непроверенного NULL-указателя или превышение лимита сложности инструкций (1M instructions).
Диагностическая таблица параметров сбоя
| Параметр | Значение | Инженерный смысл сбоя |
|---|---|---|
| eBPF Verifier | Static Code Analyzer | Ядерный верификатор безопасности: гарантирует, что программа никогда не подвесит и не уронит ядро. |
| R0..R10 Registers | Virtual Register State | Отслеживание типов и диапазонов значений (min/max bounds) регистров процессора eBPF. |
| Back-edge / Loop | Infinite Loop Detection | Обнаружение циклов без доказанного условия завершения за конечное число шагов. |
Пошаговое дерево решений и сценарии траблшутинга
Сценарий 1: Просмотр полного лога верификатора (Verifier Log Buffer)
# Запуск программы с выводом отладочного лога верификатора:
# В libbpf задайте размер буфера логов или запустите через bpftrace -v:
sudo bpftrace -v script.bt
# Пример ошибки: 'R2 min value is negative, either use unsigned or 'var &= const''Сценарий 2: Добавление явных проверок границ памяти (Bounds Checking)
Верификатор требует обязательной проверки смещений перед обращением к массиву или сетевому пакету:
// ОШИБКА: Прямой доступ к памяти пакета
// void *data = (void *)(long)ctx->data;
// char val = *((char *)data + offset);
// ИСПРАВЛЕНИЕ: Явная проверка границ перед чтением
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
if ((void *)((char *)data + offset + 1) > data_end) {
return XDP_DROP; // Проверка пройдена, верификатор спокоен
}
char val = *((char *)data + offset);Сценарий 3: Исправление циклов через #pragma unroll или ограниченные циклы (Bounded Loops)
Начиная с Linux 5.3 ядро поддерживает циклы, но требует доказуемого конечного счетчика итераций:
// Принудительное разворачивание цикла компилятором:
#pragma unroll
for (int i = 0; i < 16; i++) {
// Тело цикла
}
// Ограничение переменного цикла для верификатора:
#pragma clang loop unroll(disable)
for (int i = 0; i < 100; i++) {
if (i >= dynamic_count) break; // Явный break
}Сценарий 4: Безопасное чтение памяти ядра через bpf_probe_read_kernel
// Безопасное чтение произвольной структуры ядра без падения верификатора:
struct task_struct *task = (struct task_struct *)bpf_get_current_task();
int pid;
bpf_probe_read_kernel(&pid, sizeof(pid), &task->tgid);ITSTM проводит аудит eBPF кода, исправление состояний регистров для верификатора и оптимизацию сетевых XDP-драйверов.
Частые вопросы (FAQ)
Почему eBPF верификатор ругается на переменную, хотя в C-коде все корректно?
Компилятор Clang может оптимизировать код так, что связь между проверкой условия (if) и чтением памяти теряется в ассемблерных инструкциях BPF. Для верификатора регистр теряет статус проверенного диапазона (bounds), что приводит к отклонению программы.
Каков максимальный лимит инструкций, проверяемых BPF верификатором?
В современных ядрах Linux верификатор анализирует максимум 1 000 000 состояний инструкций (ранее лимит был 4096). Если программа слишком сложна и разветвлена, верификатор прекратит анализ с ошибкой 'program too complex'.
Что означает ошибка 'R1 invalid mem access 'inv''?
Она означает, что в регистре R1 находится невалидное скалярное значение (invalid pointer), которое программа пытается разыменовать как указатель памяти.
Как функция bpf_tail_call помогает обойти лимиты сложности верификатора?
Механизм хвоcтовых вызовов (Tail Calls) позволяет разбить монолитную сложную BPF программу на серию небольших независимых программ, каждая из которых проходит верификацию отдельно.