我正在尝试从 XDP 程序中 TLS hello 数据包的 SNI 扩展中获取服务器名称。当我尝试加载它时,我从 BPF 验证器收到以下错误:
math between pkt pointer and register with unbounded min value is not allowed
struct server_name {
char server_name[256];
};
struct extension {
__u16 type;
__u16 len;
} __attribute__((packed));
struct sni_extension {
__u16 list_len;
__u8 type;
__u16 len;
} __attribute__((packed));
#define SERVER_NAME_EXTENSION 0
SEC("xdp")
int collect_ips_prog(struct xdp_md *ctx) {
char *data_end = (char *)(long)ctx->data_end;
char *data = (char *)(long)ctx->data;
if (data_end < (data + sizeof(__u16))) {
goto end;
}
__u16 extension_method_len = __bpf_htons(*(__u16 *) data);
data += sizeof(__u16);
for(int i = 0; i < extension_method_len; i += sizeof(struct extension)) { // A
if (data_end < (data + sizeof(struct extension))) {
goto end;
}
struct extension *ext = (struct extension *) data;
data += sizeof(struct extension);
if (ext->type == SERVER_NAME_EXTENSION) {
struct server_name sn;
if (data_end < (data + sizeof(struct sni_extension))) {
goto end;
}
struct sni_extension *sni = (struct sni_extension *) data;
data += sizeof(struct sni_extension);
__u16 server_name_len = __bpf_htons(sni->len);
for(int sn_idx = 0; sn_idx < server_name_len; sn_idx++) {
if (data_end < data + sn_idx) {
goto end;
}
if (sn.server_name + sizeof(struct server_name) < sn.server_name + sn_idx) {
goto end;
}
sn.server_name[sn_idx] = data[sn_idx];
}
sn.server_name[server_name_len] = 0;
goto end;
}
volatile int ext_len = __bpf_htons(ext->len);
if (ext_len < 0) {
goto end;
}
data += ext_len;
i += ext_len; // B
} // C
end:
return XDP_PASS;
}
忽略
data
不指向TLS数据包扩展长度字段的开头;我没有包含进入该字段的代码,因为上面的代码足以重现我所看到的问题。
当我尝试加载此程序时出现此错误:
19: R0=pkt(id=0,off=2,r=6,imm=0) R1=inv(id=0) R2=pkt_end(id=0,off=0,imm=0) R3=inv0 R4=inv17179869184 R5_w=pkt(id=0,off=6,r=6,imm=0) R6_w=inv(id=0,umax_value=255,var_off=(0x0; 0xff)) R7_w=inv(id=0) R10=fp0
; __u16 ext_len = __bpf_htons(ext->len);
19: (71) r6 = *(u8 *)(r0 +2)
20: R0=pkt(id=0,off=2,r=6,imm=0) R1=inv(id=0) R2=pkt_end(id=0,off=0,imm=0) R3=inv0 R4=inv17179869184 R5_w=pkt(id=0,off=6,r=6,imm=0) R6_w=inv(id=0,umax_value=255,var_off=(0x0; 0xff)) R7_w=inv(id=0) R10=fp0
20: (71) r0 = *(u8 *)(r0 +3)
21: R0_w=inv(id=0,umax_value=255,var_off=(0x0; 0xff)) R1=inv(id=0) R2=pkt_end(id=0,off=0,imm=0) R3=inv0 R4=inv17179869184 R5_w=pkt(id=0,off=6,r=6,imm=0) R6_w=inv(id=0,umax_value=255,var_off=(0x0; 0xff)) R7_w=inv(id=0) R10=fp0
21: (67) r0 <<= 8
22: R0_w=inv(id=0,umax_value=65280,var_off=(0x0; 0xff00)) R1=inv(id=0) R2=pkt_end(id=0,off=0,imm=0) R3=inv0 R4=inv17179869184 R5_w=pkt(id=0,off=6,r=6,imm=0) R6_w=inv(id=0,umax_value=255,var_off=(0x0; 0xff)) R7_w=inv(id=0) R10=fp0
22: (4f) r0 |= r6
23: R0_w=inv(id=0) R1=inv(id=0) R2=pkt_end(id=0,off=0,imm=0) R3=inv0 R4=inv17179869184 R5_w=pkt(id=0,off=6,r=6,imm=0) R6_w=inv(id=0,umax_value=255,var_off=(0x0; 0xff)) R7_w=inv(id=0) R10=fp0
23: (dc) r0 = be16 r0
24: R0_w=inv(id=0) R1=inv(id=0) R2=pkt_end(id=0,off=0,imm=0) R3=inv0 R4=inv17179869184 R5_w=pkt(id=0,off=6,r=6,imm=0) R6_w=inv(id=0,umax_value=255,var_off=(0x0; 0xff)) R7_w=inv(id=0) R10=fp0
; if (data_end < (data + ext_len)) {
24: (0f) r5 += r0
last_idx 24 first_idx 12
regs=1 stack=0 before 23: (dc) r0 = be16 r0
regs=1 stack=0 before 22: (4f) r0 |= r6
regs=41 stack=0 before 21: (67) r0 <<= 8
regs=41 stack=0 before 20: (71) r0 = *(u8 *)(r0 +3)
regs=40 stack=0 before 19: (71) r6 = *(u8 *)(r0 +2)
math between pkt pointer and register with unbounded min value is not allowed
processed 24 insns (limit 1000000) max_states_per_insn 0 total_states 1 peak_states 1 mark_read 1
如果我注释掉外部 for 循环点
A, B, and C
,则程序加载成功。如果我注释 if (ext->type == SERVER_NAME_EXTENSION) {
块,则程序加载成功。所以我很困惑错误到底在哪里。总的来说,我使用 BPF 验证器的经验是,注释掉不相关的部分会影响其他代码。
我使用 Go 库加载此程序,但如果使用 xdp-loader 加载程序,我会得到相同的错误:
xdp-loader load -m skb -vv -s collect_ips enp0s8 dist/collect_ips.o
当我按照 pchaigno 的答案更新代码时,出现此错误:
0: (61) r2 = *(u32 *)(r1 +4)
; char *data = (char *)(long)ctx->data;
1: (61) r1 = *(u32 *)(r1 +0)
; if (data_end < (data + EXTENSION_METHODS_LEN_FIELD_SIZE)) {
2: (bf) r0 = r1
3: (07) r0 += 2
; if (data_end < (data + EXTENSION_METHODS_LEN_FIELD_SIZE)) {
4: (2d) if r0 > r2 goto pc+37
R0_w=pkt(id=0,off=2,r=2,imm=0) R1_w=pkt(id=0,off=0,r=2,imm=0) R2_w=pkt_end(id=0,off=0,imm=0) R10=fp0
; __u16 extension_methods_len = __bpf_htons(*(__u16 *) data);
5: (69) r1 = *(u16 *)(r1 +0)
; for(int i = 0; i < extension_methods_len; i += sizeof(struct extension)) {
6: (15) if r1 == 0x0 goto pc+35
R0_w=pkt(id=0,off=2,r=2,imm=0) R1_w=inv(id=0,umax_value=65535,var_off=(0x0; 0xffff)) R2_w=pkt_end(id=0,off=0,imm=0) R10=fp0
7: (dc) r1 = be16 r1
8: (b7) r3 = 0
9: (18) r4 = 0x400000000
; if (data_end < (data + sizeof(struct extension))) {
11: (bf) r5 = r0
12: (07) r5 += 4
; if (data_end < (data + sizeof(struct extension))) {
13: (2d) if r5 > r2 goto pc+28
R0=pkt(id=0,off=2,r=6,imm=0) R1=inv(id=0) R2=pkt_end(id=0,off=0,imm=0) R3=inv0 R4=inv17179869184 R5_w=pkt(id=0,off=6,r=6,imm=0) R10=fp0
; if (ext->type == SERVER_NAME_EXTENSION) {
14: (71) r6 = *(u8 *)(r0 +0)
15: (71) r7 = *(u8 *)(r0 +1)
16: (67) r7 <<= 8
17: (4f) r7 |= r6
; if (ext->type == SERVER_NAME_EXTENSION) {
18: (15) if r7 == 0x0 goto pc+23
R0=pkt(id=0,off=2,r=6,imm=0) R1=inv(id=0) R2=pkt_end(id=0,off=0,imm=0) R3=inv0 R4=inv17179869184 R5_w=pkt(id=0,off=6,r=6,imm=0) R6_w=inv(id=0,umax_value=255,var_off=(0x0; 0xff)) R7_w=inv(id=0) R10=fp0
; volatile int ext_len = __bpf_htons(ext->len);
19: (71) r6 = *(u8 *)(r0 +2)
20: (71) r0 = *(u8 *)(r0 +3)
21: (67) r0 <<= 8
22: (4f) r0 |= r6
23: (dc) r0 = be16 r0
; volatile int ext_len = __bpf_htons(ext->len);
24: (63) *(u32 *)(r10 -4) = r0
; if (ext_len < 0) {
25: (61) r0 = *(u32 *)(r10 -4)
26: (67) r0 <<= 32
27: (c7) r0 s>>= 32
; if (ext_len < 0) {
28: (65) if r0 s> 0xffffffff goto pc+1
from 28 to 30: R0=inv(id=0,umax_value=2147483647,var_off=(0x0; 0xffffffff)) R1=inv(id=0) R2=pkt_end(id=0,off=0,imm=0) R3=inv0 R4=inv17179869184 R5=pkt(id=0,off=6,r=6,imm=0) R6=inv(id=0,umax_value=255,var_off=(0x0; 0xff)) R7=inv(id=0) R10=fp0 fp-8=mmmm????
; data += ext_len;
30: (61) r0 = *(u32 *)(r10 -4)
; i += ext_len;
31: (61) r6 = *(u32 *)(r10 -4)
; i += ext_len;
32: (0f) r3 += r6
; for(int i = 0; i < extension_methods_len; i += sizeof(struct extension)) {
33: (67) r3 <<= 32
34: (0f) r3 += r4
35: (c7) r3 s>>= 32
; for(int i = 0; i < extension_methods_len; i += sizeof(struct extension)) {
36: (7d) if r3 s>= r1 goto pc+5
R0=inv(id=0,umax_value=4294967295,var_off=(0x0; 0xffffffff)) R1=inv(id=0) R2=pkt_end(id=0,off=0,imm=0) R3=inv(id=0,smin_value=-2147483648,smax_value=2147483647) R4=inv17179869184 R5=pkt(id=0,off=6,r=6,imm=0) R6=inv(id=0,umax_value=4294967295,var_off=(0x0; 0xffffffff)) R7=inv(id=0) R10=fp0 fp-8=mmmm????
;
37: (67) r0 <<= 32
38: (c7) r0 s>>= 32
39: (0f) r5 += r0
last_idx 39 first_idx 36
regs=1 stack=0 before 38: (c7) r0 s>>= 32
regs=1 stack=0 before 37: (67) r0 <<= 32
regs=1 stack=0 before 36: (7d) if r3 s>= r1 goto pc+5
R0_rw=invP(id=0,umax_value=4294967295,var_off=(0x0; 0xffffffff)) R1_r=inv(id=0) R2=pkt_end(id=0,off=0,imm=0) R3_rw=inv(id=0,smin_value=-2147483648,smax_value=2147483647) R4=inv17179869184 R5_r=pkt(id=0,off=6,r=6,imm=0) R6_w=inv(id=0,umax_value=4294967295,var_off=(0x0; 0xffffffff)) R7=inv(id=0) R10=fp0 fp-8=mmmm????
parent didn't have regs=1 stack=0 marks
last_idx 35 first_idx 28
regs=1 stack=0 before 35: (c7) r3 s>>= 32
regs=1 stack=0 before 34: (0f) r3 += r4
regs=1 stack=0 before 33: (67) r3 <<= 32
regs=1 stack=0 before 32: (0f) r3 += r6
regs=1 stack=0 before 31: (61) r6 = *(u32 *)(r10 -4)
regs=1 stack=0 before 30: (61) r0 = *(u32 *)(r10 -4)
value -2147483648 makes pkt pointer be out of bounds
processed 41 insns (limit 1000000) max_states_per_insn 0 total_states 3 peak_states 3 mark_read 3
TL;DR. 从验证者的角度来看,
ext_len
由于其计算方式而不受限制。为了允许您将此值添加到数据包指针,您需要添加新的绑定检查。请参阅下面的完整说明。
验证者错误说明
22: R0_w=inv(id=0,umax_value=65280,var_off=(0x0; 0xff00)) R1=inv(id=0) R2=pkt_end(id=0,off=0,imm=0) R3=inv0 R4=inv17179869184 R5_w=pkt(id=0,off=6,r=6,imm=0) R6_w=inv(id=0,umax_value=255,var_off=(0x0; 0xff)) R7_w=inv(id=0) R10=fp0
22: (4f) r0 |= r6
23: R0_w=inv(id=0) R1=inv(id=0) R2=pkt_end(id=0,off=0,imm=0) R3=inv0 R4=inv17179869184 R5_w=pkt(id=0,off=6,r=6,imm=0) R6_w=inv(id=0,umax_value=255,var_off=(0x0; 0xff)) R7_w=inv(id=0) R10=fp0
23: (dc) r0 = be16 r0
24: R0_w=inv(id=0) R1=inv(id=0) R2=pkt_end(id=0,off=0,imm=0) R3=inv0 R4=inv17179869184 R5_w=pkt(id=0,off=6,r=6,imm=0) R6_w=inv(id=0,umax_value=255,var_off=(0x0; 0xff)) R7_w=inv(id=0) R10=fp0
; if (data_end < (data + ext_len)) {
24: (0f) r5 += r0
[...]
math between pkt pointer and register with unbounded min value is not allowed
验证者拒绝该程序,因为它看到将无界寄存器(
R0
)添加到保存数据包指针的寄存器(R5
)。特别是,它要求 R0
具有要添加到数据包指针的最小值。如果没有它,您可以从数据包指针中减去任何值并读取任意内核内存。
为什么R0是无界的?
在指令 22 (
r0 |= r6
) 之前,R0
有边界 (umax_value=65280,var_off=(0x0; 0xff00)
)。 R6
也这么做了。不幸的是,验证者似乎无法在逻辑或之后跟踪这些边界,并丢失它们。较新的内核版本可能能够更好地跟踪这一点。
为什么添加 0 最小界限不起作用?
@Qeole 在评论中建议添加 0 的最小边界检查:
if (ext_len < 0)
goto end;
这可能不起作用,因为
ext_len
的类型为 __u16
(即 unsigned short
),因此编译器可能会优化对负号的检查。
为什么添加上限检查有效?
添加上限检查(例如 30000)是有效的,因为 验证者可以从无符号上限检查中推导出有符号下限。
如何解决?
解决此问题的最佳方法可能是按照 Quentin 的建议添加下限检查。但是,您需要对
ext_len
变量进行签名,以便编译器不会优化边界检查。
将其更改为 int 并添加绑定检查对我来说不起作用。 但这对我有用: 索引 &= 134217727u; 一个限制是 134217727u 必须是 2 的幂减 1。