Merge branch 'support-associating-bpf-programs-with-struct_ops'

Amery Hung says:

====================
Support associating BPF programs with struct_ops

Hi,

This patchset adds a new BPF command BPF_PROG_ASSOC_STRUCT_OPS to
the bpf() syscall to allow associating a BPF program with a struct_ops.
The command is introduced to address a emerging need from struct_ops
users. As the number of subsystems adopting struct_ops grows, more
users are building their struct_ops-based solution with some help from
other BPF programs. For example, scx_layer uses a syscall program as
a user space trigger to refresh layers [0]. It also uses tracing program
to infer whether a task is using GPU and needs to be prioritized [1]. In
these use cases, when there are multiple struct_ops instances, the
struct_ops kfuncs called from different BPF programs, whether struct_ops
or not needs to be able to refer to a specific one, which currently is
not possible.

The new BPF command will allow users to explicitly associate a BPF
program with a struct_ops map. The libbpf wrapper can be called after
loading programs and before attaching programs and struct_ops.

Internally, it will set prog->aux->st_ops_assoc to the struct_ops
map. struct_ops kfuncs can then get the associated struct_ops struct
by calling bpf_prog_get_assoc_struct_ops() with prog->aux, which can
be acquired from a "__prog" argument. The value of the special
argument will be fixed up by the verifier during verification.

The command conceptually associates the implementation of BPF programs
with struct_ops map, not the attachment. A program associated with the
map will take a refcount of it so that st_ops_assoc always points to a
valid struct_ops struct. struct_ops implementers can use the helper,
bpf_prog_get_assoc_struct_ops to get the pointer. The returned
struct_ops if not NULL is guaranteed to be valid and initialized.
However, it is not guaranteed that the struct_ops is attached. The
struct_ops implementer still need to take steps to track and check the
state of the struct_ops in kdata, if the use case demand the struct_ops
to be attached.

We can also consider support associating struct_ops link with BPF
programs, which on one hand make struct_ops implementer's job easier,
but might complicate libbpf workflow and does not apply to legacy
struct_ops attachment.

[0] https://github.com/sched-ext/scx/blob/main/scheds/rust/scx_layered/src/bpf/main.bpf.c#L557
[1] https://github.com/sched-ext/scx/blob/main/scheds/rust/scx_layered/src/bpf/main.bpf.c#L754
---
v7 -> v8
   - Fix libbpf return (Andrii)
   - Follow kfunc _impl suffic naming convention in selftest (Alexei)
   Link: https://lore.kernel.org/bpf/20251121231352.4032020-1-ameryhung@gmail.com/

v6 -> v7
   - Drop the guarantee that bpf_prog_get_assoc_struct_ops() will always return
     an initialized struct_ops (Martin)
   - Minor misc. changes in selftests
   Link: https://lore.kernel.org/bpf/20251114221741.317631-1-ameryhung@gmail.com/

v5 -> v6
   - Drop refcnt bumping for async callbacks and add RCU annotation (Martin)
   - Fix libbpf bug and update comments (Andrii)
   - Fix refcount bug in bpf_prog_assoc_struct_ops() (AI)
   Link: https://lore.kernel.org/bpf/20251104172652.1746988-1-ameryhung@gmail.com/

v4 -> v5
   - Simplify the API for getting associated struct_ops and dont't
     expose struct_ops map lifecycle management (Andrii, Alexei)
   Link: https://lore.kernel.org/bpf/20251024212914.1474337-1-ameryhung@gmail.com/

v3 -> v4
   - Fix potential dangling pointer in timer callback. Protect
     st_ops_assoc with RCU. The get helper now needs to be paired with
     bpf_struct_ops_put()
   - The command should only increase refcount once for a program
     (Andrii)
   - Test a struct_ops program reused in two struct_ops maps
   - Test getting associated struct_ops in timer callback
   Link: https://lore.kernel.org/bpf/20251017215627.722338-1-ameryhung@gmail.com/

v2 -> v3
   - Change the type of st_ops_assoc from void* (i.e., kdata) to bpf_map
     (Andrii)
   - Fix a bug that clears BPF_PTR_POISON when a struct_ops map is freed
     (Andrii)
   - Return NULL if the map is not fully initialized (Martin)
   - Move struct_ops map refcount inc/dec into internal helpers (Martin)
   - Add libbpf API, bpf_program__assoc_struct_ops (Andrii)
   Link: https://lore.kernel.org/bpf/20251016204503.3203690-1-ameryhung@gmail.com/

v1 -> v2
   - Poison st_ops_assoc when reusing the program in more than one
     struct_ops maps and add a helper to access the pointer (Andrii)
   - Minor style and naming changes (Andrii)
   Link: https://lore.kernel.org/bpf/20251010174953.2884682-1-ameryhung@gmail.com/

---
====================

Link: https://patch.msgid.link/20251203233748.668365-1-ameryhung@gmail.com
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
This commit is contained in:
Andrii Nakryiko 2025-12-05 16:17:58 -08:00
commit 5d9fb42f05
18 changed files with 743 additions and 2 deletions

