即使在访问数据包中的字节之前进行检查,我也会从eBPF验证器获得invalid access to packet
。偏移量存储在CCD_ 2中。循环迭代的次数并不重要,因为即使我进行一次迭代,这个问题也会发生。
#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;
}
这是错误。当j=0时,错误发生在if (data[pkt_ctx->pkt_offset + j] == 'r') {
。
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字节之外的字节。
TL;DR。您遇到了验证者的一个角落案例。看见https://stackoverflow.com/a/70731589/6884590.如@Qeole所注意到的,在pkt_ctx->pkt_offset
上添加边界检查将修复它。
验证器错误解释
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_offset
上添加边界检查,R2
的最大值将减小,验证器将考虑数据包长度检查。
我相信,由于偏移量来自映射,验证器无法直接使用它来估计访问数据包的边界(R1的范围(。
尝试在循环之前添加一个复选框来绑定偏移量:
if (pkt_ctx->pkt_offset > 1500)
goto end;
for (__u16 j = 0; j < 253; j++) {
...
我在本地尝试过,这似乎效果更好(验证器转到下一个问题,即如果您想使用bpf_printk()
,则需要将代码声明为GPL兼容(。