eBPF启用RST flag处理触发map内存访问权限错误排查
eBPF添加TCP RST标志位处理后触发内存访问权限错误
错误信息
permission denied: invalid access to map value, value_size=20 off=21 size=1: R2 max value is outside of the allowed memory range
问题背景
原有eBPF代码仅处理TCP SYN、FIN标志位时运行正常,取消RST标志位处理逻辑的注释后,触发上述内存访问权限错误。相关核心代码如下:
struct value { int index; char flags[MAX_FLAGS_LEN]; }; static __always_inline void store_flags(struct value *value, struct tcphdr *tcp){ __u8 index = value->index; if (index >= sizeof(value->flags)){ return; } if (index < sizeof(value->flags)){ if (tcp->syn){ value->flags[index] = 'S'; index++; } } if (index < sizeof(value->flags)){ if (tcp->fin){ value->flags[index] = 'F'; index++; } } // if (index < sizeof(value->flags)){ // if (tcp->rst){ // value->flags[index] = 'R'; // index++; // } // } bpf_printk("Flags: %d", tcp->syn); value->index=index; }
问题分析
反编译代码显示,RST标志位处理部分的index < sizeof(value->flags)检查被编译器重排为两次,且第二次无跳转分支,导致BPF验证器判定存在未校验的内存访问。进一步排查发现:处理RST时,变量index从寄存器r2转移到r1,BPF验证器丢失了此前的边界校验上下文;而SYN、FIN的处理逻辑未出现该寄存器转移,因此未触发错误。
解决办法
方案1:调整代码结构,减少编译器重排空间
将多次独立的边界检查改为顺序处理+即时退出,避免寄存器上下文丢失:
static __always_inline void store_flags(struct value *value, struct tcphdr *tcp){ __u8 index = value->index; const __u8 max_len = sizeof(value->flags); if (index >= max_len){ return; } if (tcp->syn) { value->flags[index++] = 'S'; if (index >= max_len) goto out; } if (tcp->fin) { value->flags[index++] = 'F'; if (index >= max_len) goto out; } if (tcp->rst) { value->flags[index++] = 'R'; if (index >= max_len) goto out; } out: bpf_printk("Flags: %d", tcp->syn); value->index = index; }
方案2:使用循环统一处理标志位
通过数组映射表+循环处理所有标志位,让验证器更容易识别边界校验逻辑:
#include <linux/tcp.h> // 定义TCP标志位与字符的映射表 struct flag_map { __u8 flag_bit; char flag_char; }; static const struct flag_map tcp_flag_map[] = { { .flag_bit = TCPHDR_SYN, .flag_char = 'S' }, { .flag_bit = TCPHDR_FIN, .flag_char = 'F' }, { .flag_bit = TCPHDR_RST, .flag_char = 'R' }, }; static __always_inline void store_flags(struct value *value, struct tcphdr *tcp){ __u8 index = value->index; const __u8 max_len = sizeof(value->flags); if (index >= max_len){ return; } for (int i = 0; i < ARRAY_SIZE(tcp_flag_map); i++) { if (tcp->flags & tcp_flag_map[i].flag_bit) { value->flags[index++] = tcp_flag_map[i].flag_char; if (index >= max_len) break; } } bpf_printk("Flags: %d", tcp->syn); value->index = index; }
方案3:显式固定寄存器使用(针对编译器重排)
通过显式赋值强制保留index的寄存器上下文,避免验证器丢失校验信息:
static __always_inline void store_flags(struct value *value, struct tcphdr *tcp){ __u8 index = value->index; const __u8 max_len = sizeof(value->flags); if (index >= max_len){ return; } // 显式复制index到临时变量,固定寄存器上下文 __u8 curr_idx = index; if (curr_idx < max_len && tcp->syn) { value->flags[curr_idx++] = 'S'; } if (curr_idx < max_len && tcp->fin) { value->flags[curr_idx++] = 'F'; } if (curr_idx < max_len && tcp->rst) { value->flags[curr_idx++] = 'R'; } bpf_printk("Flags: %d", tcp->syn); value->index = curr_idx; }
内容的提问来源于stack exchange,提问作者hdmicable
相关产品推荐
相关产品推荐

