BPF 验证器说程序超过 1M 指令

BPF verifier says program exceeds 1M instruction

对于下面的程序,我从验证器那里得到一个错误,说它超过了 1M 指令,即使它不应该。该程序查找 HTTP 数据包的主机名。

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>

struct server_name {
    char server_name[256];
    __u16 length;
};

#define MAX_SERVER_NAME_LENGTH 253
#define HEADER_LEN 6

SEC("xdp")
int collect_ips_prog(struct xdp_md *ctx) {
    char *data_end = (char *)(long)ctx->data_end;
    char *data = (char *)(long)ctx->data;
    int host_header_found = 0;

    for (__u16 i = 0; i <= 512 - HEADER_LEN; i++) {
        host_header_found = 0;

        if (data_end < data + HEADER_LEN) {
            goto end;
        }

        // Elf loader does not allow NULL terminated strings, so have to check each char manually
        if (data[0] == 'H' && data[1] == 'o' && data[2] == 's' && data[3] == 't' && data[4] == ':' && data[5] == ' ') {
            host_header_found = 1;
            data += HEADER_LEN;
            break;
        }

        data++;
    }

    if (host_header_found) {
        struct server_name sn = {"a", 0};

        for (__u16 j = 0; j < MAX_SERVER_NAME_LENGTH; j++) {
            if (data_end < data + 1) {
                goto end;
            }

            if (*data == '\r') {
                break;
            }

            sn.server_name[j] = *data++;
            sn.length++;
        }
    }

end:
    return XDP_PASS;
}

忽略 data 未指向数据包 HTTP 负载的开头。这足以重现我遇到的问题。

我收到以下错误:

; for (__u16 j = 0; j < MAX_SERVER_NAME_LENGTH; j++) {
76: (25) if r3 > 0xfb goto pc+3
77: (07) r3 += 1
78: (07) r4 += 8
79: (3d) if r1 >= r4 goto pc-15

from 79 to 65: R0_w=fp-189 R1=pkt_end(id=0,off=0,imm=0) R2=pkt(id=0,off=280,r=363,imm=0) R3_w=invP76 R4_w=pkt(id=0,off=363,r=363,imm=0) R5_w=inv(id=0,umin_value=1,umax_value=65536,var_off=(0x0; 0x1ffff)) R10=fp0 fp-8=??????mm fp-16=00000000 fp-24=00000000 fp-32=00000000 fp-40=00000000 fp-48=00000000 fp-56=00000000 fp-64=00000000 fp-72=00000000 fp-80=00000000 fp-88=00000000 fp-96=00000000 fp-104=00000000 fp-112=00000000 fp-120=00000000 fp-128=00000000 fp-136=00000000 fp-144=00000000 fp-152=00000000 fp-160=00000000 fp-168=00000000 fp-176=00000000 fp-184=00000000 fp-192=0000mmmm fp-200=mmmmmmmm fp-208=mmmmmmmm fp-216=mmmmmmmm fp-224=mmmmmmmm fp-232=mmmmmmmm fp-240=mmmmmmmm fp-248=mmmmmmmm fp-256=mmmmmmmm fp-264=mmmmmmmm
; if (*data == '\r') {
65: (bf) r4 = r2
66: (0f) r4 += r3
67: (71) r5 = *(u8 *)(r4 +6)
BPF program is too large. Processed 1000001 insn
processed 1000001 insns (limit 1000000) max_states_per_insn 34 total_states 10376 peak_states 7503 mark_read 3

这没有意义,因为在第二个 for 循环中最多应该有 20 条指令,如果达到最大迭代次数,这将导致最多 5060 条指令。我可以将 MAX_SERVER_NAME_LENGTH 减少到验证者通过的最小值是 104。如果我注释掉 if (host_header_found) { 块,那么验证者会成功。

TL;DR.您的程序太复杂,验证者无法分析,因为它必须迭代超过 100 万条指令才能验证完整程序。


验证器错误分析

BPF program is too large. Processed 1000001 insn

验证器出错,因为它已经分析了 100 万条指令。因此它达到了极限并正在放弃。

这个验证者错误确实有点误导。 BPF 程序实际上并不太大。验证者必须分析的指令数与整个程序中的指令数不同,因为验证者必须分析程序的每条路径。因此,它可能会沿着不同的路径多次分析同一条指令。

这么小的程序怎么需要超过1M的分析指令?

验证器达到了 100 万条指令,因为您的程序有很多不同的路径。事实上,您的程序有两个具有相当高边界(506 和 253)的循环,它们本身包含几个条件(为简化起见,每个循环约 2 个)。在最坏的情况下,验证者可能不得不分析通过这两个循环的所有可能路径上的每条指令。

我该如何解决?

您可以减小循环的大小(如您所想)以降低复杂性。您还可以简化循环体。

或者,您可以使用尾调用中断您的程序。也许两个循环之间的一次尾调用就足以通过验证程序。