0

即使在访问数据包中的字节之前我正在执行检查,我invalid access to packet还是从 eBPF 验证程序中获取信息。偏移量存储在BPF_MAP_TYPE_ARRAY. 循环迭代的次数无关紧要,因为即使我进行一次迭代也会发生这个问题。

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

struct packet_context {
    __u16 pkt_offset;
};

struct bpf_map_def SEC("maps") context_table = {
   .type = BPF_MAP_TYPE_ARRAY,
   .key_size = sizeof(__u32),
   .value_size = sizeof(struct packet_context),
   .max_entries = 1,
};

SEC("xdp")
int collect_ips_prog(struct xdp_md *ctx) {
    char *data_end = (char *)(long)ctx->data_end;
    char *data = (char *)(long)ctx->data;
    __u32 index = 0;
    struct packet_context *pkt_ctx = (struct packet_context *) bpf_map_lookup_elem(&context_table, &index);

    if (pkt_ctx == NULL) {
        goto end;
    }

    __u32 length = 0;

    for (__u16 j = 0; j < 253; j++) {
        if (data_end < data + pkt_ctx->pkt_offset + j + 1) {
            goto end;
        }

        if (data[pkt_ctx->pkt_offset + j] == '\r') {
            break;
        }

        length++;
    }

    bpf_printk("%d", length);

end:
    return XDP_PASS;
}

这是错误。错误发生在if (data[pkt_ctx->pkt_offset + j] == '\r') {j = 0 时。

0: (61) r7 = *(u32 *)(r1 +0)
; char *data_end = (char *)(long)ctx->data_end;
1: (61) r8 = *(u32 *)(r1 +4)
2: (b7) r6 = 0
; __u32 index = 0;
3: (63) *(u32 *)(r10 -4) = r6
last_idx 3 first_idx 0
regs=40 stack=0 before 2: (b7) r6 = 0
4: (bf) r2 = r10
; 
5: (07) r2 += -4
; struct packet_context *pkt_ctx = (struct packet_context *) bpf_map_lookup_elem(&context_table, &index);
6: (18) r1 = 0xffff9790029a2e00
8: (85) call bpf_map_lookup_elem#1
; if (pkt_ctx == NULL) {
9: (15) if r0 == 0x0 goto pc+22
 R0_w=map_value(id=0,off=0,ks=4,vs=2,imm=0) R6_w=invP0 R7_w=pkt(id=0,off=0,r=0,imm=0) R8_w=pkt_end(id=0,off=0,imm=0) R10=fp0 fp-8=mmmm????
10: (69) r1 = *(u16 *)(r0 +0)
 R0_w=map_value(id=0,off=0,ks=4,vs=2,imm=0) R6_w=invP0 R7_w=pkt(id=0,off=0,r=0,imm=0) R8_w=pkt_end(id=0,off=0,imm=0) R10=fp0 fp-8=mmmm????
; for (__u16 j = 0; j < 253; j++) {
11: (0f) r7 += r1
last_idx 11 first_idx 0
regs=2 stack=0 before 10: (69) r1 = *(u16 *)(r0 +0)
12: (b7) r3 = 253
; if (data_end < data + pkt_ctx->pkt_offset + j + 1) {
13: (bf) r1 = r7
14: (0f) r1 += r6
15: (bf) r2 = r1
16: (07) r2 += 1
; if (data_end < data + pkt_ctx->pkt_offset + j + 1) {
17: (2d) if r2 > r8 goto pc+14
 R0=map_value(id=0,off=0,ks=4,vs=2,imm=0) R1_w=pkt(id=2,off=0,r=0,umax_value=65535,var_off=(0x0; 0xffff)) R2_w=pkt(id=2,off=1,r=0,umax_value=65535,var_off=(0x0; 0xffff)) R3=inv253 R6=invP0 R7=pkt(id=2,off=0,r=0,umax_value=65535,var_off=(0x0; 0xffff)) R8=pkt_end(id=0,off=0,imm=0) R10=fp0 fp-8=mmmm????
; if (data[pkt_ctx->pkt_offset + j] == '\r') {
18: (71) r1 = *(u8 *)(r1 +0)
invalid access to packet, off=0 size=1, R1(id=2,off=0,r=0)
R1 offset is outside of the packet
processed 18 insns (limit 1000000) max_states_per_insn 0 total_states 1 peak_states 1 mark_read 1

即使pkt_ctx->pkt_offset == 0xffff,访问前的 if 条件也应该阻止访问数据包前 0xffff 字节之外的字节。

4

2 回答 2

1

我相信,因为您的偏移量来自地图,所以验证者不能直接使用它来估计访问数据包的边界(R1 的范围)。

尝试在循环之前添加检查以限制您的偏移量:

    if (pkt_ctx->pkt_offset > 1500)
        goto end;

    for (__u16 j = 0; j < 253; j++) {
        ...

我在本地尝试过,这似乎效果更好(验证程序转到下一个问题,即如果您想使用,则需要将代码声明为 GPL 兼容bpf_printk())。

于 2022-01-27T09:57:03.430 回答
1

TL;博士。您遇到了验证者的极端情况。请参阅https://stackoverflow.com/a/70731589/6884590pkt_ctx->pkt_offset正如@Qeole 所注意到的,添加边界检查将修复它。


验证程序错误说明

13: (bf) r1 = r7
14: (0f) r1 += r6
15: (bf) r2 = r1
16: (07) r2 += 1
; if (data_end < data + pkt_ctx->pkt_offset + j + 1) {
17: (2d) if r2 > r8 goto pc+14
 R0=map_value(id=0,off=0,ks=4,vs=2,imm=0) R1_w=pkt(id=2,off=0,r=0,umax_value=65535,var_off=(0x0; 0xffff)) R2_w=pkt(id=2,off=1,r=0,umax_value=65535,var_off=(0x0; 0xffff)) R3=inv253 R6=invP0 R7=pkt(    id=2,off=0,r=0,umax_value=65535,var_off=(0x0; 0xffff)) R8=pkt_end(id=0,off=0,imm=0) R10=fp0 fp-8=mmmm????
; if (data[pkt_ctx->pkt_offset + j] == '\r') {
18: (71) r1 = *(u8 *)(r1 +0)
invalid access to packet, off=0 size=1, R1(id=2,off=0,r=0)
R1 offset is outside of the packet

验证者抱怨是因为数据包访问(insn. 18)越界。这似乎出乎意料,因为您在访问之前检查了数据包的长度(insn. 17)。

不幸的是,验证者甚至没有考虑边界检查,因为您遇到了这个条件。基本上,最大值R2太高(仅在第一次迭代中为 1)并且验证者认为存在溢出风险。

R2的最大值为 65535,其偏移量为 1(对于第一次迭代),因此两者之和高于MAX_PACKET_OFF(65535)。考虑到这是溢出风险,验证者 find_good_pkt_pointers甚至在更新所有数据包指针的边界之前就返回。


解决方案

@Qeole 是正确的,添加边界检查会有所帮助,尽管并非完全出于他所说的原因。通过在 上添加边界检查pkt_ctx->pkt_offsetR2将减小 的最大值,并且验证者将考虑数据包长度检查。

于 2022-01-27T10:10:40.457 回答