View File

@ -1739,6 +1739,8 @@ struct bpf_prog_aux {
struct rcu_head rcu;
};
struct bpf_stream stream[2];
struct mutex st_ops_assoc_mutex;
struct bpf_map __rcu *st_ops_assoc;
};
struct bpf_prog {
@ -2041,6 +2043,9 @@ static inline void bpf_module_put(const void *data, struct module *owner)
module_put(owner);
}
int bpf_struct_ops_link_create(union bpf_attr *attr);
int bpf_prog_assoc_struct_ops(struct bpf_prog *prog, struct bpf_map *map);
void bpf_prog_disassoc_struct_ops(struct bpf_prog *prog);
void *bpf_prog_get_assoc_struct_ops(const struct bpf_prog_aux *aux);
u32 bpf_struct_ops_id(const void *kdata);
#ifdef CONFIG_NET
@ -2088,6 +2093,17 @@ static inline int bpf_struct_ops_link_create(union bpf_attr *attr)
{
return -EOPNOTSUPP;
}
static inline int bpf_prog_assoc_struct_ops(struct bpf_prog *prog, struct bpf_map *map)
{
return -EOPNOTSUPP;
}
static inline void bpf_prog_disassoc_struct_ops(struct bpf_prog *prog)
{
}
static inline void *bpf_prog_get_assoc_struct_ops(const struct bpf_prog_aux *aux)
{
return NULL;
}
static inline void bpf_map_struct_ops_info_fill(struct bpf_map_info *info, struct bpf_map *map)
{
}

View File

