You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

BPF TC程序验证失败求助:invalid access to memory内存访问错误

问题:BPF TC程序payload过滤时内存访问验证失败

编写BPF TC程序实现基于数据包payload的子串过滤,提取payload前若干字节到栈缓冲区后调用子串搜索函数,程序验证失败,报错:

invalid access to memory, mem_size=1 off=1 size=1
R3 min value is outside of the allowed memory range

相关代码

common.h

#define MAX_RULES 50
#define MAX_RULE_NAME 20
#define MAX_BYTE_PATTERN 11

struct filter_rule
{
   char rule_name[MAX_RULE_NAME];
   char byte_pattern[MAX_BYTE_PATTERN];
};

unsigned char mystrlen(const char *s, unsigned char max_len)
{
    unsigned char i = 0;
    if(s == NULL)
       return 0;
    for (i = 0; i < max_len; i++)
    {
       if (s[i] == '\0')
          return i;
    }
    return i;
}

bool find_substring(const char *str, const char *search)
{
   if(str != NULL && search != NULL)
   {
      unsigned char l1 = mystrlen(str,50);
      unsigned char l2 = mystrlen(search, MAX_BYTE_PATTERN);
      unsigned char i = 0, j = 0;
      unsigned char flag = 0;
      if(l1 == 0 || l2 == 0)
         return false;
      for (i = 0; i <= l1 - l2; i++)
      {
         for (j = i; j < i + l2; j++)
         {
            flag = 1;
            if (str[j] != search[j - i])
            {
               flag = 0;
               break;
            }
         }
         if (flag == 1)
         {
            break;
         }
      }
      if(flag == 1)
         return true;
      else
         return false;
   }
   else
   {
      return false;
   }
}

bpf.c

unsigned long long load_byte(void *skb, unsigned long long off) asm("llvm.bpf.load.byte");

struct {
    __uint(type, BPF_MAP_TYPE_ARRAY);
    __uint(key_size, sizeof(int));
    __uint(value_size, sizeof(struct filter_rule));
    __uint(max_entries, MAX_RULES);
} filter_rules SEC(".maps");

SEC("tc")
int ingress_hndlr(struct __sk_buff *ctx)
{
   //Extract the TCP packet payload into buff
   unsigned char buff[51] = {0};
   for(int i = 0; i<tcp_payload_length && i<50; ++i)
   {
       buff[i] = load_byte(ctx, payload_offset+i);
       //bpf_printk("%x", buff[i]);
   }

   unsigned int key = 0;
   struct filter_rule *rule = bpf_map_lookup_elem(&filter_rules, &key);
   if(rule)
   {
       bool ret = find_substring((const char*)buff, rule->byte_pattern);
       if(ret)
       {
          ++drop_cnt;
          return TC_ACT_SHOT;
       }
   }
}

错误日志

Validating find_substring() func#1...
; bool find_substring(const char *str, const char *search)
189: (b7) r0 = 0
; if(str != NULL && search != NULL)
190: (15) if r1 == 0x0 goto pc+55
 R0_w=invP0 R1=mem(id=0,ref_obj_id=0,off=0,imm=0) R2=mem_or_null(id=4,ref_obj_id=0,off=0,imm=0) R10=fp0
191: (15) if r2 == 0x0 goto pc+54
 R0_w=invP0 R1=mem(id=0,ref_obj_id=0,off=0,imm=0) R2=mem(id=0,ref_obj_id=0,off=0,imm=0) R10=fp0
192: (b7) r4 = 0
; if (s[i] == '\0')
193: (bf) r3 = r1
194: (0f) r3 += r4
195: (71) r3 = *(u8 *)(r3 +0)
 R0_w=invP0 R1=mem(id=0,ref_obj_id=0,off=0,imm=0) R2=mem(id=0,ref_obj_id=0,off=0,imm=0) R3_w=mem(id=0,ref_obj_id=0,off=0,imm=0) R4_w=invP0 R10=fp0
; if (s[i] == '\0')
196: (15) if r3 == 0x0 goto pc+4
 R0=invP0 R1=mem(id=0,ref_obj_id=0,off=0,imm=0) R2=mem(id=0,ref_obj_id=0,off=0,imm=0) R3=invP(id=0,umax_value=255,var_off=(0x0; 0xff)) R4=invP0 R10=fp0
197: (b7) r3 = 50
; for (i = 0; i < max_len; i++)
198: (07) r4 += 1
; for (i = 0; i < max_len; i++)
199: (15) if r4 == 0x32 goto pc+2
200: (05) goto pc-8
; if (s[i] == '\0')
193: (bf) r3 = r1
194: (0f) r3 += r4
195: (71) r3 = *(u8 *)(r3 +0)
 R0=invP0 R1=mem(id=0,ref_obj_id=0,off=0,imm=0) R2=mem(id=0,ref_obj_id=0,off=0,imm=0) R3_w=mem(id=0,ref_obj_id=0,off=1,imm=0) R4_w=invP1 R10=fp0
