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

eBPF哈希进程名时验证错误排查求助

问题排查:eBPF哈希函数无法通过验证器(基于libbpf-bootstrap)

基于libbpf-bootstrap原型,尝试为超长进程名生成32位哈希,因栈空间不足使用per-cpu数组作为堆存储进程名,但哈希函数无法通过eBPF验证器。

核心代码

uint32_t map_id = 0;
char *map_val = bpf_map_lookup_elem(&heap, &map_id);
if (!map_val)
  return 0;

int bytes_read = bpf_probe_read_str(map_val, sizeof(e->filename), (void *)ctx + fname_off);
if (bytes_read > 0) {
    map_val[ (bytes_read - 1) & (4096 -1) ] = 0;

    uint32_t key = hash( (unsigned char*)map_val);
    bpf_printk("process_exec count: %u, hash: %lu, full path: %s\n", bytes_read -1, key, map_val); 
}

哈希函数

uint32_t hash(unsigned char *str)
{
    int c;
    uint32_t hash = 5381;
    while ( c = *str++ )
        hash = ((hash << 5) + hash) + c; /* hash * 33 + c */

    return hash;
}

验证错误日志

; hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
91: (27) r4 *= 33
; hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
92: (0f) r4 += r1
; while ( c = *str++ )
93: (71) r1 = *(u8 *)(r2 +0)
 R0=inv(id=6,smin_value=-4096,smax_value=4095) R1_w=inv(id=0,umax_value=255,var_off=(0x0; 0xff)) R2_w=map_value(id=0,off=4096,ks=4,vs=4096,imm=0) R4_w=inv(id=0) R6=ctx(id=0,off=0,umax_value=65535,var_off=(0x0; 0xffff)) R7=map_value(id=0,off=0,ks=4,vs=4096,imm=0) R8=invP0 R10=fp0 fp-8=mmmm???? fp-16=mmmmmmmm fp-24=mmmm???? fp-32=mmmmmmmm
invalid access to map value, value_size=4096 off=4096 size=1
R2 min value is outside of the allowed memory range
processed 32861 insns (limit 1000000) max_states_per_insn 4 total_states 337 peak_states 337 mark_read 4
-- END PROG LOAD LOG --
libbpf: prog 'handle_exec': failed to load: -13
libbpf: failed to load object 'bootstrap_bpf'
libbpf: failed to load BPF skeleton 'bootstrap_bpf': -13
Failed to load and verify BPF skeleton

完整代码diff

diff --git a/examples/c/bootstrap.bpf.c b/examples/c/bootstrap.bpf.c
index d0860c0..c93ed58 100644
--- a/examples/c/bootstrap.bpf.c
+++ b/examples/c/bootstrap.bpf.c
@@ -20,6 +20,13 @@ struct {
        __uint(max_entries, 256 * 1024);
 } rb SEC(".maps");

+struct {
+       __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
+       __uint(key_size, sizeof(u32));
+       __uint(max_entries, 1);
+       __uint(value_size, 4096);
+} heap SEC(".maps");
+
 const volatile unsigned long long min_duration_ns = 0;

 SEC("tp/sched/sched_process_exec")
@@ -58,6 +65,22 @@ int handle_exec(struct trace_event_raw_sched_process_exec *ctx)

        /* successfully submit it to user-space for post-processing */
        bpf_ringbuf_submit(e, 0);
+
+
+  uint32_t map_id = 0;
+  char *map_val = bpf_map_lookup_elem(&heap, &map_id);
+  if (!map_val)
+    return 0;
+
+  int bytes_read = bpf_probe_read_str(map_val, sizeof(e->filename), (void *)ctx + fname_off);
+  if (bytes_read > 0) {
+      // tell the validator bytes ready is between 0 and 4095
+      map_val[ (bytes_read - 1) & (4096 -1) ] = 0;
+
+      uint32_t key = hash( (unsigned char*)map_val);
+      bpf_printk("process_exec count: %u, hash: %u, full path: %s\n", bytes_read -1, key, map_val);
+  }
+
        return 0;
 }

@@ -109,4 +132,3 @@ int handle_exit(struct trace_event_raw_sched_process_template* ctx)
        bpf_ringbuf_submit(e, 0);
        return 0;
 }
-
diff --git a/examples/c/bootstrap.h b/examples/c/bootstrap.h
index b49e022..d268e56 100644
--- a/examples/c/bootstrap.h
+++ b/examples/c/bootstrap.h
@@ -4,7 +4,7 @@
 #define __BOOTSTRAP_H

 #define TASK_COMM_LEN 16
-#define MAX_FILENAME_LEN 127
+#define MAX_FILENAME_LEN 4096

 struct event {
        int pid;
@@ -16,4 +16,15 @@ struct event {
        bool exit_event;
 };

+static inline
+uint32_t hash(unsigned char *str)
+{
+    int c;
+    uint32_t hash = 5381;
+    while ( c = *str++ )
+        hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
+
+    return hash;
+}
+
 #endif /* __BOOTSTRAP_H */

问题原因及修复方案

原因分析

错误日志显示invalid access to map value, value_size=4096 off=4096 size=1,说明哈希函数的循环可能访问到per-cpu数组的边界之外。eBPF验证器无法识别你通过位运算(bytes_read -1) & 4095设置的边界约束,它认为循环可能一直执行到数组的第4096字节(超出了数组的4096字节容量),因此判定访问非法。

尽管bpf_probe_read_str已经保证了字符串不会超过数组长度,但验证器无法关联这一逻辑,只能看到循环没有明确的终止边界。

修复方案

方案1:给哈希循环添加明确次数限制

修改哈希函数,用for循环替代while循环,明确限制最大执行次数为数组长度,让验证器确认不会越界:

uint32_t hash(unsigned char *str)
{
    int c;
    uint32_t hash = 5381;
    // 限制循环最多执行4096次,对应数组的最大容量
    for (int i = 0; i < 4096; i++) {
        c = *str++;
        if (!c) break; // 遇到终止符提前退出
        hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
    }
    return hash;
}

方案2:优化字符串长度约束逻辑

把位运算改成显式的长度截断,让验证器能识别有效的索引范围:

if (bytes_read > 0) {
    // 显式限制长度不超过数组容量
    if (bytes_read > 4096) bytes_read = 4096;
    map_val[bytes_read - 1] = 0; // 此时索引最大为4095,不会越界

    uint32_t key = hash( (unsigned char*)map_val);
    bpf_printk("process_exec count: %u, hash: %u, full path: %s\n", bytes_read -1, key, map_val);
}

配合方案1的循环次数限制,双重保证访问安全。

方案3:使用内核内置哈希辅助函数(可选)

如果你的内核版本在5.13及以上,可以直接使用bpf_hash_bytes辅助函数计算哈希,避免手动写循环:

#include <linux/bpf.h>

// ...

uint32_t key = 0;
// 使用FNV哈希算法计算字符串哈希
bpf_hash_bytes(BPF_HASH_FNV, map_val, bytes_read - 1, &key, sizeof(key));

这种方式无需处理循环边界,验证器会直接认可合法。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 21:36:21