@ -918,6 +918,16 @@ union bpf_iter_link_info {
* Number of bytes read from the stream on success, or -1 if an
* error occurred (in which case, *errno* is set appropriately).
*
* BPF_PROG_ASSOC_STRUCT_OPS
* Description
* Associate a BPF program with a struct_ops map. The struct_ops
* map is identified by *map_fd* and the BPF program is
* identified by *prog_fd*.
*
* Return
* 0 on success or -1 if an error occurred (in which case,
* *errno* is set appropriately).
*
* NOTES
* eBPF objects (maps and programs) can be shared between processes.
*
@ -974,6 +984,7 @@ enum bpf_cmd {
BPF_PROG_BIND_MAP,
BPF_TOKEN_CREATE,
BPF_PROG_STREAM_READ_BY_FD,
BPF_PROG_ASSOC_STRUCT_OPS,
__MAX_BPF_CMD,
};
@ -1894,6 +1905,12 @@ union bpf_attr {
__u32 prog_fd;
} prog_stream_read;
struct {
__u32 map_fd;
__u32 prog_fd;
__u32 flags;
} prog_assoc_struct_ops;
} __attribute__((aligned(8)));
/* The description below is an attempt at providing documentation to eBPF

View File

@ -533,6 +533,17 @@ static void bpf_struct_ops_map_put_progs(struct bpf_struct_ops_map *st_map)
}
}
static void bpf_struct_ops_map_dissoc_progs(struct bpf_struct_ops_map *st_map)
{
u32 i;
for (i = 0; i < st_map->funcs_cnt; i++) {
if (!st_map->links[i])
break;
bpf_prog_disassoc_struct_ops(st_map->links[i]->prog);
}
}
static void bpf_struct_ops_map_free_image(struct bpf_struct_ops_map *st_map)
{
int i;
@ -801,6 +812,9 @@ static long bpf_struct_ops_map_update_elem(struct bpf_map *map, void *key,
goto reset_unlock;
}
/* Poison pointer on error instead of return for backward compatibility */
bpf_prog_assoc_struct_ops(prog, &st_map->map);
link = kzalloc(sizeof(*link), GFP_USER);
if (!link) {
bpf_prog_put(prog);
@ -980,6 +994,8 @@ static void bpf_struct_ops_map_free(struct bpf_map *map)
if (btf_is_module(st_map->btf))
module_put(st_map->st_ops_desc->st_ops->owner);
bpf_struct_ops_map_dissoc_progs(st_map);
bpf_struct_ops_map_del_ksyms(st_map);
/* The struct_ops's function may switch to another struct_ops.
@ -1396,6 +1412,78 @@ int bpf_struct_ops_link_create(union bpf_attr *attr)
return err;
}
int bpf_prog_assoc_struct_ops(struct bpf_prog *prog, struct bpf_map *map)
{
struct bpf_map *st_ops_assoc;
guard(mutex)(&prog->aux->st_ops_assoc_mutex);
st_ops_assoc = rcu_dereference_protected(prog->aux->st_ops_assoc,
lockdep_is_held(&prog->aux->st_ops_assoc_mutex));
if (st_ops_assoc && st_ops_assoc == map)
return 0;
if (st_ops_assoc) {
if (prog->type != BPF_PROG_TYPE_STRUCT_OPS)
return -EBUSY;
rcu_assign_pointer(prog->aux->st_ops_assoc, BPF_PTR_POISON);
} else {
/*
* struct_ops map does not track associated non-struct_ops programs.
* Bump the refcount to make sure st_ops_assoc is always valid.
*/
if (prog->type != BPF_PROG_TYPE_STRUCT_OPS)
bpf_map_inc(map);
rcu_assign_pointer(prog->aux->st_ops_assoc, map);
}
return 0;
}
void bpf_prog_disassoc_struct_ops(struct bpf_prog *prog)
{
struct bpf_map *st_ops_assoc;
guard(mutex)(&prog->aux->st_ops_assoc_mutex);
st_ops_assoc = rcu_dereference_protected(prog->aux->st_ops_assoc,
lockdep_is_held(&prog->aux->st_ops_assoc_mutex));
if (!st_ops_assoc || st_ops_assoc == BPF_PTR_POISON)
return;
if (prog->type != BPF_PROG_TYPE_STRUCT_OPS)
bpf_map_put(st_ops_assoc);
RCU_INIT_POINTER(prog->aux->st_ops_assoc, NULL);
}
/*
* Get a reference to the struct_ops struct (i.e., kdata) associated with a
* program. Should only be called in BPF program context (e.g., in a kfunc).
*
* If the returned pointer is not NULL, it must points to a valid struct_ops.
* The struct_ops map is not guaranteed to be initialized nor attached.
* Kernel struct_ops implementers are responsible for tracking and checking
* the state of the struct_ops if the use case requires an initialized or
* attached struct_ops.
*/
void *bpf_prog_get_assoc_struct_ops(const struct bpf_prog_aux *aux)
{
struct bpf_struct_ops_map *st_map;
struct bpf_map *st_ops_assoc;
st_ops_assoc = rcu_dereference_check(aux->st_ops_assoc, bpf_rcu_lock_held());
if (!st_ops_assoc || st_ops_assoc == BPF_PTR_POISON)
return NULL;
st_map = (struct bpf_struct_ops_map *)st_ops_assoc;
return &st_map->kvalue.data;
}
EXPORT_SYMBOL_GPL(bpf_prog_get_assoc_struct_ops);
void bpf_map_struct_ops_info_fill(struct bpf_map_info *info, struct bpf_map *map)
{
struct bpf_struct_ops_map *st_map = (struct bpf_struct_ops_map *)map;

View File

@ -136,6 +136,7 @@ struct bpf_prog *bpf_prog_alloc_no_stats(unsigned int size, gfp_t gfp_extra_flag
mutex_init(&fp->aux->used_maps_mutex);
mutex_init(&fp->aux->ext_mutex);
mutex_init(&fp->aux->dst_mutex);
mutex_init(&fp->aux->st_ops_assoc_mutex);
#ifdef CONFIG_BPF_SYSCALL
bpf_prog_stream_init(fp);
@ -286,6 +287,7 @@ void __bpf_prog_free(struct bpf_prog *fp)
if (fp->aux) {
mutex_destroy(&fp->aux->used_maps_mutex);
mutex_destroy(&fp->aux->dst_mutex);
mutex_destroy(&fp->aux->st_ops_assoc_mutex);
kfree(fp->aux->poke_tab);
kfree(fp->aux);
}
@ -2896,6 +2898,7 @@ static void bpf_prog_free_deferred(struct work_struct *work)
#endif
bpf_free_used_maps(aux);
bpf_free_used_btfs(aux);
bpf_prog_disassoc_struct_ops(aux->prog);
if (bpf_prog_is_dev_bound(aux))
bpf_prog_dev_bound_destroy(aux->prog);
#ifdef CONFIG_PERF_EVENTS

View File

@ -6122,6 +6122,49 @@ static int prog_stream_read(union bpf_attr *attr)
return ret;
}
#define BPF_PROG_ASSOC_STRUCT_OPS_LAST_FIELD prog_assoc_struct_ops.prog_fd
static int prog_assoc_struct_ops(union bpf_attr *attr)
{
struct bpf_prog *prog;
struct bpf_map *map;
int ret;
if (CHECK_ATTR(BPF_PROG_ASSOC_STRUCT_OPS))
return -EINVAL;
if (attr->prog_assoc_struct_ops.flags)
return -EINVAL;
prog = bpf_prog_get(attr->prog_assoc_struct_ops.prog_fd);
if (IS_ERR(prog))
return PTR_ERR(prog);
if (prog->type == BPF_PROG_TYPE_STRUCT_OPS) {
ret = -EINVAL;
goto put_prog;
}
map = bpf_map_get(attr->prog_assoc_struct_ops.map_fd);
if (IS_ERR(map)) {
ret = PTR_ERR(map);
goto put_prog;
}
if (map->map_type != BPF_MAP_TYPE_STRUCT_OPS) {
ret = -EINVAL;
goto put_map;
}
ret = bpf_prog_assoc_struct_ops(prog, map);
put_map:
bpf_map_put(map);
put_prog:
bpf_prog_put(prog);
return ret;
}
static int __sys_bpf(enum bpf_cmd cmd, bpfptr_t uattr, unsigned int size)
{
union bpf_attr attr;
@ -6261,6 +6304,9 @@ static int __sys_bpf(enum bpf_cmd cmd, bpfptr_t uattr, unsigned int size)
case BPF_PROG_STREAM_READ_BY_FD:
err = prog_stream_read(&attr);
break;
case BPF_PROG_ASSOC_STRUCT_OPS:
err = prog_assoc_struct_ops(&attr);
break;
default:
err = -EINVAL;
break;

View File

@ -22493,8 +22493,7 @@ static int fixup_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
if (!bpf_jit_supports_far_kfunc_call())
insn->imm = BPF_CALL_IMM(desc->addr);
if (insn->off)
return 0;
if (desc->func_id == special_kfunc_list[KF_bpf_obj_new_impl] ||
desc->func_id == special_kfunc_list[KF_bpf_percpu_obj_new_impl]) {
struct btf_struct_meta *kptr_struct_meta = env->insn_aux_data[insn_idx].kptr_struct_meta;

View File

@ -918,6 +918,16 @@ union bpf_iter_link_info {
* Number of bytes read from the stream on success, or -1 if an
* error occurred (in which case, *errno* is set appropriately).
*
* BPF_PROG_ASSOC_STRUCT_OPS
* Description
* Associate a BPF program with a struct_ops map. The struct_ops
* map is identified by *map_fd* and the BPF program is
* identified by *prog_fd*.
*
* Return
* 0 on success or -1 if an error occurred (in which case,
* *errno* is set appropriately).
*
* NOTES
* eBPF objects (maps and programs) can be shared between processes.
*
@ -974,6 +984,7 @@ enum bpf_cmd {
BPF_PROG_BIND_MAP,
BPF_TOKEN_CREATE,
BPF_PROG_STREAM_READ_BY_FD,
BPF_PROG_ASSOC_STRUCT_OPS,
__MAX_BPF_CMD,
};
@ -1894,6 +1905,12 @@ union bpf_attr {
__u32 prog_fd;
} prog_stream_read;
struct {
__u32 map_fd;
__u32 prog_fd;
__u32 flags;
} prog_assoc_struct_ops;
} __attribute__((aligned(8)));
/* The description below is an attempt at providing documentation to eBPF

View File

@ -1397,3 +1397,22 @@ int bpf_prog_stream_read(int prog_fd, __u32 stream_id, void *buf, __u32 buf_len,
err = sys_bpf(BPF_PROG_STREAM_READ_BY_FD, &attr, attr_sz);
return libbpf_err_errno(err);
}
int bpf_prog_assoc_struct_ops(int prog_fd, int map_fd,
struct bpf_prog_assoc_struct_ops_opts *opts)
{
const size_t attr_sz = offsetofend(union bpf_attr, prog_assoc_struct_ops);
union bpf_attr attr;
int err;
if (!OPTS_VALID(opts, bpf_prog_assoc_struct_ops_opts))
return libbpf_err(-EINVAL);
memset(&attr, 0, attr_sz);
attr.prog_assoc_struct_ops.map_fd = map_fd;
attr.prog_assoc_struct_ops.prog_fd = prog_fd;
attr.prog_assoc_struct_ops.flags = OPTS_GET(opts, flags, 0);
err = sys_bpf(BPF_PROG_ASSOC_STRUCT_OPS, &attr, attr_sz);
return libbpf_err_errno(err);
}

View File

@ -733,6 +733,27 @@ struct bpf_prog_stream_read_opts {
LIBBPF_API int bpf_prog_stream_read(int prog_fd, __u32 stream_id, void *buf, __u32 buf_len,
struct bpf_prog_stream_read_opts *opts);
struct bpf_prog_assoc_struct_ops_opts {
size_t sz;
__u32 flags;
size_t :0;
};
#define bpf_prog_assoc_struct_ops_opts__last_field flags
/**
* @brief **bpf_prog_assoc_struct_ops** associates a BPF program with a
* struct_ops map.
*
* @param prog_fd FD for the BPF program
* @param map_fd FD for the struct_ops map to be associated with the BPF program
* @param opts optional options, can be NULL
*
* @return 0 on success; negative error code, otherwise (errno is also set to
* the error code)
*/
LIBBPF_API int bpf_prog_assoc_struct_ops(int prog_fd, int map_fd,
struct bpf_prog_assoc_struct_ops_opts *opts);
#ifdef __cplusplus
} /* extern "C" */
#endif

View File

@ -14133,6 +14133,37 @@ int bpf_program__set_attach_target(struct bpf_program *prog,
return 0;
}
int bpf_program__assoc_struct_ops(struct bpf_program *prog, struct bpf_map *map,
struct bpf_prog_assoc_struct_ops_opts *opts)
{
int prog_fd, map_fd;
prog_fd = bpf_program__fd(prog);
if (prog_fd < 0) {
pr_warn("prog '%s': can't associate BPF program without FD (was it loaded?)\n",
prog->name);
return libbpf_err(-EINVAL);
}
if (prog->type == BPF_PROG_TYPE_STRUCT_OPS) {
pr_warn("prog '%s': can't associate struct_ops program\n", prog->name);
return libbpf_err(-EINVAL);
}
map_fd = bpf_map__fd(map);
if (map_fd < 0) {
pr_warn("map '%s': can't associate BPF map without FD (was it created?)\n", map->name);
return libbpf_err(-EINVAL);
}
if (!bpf_map__is_struct_ops(map)) {
pr_warn("map '%s': can't associate non-struct_ops map\n", map->name);
return libbpf_err(-EINVAL);
}
return bpf_prog_assoc_struct_ops(prog_fd, map_fd, opts);
}
int parse_cpu_mask_str(const char *s, bool **mask, int *mask_sz)
{
int err = 0, n, len, start, end = -1;

View File

@ -1006,6 +1006,22 @@ LIBBPF_API int
bpf_program__set_attach_target(struct bpf_program *prog, int attach_prog_fd,
const char *attach_func_name);
struct bpf_prog_assoc_struct_ops_opts; /* defined in bpf.h */
/**
* @brief **bpf_program__assoc_struct_ops()** associates a BPF program with a
* struct_ops map.
*
* @param prog BPF program
* @param map struct_ops map to be associated with the BPF program
* @param opts optional options, can be NULL
*
* @return 0, on success; negative error code, otherwise
*/
LIBBPF_API int
bpf_program__assoc_struct_ops(struct bpf_program *prog, struct bpf_map *map,
struct bpf_prog_assoc_struct_ops_opts *opts);
/**
* @brief **bpf_object__find_map_by_name()** returns BPF map of
* the given name, if it exists within the passed BPF object

View File

@ -451,4 +451,6 @@ LIBBPF_1.7.0 {
global:
bpf_map__set_exclusive_program;
bpf_map__exclusive_program;
bpf_prog_assoc_struct_ops;
bpf_program__assoc_struct_ops;
} LIBBPF_1.6.0;

View File

@ -0,0 +1,191 @@
// SPDX-License-Identifier: GPL-2.0
#include <test_progs.h>
#include "struct_ops_assoc.skel.h"
#include "struct_ops_assoc_reuse.skel.h"
#include "struct_ops_assoc_in_timer.skel.h"
static void test_st_ops_assoc(void)
{
struct struct_ops_assoc *skel = NULL;
int err, pid;
skel = struct_ops_assoc__open_and_load();
if (!ASSERT_OK_PTR(skel, "struct_ops_assoc__open"))
goto out;
/* cannot explicitly associate struct_ops program */
err = bpf_program__assoc_struct_ops(skel->progs.test_1_a,
skel->maps.st_ops_map_a, NULL);
ASSERT_ERR(err, "bpf_program__assoc_struct_ops(test_1_a, st_ops_map_a)");
err = bpf_program__assoc_struct_ops(skel->progs.syscall_prog_a,
skel->maps.st_ops_map_a, NULL);
ASSERT_OK(err, "bpf_program__assoc_struct_ops(syscall_prog_a, st_ops_map_a)");
err = bpf_program__assoc_struct_ops(skel->progs.sys_enter_prog_a,
skel->maps.st_ops_map_a, NULL);
ASSERT_OK(err, "bpf_program__assoc_struct_ops(sys_enter_prog_a, st_ops_map_a)");
err = bpf_program__assoc_struct_ops(skel->progs.syscall_prog_b,
skel->maps.st_ops_map_b, NULL);
ASSERT_OK(err, "bpf_program__assoc_struct_ops(syscall_prog_b, st_ops_map_b)");
err = bpf_program__assoc_struct_ops(skel->progs.sys_enter_prog_b,
skel->maps.st_ops_map_b, NULL);
ASSERT_OK(err, "bpf_program__assoc_struct_ops(sys_enter_prog_b, st_ops_map_b)");
/* sys_enter_prog_a already associated with map_a */
err = bpf_program__assoc_struct_ops(skel->progs.sys_enter_prog_a,
skel->maps.st_ops_map_b, NULL);
ASSERT_ERR(err, "bpf_program__assoc_struct_ops(sys_enter_prog_a, st_ops_map_b)");
err = struct_ops_assoc__attach(skel);
if (!ASSERT_OK(err, "struct_ops_assoc__attach"))
goto out;
/* run tracing prog that calls .test_1 and checks return */
pid = getpid();
skel->bss->test_pid = pid;
sys_gettid();
skel->bss->test_pid = 0;
ASSERT_EQ(skel->bss->test_err_a, 0, "skel->bss->test_err_a");
ASSERT_EQ(skel->bss->test_err_b, 0, "skel->bss->test_err_b");
/* run syscall_prog that calls .test_1 and checks return */
err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.syscall_prog_a), NULL);
ASSERT_OK(err, "bpf_prog_test_run_opts");
err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.syscall_prog_b), NULL);
ASSERT_OK(err, "bpf_prog_test_run_opts");
ASSERT_EQ(skel->bss->test_err_a, 0, "skel->bss->test_err_a");
ASSERT_EQ(skel->bss->test_err_b, 0, "skel->bss->test_err_b");
out:
struct_ops_assoc__destroy(skel);
}
static void test_st_ops_assoc_reuse(void)
{
struct struct_ops_assoc_reuse *skel = NULL;
int err;
skel = struct_ops_assoc_reuse__open_and_load();
if (!ASSERT_OK_PTR(skel, "struct_ops_assoc_reuse__open"))
goto out;
err = bpf_program__assoc_struct_ops(skel->progs.syscall_prog_a,
skel->maps.st_ops_map_a, NULL);
ASSERT_OK(err, "bpf_program__assoc_struct_ops(syscall_prog_a, st_ops_map_a)");
err = bpf_program__assoc_struct_ops(skel->progs.syscall_prog_b,
skel->maps.st_ops_map_b, NULL);
ASSERT_OK(err, "bpf_program__assoc_struct_ops(syscall_prog_b, st_ops_map_b)");
err = struct_ops_assoc_reuse__attach(skel);
if (!ASSERT_OK(err, "struct_ops_assoc__attach"))
goto out;
/* run syscall_prog that calls .test_1 and checks return */
err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.syscall_prog_a), NULL);
ASSERT_OK(err, "bpf_prog_test_run_opts");
err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.syscall_prog_b), NULL);
ASSERT_OK(err, "bpf_prog_test_run_opts");
ASSERT_EQ(skel->bss->test_err_a, 0, "skel->bss->test_err_a");
ASSERT_EQ(skel->bss->test_err_b, 0, "skel->bss->test_err_b");
out:
struct_ops_assoc_reuse__destroy(skel);
}
static void test_st_ops_assoc_in_timer(void)
{
struct struct_ops_assoc_in_timer *skel = NULL;
int err;
skel = struct_ops_assoc_in_timer__open_and_load();
if (!ASSERT_OK_PTR(skel, "struct_ops_assoc_in_timer__open"))
goto out;
err = bpf_program__assoc_struct_ops(skel->progs.syscall_prog,
skel->maps.st_ops_map, NULL);
ASSERT_OK(err, "bpf_program__assoc_struct_ops");
err = struct_ops_assoc_in_timer__attach(skel);
if (!ASSERT_OK(err, "struct_ops_assoc__attach"))
goto out;
/*
* Run .test_1 by calling kfunc bpf_kfunc_multi_st_ops_test_1_prog_arg() and checks
* the return value. .test_1 will also schedule timer_cb that runs .test_1 again
* immediately.
*/
err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.syscall_prog), NULL);
ASSERT_OK(err, "bpf_prog_test_run_opts");
/* Check the return of the kfunc after timer_cb runs */
while (!READ_ONCE(skel->bss->timer_cb_run))
sched_yield();
ASSERT_EQ(skel->bss->timer_test_1_ret, 1234, "skel->bss->timer_test_1_ret");
ASSERT_EQ(skel->bss->test_err, 0, "skel->bss->test_err_a");
out:
struct_ops_assoc_in_timer__destroy(skel);
}
static void test_st_ops_assoc_in_timer_no_uref(void)
{
struct struct_ops_assoc_in_timer *skel = NULL;
struct bpf_link *link;
int err;
skel = struct_ops_assoc_in_timer__open_and_load();
if (!ASSERT_OK_PTR(skel, "struct_ops_assoc_in_timer__open"))
goto out;
err = bpf_program__assoc_struct_ops(skel->progs.syscall_prog,
skel->maps.st_ops_map, NULL);
ASSERT_OK(err, "bpf_program__assoc_struct_ops");
link = bpf_map__attach_struct_ops(skel->maps.st_ops_map);
if (!ASSERT_OK_PTR(link, "bpf_map__attach_struct_ops"))
goto out;
/*
* Run .test_1 by calling kfunc bpf_kfunc_multi_st_ops_test_1_prog_arg() and checks
* the return value. .test_1 will also schedule timer_cb that runs .test_1 again.
* timer_cb will run 500ms after syscall_prog runs, when the user space no longer
* holds a reference to st_ops_map.
*/
skel->bss->timer_ns = 500000000;
err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.syscall_prog), NULL);
ASSERT_OK(err, "bpf_prog_test_run_opts");
/* Detach and close struct_ops map to cause it to be freed */
bpf_link__destroy(link);
close(bpf_program__fd(skel->progs.syscall_prog));
close(bpf_map__fd(skel->maps.st_ops_map));
/* Check the return of the kfunc after timer_cb runs */
while (!READ_ONCE(skel->bss->timer_cb_run))
sched_yield();
ASSERT_EQ(skel->bss->timer_test_1_ret, -1, "skel->bss->timer_test_1_ret");
ASSERT_EQ(skel->bss->test_err, 0, "skel->bss->test_err_a");
out:
struct_ops_assoc_in_timer__destroy(skel);
}
void test_struct_ops_assoc(void)
{
if (test__start_subtest("st_ops_assoc"))
test_st_ops_assoc();
if (test__start_subtest("st_ops_assoc_reuse"))
test_st_ops_assoc_reuse();
if (test__start_subtest("st_ops_assoc_in_timer"))
test_st_ops_assoc_in_timer();
if (test__start_subtest("st_ops_assoc_in_timer_no_uref"))
test_st_ops_assoc_in_timer_no_uref();
}

