1

对于以下程序,我从验证者那里得到一个错误,说它超过了 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) {块,则验证者成功。

4

1 回答 1

2

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


验证者错误分析

BPF 程序太大。已处理 1000001 insn

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

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

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

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

我该如何解决?

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

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

于 2022-01-25T11:56:17.847 回答