解决 ebpf 验证器提示类型错误问题的一种方法

有时当我们加载编译后的 eBPF 程序的时候,eBPF 验证器会提示程序中有类型错误的问题导致程序加载失败。 本文记录一下这种错误的一种解决方法。

错误示例

比如,当下面这段 eBPF 程序

SEC("iter/bpf_sk_storage_map")
int iter__bpf_sk_storage_map(struct bpf_iter__bpf_sk_storage_map *ctx)
{
    if (ctx->sk)
        bpf_sk_storage_delete(&sk_storage_map, ctx->sk);

    return 0;
}

被加载到内核中时会提示如下类型错误:

libbpf: prog 'iter__bpf_sk_storage_map': BPF program load failed: Permission denied
libbpf: prog 'iter__bpf_sk_storage_map': -- BEGIN PROG LOAD LOG --
R1 type=ctx expected=fp
; if (ctx->sk)
0: (79) r2 = *(u64 *)(r1 +16)
; if (ctx->sk)
1: (15) if r2 == 0x0 goto pc+4
 R1=ctx(id=0,off=0,imm=0) R2_w=ptr_sock(id=0,off=0,imm=0) R10=fp0
; bpf_sk_storage_delete(&sk_storage_map, ctx->sk);
2: (79) r2 = *(u64 *)(r1 +16)
; bpf_sk_storage_delete(&sk_storage_map, ctx->sk);
3: (18) r1 = 0xffffa0658305aa00
5: (85) call bpf_sk_storage_delete#108
R2 type=ptr_or_null_ expected=ptr_
processed 5 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0
-- END PROG LOAD LOG --
libbpf: prog 'iter__bpf_sk_storage_map': failed to load: -13
libbpf: failed to load object 'main.bpf.o'
failed to load BPF object: permission denied

解决办法

这个错误信息有两个关键错误,一个错误是:

R1 type=ctx expected=fp
; if (ctx->sk)
0: (79) r2 = *(u64 *)(r1 +16)
; if (ctx->sk)
1: (15) if r2 == 0x0 goto pc+4

其中 R1 type=ctx expected=fp 说的是,验证器期望 R1 的类型是 fp 而不是 ctx 。 所谓的 fp 指的是栈上的指针类型,即期望 R1 是栈上的数据而不是 ctx

另一个错误是:

 R1=ctx(id=0,off=0,imm=0) R2_w=ptr_sock(id=0,off=0,imm=0) R10=fp0
; bpf_sk_storage_delete(&sk_storage_map, ctx->sk);
2: (79) r2 = *(u64 *)(r1 +16)
; bpf_sk_storage_delete(&sk_storage_map, ctx->sk);
3: (18) r1 = 0xffffa0658305aa00
5: (85) call bpf_sk_storage_delete#108
R2 type=ptr_or_null_ expected=ptr_

其中 R2 type=ptr_or_null_ expected=ptr_ 说的是,验证器期望 R2 的类型是 ptr 而不是 prt_or_null ,即,期望 R2 是一个指针而不是一个指针或 NULL 。 这里可能会有点疑惑,前面的判断 if (ctx->sk) 已经确保了不会为 NULL , 为啥这里还会认为它有可能为 NULL ,这是因为前面的 if 判断的不是栈变量, 存在 R1 type=ctx expected=fp 的问题也就无法保证它一定不是 NULL 了。

解决办法也很简单,就是用一个临时变量保存 ctx->sk 的值, 然后用这个栈上的临时变量做后续的操作:

 SEC("iter/bpf_sk_storage_map")
 int iter__bpf_sk_storage_map(struct bpf_iter__bpf_sk_storage_map *ctx)
 {
-    if (ctx->sk)
-        bpf_sk_storage_delete(&sk_storage_map, ctx->sk);
+    struct sock *sk = ctx->sk;
+    if (sk)
+        bpf_sk_storage_delete(&sk_storage_map, sk);

     return 0;
 }

常见类型关键字的含义

这里记录一下类似前面 fp 这样的常见类型关键字具体的含义:

关键字 含义
scalar 标量类型(scalar type),不是一个有效的指针类型
ctx bpf_context 指针
map_ptr bpf_map 类型的指针
map_value 指向 map 中的元素 value 的指针
map_value_or_null 指向 map 中的元素 value 的指针或 NULL
map_key 指向 map 中的元素 key 的指针
fp 栈上的指针(frame pointer)
pkt skb->data 指针
pkt_meta skb->data - meta_len 位置的指针
pkt_end skb->data + headlen 位置的指针
sock bpf_sock 类型的指针
sock_or_null bpf_sock 类型的指针或 NULL
sock_common sock_common 类型指针
sock_common_or_null sock_common 类型指针或 NULL
tcp_sock tcp_sock 类型指针
tcp_sock_or_null tcp_sock 类型指针或 NULL
tp_buffer 可写的 raw tracepoint buffer 指针
xdp_sock xdp_sock 类型指针
ptr_ 一个 BTF ID,非空指针
ptr_or_null_ 一个 BTF ID 或 NULL,可能为空的指针
dynptr_ptr 动态指针(dynptr 指针)
mem 指向一块有效内存区域的指针
mem_or_null 指向一块有效内存区域的指针或 NULL
buf 指向一个读/写 buffer 的指针
func BPF 程序函数指针
inv 无效类型(invalid type),不是一个有效的指针类型
flow_keys bpf_flow_keys 类型的指针
percpu_ptr_ 指向一个 percpu 内核变量的指针
rdonly_buf 指向一个只读 buffer 的指针
rdonly_buf_or_null 指向一个只读 buffer 的指针或 NULL
rdwr_buf 指向一个读/写 buffer 的指针
rdwr_buf_or_null 指向一个读/写 buffer 的指针或 NULL

Comments