ebpfbpf

How to create a new kfunc and pass an entry from an eBPF map to it as an argument?


I am exploring kfunc feature of eBPF. I was wondering if it is possible to pass a pointer acquired from a map (e.g, an array) to a function I define in a custom kernel module which I mark as kfunc? I have looked at this documentation page from kernel, and I prepared a simple kernel module adding a function wrapping memcpy as an example. When trying to load a BPF program using this kfunc the verifier complains as shown below:

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 --

Looking at the docs, I am not sure what is the right way of annotating the parameters of my kfunc so that it is not UNKNOWN. So I was wondering if it is, at the moment, allowed to pass pointers from a map to a custom kfunc?

Below is what I am using to test this feature.

Kernel module

#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 Program

#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";

Solution

  • arg#0 pointer type UNKNOWN must point to scalar, or struct with scalar

    This error is thrown because the kernel does strict type checking by default. So lets say your argument was a struct sometype* then the verifier would make sure that this type was indeed passed in and that the pointer points to a valid memory area (no accidental overflow allowed, ect.)

    void *my_kfunc_memcpy(void *dst, void *src, __u32 src__sz)

    In your function signature you already annotate the src_sz which informs the verifier to ignore type checks on src but dst is still being type checked. One way to fix the issue might be to to add a size of destination as well.

    void *my_kfunc_memcpy(void *dst, __u32 dst__sz, void *src, __u32 src__sz)

    This would be the safes option since it should ensure memory safety on both the reading and writing side. Though I am not sure if these checks work with pointers to map values.

    Second option you can ignore the type checks. The official docs are currently a bit outdated, there are more suffixes available for arguments in newer kernel versions. In this case the _ign suffix which was added in v6.2 might do the trick.

    Actually an official kfunc was added recently that is similar to what you want to do we can use it as example.

    __bpf_kfunc int bpf_copy_from_user_str(void *dst, u32 dst__sz, const void __user *unsafe_ptr__ign, u64 flags)

    So changing your function to be:

    void *my_kfunc_memcpy(void *dst, u32 dst__sz, void *src__ign)

    Might just do the trick. Now this doesn't provide safety on the reading side. The reason the builtin-kfunc is allowed to do this is because its reading from user memory.

    As a final alternative, if the verifier doesn't allow dst to be a map value, you can also ignore it:

    void *my_kfunc_memcpy(void *dst__ign, u32 size, void *src__ign)

    Which I think will make it past the verifier, but again, use at your own risk.