eBPF:'bpf_map_update()' returns 'invalid indirect read from stack' 错误
eBPF: 'bpf_map_update()' returns the 'invalid indirect read from stack' error
我有一个具有以下映射定义的 eBPF 程序:
struct bpf_map_def SEC("maps") servers = {
.type = BPF_MAP_TYPE_HASH,
.key_size = sizeof(struct ip_key),
.value_size = sizeof(struct dest_info),
.max_entries = MAX_SERVERS,
};
struct bpf_map_def SEC("maps") client_addrs = {
.type = BPF_MAP_TYPE_HASH,
.key_size = sizeof(struct port_key),
.value_size = sizeof(struct client_port_addr),
.max_entries = MAX_CLIENTS,
};
结构定义如下:
struct port_key {
__u16 port;
__u16 pad[3];
};
struct ip_key {
__u32 key;
__u32 pad;
};
struct dest_info {
__u32 saddr;
__u32 daddr;
__u64 bytes;
__u64 pkts;
__u8 dmac[6];
__u16 pad;
};
struct client_port_addr {
__u32 client_ip;
__u8 dmac[6];
__u16 pad[3];
};
在指针验证和初始检查之后,程序本身如下所示。
struct port_key key = {0};
struct client_port_addr val;
key.port = udp->source;
val.client_ip = iph->saddr;
memcpy (val.dmac, eth->h_source, 6 * sizeof(__u8));
bpf_map_update_elem(&client_addrs, &key, &val, BPF_ANY);
iph->saddr = IP_ADDRESS(BALANCER);
iph->daddr = dest_tnl->daddr;
memcpy (eth->h_source, eth->h_dest, 6 * sizeof(__u8));
memcpy (eth->h_dest, dest_tnl->dmac, 6 * sizeof(__u8));
所以,问题是我在我的代码中使用了 bpf_map_update()
,但是在使用它时,我得到了如下所示的 invalid indirect read from the stack
错误。
libbpf:
0: (bf) r6 = r1
1: (61) r9 = *(u32 *)(r6 +4)
2: (61) r7 = *(u32 *)(r6 +0)
3: (18) r1 = 0xffffa59ac00b6000
5: (b7) r2 = 24
6: (85) call bpf_trace_printk#6
R1_w=map_value(id=0,off=0,ks=4,vs=50,imm=0) R2_w=inv24 R6_w=ctx(id=0,off=0,imm=0) R7_w=pkt(id=0,off=0,r=0,imm=0) R9_w=pkt_end(id=0,off=0,imm=0) R10=fp0
last_idx 6 first_idx 0
regs=4 stack=0 before 5: (b7) r2 = 24
7: (b7) r8 = 1
8: (bf) r1 = r7
9: (07) r1 += 14
10: (2d) if r1 > r9 goto pc+130
R0_w=inv(id=0) R1_w=pkt(id=0,off=14,r=14,imm=0) R6_w=ctx(id=0,off=0,imm=0) R7_w=pkt(id=0,off=0,r=14,imm=0) R8_w=inv1 R9_w=pkt_end(id=0,off=0,imm=0) R10=fp0
11: (71) r1 = *(u8 *)(r7 +12)
12: (71) r2 = *(u8 *)(r7 +13)
13: (67) r2 <<= 8
14: (4f) r2 |= r1
15: (b7) r8 = 2
16: (55) if r2 != 0x8 goto pc+124
R0=inv(id=0) R1=inv(id=0,umax_value=255,var_off=(0x0; 0xff)) R2=inv8 R6=ctx(id=0,off=0,imm=0) R7=pkt(id=0,off=0,r=14,imm=0) R8=inv2 R9=pkt_end(id=0,off=0,imm=0) R10=fp0
17: (61) r7 = *(u32 *)(r6 +4)
18: (61) r9 = *(u32 *)(r6 +0)
19: (bf) r6 = r9
20: (07) r6 += 14
21: (b7) r8 = 1
22: (2d) if r6 > r7 goto pc+118
R0=inv(id=0) R1=inv(id=0,umax_value=255,var_off=(0x0; 0xff)) R2=inv8 R6_w=pkt(id=0,off=14,r=14,imm=0) R7_w=pkt_end(id=0,off=0,imm=0) R8_w=inv1 R9_w=pkt(id=0,off=0,r=14,imm=0) R10=fp0
23: (bf) r1 = r9
24: (07) r1 += 34
25: (b7) r8 = 1
26: (2d) if r1 > r7 goto pc+114
R0=inv(id=0) R1=pkt(id=0,off=34,r=34,imm=0) R2=inv8 R6=pkt(id=0,off=14,r=34,imm=0) R7=pkt_end(id=0,off=0,imm=0) R8=inv1 R9=pkt(id=0,off=0,r=34,imm=0) R10=fp0
27: (71) r1 = *(u8 *)(r6 +0)
28: (57) r1 &= 15
29: (b7) r8 = 1
30: (55) if r1 != 0x5 goto pc+110
R0=inv(id=0) R1_w=inv5 R2=inv8 R6=pkt(id=0,off=14,r=34,imm=0) R7=pkt_end(id=0,off=0,imm=0) R8_w=inv1 R9=pkt(id=0,off=0,r=34,imm=0) R10=fp0
31: (61) r3 = *(u32 *)(r9 +26)
32: (18) r1 = 0xffffa59ac00b6018
34: (b7) r2 = 26
35: (85) call bpf_trace_printk#6
R0=inv(id=0) R1_w=map_value(id=0,off=24,ks=4,vs=50,imm=0) R2_w=inv26 R3_w=inv(id=0,umax_value=4294967295,var_off=(0x0; 0xffffffff)) R6=pkt(id=0,off=14,r=34,imm=0) R7=pkt_end(id=0,off=0,imm=0) R8_w=inv1 R9=pkt(id=0,off=0,r=34,imm=0) R10=fp0
last_idx 35 first_idx 26
regs=4 stack=0 before 34: (b7) r2 = 26
36: (69) r1 = *(u16 *)(r9 +20)
37: (57) r1 &= 65343
38: (b7) r8 = 1
39: (55) if r1 != 0x0 goto pc+101
R0=inv(id=0) R1_w=inv0 R6=pkt(id=0,off=14,r=34,imm=0) R7=pkt_end(id=0,off=0,imm=0) R8_w=inv1 R9=pkt(id=0,off=0,r=34,imm=0) R10=fp0
40: (71) r1 = *(u8 *)(r9 +23)
41: (b7) r8 = 2
42: (55) if r1 != 0x11 goto pc+98
R0=inv(id=0) R1_w=inv17 R6=pkt(id=0,off=14,r=34,imm=0) R7=pkt_end(id=0,off=0,imm=0) R8_w=inv2 R9=pkt(id=0,off=0,r=34,imm=0) R10=fp0
43: (bf) r1 = r9
44: (07) r1 += 42
45: (b7) r8 = 1
46: (2d) if r1 > r7 goto pc+94
R0=inv(id=0) R1=pkt(id=0,off=42,r=42,imm=0) R6=pkt(id=0,off=14,r=42,imm=0) R7=pkt_end(id=0,off=0,imm=0) R8=inv1 R9=pkt(id=0,off=0,r=42,imm=0) R10=fp0
47: (b7) r8 = 0
48: (7b) *(u64 *)(r10 -8) = r8
last_idx 48 first_idx 46
regs=100 stack=0 before 47: (b7) r8 = 0
49: (bf) r2 = r10
50: (07) r2 += -8
51: (18) r1 = 0xffff9a7bed1bc000
53: (85) call bpf_map_lookup_elem#1
54: (bf) r7 = r0
55: (15) if r7 == 0x0 goto pc+85
R0=map_value(id=0,off=0,ks=8,vs=32,imm=0) R6=pkt(id=0,off=14,r=42,imm=0) R7=map_value(id=0,off=0,ks=8,vs=32,imm=0) R8=invP0 R9=pkt(id=0,off=0,r=42,imm=0) R10=fp0 fp-8=mmmmmmmm
56: (b7) r8 = 0
57: (7b) *(u64 *)(r10 -16) = r8
last_idx 57 first_idx 55
regs=100 stack=0 before 56: (b7) r8 = 0
58: (69) r1 = *(u16 *)(r9 +34)
59: (6b) *(u16 *)(r10 -16) = r1
60: (61) r1 = *(u32 *)(r9 +26)
61: (63) *(u32 *)(r10 -32) = r1
62: (71) r1 = *(u8 *)(r9 +11)
63: (73) *(u8 *)(r10 -23) = r1
64: (71) r1 = *(u8 *)(r9 +10)
65: (73) *(u8 *)(r10 -24) = r1
66: (71) r1 = *(u8 *)(r9 +7)
67: (67) r1 <<= 8
68: (71) r2 = *(u8 *)(r9 +6)
69: (4f) r1 |= r2
70: (71) r2 = *(u8 *)(r9 +9)
71: (67) r2 <<= 8
72: (71) r3 = *(u8 *)(r9 +8)
73: (4f) r2 |= r3
74: (67) r2 <<= 16
75: (4f) r2 |= r1
76: (63) *(u32 *)(r10 -28) = r2
77: (bf) r2 = r10
78: (07) r2 += -16
79: (bf) r3 = r10
80: (07) r3 += -32
81: (18) r1 = 0xffff9a7bed1bf400
83: (b7) r4 = 0
84: (85) call bpf_map_update_elem#2
invalid indirect read from stack R3 off -32+10 size 16
processed 81 insns (limit 1000000) max_states_per_insn 0 total_states 5 peak_states 5 mark_read 2
libbpf: -- END LOG --
libbpf: failed to load program 'loadbal'
所有定义的键和值结构都被填充到下一个 8 字节的倍数。由于我找不到关于我的问题的任何有用和描述性的解释,非常感谢对这个主题的解释,甚至是一些细节。
如果您需要更多信息,请告诉我。
验证者抱怨是因为你的代码试图从堆栈中读取未初始化的数据,特别是在你的变量val
.
如果我们查看您的代码:
struct client_port_addr {
__u32 client_ip;
__u8 dmac[6];
__u16 pad[3];
};
struct client_port_addr val;
[...]
val.client_ip = iph->saddr; // val.client_ip
memcpy (val.dmac, eth->h_source, 6 * sizeof(__u8)); // val.dmac
// val.pad where??
bpf_map_update_elem(&client_addrs, &key, &val, BPF_ANY);
您初始化了 val.client_ip
和 val.dmac
,但从未初始化 val.pad
。当你将 val
传递给 bpf_map_update_elem()
时,eBPF 验证器意识到辅助函数可能会从内核 space 读取这个包含未初始化内存的变量。这是一个安全风险,因此,验证者拒绝该程序。
要解决此问题,确保在使用前初始化内存。您至少有三种方法可以这样做:
- 您可以在声明时初始化
val
,例如您的 key
:
struct client_port_addr val = {0};
这应该适用于您的情况,但通常不建议这样做,因为这会将所有字段设置为 0,但如果您的结构包含未明确添加的填充,它可能会保持未初始化状态。
- 在您的情况下,您可以用
memcpy()
填充零 val.pad
。与第一个选项相同,如果编译器填充您的结构,这将无济于事。
- 最安全的选择是
memset()
声明结构后:
struct client_port_addr val;
memset(&val, 0, sizeof(val));
然后就可以填写struct的相关字段,传给地图更新助手
我有一个具有以下映射定义的 eBPF 程序:
struct bpf_map_def SEC("maps") servers = {
.type = BPF_MAP_TYPE_HASH,
.key_size = sizeof(struct ip_key),
.value_size = sizeof(struct dest_info),
.max_entries = MAX_SERVERS,
};
struct bpf_map_def SEC("maps") client_addrs = {
.type = BPF_MAP_TYPE_HASH,
.key_size = sizeof(struct port_key),
.value_size = sizeof(struct client_port_addr),
.max_entries = MAX_CLIENTS,
};
结构定义如下:
struct port_key {
__u16 port;
__u16 pad[3];
};
struct ip_key {
__u32 key;
__u32 pad;
};
struct dest_info {
__u32 saddr;
__u32 daddr;
__u64 bytes;
__u64 pkts;
__u8 dmac[6];
__u16 pad;
};
struct client_port_addr {
__u32 client_ip;
__u8 dmac[6];
__u16 pad[3];
};
在指针验证和初始检查之后,程序本身如下所示。
struct port_key key = {0};
struct client_port_addr val;
key.port = udp->source;
val.client_ip = iph->saddr;
memcpy (val.dmac, eth->h_source, 6 * sizeof(__u8));
bpf_map_update_elem(&client_addrs, &key, &val, BPF_ANY);
iph->saddr = IP_ADDRESS(BALANCER);
iph->daddr = dest_tnl->daddr;
memcpy (eth->h_source, eth->h_dest, 6 * sizeof(__u8));
memcpy (eth->h_dest, dest_tnl->dmac, 6 * sizeof(__u8));
所以,问题是我在我的代码中使用了 bpf_map_update()
,但是在使用它时,我得到了如下所示的 invalid indirect read from the stack
错误。
libbpf:
0: (bf) r6 = r1
1: (61) r9 = *(u32 *)(r6 +4)
2: (61) r7 = *(u32 *)(r6 +0)
3: (18) r1 = 0xffffa59ac00b6000
5: (b7) r2 = 24
6: (85) call bpf_trace_printk#6
R1_w=map_value(id=0,off=0,ks=4,vs=50,imm=0) R2_w=inv24 R6_w=ctx(id=0,off=0,imm=0) R7_w=pkt(id=0,off=0,r=0,imm=0) R9_w=pkt_end(id=0,off=0,imm=0) R10=fp0
last_idx 6 first_idx 0
regs=4 stack=0 before 5: (b7) r2 = 24
7: (b7) r8 = 1
8: (bf) r1 = r7
9: (07) r1 += 14
10: (2d) if r1 > r9 goto pc+130
R0_w=inv(id=0) R1_w=pkt(id=0,off=14,r=14,imm=0) R6_w=ctx(id=0,off=0,imm=0) R7_w=pkt(id=0,off=0,r=14,imm=0) R8_w=inv1 R9_w=pkt_end(id=0,off=0,imm=0) R10=fp0
11: (71) r1 = *(u8 *)(r7 +12)
12: (71) r2 = *(u8 *)(r7 +13)
13: (67) r2 <<= 8
14: (4f) r2 |= r1
15: (b7) r8 = 2
16: (55) if r2 != 0x8 goto pc+124
R0=inv(id=0) R1=inv(id=0,umax_value=255,var_off=(0x0; 0xff)) R2=inv8 R6=ctx(id=0,off=0,imm=0) R7=pkt(id=0,off=0,r=14,imm=0) R8=inv2 R9=pkt_end(id=0,off=0,imm=0) R10=fp0
17: (61) r7 = *(u32 *)(r6 +4)
18: (61) r9 = *(u32 *)(r6 +0)
19: (bf) r6 = r9
20: (07) r6 += 14
21: (b7) r8 = 1
22: (2d) if r6 > r7 goto pc+118
R0=inv(id=0) R1=inv(id=0,umax_value=255,var_off=(0x0; 0xff)) R2=inv8 R6_w=pkt(id=0,off=14,r=14,imm=0) R7_w=pkt_end(id=0,off=0,imm=0) R8_w=inv1 R9_w=pkt(id=0,off=0,r=14,imm=0) R10=fp0
23: (bf) r1 = r9
24: (07) r1 += 34
25: (b7) r8 = 1
26: (2d) if r1 > r7 goto pc+114
R0=inv(id=0) R1=pkt(id=0,off=34,r=34,imm=0) R2=inv8 R6=pkt(id=0,off=14,r=34,imm=0) R7=pkt_end(id=0,off=0,imm=0) R8=inv1 R9=pkt(id=0,off=0,r=34,imm=0) R10=fp0
27: (71) r1 = *(u8 *)(r6 +0)
28: (57) r1 &= 15
29: (b7) r8 = 1
30: (55) if r1 != 0x5 goto pc+110
R0=inv(id=0) R1_w=inv5 R2=inv8 R6=pkt(id=0,off=14,r=34,imm=0) R7=pkt_end(id=0,off=0,imm=0) R8_w=inv1 R9=pkt(id=0,off=0,r=34,imm=0) R10=fp0
31: (61) r3 = *(u32 *)(r9 +26)
32: (18) r1 = 0xffffa59ac00b6018
34: (b7) r2 = 26
35: (85) call bpf_trace_printk#6
R0=inv(id=0) R1_w=map_value(id=0,off=24,ks=4,vs=50,imm=0) R2_w=inv26 R3_w=inv(id=0,umax_value=4294967295,var_off=(0x0; 0xffffffff)) R6=pkt(id=0,off=14,r=34,imm=0) R7=pkt_end(id=0,off=0,imm=0) R8_w=inv1 R9=pkt(id=0,off=0,r=34,imm=0) R10=fp0
last_idx 35 first_idx 26
regs=4 stack=0 before 34: (b7) r2 = 26
36: (69) r1 = *(u16 *)(r9 +20)
37: (57) r1 &= 65343
38: (b7) r8 = 1
39: (55) if r1 != 0x0 goto pc+101
R0=inv(id=0) R1_w=inv0 R6=pkt(id=0,off=14,r=34,imm=0) R7=pkt_end(id=0,off=0,imm=0) R8_w=inv1 R9=pkt(id=0,off=0,r=34,imm=0) R10=fp0
40: (71) r1 = *(u8 *)(r9 +23)
41: (b7) r8 = 2
42: (55) if r1 != 0x11 goto pc+98
R0=inv(id=0) R1_w=inv17 R6=pkt(id=0,off=14,r=34,imm=0) R7=pkt_end(id=0,off=0,imm=0) R8_w=inv2 R9=pkt(id=0,off=0,r=34,imm=0) R10=fp0
43: (bf) r1 = r9
44: (07) r1 += 42
45: (b7) r8 = 1
46: (2d) if r1 > r7 goto pc+94
R0=inv(id=0) R1=pkt(id=0,off=42,r=42,imm=0) R6=pkt(id=0,off=14,r=42,imm=0) R7=pkt_end(id=0,off=0,imm=0) R8=inv1 R9=pkt(id=0,off=0,r=42,imm=0) R10=fp0
47: (b7) r8 = 0
48: (7b) *(u64 *)(r10 -8) = r8
last_idx 48 first_idx 46
regs=100 stack=0 before 47: (b7) r8 = 0
49: (bf) r2 = r10
50: (07) r2 += -8
51: (18) r1 = 0xffff9a7bed1bc000
53: (85) call bpf_map_lookup_elem#1
54: (bf) r7 = r0
55: (15) if r7 == 0x0 goto pc+85
R0=map_value(id=0,off=0,ks=8,vs=32,imm=0) R6=pkt(id=0,off=14,r=42,imm=0) R7=map_value(id=0,off=0,ks=8,vs=32,imm=0) R8=invP0 R9=pkt(id=0,off=0,r=42,imm=0) R10=fp0 fp-8=mmmmmmmm
56: (b7) r8 = 0
57: (7b) *(u64 *)(r10 -16) = r8
last_idx 57 first_idx 55
regs=100 stack=0 before 56: (b7) r8 = 0
58: (69) r1 = *(u16 *)(r9 +34)
59: (6b) *(u16 *)(r10 -16) = r1
60: (61) r1 = *(u32 *)(r9 +26)
61: (63) *(u32 *)(r10 -32) = r1
62: (71) r1 = *(u8 *)(r9 +11)
63: (73) *(u8 *)(r10 -23) = r1
64: (71) r1 = *(u8 *)(r9 +10)
65: (73) *(u8 *)(r10 -24) = r1
66: (71) r1 = *(u8 *)(r9 +7)
67: (67) r1 <<= 8
68: (71) r2 = *(u8 *)(r9 +6)
69: (4f) r1 |= r2
70: (71) r2 = *(u8 *)(r9 +9)
71: (67) r2 <<= 8
72: (71) r3 = *(u8 *)(r9 +8)
73: (4f) r2 |= r3
74: (67) r2 <<= 16
75: (4f) r2 |= r1
76: (63) *(u32 *)(r10 -28) = r2
77: (bf) r2 = r10
78: (07) r2 += -16
79: (bf) r3 = r10
80: (07) r3 += -32
81: (18) r1 = 0xffff9a7bed1bf400
83: (b7) r4 = 0
84: (85) call bpf_map_update_elem#2
invalid indirect read from stack R3 off -32+10 size 16
processed 81 insns (limit 1000000) max_states_per_insn 0 total_states 5 peak_states 5 mark_read 2
libbpf: -- END LOG --
libbpf: failed to load program 'loadbal'
所有定义的键和值结构都被填充到下一个 8 字节的倍数。由于我找不到关于我的问题的任何有用和描述性的解释,非常感谢对这个主题的解释,甚至是一些细节。
如果您需要更多信息,请告诉我。
验证者抱怨是因为你的代码试图从堆栈中读取未初始化的数据,特别是在你的变量val
.
如果我们查看您的代码:
struct client_port_addr {
__u32 client_ip;
__u8 dmac[6];
__u16 pad[3];
};
struct client_port_addr val;
[...]
val.client_ip = iph->saddr; // val.client_ip
memcpy (val.dmac, eth->h_source, 6 * sizeof(__u8)); // val.dmac
// val.pad where??
bpf_map_update_elem(&client_addrs, &key, &val, BPF_ANY);
您初始化了 val.client_ip
和 val.dmac
,但从未初始化 val.pad
。当你将 val
传递给 bpf_map_update_elem()
时,eBPF 验证器意识到辅助函数可能会从内核 space 读取这个包含未初始化内存的变量。这是一个安全风险,因此,验证者拒绝该程序。
要解决此问题,确保在使用前初始化内存。您至少有三种方法可以这样做:
- 您可以在声明时初始化
val
,例如您的key
:
这应该适用于您的情况,但通常不建议这样做,因为这会将所有字段设置为 0,但如果您的结构包含未明确添加的填充,它可能会保持未初始化状态。struct client_port_addr val = {0};
- 在您的情况下,您可以用
memcpy()
填充零val.pad
。与第一个选项相同,如果编译器填充您的结构,这将无济于事。 - 最安全的选择是
memset()
声明结构后:
然后就可以填写struct的相关字段,传给地图更新助手struct client_port_addr val; memset(&val, 0, sizeof(val));