invalid access to memory, mem_size=1 off=1 size=1
R3 min value is outside of the allowed memory range
processed 15 insns (limit 1000000) max_states_per_insn 0 total_states 1 peak_states 1 mark_read 1
-- END PROG LOAD LOG --
libbpf: prog 'ingress_hndlr': failed to load: -13
libbpf: failed to load object 'minimal_bpf'
libbpf: failed to load BPF skeleton 'minimal_bpf': -13
Failed to load and verify BPF skeleton

错误原因分析

BPF验证器的核心作用是确保程序不会非法访问内存。这里的错误是因为验证器无法推断出find_substring函数中str参数(对应栈上的buff数组)的内存边界:

  • 虽然buff是栈上分配的51字节数组,但传递给find_substring时仅以const char*形式传递,验证器无法关联到它的实际大小。
  • 在mystrlen循环中,当i增加到1时,访问str[1],验证器无法确认该地址在合法内存范围内,因此判定为非法访问。

解决方法

1. 给字符串函数补充长度参数,明确内存边界

修改find_substring,让它同时接收目标字符串的最大长度,这样验证器能明确知道内存访问的上限,避免越界判断错误。

2. 优化循环边界检查

确保所有内存访问的索引都被严格限制在缓冲区范围内,让验证器能清晰推断出访问的合法性。

修改后的代码示例

common.h 修改

#define MAX_RULES 50
#define MAX_RULE_NAME 20
#define MAX_BYTE_PATTERN 11
#define MAX_PAYLOAD_BUFF_LEN 50  // 和bpf.c中buff的有效长度对应

struct filter_rule
{
   char rule_name[MAX_RULE_NAME];
   char byte_pattern[MAX_BYTE_PATTERN];
};

unsigned char mystrlen(const char *s, unsigned char max_len)
{
    unsigned char i = 0;
    if(s == NULL)
       return 0;
    for (i = 0; i < max_len; i++)
    {
       if (s[i] == '\0')
          return i;
    }
    return i;
}

// 新增目标字符串的最大长度参数str_max_len
bool find_substring(const char *str, unsigned char str_max_len, const char *search)
{
   if(str != NULL && search != NULL)
   {
      unsigned char l1 = mystrlen(str, str_max_len);
      unsigned char l2 = mystrlen(search, MAX_BYTE_PATTERN);
      unsigned char i = 0, j = 0;
      unsigned char flag = 0;
      
      if(l1 == 0 || l2 == 0 || l2 > l1)
         return false;
      
      // 确保外层循环i的上限不会导致内层j越界
      for (i = 0; i <= l1 - l2; i++)
      {
          // 额外添加检查,确保j不会超过str_max_len
          if (i + l2 > str_max_len)
              break;
              
          flag = 1;
          for (j = 0; j < l2; j++)
          {
              if (str[i + j] != search[j])
              {
                  flag = 0;
                  break;
              }
          }
          if (flag == 1)
          {
              break;
          }
      }
      return flag == 1;
   }
   else
   {
      return false;
   }
}

bpf.c 修改

unsigned long long load_byte(void *skb, unsigned long long off) asm("llvm.bpf.load.byte");

struct {
    __uint(type, BPF_MAP_TYPE_ARRAY);
    __uint(key_size, sizeof(int));
    __uint(value_size, sizeof(struct filter_rule));
    __uint(max_entries, MAX_RULES);
} filter_rules SEC(".maps");

// 补充定义tcp_payload_length和payload_offset(原代码中缺失)
unsigned int tcp_payload_length = 0;
unsigned int payload_offset = 0;
unsigned int drop_cnt = 0;

SEC("tc")
int ingress_hndlr(struct __sk_buff *ctx)
{
   //Extract the TCP packet payload into buff
   unsigned char buff[51] = {0};
   for(int i = 0; i < tcp_payload_length && i < MAX_PAYLOAD_BUFF_LEN; ++i)
   {
       buff[i] = load_byte(ctx, payload_offset + i);
       //bpf_printk("%x", buff[i]);
   }

   unsigned int key = 0;
   struct filter_rule *rule = bpf_map_lookup_elem(&filter_rules, &key);
   if(rule)
   {
       // 传递buff的最大有效长度MAX_PAYLOAD_BUFF_LEN
       bool ret = find_substring((const char*)buff, MAX_PAYLOAD_BUFF_LEN, rule->byte_pattern);
       if(ret)
       {
          ++drop_cnt;
          return TC_ACT_SHOT;
       }
   }
   return TC_ACT_OK; // 补充默认返回值,避免程序异常
}

其他注意事项

  • 原代码中tcp_payload_length、payload_offset、drop_cnt未定义,需补充定义或通过合法方式获取(比如从数据包解析得到)。
  • 确保所有路径都有返回值,原ingress_hndlr函数在不满足条件时没有返回值,会导致验证失败。

内容的提问来源于stack exchange,提问作者unmukt jain

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 09:03:08