如何通过eBPF数组映射从用户空间正确传递定长字符串?
eBPF中用户空间传递字符串及字符串比较的问题
问题场景与代码
我使用BPF_MAP_TYPE_ARRAY类型的映射存储以下结构体实例:
struct target_d_name { unsigned long int len; char name[PID_LEN_MAX]; // PID_LEN_MAX = 8,宏定义 };
映射定义如下:
struct { __uint(type, BPF_MAP_TYPE_ARRAY); __uint(max_entries, 8); __type(key, u32); __type(value, sizeof(struct target_d_name)); } map_d_name_tgts SEC(".maps");
操作流程:
- Hook
getdents64的exit阶段 - 遍历所有返回的
dirent64结构体 - 检查
dirent64->d_name是否与target_d_name->name匹配
字符串对比代码:
// iterator_limit为filename长度与PID_LEN_MAX中的较小值 for (k = 0; k < iterator_limit; ++k) { if (filename[k] != target_d_name_instance->name[k]) break; }
加载eBPF程序时,验证器报错:
invalid access to map value, value_size=8 off=8 size=1 R5 min value is outside of the allowed memory range
报错指向访问target_d_name_instance->name[k]的操作。经调研,eBPF验证器无法追踪结构体内部字符缓冲区的大小,即使循环范围在合法边界内,仍会判定为可能越界。
更新:以下最小示例会触发相同报错:
u32 index = 0; struct target_d_name * current_d_name = bpf_map_lookup_elem(&map_d_name_tgts, &index); if (current_d_name == 0) return -1; for (int i = 0; i < PID_LEN_MAX; ++i) { current_d_name->name[i] = 'u'; } return 0;
疑问
除当前方式外,如何从用户空间向eBPF程序传递字符串?若需要遍历和比较字符串该怎么做?注意到bpf_strncmp()辅助函数,但它仅支持常量字符串,对动态生成的字符串无效。
解决方案
1. 将结构体中的字符串读取到栈上操作
eBPF验证器能可靠追踪栈上变量的大小,因此可以先将映射中结构体的字符串字段读取到栈数组中,再进行遍历或比较:
u32 index = 0; struct target_d_name *current_d_name = bpf_map_lookup_elem(&map_d_name_tgts, &index); if (!current_d_name) return -1; // 将字符串读取到栈上 char target_name[PID_LEN_MAX]; bpf_probe_read(target_name, sizeof(target_name), ¤t_d_name->name); // 使用栈上的字符串进行比较 int iterator_limit = min(strlen(filename), PID_LEN_MAX); for (int k = 0; k < iterator_limit; ++k) { if (filename[k] != target_name[k]) break; }
2. 使用紧凑结构体消除对齐间隙
给结构体添加__attribute__((packed))修饰,消除字段间的内存对齐间隙,帮助验证器正确计算内存偏移:
struct target_d_name { unsigned long int len; char name[PID_LEN_MAX]; } __attribute__((packed));
3. 单独存储字符串到映射
若不需要同时传递len字段,可以直接用BPF_MAP_TYPE_ARRAY或BPF_MAP_TYPE_HASH存储字符串数组:
// 数组映射直接存字符串 struct { __uint(type, BPF_MAP_TYPE_ARRAY); __uint(max_entries, 8); __type(key, u32); __type(value, char[PID_LEN_MAX]); } map_direct_name SEC(".maps");
访问时直接读取映射元素到栈上,再进行操作即可。
4. 实现安全的动态字符串比较函数
手动实现支持动态字符串的strncmp替代函数,基于栈上变量操作,避免直接访问映射中的结构体字段:
static __always_inline int safe_strncmp(const char *s1, const char *s2, size_t max_len) { for (size_t i = 0; i < max_len; ++i) { if (s1[i] != s2[i]) { return s1[i] - s2[i]; } if (s1[i] == '\0') { return 0; } } return 0; } // 使用示例 char target_name[PID_LEN_MAX]; bpf_probe_read(target_name, sizeof(target_name), ¤t_d_name->name); if (safe_strncmp(filename, target_name, PID_LEN_MAX) == 0) { // 匹配逻辑 }
内容的提问来源于stack exchange,提问作者vykt
相关产品推荐
相关产品推荐