View File

@ -0,0 +1,105 @@
// SPDX-License-Identifier: GPL-2.0
#include <vmlinux.h>
#include <bpf/bpf_tracing.h>
#include "bpf_misc.h"
#include "../test_kmods/bpf_testmod.h"
#include "../test_kmods/bpf_testmod_kfunc.h"
char _license[] SEC("license") = "GPL";
int test_pid;
/* Programs associated with st_ops_map_a */
#define MAP_A_MAGIC 1234
int test_err_a;
SEC("struct_ops")
int BPF_PROG(test_1_a, struct st_ops_args *args)
{
return MAP_A_MAGIC;
}
SEC("tp_btf/sys_enter")
int BPF_PROG(sys_enter_prog_a, struct pt_regs *regs, long id)
{
struct st_ops_args args = {};
struct task_struct *task;
int ret;
task = bpf_get_current_task_btf();
if (!test_pid || task->pid != test_pid)
return 0;
ret = bpf_kfunc_multi_st_ops_test_1_impl(&args, NULL);
if (ret != MAP_A_MAGIC)
test_err_a++;
return 0;
}
SEC("syscall")
int syscall_prog_a(void *ctx)
{
struct st_ops_args args = {};
int ret;
ret = bpf_kfunc_multi_st_ops_test_1_impl(&args, NULL);
if (ret != MAP_A_MAGIC)
test_err_a++;
return 0;
}
SEC(".struct_ops.link")
struct bpf_testmod_multi_st_ops st_ops_map_a = {
.test_1 = (void *)test_1_a,
};
/* Programs associated with st_ops_map_b */
#define MAP_B_MAGIC 5678
int test_err_b;
SEC("struct_ops")
int BPF_PROG(test_1_b, struct st_ops_args *args)
{
return MAP_B_MAGIC;
}
SEC("tp_btf/sys_enter")
int BPF_PROG(sys_enter_prog_b, struct pt_regs *regs, long id)
{
struct st_ops_args args = {};
struct task_struct *task;
int ret;
task = bpf_get_current_task_btf();
if (!test_pid || task->pid != test_pid)
return 0;
ret = bpf_kfunc_multi_st_ops_test_1_impl(&args, NULL);
if (ret != MAP_B_MAGIC)
test_err_b++;
return 0;
}
SEC("syscall")
int syscall_prog_b(void *ctx)
{
struct st_ops_args args = {};
int ret;
ret = bpf_kfunc_multi_st_ops_test_1_impl(&args, NULL);
if (ret != MAP_B_MAGIC)
test_err_b++;
return 0;
}
SEC(".struct_ops.link")
struct bpf_testmod_multi_st_ops st_ops_map_b = {
.test_1 = (void *)test_1_b,
};

