eBPF:即使进行了边界检查,对映射值的访问也无效



基本上我想通过索引和偏移量从数组映射中读取字节,但我得到了invalid access to map value,即使我已经执行了边界检查

我真的不知道出了什么问题,可能与溢出风险有关?

如有任何意见,不胜感激!

这是代码:

struct bpf_elf_map __section("maps") data_store = {
.type = BPF_MAP_TYPE_ARRAY,
.size_key = sizeof(__u32),
.size_value = 1024,
.max_elem = 4096,
.pinning = PIN_GLOBAL_NS,
};
static __always_inline void read_data(__u32 idx, __u32 offset, void *dst,
__u32 size) {
if (size > 512 || offset >= 1024) {
// for the ebpf verifier
return;
}
void *b = bpf_map_lookup_elem(&data_store, &idx);
if (!b) {
// shouldn't happen
return;
}
if (offset + size <= 1024) {
for (__u32 i = 0; i < size && i < 512; ++i) {
if (offset + i >= 1024) {
return;
}
// !! where the verifier complains
memcpy(dst + i, b + offset + i, sizeof(__u8));
}
} else {
// ...
}
}

验证器日志:

; for (__u32 i = 0; i < size && i < 512; ++i) {
197: (25) if r1 > 0x1fe goto pc+8
;
198: (bf) r2 = r1
199: (07) r2 += 1
; for (__u32 i = 0; i < size && i < 512; ++i) {
200: (3d) if r2 >= r8 goto pc+5
R0=map_value(id=0,off=0,ks=4,vs=1024,umax_value=1023,var_off=(0x0; 0x3ff)) R1=invP0 R2_w=invP1 R3_w=inv(id=0,umax_value=255,var_off=(0x0; 0xff)) R6=map_value(id=0,off=0,ks=4,vs=32,imm=0) R7=map_value(id=0,off=0,ks=4,vs=512,imm=0) R8=inv(id=261,umin_value=2,umax_value=512,var_off=(0x0; 0x3ff)) R9=invP(id=0,umin_value=1,umax_value=1024,var_off=(0x0; 0x7ff)) R10=fp0 fp-8=mmmmm??? fp-48=????mmmm
201: (bf) r3 = r9
202: (0f) r3 += r1
203: (bf) r1 = r2
204: (25) if r3 > 0x3ff goto pc+1
R0=map_value(id=0,off=0,ks=4,vs=1024,umax_value=1023,var_off=(0x0; 0x3ff)) R1=invP1 R2=invP1 R3=invP(id=0,umin_value=1,umax_value=1023,var_off=(0x0; 0x3ff)) R6=map_value(id=0,off=0,ks=4,vs=32,imm=0) R7=map_value(id=0,off=0,ks=4,vs=512,imm=0) R8=inv(id=261,umin_value=2,umax_value=512,var_off=(0x0; 0x3ff)) R9=invP(id=263,umin_value=1,umax_value=1024,var_off=(0x0; 0x7ff)) R10=fp0 fp-8=mmmmm??? fp-48=????mmmm
205: (05) goto pc-15
; memcpy(dst + i, b + offset + i, sizeof(__u8));
191: (bf) r2 = r7
192: (0f) r2 += r1
193: (bf) r3 = r0
194: (0f) r3 += r1
195: (71) r3 = *(u8 *)(r3 +0)
R0=map_value(id=0,off=0,ks=4,vs=1024,umax_value=1023,var_off=(0x0; 0x3ff)) R1=invP1 R2_w=map_value(id=0,off=1,ks=4,vs=512,imm=0) R3_w=map_value(id=0,off=1,ks=4,vs=1024,umax_value=1023,var_off=(0x0; 0x3ff)) R6=map_value(id=0,off=0,ks=4,vs=32,imm=0) R7=map_value(id=0,off=0,ks=4,vs=512,imm=0) R8=inv(id=261,umin_value=2,umax_value=512,var_off=(0x0; 0x3ff)) R9=invP(id=263,umin_value=1,umax_value=1024,var_off=(0x0; 0x7ff)) R10=fp0 fp-8=mmmmm??? fp-48=????mmmm
invalid access to map value, value_size=1024 off=1024 size=1
R3 max value is outside of the allowed memory range

TL;DR。当它检查memcpy的内存访问时,验证器丢失了上一次边界检查(offset + i >= 1024(中的信息,因此会出错。


验证程序错误解释

193: (bf) r3 = r0
194: (0f) r3 += r1
195: (71) r3 = *(u8 *)(r3 +0)
R0=map_value(id=0,off=0,ks=4,vs=1024,umax_value=1023,var_off=(0x0; 0x3ff)) R1=invP1 R2_w=map_value(id=0,off=1,ks=4,vs=512,imm=0) R3_w=map_value(id=0,off=1,ks=4,vs=1024,umax_value=1023,var_off=(0x0; 0x3ff)) R6=map_value(id=0,off=0,ks=4,vs=32,imm=0) R7=map_value(id=0,off=0,ks=4,vs=512,imm=0) R8=inv(id=261,umin_value=2,umax_value=512,var_off=(0x0; 0x3ff)) R9=invP(id=263,umin_value=1,umax_value=1024,var_off=(0x0; 0x7ff)) R10=fp0 fp-8=mmmmm??? fp-48=????mmmm
invalid access to map value, value_size=1024 off=1024 size=1

在该输出中,R1是i,而R0b + offset

验证器抱怨说,当i=1(即r1=invP1(和offset=1023(即R0的umax_value=1023(时,内存负载可能读取值的1024字节之外。这可以通过将R3的umax_value到R3的off添加到访问大小(1023+1+1(来容易地检查。

根本原因

乍一看,这似乎出乎意料,因为您确实在之前检查了内存负载是否有界

if (offset + i >= 1024) {
return;
}

然而,相加offset + ib + offset + i是分开计算的,并且因此丢失了边界。我们可以在验证器输出中看到,其中第一个加法是在指令202处计算的,而第二个是在指令194处计算的。

我相信发生这种情况是因为编译器重新组织了代码,将b + offset的计算转移到循环体之外。

潜在的变通办法

为了防止编译器以这种方式重新组织代码,您可以将循环更改为:

dst -= offset;
for (__u32 i = offset; i < size + offset && i < 1024; ++i)
memcpy(dst + i, b + i, sizeof(__u8));

最新更新