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
相关产品推荐
相关产品推荐