View File

@ -0,0 +1,77 @@
// SPDX-License-Identifier: GPL-2.0
#include <vmlinux.h>
#include <bpf/bpf_tracing.h>
#include "bpf_misc.h"
#include "../test_kmods/bpf_testmod.h"
#include "../test_kmods/bpf_testmod_kfunc.h"
char _license[] SEC("license") = "GPL";
struct elem {
struct bpf_timer timer;
};
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, 1);
__type(key, int);
__type(value, struct elem);
} array_map SEC(".maps");
#define MAP_MAGIC 1234
int recur;
int test_err;
int timer_ns;
int timer_test_1_ret;
int timer_cb_run;
__noinline static int timer_cb(void *map, int *key, struct bpf_timer *timer)
{
struct st_ops_args args = {};
recur++;
timer_test_1_ret = bpf_kfunc_multi_st_ops_test_1_impl(&args, NULL);
recur--;
timer_cb_run++;
return 0;
}
SEC("struct_ops")
int BPF_PROG(test_1, struct st_ops_args *args)
{
struct bpf_timer *timer;
int key = 0;
if (!recur) {
timer = bpf_map_lookup_elem(&array_map, &key);
if (!timer)
return 0;
bpf_timer_init(timer, &array_map, 1);
bpf_timer_set_callback(timer, timer_cb);
bpf_timer_start(timer, timer_ns, 0);
}
return MAP_MAGIC;
}
SEC("syscall")
int syscall_prog(void *ctx)
{
struct st_ops_args args = {};
int ret;
ret = bpf_kfunc_multi_st_ops_test_1_impl(&args, NULL);
if (ret != MAP_MAGIC)
test_err++;
return 0;
}
SEC(".struct_ops.link")
struct bpf_testmod_multi_st_ops st_ops_map = {
.test_1 = (void *)test_1,
};

