我有以下结构:
struct ip_event_t {
__u32 src_ip;
__u32 dst_ip;
__u16 src_port;
__u16 dst_port;
__u32 payload_length;
char payload[MAX_PAYLOAD_LENGTH];
};
还有有问题的代码:
if (total_len + to_read > MAX_PAYLOAD_LENGTH) {
to_read = 0;
return -1;
}
if (bpf_probe_read_user(&event->payload[total_len], to_read, iov_entry.iov_base) < 0) {
return -1;
}
验证者似乎无法理解绑定检查:
; if (total_len + to_read > MAX_PAYLOAD_LENGTH) {
191: (bf) r8 = r2 ; frame1: R2_w=2000 R8_w=2000
192: (0f) r8 += r7 ; frame1: R7=scalar(id=10,smin=smin32=0,smax=umax=smax32=umax32=1999,var_off=(0x0; 0x7ff)) R8_w=scalar(smin=umin=smin32=umin32=2000,smax=umax=smax32=umax32=3999,var_off=(0x0; 0xfff))
; if (total_len + to_read > MAX_PAYLOAD_LENGTH) {
193: (25) if r8 > 0x7d0 goto pc+88 ; frame1: R8_w=2000
; if (bpf_probe_read_user(&event->payload[total_len], to_read, iov_entry.iov_base) < 0) {
194: (bf) r1 = r6 ; frame1: R1_w=map_value(map=ip_event_map,ks=4,vs=2016) R6=map_value(map=ip_event_map,ks=4,vs=2016)
195: (0f) r1 += r7 ; frame1: R1_w=map_value(map=ip_event_map,ks=4,vs=2016,smin=smin32=0,smax=umax=smax32=umax32=1999,var_off=(0x0; 0x7ff)) R7=scalar(id=10,smin=smin32=0,smax=umax=smax32=umax32=1999,var_off=(0x0; 0x7ff))
196: (bf) r3 = r10 ; frame1: R3_w=fp0 R10=fp0
197: (07) r3 += -56 ; frame1: R3_w=fp-56
; if (bpf_probe_read_user(&event->payload[total_len], to_read, iov_entry.iov_base) < 0) {
198: (79) r3 = *(u64 *)(r3 +0) ; frame1: R3_w=scalar() fp-56=mmmmmmmm
; if (bpf_probe_read_user(&event->payload[total_len], to_read, iov_entry.iov_base) < 0) {
199: (07) r1 += 16 ; frame1: R1_w=map_value(map=ip_event_map,ks=4,vs=2016,off=16,smin=smin32=0,smax=umax=smax32=umax32=1999,var_off=(0x0; 0x7ff))
; if (bpf_probe_read_user(&event->payload[total_len], to_read, iov_entry.iov_base) < 0) {
200: (85) call bpf_probe_read_user#112
invalid access to map value, value_size=2016 off=2015 size=2000
R1 max value is outside of the allowed memory range
processed 278 insns (limit 1000000) max_states_per_insn 2 total_states 21 peak_states 21 mark_read 9
-- END PROG LOAD LOG --
libbpf: prog 'bpf_prog_tcp_sendmsg': failed to load: -13
让验证者了解写操作是安全的正确方法是什么?
TL;DR. 您遇到了 Linux eBPF 验证器的限制,它无法跟踪变量之间的关系。一种解决方案可能是将
to_read
替换为常数。
验证器错误说明
invalid access to map value, value_size=2016 off=2015 size=2000 R1 max value is outside of the allowed memory range
您的程序被拒绝,因为验证程序检测到您可能使用
bpf_probe_read_user
读取超出了地图值范围。更具体地说,它检测到偏移量 2015(total_len
的最大值 + offsetof(ip_event_t, payload)
)处的潜在映射值访问,访问大小为 2000(to_read
的值)。地图值的大小为 2016,因此超出范围。
为什么边界检查无效?
正如您所指出的,边界检查应该可以防止这种情况发生。然而,正如我们在验证器返回的字节码中看到的那样,边界检查发生在寄存器
r8
上(指令 191-193)。该寄存器保存 r2
(to_read
) 和 r7
(total_len
) 的加法。通过 bpf_probe_read_user
访问地图,但没有使用 r8
,而是直接使用 r2
和 r7
。
您可能想知道为什么验证者无法从
r2
边界检查中推断出 r7
和 r8
的信息。它可以意识到r8 < Z
意味着r2 + r7 < Z
。不幸的是,验证者无法跟踪变量之间的此类条件。跟踪变量之间的关系需要很高的成本,如 Windows eBPF 验证器所示,它确实支持它。
解决方案
我注意到
to_read
的值在运行时是恒定的(根据验证者)。一种解决方案可能是将 to_read
变量替换为常量保持值 2000。然后编译器将从字节码中优化 r2
,它可能足以通过验证程序。