Linux 4.14内核EBPF技术问询:如何实现将变长用户态数据复制到BPF Map中
适配Linux 4.14内核的eBPF read()追踪解决方案
我碰到过不少4.14内核BPF验证器的“奇葩”限制,你的问题核心是4.14的验证器无法正确推断三元运算符(MIN宏展开的结果)的变量范围,再加上多余的位操作干扰了验证器的分析,导致它认为bpf_probe_read_user的size参数可能超过heap buffer的大小,从而拒绝加载程序。
下面是具体的修复方案,附修改后的完整代码:
关键修改点
- 把
MIN宏替换为显式的if-else条件判断:4.14验证器对三元运算符的范围推导支持不足,显式的分支判断能让验证器清晰跟踪到min的上限不会超过HEAP_BUFFER_SIZE。 - 移除不必要的
min &= 0xffff操作:HEAP_BUFFER_SIZE是2048,远小于65535,这个操作不会改变min的值,反而会让验证器误以为min的范围是0-65535,触发越界检查错误。 - 强化变量范围的显式约束:确保验证器能明确
min的取值范围是1到2048之间。
修改后的完整代码
#include <stddef.h> #include <linux/bpf.h> #include <bpf/bpf_helpers.h> struct read_enter_ctx { unsigned long long unused; int __syscall_nr; unsigned long fd; char* buf; size_t count; }; struct read_exit_ctx { unsigned long long unused; int __syscall_nr; long ret; }; struct { __uint(type, BPF_MAP_TYPE_ARRAY); __uint(max_entries, 1); __type(key, int); __type(value, void*); } saved_read_ctx SEC(".maps"); #define HEAP_BUFFER_SIZE (2 * 1024) struct heap_buffer { char buf[HEAP_BUFFER_SIZE]; }; struct { __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); __uint(max_entries, 1); __type(key, int); __type(value, struct heap_buffer); } heap SEC(".maps"); SEC("tracepoint/syscalls/sys_enter_read") int trace_read_enter(struct read_enter_ctx *ctx) { int zero = 0; void *p = ctx->buf; bpf_map_update_elem(&saved_read_ctx, &zero, &p, BPF_ANY); return 0; } SEC("tracepoint/syscalls/sys_exit_read") int trace_read_exit(struct read_exit_ctx *ctx) { char *map_buf; void **ubuf; int zero = 0; unsigned long min; ubuf = bpf_map_lookup_elem(&saved_read_ctx, &zero); if (!ubuf) return 0; if (ctx->ret <= 0) return 0; map_buf = bpf_map_lookup_elem(&heap, &zero); if (!map_buf) { return 0; } // 替换MIN宏为显式if判断,让4.14验证器能跟踪变量范围 min = ctx->ret; if (min > HEAP_BUFFER_SIZE) { min = HEAP_BUFFER_SIZE; } // 移除多余的位操作,避免干扰验证器 // min &= 0xffff; if (bpf_probe_read_user(map_buf, min, *ubuf)) { return 0; } return 0; } char _license[] SEC("license") = "GPL";
额外优化建议(可选)
- 把
saved_read_ctx改成BPF_MAP_TYPE_PERCPU_ARRAY:全局数组在多CPU并发场景下会有竞争问题,percpu数组能避免不同CPU的tracepoint程序互相覆盖缓冲区指针,同时也能让验证器更“放心”(因为percpu变量的访问不会有跨CPU的竞态)。
修改后的saved_read_ctx定义:
struct { __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); __uint(max_entries, 1); __type(key, int); __type(value, void*); } saved_read_ctx SEC(".maps");
这样修改后,程序应该能顺利通过4.14内核的BPF验证器,同时保留原有功能。
内容的提问来源于stack exchange,提问作者joz
相关产品推荐
相关产品推荐