View File

@ -0,0 +1,75 @@
// SPDX-License-Identifier: GPL-2.0
#include <vmlinux.h>
#include <bpf/bpf_tracing.h>
#include "bpf_misc.h"
#include "../test_kmods/bpf_testmod.h"
#include "../test_kmods/bpf_testmod_kfunc.h"
char _license[] SEC("license") = "GPL";
#define MAP_A_MAGIC 1234
int test_err_a;
int recur;
/*
* test_1_a is reused. The kfunc should not be able to get the associated
* struct_ops and call test_1 recursively as it is ambiguous.
*/
SEC("struct_ops")
int BPF_PROG(test_1_a, struct st_ops_args *args)
{
int ret;
if (!recur) {
recur++;
ret = bpf_kfunc_multi_st_ops_test_1_impl(args, NULL);
if (ret != -1)
test_err_a++;
recur--;
}
return MAP_A_MAGIC;
}
/* Programs associated with st_ops_map_a */
SEC("syscall")
int syscall_prog_a(void *ctx)
{
struct st_ops_args args = {};
int ret;
ret = bpf_kfunc_multi_st_ops_test_1_impl(&args, NULL);
if (ret != MAP_A_MAGIC)
test_err_a++;
return 0;
}
SEC(".struct_ops.link")
struct bpf_testmod_multi_st_ops st_ops_map_a = {
.test_1 = (void *)test_1_a,
};
/* Programs associated with st_ops_map_b */
int test_err_b;
SEC("syscall")
int syscall_prog_b(void *ctx)
{
struct st_ops_args args = {};
int ret;
ret = bpf_kfunc_multi_st_ops_test_1_impl(&args, NULL);
if (ret != MAP_A_MAGIC)
test_err_b++;
return 0;
}
SEC(".struct_ops.link")
struct bpf_testmod_multi_st_ops st_ops_map_b = {
.test_1 = (void *)test_1_a,
};

