我正在开发一个使用BCC框架来分析HTTP请求的eBPF程序。该程序旨在拦截端口 8000 上的 TCP 数据包并在有效负载中搜索特定模式 (“cmd=”)。下面是我的 eBPF 代码的简化版本:
#include <uapi/linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#define TCP_PORT_HTTP 8000
#define MAX_ITERATIONS 100
#define MAX_CMD_LENGTH 100
int xdp_search_http_request(struct xdp_md *ctx) {
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end)
return XDP_PASS;
if (eth->h_proto != htons(ETH_P_IP))
return XDP_PASS;
struct iphdr *ip = (void *)eth + sizeof(*eth);
if ((void *)(ip + 1) > data_end || ip->protocol != IPPROTO_TCP)
return XDP_PASS;
struct tcphdr *tcp = (void *)ip + ip->ihl * 4;
if ((void *)(tcp + 1) > data_end || tcp->dest != htons(TCP_PORT_HTTP))
return XDP_PASS;
unsigned char *payload = (unsigned char *)tcp + (tcp->doff * 4);
if ((void *)(payload + 1) > data_end)
return XDP_PASS;
unsigned char *end = data_end;
int cmd_value_start = 0;
// Simple search for "cmd=" in the payload
for (int i = 0; i < MAX_ITERATIONS && (payload + i + 4) <= end; i++) {
if (payload[i] == 'c' && payload[i+1] == 'm' && payload[i+2] == 'd' && payload[i+3] == '=') {
// Found the "cmd=" string
bpf_trace_printk("Found cmd parameter in HTTP request\\n");
// Move the pointer to the value part of "cmd="
cmd_value_start = i + 4;
int j = 0;
char cmd [100] = {0};
for (j = cmd_value_start; (j < (cmd_value_start + MAX_CMD_LENGTH)) && ((payload + j) < end) && payload[j] != ' ' && payload[j] != '&'; j++) {
cmd[j - cmd_value_start] = payload[j];
}
bpf_trace_printk("cmd value: %s", cmd);
break; // Exit after finding the first occurrence
}
}
return XDP_PASS;
}
当我尝试加载并执行该程序时,遇到以下错误:
bpf: Failed to load program: Permission denied
; void *data_end = (void *)(long)ctx->data_end;
0: (61) r6 = *(u32 *)(r1 +4)
; void *data = (void *)(long)ctx->data;
1: (61) r7 = *(u32 *)(r1 +0)
; if ((void *)(eth + 1) > data_end)
2: (bf) r1 = r7
3: (07) r1 += 14
; if ((void *)(eth + 1) > data_end)
4: (2d) if r1 > r6 goto pc+41
R1_w=pkt(id=0,off=14,r=14,imm=0) R6_w=pkt_end(id=0,off=0,imm=0) R7_w=pkt(id=0,off=0,r=14,imm=0) R10=fp0
; if (eth->h_proto != htons(ETH_P_IP))
5: (71) r1 = *(u8 *)(r7 +12)
6: (71) r2 = *(u8 *)(r7 +13)
7: (67) r2 <<= 8
8: (4f) r2 |= r1
; if (eth->h_proto != htons(ETH_P_IP))
9: (55) if r2 != 0x8 goto pc+36
R1_w=inv(id=0,umax_value=255,var_off=(0x0; 0xff)) R2_w=inv8 R6_w=pkt_end(id=0,off=0,imm=0) R7_w=pkt(id=0,off=0,r=14,imm=0) R10=fp0
; if ((void *)(ip + 1) > data_end || ip->protocol != IPPROTO_TCP)
10: (bf) r1 = r7
11: (07) r1 += 34
; if ((void *)(ip + 1) > data_end || ip->protocol != IPPROTO_TCP)
12: (2d) if r1 > r6 goto pc+33
R1=pkt(id=0,off=34,r=34,imm=0) R2=inv8 R6=pkt_end(id=0,off=0,imm=0) R7=pkt(id=0,off=0,r=34,imm=0) R10=fp0
; if ((void *)(ip + 1) > data_end || ip->protocol != IPPROTO_TCP)
13: (71) r1 = *(u8 *)(r7 +23)
; if ((void *)(ip + 1) > data_end || ip->protocol != IPPROTO_TCP)
14: (55) if r1 != 0x6 goto pc+31
R1_w=inv6 R2=inv8 R6=pkt_end(id=0,off=0,imm=0) R7=pkt(id=0,off=0,r=34,imm=0) R10=fp0
;
15: (bf) r1 = r7
16: (07) r1 += 14
; struct tcphdr *tcp = (void *)ip + ip->ihl * 4;
17: (71) r8 = *(u8 *)(r1 +0)
; struct tcphdr *tcp = (void *)ip + ip->ihl * 4;
18: (67) r8 <<= 2
19: (57) r8 &= 60
; struct tcphdr *tcp = (void *)ip + ip->ihl * 4;
20: (0f) r1 += r8
last_idx 20 first_idx 12
regs=100 stack=0 before 19: (57) r8 &= 60
regs=100 stack=0 before 18: (67) r8 <<= 2
regs=100 stack=0 before 17: (71) r8 = *(u8 *)(r1 +0)
; if ((void *)(tcp + 1) > data_end || tcp->dest != htons(TCP_PORT_HTTP))
21: (bf) r2 = r1
22: (07) r2 += 20
; if ((void *)(tcp + 1) > data_end || tcp->dest != htons(TCP_PORT_HTTP))
23: (2d) if r2 > r6 goto pc+22
R1=pkt(id=1,off=14,r=34,umax_value=60,var_off=(0x0; 0x3c)) R2=pkt(id=1,off=34,r=34,umax_value=60,var_off=(0x0; 0x3c)) R6=pkt_end(id=0,off=0,imm=0) R7=pkt(id=0,off=0,r=34,imm=0) R8=inv(id=0,umax_value=60,var_off=(0x0; 0x3c)) R10=fp0
; if ((void *)(tcp + 1) > data_end || tcp->dest != htons(TCP_PORT_HTTP))
24: (69) r2 = *(u16 *)(r1 +2)
; if ((void *)(tcp + 1) > data_end || tcp->dest != htons(TCP_PORT_HTTP))
25: (55) if r2 != 0x401f goto pc+20
R1=pkt(id=1,off=14,r=34,umax_value=60,var_off=(0x0; 0x3c)) R2_w=inv16415 R6=pkt_end(id=0,off=0,imm=0) R7=pkt(id=0,off=0,r=34,imm=0) R8=inv(id=0,umax_value=60,var_off=(0x0; 0x3c)) R10=fp0
; unsigned char *payload = (unsigned char *)tcp + (tcp->doff * 4);
26: (69) r5 = *(u16 *)(r1 +12)
; unsigned char *payload = (unsigned char *)tcp + (tcp->doff * 4);
27: (77) r5 >>= 2
28: (57) r5 &= 60
; unsigned char *payload = (unsigned char *)tcp + (tcp->doff * 4);
29: (0f) r1 += r5
last_idx 29 first_idx 23
regs=20 stack=0 before 28: (57) r5 &= 60
regs=20 stack=0 before 27: (77) r5 >>= 2
regs=20 stack=0 before 26: (69) r5 = *(u16 *)(r1 +12)
; if ((void *)(payload + 1) > data_end)
30: (bf) r2 = r1
31: (07) r2 += 1
; if ((void *)(payload + 1) > data_end)
32: (2d) if r2 > r6 goto pc+13
R1=pkt(id=2,off=14,r=15,umax_value=120,var_off=(0x0; 0x7c),s32_max_value=124,u32_max_value=124) R2=pkt(id=2,off=15,r=15,umax_value=120,var_off=(0x0; 0x7c),s32_max_value=124,u32_max_value=124) R5=inv(id=0,umax_value=60,var_off=(0x0; 0x3c)) R6=pkt_end(id=0,off=0,imm=0) R7=pkt(id=0,off=0,r=34,imm=0) R8=inv(id=0,umax_value=60,var_off=(0x0; 0x3c)) R10=fp0
33: (07) r1 += 4
34: (2d) if r1 > r6 goto pc+11
R1_w=pkt(id=2,off=18,r=18,umax_value=120,var_off=(0x0; 0x7c),s32_max_value=124,u32_max_value=124) R2=pkt(id=2,off=15,r=18,umax_value=120,var_off=(0x0; 0x7c),s32_max_value=124,u32_max_value=124) R5=inv(id=0,umax_value=60,var_off=(0x0; 0x3c)) R6=pkt_end(id=0,off=0,imm=0) R7=pkt(id=0,off=0,r=34,imm=0) R8=inv(id=0,umax_value=60,var_off=(0x0; 0x3c)) R10=fp0
; for (int i = 0; i < MAX_ITERATIONS && (payload + i + 4) <= end; i++) {
35: (bf) r2 = r8
36: (0f) r2 += r5
37: (bf) r1 = r7
38: (0f) r1 += r2
last_idx 38 first_idx 32
regs=4 stack=0 before 37: (bf) r1 = r7
regs=4 stack=0 before 36: (0f) r2 += r5
regs=24 stack=0 before 35: (bf) r2 = r8
regs=120 stack=0 before 34: (2d) if r1 > r6 goto pc+11
regs=120 stack=0 before 33: (07) r1 += 4
regs=120 stack=0 before 32: (2d) if r2 > r6 goto pc+13
R1_rw=pkt(id=2,off=14,r=0,umax_value=120,var_off=(0x0; 0x7c),s32_max_value=124,u32_max_value=124) R2_rw=pkt(id=2,off=15,r=0,umax_value=120,var_off=(0x0; 0x7c),s32_max_value=124,u32_max_value=124) R5_rw=invP(id=0,umax_value=60,var_off=(0x0; 0x3c)) R6_r=pkt_end(id=0,off=0,imm=0) R7_r=pkt(id=0,off=0,r=34,imm=0) R8_r=invP(id=0,umax_value=60,var_off=(0x0; 0x3c)) R10=fp0
parent didn't have regs=120 stack=0 marks
last_idx 31 first_idx 23
regs=120 stack=0 before 31: (07) r2 += 1
regs=120 stack=0 before 30: (bf) r2 = r1
regs=120 stack=0 before 29: (0f) r1 += r5
regs=120 stack=0 before 28: (57) r5 &= 60
regs=120 stack=0 before 27: (77) r5 >>= 2
regs=120 stack=0 before 26: (69) r5 = *(u16 *)(r1 +12)
regs=100 stack=0 before 25: (55) if r2 != 0x401f goto pc+20
regs=100 stack=0 before 24: (69) r2 = *(u16 *)(r1 +2)
regs=100 stack=0 before 23: (2d) if r2 > r6 goto pc+22
R1_rw=pkt(id=1,off=14,r=0,umax_value=60,var_off=(0x0; 0x3c)) R2_rw=pkt(id=1,off=34,r=0,umax_value=60,var_off=(0x0; 0x3c)) R6_r=pkt_end(id=0,off=0,imm=0) R7_r=pkt(id=0,off=0,r=34,imm=0) R8_rw=invP(id=0,umax_value=60,var_off=(0x0; 0x3c)) R10=fp0
parent didn't have regs=100 stack=0 marks
last_idx 22 first_idx 12
regs=100 stack=0 before 22: (07) r2 += 20
regs=100 stack=0 before 21: (bf) r2 = r1
regs=100 stack=0 before 20: (0f) r1 += r8
regs=100 stack=0 before 19: (57) r8 &= 60
regs=100 stack=0 before 18: (67) r8 <<= 2
regs=100 stack=0 before 17: (71) r8 = *(u8 *)(r1 +0)
39: (b7) r9 = 0
40: (05) goto pc+7
; if (payload[i] == 'c' && payload[i+1] == 'm' && payload[i+2] == 'd' && payload[i+3] == '=') {
48: (bf) r2 = r1
49: (0f) r2 += r9
last_idx 49 first_idx 48
regs=200 stack=0 before 48: (bf) r2 = r1
R1_rw=pkt(id=4,off=0,r=0,umax_value=120,var_off=(0x0; 0x7c),s32_max_value=124,u32_max_value=124) R2_w=inv(id=0,umax_value=120,var_off=(0x0; 0x7c)) R5=inv(id=0,umax_value=60,var_off=(0x0; 0x3c)) R6=pkt_end(id=0,off=0,imm=0) R7=pkt(id=0,off=0,r=34,imm=0) R8=inv(id=3,umax_value=60,var_off=(0x0; 0x3c)) R9_rw=invP0 R10=fp0
parent didn't have regs=200 stack=0 marks
last_idx 40 first_idx 32
regs=200 stack=0 before 40: (05) goto pc+7
regs=200 stack=0 before 39: (b7) r9 = 0
50: (71) r3 = *(u8 *)(r2 +14)
invalid access to packet, off=14 size=1, R2(id=4,off=14,r=0)
R2 offset is outside of the packet
processed 44 insns (limit 1000000) max_states_per_insn 0 total_states 4 peak_states 4 mark_read 2
Traceback (most recent call last):
File "/home/parallels/Desktop/test5.py", line 87, in <module>
main()
File "/home/parallels/Desktop/test5.py", line 71, in main
xdp_func = b.load_func("xdp_search_http_request", BPF.XDP)
File "/usr/lib/python3/dist-packages/bcc/__init__.py", line 526, in load_func
raise Exception("Failed to load BPF program %s: %s" %
Exception: Failed to load BPF program b'xdp_search_http_request': Permission denied
有趣的是,如果我注释掉 bpf_trace_printk("cmd value: %s", cmd);行,程序加载并执行没有任何问题。这让我相信问题可能与 bpf_trace_printk 的使用有关,尤其是格式化字符串。
我已确保启用了必要的内核功能,并尝试以提升的权限运行该程序,但问题仍然存在。我当前正在使用 [Linux 内核版本、BCC 版本以及任何其他相关环境详细信息]。
是否有人遇到过类似的问题,或者可以深入了解为什么 bpf_trace_printk 可能导致程序无法加载?任何有关如何解决此问题或在 eBPF 程序中进行调试的替代方法的建议将不胜感激。
我在 ubuntu 22.04 中使用 5.15.0-100-generic 内核。
TL;DR. 验证者在您绑定检查和使用它之间失去了对
payload + i
的追踪。我建议将其设为自己的变量。
验证器错误说明
invalid access to packet, off=14 size=1, R2(id=4,off=14,r=0)
R2 offset is outside of the packet
这里,验证者抱怨您正在尝试访问偏移量 14 处的数据包 (
off=14
),但您尚未检查该数据包实际上是否足够大 (r0
)。
这可能会令人惊讶,因为您确实检查了数据包的长度。我们可以在指令34处看到对应的字节码:
33: (07) r1 += 4
34: (2d) if r1 > r6 goto pc+11
R1_w=pkt(id=2,off=18,r=18,umax_value=120,var_off=(0x0; 0x7c),s32_max_value=124,u32_max_value=124) R2=pkt(id=2,off=15,r=18,umax_value=120,var_off=(0x0; 0x7c),s32_max_value=124,u32_max_value=124) R5=inv(id=0,umax_value=60,var_off=(0x0; 0x3c)) R6=pkt_end(id=0,off=0,imm=0) R7=pkt(id=0,off=0,r=34,imm=0) R8=inv(id=0,umax_value=60,var_off=(0x0; 0x3c)) R10=fp0
这里
r1
是 payload + i + 4
,它与指向数据包末尾的指针 r6
进行比较。然后,数据包的长度在验证者状态下正确更新:r1
的范围设置为 18 (r=18
)。
但是,当为
payload[i] == 'c'
准备内存访问时,编译器会从头开始重新计算指针,而不是使用刚刚验证的r1
寄存器。因此它失去了对数据包边界的跟踪。
为什么
bpf_trace_printk
有影响?
如果删除对
bpf_trace_printk
的调用,则永远不会使用 cmd
变量。因此,编译器会优化它以及更新它的任何代码。实际上,这可能意味着几乎所有代码都被优化掉了。
解决方案
解决这个问题可能需要多次迭代才能将您的代码“揉捏”成验证者能够跟踪的内容。我首先将
payload + i
作为自己的迭代变量:
max_ptr = payload + MAX_ITERATIONS;
for (int ptr = payload; ptr < max_ptr && (ptr + 4) <= end; ptr++) {
if (*ptr == 'c' && ...