我正在探索 eBPF 的
kfunc
功能。我想知道是否可以将从映射(例如数组)获取的指针传递给我在自定义内核模块中定义的函数(我将其标记为kfunc
)?我查看了 来自内核的文档页面,并准备了一个简单的内核模块,添加了函数包装 memcpy
作为示例。当尝试使用此 kfunc
加载 BPF 程序时,验证者会抱怨如下所示:
arg#0 pointer type UNKNOWN must point to scalar, or struct with scalar
processed 66 insns (limit 1000000) max_states_per_insn 1 total_states 6 peak_states 6 mark_read 4
-- END PROG LOAD LOG --
查看文档,我不确定注释我的
kfunc
参数的正确方法是什么,这样它就不是未知的。所以我想知道目前是否允许将指针从地图传递到自定义kfunc
?
下面是我用来测试此功能的。
内核模块
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/string.h> /* memcpy */
#include <linux/btf.h>
MODULE_LICENSE("GPL");
/* Define a kfunc function */
__bpf_kfunc_start_defs();
__bpf_kfunc void *my_kfunc_memcpy(void *dst, void *src, __u32 src__sz)
{
return memcpy(dst, src, src__sz);
}
__bpf_kfunc_end_defs();
/* Encode the function(s) into BTF */
/*
* These will probably be the new API
* */
/* BTF_KFUNCS_START(bpf_my_string_set) */
/* BTF_ID_FLAGS(func, my_kfunc_memcpy, 0) */
/* BTF_KFUNCS_END(bpf_my_string_set) */
BTF_SET8_START(bpf_my_string_set)
BTF_ID_FLAGS(func, my_kfunc_memcpy, 0)
BTF_SET8_END(bpf_my_string_set)
static const struct btf_kfunc_id_set my_kfunc_memcpy_kfunc_set = {
.owner = THIS_MODULE,
.set = &bpf_my_string_set,
};
static int myinit(void)
{
/* Register the BTF */
register_btf_kfunc_id_set(BPF_PROG_TYPE_XDP, &my_kfunc_memcpy_kfunc_set);
pr_info("Load memcpy kfunc\n");
return 0;
}
static void myexit(void)
{
pr_info("Unloading memcpy kfunc\n");
}
module_init(myinit)
module_exit(myexit)
BPF 计划
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
extern void *my_kfunc_memcpy(void *dst, void *src, __u32 src__sz) __ksym;
#define MEMCPY(...) my_kfunc_memcpy(__VA_ARGS__)
struct item {
char data[1000];
};
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__type(key, __u32);
__type(value, struct item);
__uint(max_entries, REPEAT + 1);
} a_map SEC(".maps");
SEC("xdp")
int prog(struct xdp_md *xdp)
{
int i = 0, ii = 1;
struct item *d = bpf_map_lookup_elem(&a_map, &i);
if (d == NULL) return XDP_PASS;
struct item *it = bpf_map_lookup_elem(&a_map, &ii);
if (it == NULL) return XDP_PASS;
MEMCPY(it->data, d->data, 1000);
return XDP_PASS;
}
char _license[] SEC("license") = "GPL";
arg#0 指针类型 UNKNOWN 必须指向标量,或带有标量的结构
抛出此错误是因为内核默认进行严格的类型检查。因此,假设您的参数是
struct sometype*
,那么验证程序将确保该类型确实已传入,并且指针指向有效的内存区域(不允许意外溢出等)
void *my_kfunc_memcpy(void *dst, void *src, __u32 src__sz)
在函数签名中,您已经注释了
src_sz
,它通知验证者忽略对 src
的类型检查,但 dst
仍在进行类型检查。解决此问题的一种方法可能是添加目的地的大小。
void *my_kfunc_memcpy(void *dst, __u32 dst__sz, void *src, __u32 src__sz)
这将是安全选项,因为它应该确保读取和写入方面的内存安全。虽然我不确定这些检查是否适用于指向映射值的指针。
第二个选项您可以忽略类型检查。官方文档目前有点过时,在较新的内核版本中有更多后缀可用于参数。在这种情况下,在 v6.2
中添加的
_ign
后缀可能会起到作用。
实际上最近添加了一个官方kfunc,与您想要做的类似,我们可以使用它作为示例。
__bpf_kfunc int bpf_copy_from_user_str(void *dst, u32 dst__sz, const void __user *unsafe_ptr__ign, u64 标志)因此将您的功能更改为:
void *my_kfunc_memcpy(void *dst, u32 dst__sz, void *src__ign)
也许可以解决问题。现在这并不能保证阅读方面的安全。允许内置 kfunc 执行此操作的原因是因为它从用户内存中读取。
作为最后的替代方案,如果验证者不允许
dst
成为映射值,您也可以忽略它:
void *my_kfunc_memcpy(void *dst__ign, u32 size, void *src__ign)
我认为这会通过验证程序,但再次强调,使用风险自负。