View File

@ -1134,6 +1134,7 @@ __bpf_kfunc int bpf_kfunc_st_ops_inc10(struct st_ops_args *args)
}
__bpf_kfunc int bpf_kfunc_multi_st_ops_test_1(struct st_ops_args *args, u32 id);
__bpf_kfunc int bpf_kfunc_multi_st_ops_test_1_impl(struct st_ops_args *args, void *aux_prog);
BTF_KFUNCS_START(bpf_testmod_check_kfunc_ids)
BTF_ID_FLAGS(func, bpf_testmod_test_mod_kfunc)
@ -1176,6 +1177,7 @@ BTF_ID_FLAGS(func, bpf_kfunc_st_ops_test_epilogue, KF_TRUSTED_ARGS | KF_SLEEPABL
BTF_ID_FLAGS(func, bpf_kfunc_st_ops_test_pro_epilogue, KF_TRUSTED_ARGS | KF_SLEEPABLE)
BTF_ID_FLAGS(func, bpf_kfunc_st_ops_inc10, KF_TRUSTED_ARGS)
BTF_ID_FLAGS(func, bpf_kfunc_multi_st_ops_test_1, KF_TRUSTED_ARGS)
BTF_ID_FLAGS(func, bpf_kfunc_multi_st_ops_test_1_impl, KF_TRUSTED_ARGS)
BTF_KFUNCS_END(bpf_testmod_check_kfunc_ids)
static int bpf_testmod_ops_init(struct btf *btf)
@ -1637,6 +1639,7 @@ static struct bpf_testmod_multi_st_ops *multi_st_ops_find_nolock(u32 id)
return NULL;
}
/* Call test_1() of the struct_ops map identified by the id */
int bpf_kfunc_multi_st_ops_test_1(struct st_ops_args *args, u32 id)
{
struct bpf_testmod_multi_st_ops *st_ops;
@ -1652,6 +1655,20 @@ int bpf_kfunc_multi_st_ops_test_1(struct st_ops_args *args, u32 id)
return ret;
}
/* Call test_1() of the associated struct_ops map */
int bpf_kfunc_multi_st_ops_test_1_impl(struct st_ops_args *args, void *aux__prog)
{
struct bpf_prog_aux *prog_aux = (struct bpf_prog_aux *)aux__prog;
struct bpf_testmod_multi_st_ops *st_ops;
int ret = -1;
st_ops = (struct bpf_testmod_multi_st_ops *)bpf_prog_get_assoc_struct_ops(prog_aux);
if (st_ops)
ret = st_ops->test_1(args);
return ret;
}
static int multi_st_ops_reg(void *kdata, struct bpf_link *link)
{
struct bpf_testmod_multi_st_ops *st_ops =

View File

@ -162,5 +162,6 @@ struct task_struct *bpf_kfunc_ret_rcu_test(void) __ksym;
int *bpf_kfunc_ret_rcu_test_nostruct(int rdonly_buf_size) __ksym;
int bpf_kfunc_multi_st_ops_test_1(struct st_ops_args *args, u32 id) __ksym;
int bpf_kfunc_multi_st_ops_test_1_impl(struct st_ops_args *args, void *aux__prog) __ksym;
#endif /* _BPF_TESTMOD_KFUNC_H */