* [PATCH bpf-next 1/9] bpf: Add log support for tracing link_create
2026-11-09 14:08 [PATCH bpf-next 0/9] bpf: Add log support for tracing link_create Leon Hwang
@ 2026-11-09 14:08 ` Leon Hwang
2026-11-09 14:47 ` bot+bpf-ci
2026-11-09 14:08 ` [PATCH bpf-next 2/9] bpf: Add log support for tracing_multi link_create Leon Hwang
` (7 subsequent siblings)
8 siblings, 1 reply; 14+ messages in thread
From: Leon Hwang @ 2026-11-09 14:08 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, Ihor Solodrai, Steven Rostedt, Masami Hiramatsu,
Mathieu Desnoyers, KP Singh, Matt Bobrowski, Shuah Khan,
Leon Hwang, linux-kernel, linux-trace-kernel, linux-kselftest,
netdev
Tracing BPF_LINK_CREATE returns an error without explaining why attachment
failed.
Pass the BPF_LINK_CREATE log to the tracing attach path and report invalid
attach parameters. Also pass it to bpf_check_attach_target() so freplace
attachment to a new target can report target-check failures.
Signed-off-by: Leon Hwang <leon.hwang@linux.dev>
---
kernel/bpf/syscall.c | 20 +++++++++++++++-----
1 file changed, 15 insertions(+), 5 deletions(-)
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index 9004f2673ccf..6147d386b777 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -3674,7 +3674,8 @@ static int bpf_tracing_prog_attach(struct bpf_prog *prog,
int tgt_prog_fd,
u32 btf_id,
u64 bpf_cookie,
- enum bpf_attach_type attach_type)
+ enum bpf_attach_type attach_type,
+ struct bpf_verifier_log *log)
{
struct bpf_link_primer link_primer;
struct bpf_prog *tgt_prog = NULL;
@@ -3711,6 +3712,7 @@ static int bpf_tracing_prog_attach(struct bpf_prog *prog,
}
if (!!tgt_prog_fd != !!btf_id) {
+ bpf_log(log, "tgt_prog_fd and btf_id must be both set or both unset.\n");
err = -EINVAL;
goto out_put_prog;
}
@@ -3723,12 +3725,14 @@ static int bpf_tracing_prog_attach(struct bpf_prog *prog,
* attach_tracing_prog flag is set.
*/
if (prog->type != BPF_PROG_TYPE_EXT) {
+ bpf_log(log, "tgt_prog_fd is only allowed for freplace prog.\n");
err = -EINVAL;
goto out_put_prog;
}
tgt_prog = bpf_prog_get(tgt_prog_fd);
if (IS_ERR(tgt_prog)) {
+ bpf_log(log, "Invalid tgt_prog_fd.\n");
err = PTR_ERR(tgt_prog);
tgt_prog = NULL;
goto out_put_prog;
@@ -3783,11 +3787,13 @@ static int bpf_tracing_prog_attach(struct bpf_prog *prog,
*/
if (prog->type != BPF_PROG_TYPE_TRACING &&
prog->type != BPF_PROG_TYPE_LSM) {
+ bpf_log(log, "Only allow re-attach for tracing and lsm progs.\n");
err = -EINVAL;
goto out_unlock;
}
/* We can allow re-attach only if we have valid attach_btf. */
if (!prog->aux->attach_btf) {
+ bpf_log(log, "Invalid attach_btf.\n");
err = -EINVAL;
goto out_unlock;
}
@@ -3803,7 +3809,7 @@ static int bpf_tracing_prog_attach(struct bpf_prog *prog,
*/
struct bpf_attach_target_info tgt_info = {};
- err = bpf_check_attach_target(NULL, prog, tgt_prog, btf_id,
+ err = bpf_check_attach_target(log, prog, tgt_prog, btf_id,
&tgt_info);
if (err)
goto out_unlock;
@@ -3843,6 +3849,7 @@ static int bpf_tracing_prog_attach(struct bpf_prog *prog,
*/
if (prog->type == BPF_PROG_TYPE_EXT &&
prog->aux->kprobe_write_ctx != tgt_prog->aux->kprobe_write_ctx) {
+ bpf_log(log, "Invalid kprobe_write_ctx.\n");
err = -EINVAL;
goto out_unlock;
}
@@ -4374,7 +4381,7 @@ static int bpf_raw_tp_link_attach(struct bpf_prog *prog,
tp_name = prog->aux->attach_func_name;
break;
}
- return bpf_tracing_prog_attach(prog, 0, 0, 0, attach_type);
+ return bpf_tracing_prog_attach(prog, 0, 0, 0, attach_type, NULL);
case BPF_PROG_TYPE_RAW_TRACEPOINT:
case BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE:
if (strncpy_from_user(buf, user_tp_name, sizeof(buf) - 1) < 0)
@@ -5907,11 +5914,13 @@ static int link_create(union bpf_attr *attr, bpfptr_t uattr, struct bpf_common_a
attr->link_create.target_fd,
attr->link_create.target_btf_id,
attr->link_create.tracing.cookie,
- attr->link_create.attach_type);
+ attr->link_create.attach_type,
+ log);
break;
case BPF_PROG_TYPE_LSM:
case BPF_PROG_TYPE_TRACING:
if (attr->link_create.attach_type != prog->expected_attach_type) {
+ bpf_log(log, "Invalid attach_type.\n");
ret = -EINVAL;
goto out;
}
@@ -5929,7 +5938,8 @@ static int link_create(union bpf_attr *attr, bpfptr_t uattr, struct bpf_common_a
attr->link_create.target_fd,
attr->link_create.target_btf_id,
attr->link_create.tracing.cookie,
- attr->link_create.attach_type);
+ attr->link_create.attach_type,
+ log);
break;
case BPF_PROG_TYPE_FLOW_DISSECTOR:
case BPF_PROG_TYPE_SK_LOOKUP:
--
2.55.0
^ permalink raw reply [flat|nested] 14+ messages in thread* Re: [PATCH bpf-next 1/9] bpf: Add log support for tracing link_create
2026-11-09 14:08 ` [PATCH bpf-next 1/9] " Leon Hwang
@ 2026-11-09 14:47 ` bot+bpf-ci
0 siblings, 0 replies; 14+ messages in thread
From: bot+bpf-ci @ 2026-11-09 14:47 UTC (permalink / raw)
To: leon.hwang, bpf
Cc: ast, daniel, john.fastabend, andrii, eddyz87, memxor, martin.lau,
song, yonghong.song, jolsa, emil, ihor.solodrai, rostedt,
mhiramat, mathieu.desnoyers, kpsingh, matt, shuah, leon.hwang,
linux-kernel, linux-trace-kernel, linux-kselftest, netdev, ast,
andrii, daniel, martin.lau, eddyz87, yonghong.song, mason,
ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 2094 bytes --]
> bpf: Add log support for tracing link_create
TL;DR: Logging is incomplete: the freplace max_ctx_offset check in
bpf_tracing_prog_attach() still returns -EINVAL with an empty log, and the
saved_dst type mismatch may too. No functional regression.
> diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
> --- a/kernel/bpf/syscall.c
> +++ b/kernel/bpf/syscall.c
[ ... ]
> @@ -3843,6 +3849,7 @@ static int bpf_tracing_prog_attach(struct bpf_prog *prog,
> */
> if (prog->type == BPF_PROG_TYPE_EXT &&
> prog->aux->kprobe_write_ctx != tgt_prog->aux->kprobe_write_ctx) {
> + bpf_log(log, "Invalid kprobe_write_ctx.\n");
> err = -EINVAL;
> goto out_unlock;
> }
The check right after this one still returns -EINVAL without writing
anything to the log:
if (prog->type == BPF_PROG_TYPE_EXT &&
(tgt_prog->type == BPF_PROG_TYPE_RAW_TRACEPOINT ||
tgt_prog->type == BPF_PROG_TYPE_TRACEPOINT ||
tgt_prog->type == BPF_PROG_TYPE_SYSCALL) &&
prog->aux->max_ctx_offset > tgt_prog->aux->max_ctx_offset) {
err = -EINVAL;
goto out_unlock;
}
An freplace program whose max_ctx_offset is larger than that of a
RAW_TRACEPOINT, TRACEPOINT or SYSCALL target will get -EINVAL from
link_create with an empty log and log_true_size of 0. That is the same
kind of invalid freplace target as the kprobe_write_ctx mismatch above,
which now logs. This isn't a functional regression, but was this check
missed?
Would something like this cover it?
bpf_log(log, "Extension accesses more ctx than its target.\n");
Also, bpf_check_attach_target() still returns a bare -EINVAL for the
saved_dst_prog_type / saved_dst_attach_type mismatch, which is the
"freplace attachment to a new target" case the commit message mentions.
That code is in verifier.c, so it is outside this patch, but is it intended
to be left without a message?
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/42633880300
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH bpf-next 2/9] bpf: Add log support for tracing_multi link_create
2026-11-09 14:08 [PATCH bpf-next 0/9] bpf: Add log support for tracing link_create Leon Hwang
2026-11-09 14:08 ` [PATCH bpf-next 1/9] " Leon Hwang
@ 2026-11-09 14:08 ` Leon Hwang
2026-11-09 14:08 ` [PATCH bpf-next 3/9] bpf: Add log support for iter link_create Leon Hwang
` (6 subsequent siblings)
8 siblings, 0 replies; 14+ messages in thread
From: Leon Hwang @ 2026-11-09 14:08 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, Ihor Solodrai, Steven Rostedt, Masami Hiramatsu,
Mathieu Desnoyers, KP Singh, Matt Bobrowski, Shuah Khan,
Leon Hwang, linux-kernel, linux-trace-kernel, linux-kselftest,
netdev
tracing_multi BPF_LINK_CREATE returns an error for invalid counts, missing
IDs, unsupported flags, or a nonzero target FD without explaining which
check failed.
Report these failures through the BPF_LINK_CREATE log.
Signed-off-by: Leon Hwang <leon.hwang@linux.dev>
---
include/linux/trace_events.h | 7 +++++--
kernel/bpf/syscall.c | 2 +-
kernel/trace/bpf_trace.c | 26 +++++++++++++++++++++-----
3 files changed, 27 insertions(+), 8 deletions(-)
diff --git a/include/linux/trace_events.h b/include/linux/trace_events.h
index 321f2f3a6c8e..bb825ea5d09c 100644
--- a/include/linux/trace_events.h
+++ b/include/linux/trace_events.h
@@ -16,6 +16,7 @@ struct tracer;
struct dentry;
struct bpf_prog;
union bpf_attr;
+struct bpf_verifier_log;
/* Used for event string fields when they are NULL */
#define EVENT_NULL_STR "(null)"
@@ -796,7 +797,8 @@ int bpf_get_perf_event_info(const struct perf_event *event, u32 *prog_id,
unsigned long *missed);
int bpf_kprobe_multi_link_attach(const union bpf_attr *attr, struct bpf_prog *prog);
int bpf_uprobe_multi_link_attach(const union bpf_attr *attr, struct bpf_prog *prog);
-int bpf_tracing_multi_attach(struct bpf_prog *prog, const union bpf_attr *attr);
+int bpf_tracing_multi_attach(struct bpf_prog *prog, const union bpf_attr *attr,
+ struct bpf_verifier_log *log);
#else
static inline unsigned int trace_call_bpf(struct trace_event_call *call, void *ctx)
{
@@ -855,7 +857,8 @@ bpf_uprobe_multi_link_attach(const union bpf_attr *attr, struct bpf_prog *prog)
return -EOPNOTSUPP;
}
static inline int
-bpf_tracing_multi_attach(struct bpf_prog *prog, const union bpf_attr *attr)
+bpf_tracing_multi_attach(struct bpf_prog *prog, const union bpf_attr *attr,
+ struct bpf_verifier_log *log)
{
return -EOPNOTSUPP;
}
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index 6147d386b777..aed1c6a2f203 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -5932,7 +5932,7 @@ static int link_create(union bpf_attr *attr, bpfptr_t uattr, struct bpf_common_a
else if (prog->expected_attach_type == BPF_LSM_CGROUP)
ret = cgroup_bpf_link_attach(attr, prog);
else if (is_tracing_multi(prog->expected_attach_type))
- ret = bpf_tracing_multi_attach(prog, attr);
+ ret = bpf_tracing_multi_attach(prog, attr, log);
else
ret = bpf_tracing_prog_attach(prog,
attr->link_create.target_fd,
diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c
index 2aec56d7f3d3..352a1cd0c863 100644
--- a/kernel/trace/bpf_trace.c
+++ b/kernel/trace/bpf_trace.c
@@ -3861,7 +3861,8 @@ static int check_dup_ids(u32 *ids, u64 *cookies, u32 cnt)
return err;
}
-int bpf_tracing_multi_attach(struct bpf_prog *prog, const union bpf_attr *attr)
+int bpf_tracing_multi_attach(struct bpf_prog *prog, const union bpf_attr *attr,
+ struct bpf_verifier_log *log)
{
struct bpf_tracing_multi_link *link = NULL;
struct bpf_tramp_node *fexits = NULL;
@@ -3875,12 +3876,26 @@ int bpf_tracing_multi_attach(struct bpf_prog *prog, const union bpf_attr *attr)
uids = u64_to_user_ptr(attr->link_create.tracing_multi.ids);
cnt = attr->link_create.tracing_multi.cnt;
- if (!cnt || !uids)
+ if (!cnt) {
+ bpf_log(log, "tracing_multi.cnt cannot be zero.\n");
return -EINVAL;
- if (cnt > MAX_TRACING_MULTI_CNT)
+ }
+ if (!uids) {
+ bpf_log(log, "tracing_multi.ids must be set.\n");
+ return -EINVAL;
+ }
+ if (cnt > MAX_TRACING_MULTI_CNT) {
+ bpf_log(log, "tracing_multi.cnt is too large.\n");
return -E2BIG;
- if (attr->link_create.flags || attr->link_create.target_fd)
+ }
+ if (attr->link_create.flags) {
+ bpf_log(log, "Invalid flags.\n");
return -EINVAL;
+ }
+ if (attr->link_create.target_fd) {
+ bpf_log(log, "Invalid target_fd.\n");
+ return -EINVAL;
+ }
ids = kvmalloc_objs(*ids, cnt);
if (!ids)
@@ -3951,7 +3966,8 @@ int bpf_tracing_multi_attach(struct bpf_prog *prog, const union bpf_attr *attr)
#else
-int bpf_tracing_multi_attach(struct bpf_prog *prog, const union bpf_attr *attr)
+int bpf_tracing_multi_attach(struct bpf_prog *prog, const union bpf_attr *attr,
+ struct bpf_verifier_log *log)
{
return -EOPNOTSUPP;
}
--
2.55.0
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH bpf-next 3/9] bpf: Add log support for iter link_create
2026-11-09 14:08 [PATCH bpf-next 0/9] bpf: Add log support for tracing link_create Leon Hwang
2026-11-09 14:08 ` [PATCH bpf-next 1/9] " Leon Hwang
2026-11-09 14:08 ` [PATCH bpf-next 2/9] bpf: Add log support for tracing_multi link_create Leon Hwang
@ 2026-11-09 14:08 ` Leon Hwang
2026-11-09 14:08 ` [PATCH bpf-next 4/9] bpf: Add log support for cgroup prog link_create Leon Hwang
` (5 subsequent siblings)
8 siblings, 0 replies; 14+ messages in thread
From: Leon Hwang @ 2026-11-09 14:08 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, Ihor Solodrai, Steven Rostedt, Masami Hiramatsu,
Mathieu Desnoyers, KP Singh, Matt Bobrowski, Shuah Khan,
Leon Hwang, linux-kernel, linux-trace-kernel, linux-kselftest,
netdev
iter BPF_LINK_CREATE returns an error without explaining invalid target
FDs, flags, or iterator information, or an unsupported program.
Pass the BPF_LINK_CREATE log to the iter attach path and report these
failures.
Signed-off-by: Leon Hwang <leon.hwang@linux.dev>
---
include/linux/bpf.h | 3 ++-
kernel/bpf/bpf_iter.c | 27 +++++++++++++++++++++------
kernel/bpf/syscall.c | 2 +-
3 files changed, 24 insertions(+), 8 deletions(-)
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 520fd72e21fd..7ff256a5d98b 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -3276,7 +3276,8 @@ void bpf_iter_unreg_target(const struct bpf_iter_reg *reg_info);
int bpf_iter_prog_supported(struct bpf_prog *prog);
const struct bpf_func_proto *
bpf_iter_get_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog);
-int bpf_iter_link_attach(const union bpf_attr *attr, bpfptr_t uattr, struct bpf_prog *prog);
+int bpf_iter_link_attach(const union bpf_attr *attr, bpfptr_t uattr, struct bpf_prog *prog,
+ struct bpf_verifier_log *log);
int bpf_iter_new_fd(struct bpf_link *link);
bool bpf_link_is_iter(struct bpf_link *link);
struct bpf_prog *bpf_iter_get_info(struct bpf_iter_meta *meta, bool in_stop);
diff --git a/kernel/bpf/bpf_iter.c b/kernel/bpf/bpf_iter.c
index f0b29a170935..dfefa5579e03 100644
--- a/kernel/bpf/bpf_iter.c
+++ b/kernel/bpf/bpf_iter.c
@@ -5,6 +5,7 @@
#include <linux/anon_inodes.h>
#include <linux/filter.h>
#include <linux/bpf.h>
+#include <linux/bpf_verifier.h>
#include <linux/rcupdate_trace.h>
struct bpf_iter_target_info {
@@ -528,7 +529,7 @@ bool bpf_link_is_iter(struct bpf_link *link)
}
int bpf_iter_link_attach(const union bpf_attr *attr, bpfptr_t uattr,
- struct bpf_prog *prog)
+ struct bpf_prog *prog, struct bpf_verifier_log *log)
{
struct bpf_iter_target_info *tinfo = NULL, *iter;
struct bpf_link_primer link_primer;
@@ -538,24 +539,36 @@ int bpf_iter_link_attach(const union bpf_attr *attr, bpfptr_t uattr,
bpfptr_t ulinfo;
int err;
- if (attr->link_create.target_fd || attr->link_create.flags)
+ if (attr->link_create.target_fd) {
+ bpf_log(log, "Invalid target_fd.\n");
+ return -EINVAL;
+ }
+ if (attr->link_create.flags) {
+ bpf_log(log, "Invalid flags.\n");
return -EINVAL;
+ }
memset(&linfo, 0, sizeof(union bpf_iter_link_info));
ulinfo = make_bpfptr(attr->link_create.iter_info, uattr.is_kernel);
linfo_len = attr->link_create.iter_info_len;
- if (bpfptr_is_null(ulinfo) ^ !linfo_len)
+ if (bpfptr_is_null(ulinfo) ^ !linfo_len) {
+ bpf_log(log, "iter_info and iter_info_len must be both set or both unset.\n");
return -EINVAL;
+ }
if (!bpfptr_is_null(ulinfo)) {
err = bpf_check_uarg_tail_zero(ulinfo, sizeof(linfo),
linfo_len);
- if (err)
+ if (err) {
+ bpf_log(log, "Invalid iter_info.\n");
return err;
+ }
linfo_len = min_t(u32, linfo_len, sizeof(linfo));
- if (copy_from_bpfptr(&linfo, ulinfo, linfo_len))
+ if (copy_from_bpfptr(&linfo, ulinfo, linfo_len)) {
+ bpf_log(log, "Invalid iter_info_len.\n");
return -EFAULT;
+ }
}
prog_btf_id = prog->aux->attach_btf_id;
@@ -570,8 +583,10 @@ int bpf_iter_link_attach(const union bpf_attr *attr, bpfptr_t uattr,
if (!tinfo)
return -ENOENT;
- if (!bpf_iter_target_support_prog(tinfo, prog))
+ if (!bpf_iter_target_support_prog(tinfo, prog)) {
+ bpf_log(log, "The iter target does not support the bpf prog.\n");
return -EINVAL;
+ }
link = kzalloc_obj(*link, GFP_USER | __GFP_NOWARN);
if (!link)
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index aed1c6a2f203..db135859a2d1 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -5928,7 +5928,7 @@ static int link_create(union bpf_attr *attr, bpfptr_t uattr, struct bpf_common_a
ret = bpf_raw_tp_link_attach(prog, NULL, attr->link_create.tracing.cookie,
attr->link_create.attach_type);
else if (prog->expected_attach_type == BPF_TRACE_ITER)
- ret = bpf_iter_link_attach(attr, uattr, prog);
+ ret = bpf_iter_link_attach(attr, uattr, prog, log);
else if (prog->expected_attach_type == BPF_LSM_CGROUP)
ret = cgroup_bpf_link_attach(attr, prog);
else if (is_tracing_multi(prog->expected_attach_type))
--
2.55.0
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH bpf-next 4/9] bpf: Add log support for cgroup prog link_create
2026-11-09 14:08 [PATCH bpf-next 0/9] bpf: Add log support for tracing link_create Leon Hwang
` (2 preceding siblings ...)
2026-11-09 14:08 ` [PATCH bpf-next 3/9] bpf: Add log support for iter link_create Leon Hwang
@ 2026-11-09 14:08 ` Leon Hwang
2026-11-09 14:47 ` bot+bpf-ci
2026-11-09 14:08 ` [PATCH bpf-next 5/9] libbpf: Add log_opts support for BPF_TRACE_ITER Leon Hwang
` (4 subsequent siblings)
8 siblings, 1 reply; 14+ messages in thread
From: Leon Hwang @ 2026-11-09 14:08 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, Ihor Solodrai, Steven Rostedt, Masami Hiramatsu,
Mathieu Desnoyers, KP Singh, Matt Bobrowski, Shuah Khan,
Leon Hwang, linux-kernel, linux-trace-kernel, linux-kselftest,
netdev
Cgroup prog BPF_LINK_CREATE returns errors without explaining invalid
flags, target FDs, or relative attachment parameters. Revision mismatches,
cgroup hierarchy restrictions, conflicting attachment flags, and the
per-cgroup program limit can also cause unexplained failures.
Pass the BPF_LINK_CREATE log through the cgroup attachment helpers and
report these failures at the checks that reject the attachment. Diagnose
relative link attachments that omit BPF_F_LINK.
Signed-off-by: Leon Hwang <leon.hwang@linux.dev>
---
include/linux/bpf-cgroup.h | 7 +++-
kernel/bpf/cgroup.c | 76 +++++++++++++++++++++++++++-----------
kernel/bpf/syscall.c | 4 +-
3 files changed, 61 insertions(+), 26 deletions(-)
diff --git a/include/linux/bpf-cgroup.h b/include/linux/bpf-cgroup.h
index 02ef7f899b3d..ddcdd136fd49 100644
--- a/include/linux/bpf-cgroup.h
+++ b/include/linux/bpf-cgroup.h
@@ -4,6 +4,7 @@
#include <linux/bpf.h>
#include <linux/bpf-cgroup-defs.h>
+#include <linux/bpf_verifier.h>
#include <linux/errno.h>
#include <linux/jump_label.h>
#include <linux/percpu.h>
@@ -425,7 +426,8 @@ int cgroup_bpf_prog_attach(const union bpf_attr *attr,
enum bpf_prog_type ptype, struct bpf_prog *prog);
int cgroup_bpf_prog_detach(const union bpf_attr *attr,
enum bpf_prog_type ptype);
-int cgroup_bpf_link_attach(const union bpf_attr *attr, struct bpf_prog *prog);
+int cgroup_bpf_link_attach(const union bpf_attr *attr, struct bpf_prog *prog,
+ struct bpf_verifier_log *log);
int cgroup_bpf_prog_query(const union bpf_attr *attr,
union bpf_attr __user *uattr, u32 uattr_size);
@@ -452,7 +454,8 @@ static inline int cgroup_bpf_prog_detach(const union bpf_attr *attr,
}
static inline int cgroup_bpf_link_attach(const union bpf_attr *attr,
- struct bpf_prog *prog)
+ struct bpf_prog *prog,
+ struct bpf_verifier_log *log)
{
return -EINVAL;
}
diff --git a/kernel/bpf/cgroup.c b/kernel/bpf/cgroup.c
index 57a785241d42..a4d49c4df712 100644
--- a/kernel/bpf/cgroup.c
+++ b/kernel/bpf/cgroup.c
@@ -854,7 +854,8 @@ static struct bpf_prog *bpf_get_anchor_prog(u32 flags, u32 id_or_fd)
}
static struct bpf_prog_list *get_prog_list(struct hlist_head *progs, struct bpf_prog *prog,
- struct bpf_cgroup_link *link, u32 flags, u32 id_or_fd)
+ struct bpf_cgroup_link *link, u32 flags, u32 id_or_fd,
+ struct bpf_verifier_log *log)
{
bool is_link = flags & BPF_F_LINK, is_id = flags & BPF_F_ID;
struct bpf_prog_list *pltmp, *pl = ERR_PTR(-EINVAL);
@@ -867,20 +868,30 @@ static struct bpf_prog_list *get_prog_list(struct hlist_head *progs, struct bpf_
is_after = flags & BPF_F_AFTER;
if (is_link || is_id || id_or_fd) {
/* flags must have either BPF_F_BEFORE or BPF_F_AFTER */
- if (is_before == is_after)
+ if (is_before == is_after) {
+ bpf_log(log, "Specify exactly one of BPF_F_BEFORE or BPF_F_AFTER.\n");
return ERR_PTR(-EINVAL);
- if ((is_link && !link) || (!is_link && !prog))
+ }
+ if (is_link && !link)
+ return ERR_PTR(-EINVAL);
+ if (!is_link && !prog) {
+ bpf_log(log, "BPF_F_LINK is required for relative link attachment.\n");
return ERR_PTR(-EINVAL);
+ }
} else if (!hlist_empty(progs)) {
/* flags cannot have both BPF_F_BEFORE and BPF_F_AFTER */
- if (is_before && is_after)
+ if (is_before && is_after) {
+ bpf_log(log, "BPF_F_BEFORE and BPF_F_AFTER are mutually exclusive.\n");
return ERR_PTR(-EINVAL);
+ }
}
if (is_link) {
anchor_link = bpf_get_anchor_link(flags, id_or_fd);
- if (IS_ERR(anchor_link))
+ if (IS_ERR(anchor_link)) {
+ bpf_log(log, "Invalid relative link %s.\n", is_id ? "ID" : "FD");
return ERR_CAST(anchor_link);
+ }
} else if (is_id || id_or_fd) {
anchor_prog = bpf_get_anchor_prog(flags, id_or_fd);
if (IS_ERR(anchor_prog))
@@ -905,13 +916,16 @@ static struct bpf_prog_list *get_prog_list(struct hlist_head *progs, struct bpf_
hlist_for_each_entry(pltmp, progs, node) {
if ((anchor_prog && anchor_prog == pltmp->prog) ||
(anchor_link && anchor_link == &pltmp->link->link)) {
- if (!!(pltmp->flags & BPF_F_PREORDER) != preorder)
+ if (!!(pltmp->flags & BPF_F_PREORDER) != preorder) {
+ bpf_log(log, "Relative attachment BPF_F_PREORDER mismatch.\n");
goto out;
+ }
pl = pltmp;
goto out;
}
}
+ bpf_log(log, "Relative attachment not found in target cgroup.\n");
pl = ERR_PTR(-ENOENT);
out:
if (anchor_link)
@@ -923,11 +937,11 @@ static struct bpf_prog_list *get_prog_list(struct hlist_head *progs, struct bpf_
static int insert_pl_to_hlist(struct bpf_prog_list *pl, struct hlist_head *progs,
struct bpf_prog *prog, struct bpf_cgroup_link *link,
- u32 flags, u32 id_or_fd)
+ u32 flags, u32 id_or_fd, struct bpf_verifier_log *log)
{
struct bpf_prog_list *pltmp;
- pltmp = get_prog_list(progs, prog, link, flags, id_or_fd);
+ pltmp = get_prog_list(progs, prog, link, flags, id_or_fd, log);
if (IS_ERR(pltmp))
return PTR_ERR(pltmp);
@@ -952,6 +966,7 @@ static int insert_pl_to_hlist(struct bpf_prog_list *pl, struct hlist_head *progs
* @flags: Option flags
* @id_or_fd: Relative prog id or fd
* @revision: bpf_prog_list revision
+ * @log: Optional log for attachment errors
*
* Exactly one of @prog or @link can be non-null.
* Must be called with cgroup_mutex held.
@@ -960,7 +975,7 @@ static int __cgroup_bpf_attach(struct cgroup *cgrp,
struct bpf_prog *prog, struct bpf_prog *replace_prog,
struct bpf_cgroup_link *link,
enum bpf_attach_type type, u32 flags, u32 id_or_fd,
- u64 revision)
+ u64 revision, struct bpf_verifier_log *log)
{
u32 saved_flags = (flags & (BPF_F_ALLOW_OVERRIDE | BPF_F_ALLOW_MULTI));
struct bpf_prog *old_prog = NULL;
@@ -991,23 +1006,33 @@ static int __cgroup_bpf_attach(struct cgroup *cgrp,
atype = bpf_cgroup_atype_find(type, new_prog->aux->attach_btf_id);
if (atype < 0)
return -EINVAL;
- if (revision && revision != cgrp->bpf.revisions[atype])
+ if (revision && revision != cgrp->bpf.revisions[atype]) {
+ bpf_log(log, "Cgroup revision mismatch: expected %llu, actual %llu.\n",
+ revision, cgrp->bpf.revisions[atype]);
return -ESTALE;
+ }
progs = &cgrp->bpf.progs[atype];
- if (!hierarchy_allows_attach(cgrp, atype))
+ if (!hierarchy_allows_attach(cgrp, atype)) {
+ bpf_log(log, "Cgroup hierarchy does not allow attachment.\n");
return -EPERM;
+ }
- if (!hlist_empty(progs) && cgrp->bpf.flags[atype] != saved_flags)
+ if (!hlist_empty(progs) && cgrp->bpf.flags[atype] != saved_flags) {
/* Disallow attaching non-overridable on top
* of existing overridable in this cgroup.
* Disallow attaching multi-prog if overridable or none
*/
+ bpf_log(log, "Attachment flags conflict with existing flags.\n");
return -EPERM;
+ }
- if (prog_list_length(progs, NULL) >= BPF_CGROUP_MAX_PROGS)
+ if (prog_list_length(progs, NULL) >= BPF_CGROUP_MAX_PROGS) {
+ bpf_log(log, "Too many programs attached to cgroup (limit %u).\n",
+ BPF_CGROUP_MAX_PROGS);
return -E2BIG;
+ }
pl = find_attach_entry(progs, prog, link, replace_prog,
flags & BPF_F_ALLOW_MULTI);
@@ -1029,7 +1054,7 @@ static int __cgroup_bpf_attach(struct cgroup *cgrp,
return -ENOMEM;
}
- err = insert_pl_to_hlist(pl, progs, prog, link, flags, id_or_fd);
+ err = insert_pl_to_hlist(pl, progs, prog, link, flags, id_or_fd, log);
if (err) {
kfree(pl);
bpf_cgroup_storages_free(new_storage);
@@ -1089,13 +1114,14 @@ static int cgroup_bpf_attach(struct cgroup *cgrp,
struct bpf_prog *prog, struct bpf_prog *replace_prog,
struct bpf_cgroup_link *link,
enum bpf_attach_type type,
- u32 flags, u32 id_or_fd, u64 revision)
+ u32 flags, u32 id_or_fd, u64 revision,
+ struct bpf_verifier_log *log)
{
int ret;
cgroup_lock();
ret = __cgroup_bpf_attach(cgrp, prog, replace_prog, link, type, flags,
- id_or_fd, revision);
+ id_or_fd, revision, log);
cgroup_unlock();
return ret;
}
@@ -1572,7 +1598,7 @@ int cgroup_bpf_prog_attach(const union bpf_attr *attr,
ret = cgroup_bpf_attach(cgrp, prog, replace_prog, NULL,
attr->attach_type, attr->attach_flags,
- attr->relative_fd, attr->expected_revision);
+ attr->relative_fd, attr->expected_revision, NULL);
if (replace_prog)
bpf_prog_put(replace_prog);
@@ -1702,19 +1728,24 @@ static const struct bpf_link_ops bpf_cgroup_link_lops = {
BPF_F_PREORDER | \
BPF_F_LINK)
-int cgroup_bpf_link_attach(const union bpf_attr *attr, struct bpf_prog *prog)
+int cgroup_bpf_link_attach(const union bpf_attr *attr, struct bpf_prog *prog,
+ struct bpf_verifier_log *log)
{
struct bpf_link_primer link_primer;
struct bpf_cgroup_link *link;
struct cgroup *cgrp;
int err;
- if (attr->link_create.flags & (~BPF_F_LINK_ATTACH_MASK))
+ if (attr->link_create.flags & (~BPF_F_LINK_ATTACH_MASK)) {
+ bpf_log(log, "Invalid flags.\n");
return -EINVAL;
+ }
cgrp = cgroup_get_from_fd(attr->link_create.target_fd);
- if (IS_ERR(cgrp))
+ if (IS_ERR(cgrp)) {
+ bpf_log(log, "Invalid target_fd.\n");
return PTR_ERR(cgrp);
+ }
link = kzalloc_obj(*link, GFP_USER);
if (!link) {
@@ -1734,7 +1765,7 @@ int cgroup_bpf_link_attach(const union bpf_attr *attr, struct bpf_prog *prog)
err = cgroup_bpf_attach(cgrp, NULL, NULL, link,
link->link.attach_type, BPF_F_ALLOW_MULTI | attr->link_create.flags,
attr->link_create.cgroup.relative_fd,
- attr->link_create.cgroup.expected_revision);
+ attr->link_create.cgroup.expected_revision, log);
if (err) {
bpf_link_cleanup(&link_primer);
goto out_put_cgroup;
@@ -3184,7 +3215,8 @@ int cgroup_bpf_struct_ops_attach(struct bpf_map *map, const union bpf_attr *attr
cgrp->bpf.flags[atype] = BPF_F_ALLOW_MULTI;
err = insert_pl_to_hlist(pl, progs, NULL, link,
- flags | BPF_F_ALLOW_MULTI, attr->link_create.cgroup.relative_fd);
+ flags | BPF_F_ALLOW_MULTI,
+ attr->link_create.cgroup.relative_fd, NULL);
if (err)
goto unlock;
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index db135859a2d1..43417824d851 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -5907,7 +5907,7 @@ static int link_create(union bpf_attr *attr, bpfptr_t uattr, struct bpf_common_a
case BPF_PROG_TYPE_CGROUP_DEVICE:
case BPF_PROG_TYPE_CGROUP_SYSCTL:
case BPF_PROG_TYPE_CGROUP_SOCKOPT:
- ret = cgroup_bpf_link_attach(attr, prog);
+ ret = cgroup_bpf_link_attach(attr, prog, log);
break;
case BPF_PROG_TYPE_EXT:
ret = bpf_tracing_prog_attach(prog,
@@ -5930,7 +5930,7 @@ static int link_create(union bpf_attr *attr, bpfptr_t uattr, struct bpf_common_a
else if (prog->expected_attach_type == BPF_TRACE_ITER)
ret = bpf_iter_link_attach(attr, uattr, prog, log);
else if (prog->expected_attach_type == BPF_LSM_CGROUP)
- ret = cgroup_bpf_link_attach(attr, prog);
+ ret = cgroup_bpf_link_attach(attr, prog, log);
else if (is_tracing_multi(prog->expected_attach_type))
ret = bpf_tracing_multi_attach(prog, attr, log);
else
--
2.55.0
^ permalink raw reply [flat|nested] 14+ messages in thread* Re: [PATCH bpf-next 4/9] bpf: Add log support for cgroup prog link_create
2026-11-09 14:08 ` [PATCH bpf-next 4/9] bpf: Add log support for cgroup prog link_create Leon Hwang
@ 2026-11-09 14:47 ` bot+bpf-ci
0 siblings, 0 replies; 14+ messages in thread
From: bot+bpf-ci @ 2026-11-09 14:47 UTC (permalink / raw)
To: leon.hwang, bpf
Cc: ast, daniel, john.fastabend, andrii, eddyz87, memxor, martin.lau,
song, yonghong.song, jolsa, emil, ihor.solodrai, rostedt,
mhiramat, mathieu.desnoyers, kpsingh, matt, shuah, leon.hwang,
linux-kernel, linux-trace-kernel, linux-kselftest, netdev, ast,
andrii, daniel, martin.lau, eddyz87, yonghong.song, mason,
ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 1291 bytes --]
> bpf: Add log support for cgroup prog link_create
TL;DR: Nit only: the new <linux/bpf_verifier.h> include in bpf-cgroup.h may be
unnecessary, since a forward declaration would likely suffice and avoid
pulling it into tcp.h users.
> diff --git a/include/linux/bpf-cgroup.h b/include/linux/bpf-cgroup.h
> --- a/include/linux/bpf-cgroup.h
> +++ b/include/linux/bpf-cgroup.h
> @@ -4,6 +4,7 @@
>
> #include <linux/bpf.h>
> #include <linux/bpf-cgroup-defs.h>
> +#include <linux/bpf_verifier.h>
> #include <linux/errno.h>
> #include <linux/jump_label.h>
> #include <linux/percpu.h>
this isn't a bug, but is the <linux/bpf_verifier.h> include needed here?
bpf.h already forward-declares struct bpf_verifier_log, which is all the
new cgroup_bpf_link_attach() prototype and its stub need, and cgroup.c
already includes bpf_verifier.h for bpf_log().
Since bpf-cgroup.h is included from include/net/tcp.h, would a plain
forward declaration (as done for trace_events.h earlier in this series)
keep the verifier header out of those translation units?
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/42633880300
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH bpf-next 5/9] libbpf: Add log_opts support for BPF_TRACE_ITER
2026-11-09 14:08 [PATCH bpf-next 0/9] bpf: Add log support for tracing link_create Leon Hwang
` (3 preceding siblings ...)
2026-11-09 14:08 ` [PATCH bpf-next 4/9] bpf: Add log support for cgroup prog link_create Leon Hwang
@ 2026-11-09 14:08 ` Leon Hwang
2026-11-09 14:47 ` bot+bpf-ci
2026-11-09 14:08 ` [PATCH bpf-next 6/9] selftests/bpf: Verify tracing link_create logs Leon Hwang
` (3 subsequent siblings)
8 siblings, 1 reply; 14+ messages in thread
From: Leon Hwang @ 2026-11-09 14:08 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, Ihor Solodrai, Steven Rostedt, Masami Hiramatsu,
Mathieu Desnoyers, KP Singh, Matt Bobrowski, Shuah Khan,
Leon Hwang, linux-kernel, linux-trace-kernel, linux-kselftest,
netdev
Add iter.info and iter.len to pass iterator information separately from
log_opts. Keep the existing iter_info and iter_info_len fields working
for callers that do not request logs.
Thereafter, log_opts can work for iter link_create.
Signed-off-by: Leon Hwang <leon.hwang@linux.dev>
---
tools/lib/bpf/bpf.c | 17 +++++++++++------
tools/lib/bpf/bpf.h | 8 ++++++++
2 files changed, 19 insertions(+), 6 deletions(-)
diff --git a/tools/lib/bpf/bpf.c b/tools/lib/bpf/bpf.c
index 38dcb31e8c0e..174a8bfaf663 100644
--- a/tools/lib/bpf/bpf.c
+++ b/tools/lib/bpf/bpf.c
@@ -808,11 +808,6 @@ int bpf_link_create(int prog_fd, int target_fd,
if (iter_info_len && attach_type != BPF_TRACE_ITER)
return libbpf_err(-EINVAL);
- if (iter_info_len)
- iter_info = OPTS_GET(opts, iter_info, (void *)0);
- else
- log_opts = OPTS_GET(opts, log_opts, (void *)0);
-
/* validate we don't have unexpected combinations of non-zero fields */
if (iter_info_len || target_btf_id) {
if (iter_info_len && target_btf_id)
@@ -821,6 +816,16 @@ int bpf_link_create(int prog_fd, int target_fd,
return libbpf_err(-EINVAL);
}
+ if (iter_info_len) {
+ if (OPTS_GET(opts, iter.len, 0))
+ return libbpf_err(-EINVAL);
+ iter_info = OPTS_GET(opts, iter_info, (void *)0);
+ } else {
+ log_opts = OPTS_GET(opts, log_opts, (void *)0);
+ iter_info = OPTS_GET(opts, iter.info, (void *)0);
+ iter_info_len = OPTS_GET(opts, iter.len, 0);
+ }
+
memset(&attr, 0, attr_sz);
attr.link_create.prog_fd = prog_fd;
attr.link_create.target_fd = target_fd;
@@ -970,7 +975,7 @@ int bpf_link_create(int prog_fd, int target_fd,
return libbpf_err(-EINVAL);
break;
default:
- if (!OPTS_ZEROED(opts, iter_info_len))
+ if (!OPTS_ZEROED(opts, log_opts))
return libbpf_err(-EINVAL);
break;
}
diff --git a/tools/lib/bpf/bpf.h b/tools/lib/bpf/bpf.h
index fe3b7fd8512b..2ac143fd1102 100644
--- a/tools/lib/bpf/bpf.h
+++ b/tools/lib/bpf/bpf.h
@@ -422,6 +422,10 @@ union bpf_iter_link_info; /* defined in up-to-date linux/bpf.h */
struct bpf_link_create_opts {
size_t sz; /* size of this struct for forward/backward compatibility */
__u32 flags;
+ /*
+ * iter_info and iter_info_len are deprecated, use iter.info and
+ * iter.len instead.
+ */
union {
union bpf_iter_link_info *iter_info;
struct bpf_log_opts *log_opts;
@@ -478,6 +482,10 @@ struct bpf_link_create_opts {
const __u64 *cookies;
__u32 cnt;
} tracing_multi;
+ struct {
+ union bpf_iter_link_info *info;
+ __u32 len;
+ } iter;
};
size_t :0;
};
--
2.55.0
^ permalink raw reply [flat|nested] 14+ messages in thread* Re: [PATCH bpf-next 5/9] libbpf: Add log_opts support for BPF_TRACE_ITER
2026-11-09 14:08 ` [PATCH bpf-next 5/9] libbpf: Add log_opts support for BPF_TRACE_ITER Leon Hwang
@ 2026-11-09 14:47 ` bot+bpf-ci
0 siblings, 0 replies; 14+ messages in thread
From: bot+bpf-ci @ 2026-11-09 14:47 UTC (permalink / raw)
To: leon.hwang, bpf
Cc: ast, daniel, john.fastabend, andrii, eddyz87, memxor, martin.lau,
song, yonghong.song, jolsa, emil, ihor.solodrai, rostedt,
mhiramat, mathieu.desnoyers, kpsingh, matt, shuah, leon.hwang,
linux-kernel, linux-trace-kernel, linux-kselftest, netdev, ast,
andrii, daniel, martin.lau, eddyz87, yonghong.song, mason,
ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 2387 bytes --]
> libbpf: Add log_opts support for BPF_TRACE_ITER
TL;DR:
- libbpf BPF_TRACE_ITER case may not reject stray bytes after iter.len
(missing OPTS_ZEROED check), so unknown fields could be silently ignored
- Nit: iter.len test in the legacy branch looks like dead code
> diff --git a/tools/lib/bpf/bpf.c b/tools/lib/bpf/bpf.c
> --- a/tools/lib/bpf/bpf.c
> +++ b/tools/lib/bpf/bpf.c
[ ... ]
> @@ -821,6 +816,16 @@ int bpf_link_create(int prog_fd, int target_fd,
> return libbpf_err(-EINVAL);
> }
>
> + if (iter_info_len) {
> + if (OPTS_GET(opts, iter.len, 0))
> + return libbpf_err(-EINVAL);
> + iter_info = OPTS_GET(opts, iter_info, (void *)0);
> + } else {
> + log_opts = OPTS_GET(opts, log_opts, (void *)0);
> + iter_info = OPTS_GET(opts, iter.info, (void *)0);
> + iter_info_len = OPTS_GET(opts, iter.len, 0);
> + }
> +
When the legacy iter_info_len is non-zero, the check above already requires
OPTS_ZEROED(opts, target_btf_id), which covers every byte from that offset
up to sz, including iter.info and iter.len. Can this OPTS_GET(opts,
iter.len, 0) test ever be true? It looks like dead code.
[ ... ]
> switch (attach_type) {
> case BPF_TRACE_ITER:
> attr.link_create.iter_info = ptr_to_u64(iter_info);
> attr.link_create.iter_info_len = iter_info_len;
> break;
Every other union member's case ends with a check such as:
if (!OPTS_ZEROED(opts, tracing_multi))
return libbpf_err(-EINVAL);
Should the BPF_TRACE_ITER case do the same with OPTS_ZEROED(opts, iter)?
On the new iter.info/iter.len path, nothing checks the bytes after iter.len.
A caller that sets opts.iter.info, opts.iter.len and opts.tracing_multi.cnt
gets the stray field silently ignored instead of -EINVAL. Likewise, a
later field added to the iter struct after len, built against newer
headers, would be silently dropped by this libbpf because sz still equals
sizeof() and OPTS_VALID() cannot see it.
Legacy callers would be unaffected, since their union is already zero, and
the iter.len test in the legacy branch could then be dropped. The later
commits in this series only touch selftests and do not address this.
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/42633880300
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH bpf-next 6/9] selftests/bpf: Verify tracing link_create logs
2026-11-09 14:08 [PATCH bpf-next 0/9] bpf: Add log support for tracing link_create Leon Hwang
` (4 preceding siblings ...)
2026-11-09 14:08 ` [PATCH bpf-next 5/9] libbpf: Add log_opts support for BPF_TRACE_ITER Leon Hwang
@ 2026-11-09 14:08 ` Leon Hwang
2026-11-09 14:47 ` bot+bpf-ci
2026-11-09 14:08 ` [PATCH bpf-next 7/9] selftests/bpf: Verify tracing_multi " Leon Hwang
` (2 subsequent siblings)
8 siblings, 1 reply; 14+ messages in thread
From: Leon Hwang @ 2026-11-09 14:08 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, Ihor Solodrai, Steven Rostedt, Masami Hiramatsu,
Mathieu Desnoyers, KP Singh, Matt Bobrowski, Shuah Khan,
Leon Hwang, linux-kernel, linux-trace-kernel, linux-kselftest,
netdev
Check the error code, log message, and reported log size for invalid
tracing and freplace link creation parameters. Cover reattachment and
attach-target checks, including incompatible kprobe_write_ctx settings.
Signed-off-by: Leon Hwang <leon.hwang@linux.dev>
---
.../selftests/bpf/prog_tests/tracing_link.c | 275 ++++++++++++++++++
1 file changed, 275 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/tracing_link.c
diff --git a/tools/testing/selftests/bpf/prog_tests/tracing_link.c b/tools/testing/selftests/bpf/prog_tests/tracing_link.c
new file mode 100644
index 000000000000..1b64cb3e9983
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/tracing_link.c
@@ -0,0 +1,275 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <test_progs.h>
+#include <bpf/btf.h>
+#include "fentry_recursive.skel.h"
+#include "priv_freplace_prog.skel.h"
+#include "xdp_dummy.skel.h"
+#include "kprobe_write_ctx.skel.h"
+
+static void test_fentry_link_create_log(int target_fd, __u32 target_btf_id, int cookie,
+ enum bpf_attach_type attach_type,
+ bool reattach, const char *msg)
+{
+ struct fentry_recursive *skel = NULL;
+ struct xdp_dummy *target;
+ char log_buf[128] = {};
+ int err, fd, prog_fd;
+ LIBBPF_OPTS(bpf_log_opts, log_opts,
+ .buf = log_buf,
+ .size = sizeof(log_buf),
+ .level = 1,
+ );
+ LIBBPF_OPTS(bpf_link_create_opts, opts,
+ .log_opts = &log_opts,
+ .target_btf_id = target_btf_id,
+ .tracing.cookie = cookie,
+ );
+
+ target = xdp_dummy__open_and_load();
+ if (!ASSERT_OK_PTR(target, "xdp_dummy__open_and_load"))
+ return;
+
+ skel = fentry_recursive__open();
+ if (!ASSERT_OK_PTR(skel, "fentry_recursive__open"))
+ goto out;
+
+ err = bpf_program__set_attach_target(skel->progs.recursive_attach,
+ bpf_program__fd(target->progs.xdp_dummy_prog),
+ "xdp_dummy_prog");
+ if (!ASSERT_OK(err, "bpf_program__set_attach_target"))
+ goto out;
+
+ err = fentry_recursive__load(skel);
+ if (!ASSERT_OK(err, "fentry_recursive__load"))
+ goto out;
+
+ prog_fd = bpf_program__fd(skel->progs.recursive_attach);
+ if (reattach) {
+ /* Consume the initial target saved when the program was loaded. */
+ fd = bpf_link_create(prog_fd, 0, BPF_TRACE_FENTRY, &opts);
+ if (!ASSERT_GE(fd, 0, "bpf_link_create first"))
+ goto out;
+
+ close(fd);
+ memset(log_buf, 0, sizeof(log_buf));
+ log_opts.true_size = 0;
+ }
+
+ fd = bpf_link_create(prog_fd, target_fd, attach_type, &opts);
+ if (!ASSERT_LT(fd, 0, "bpf_link_create")) {
+ close(fd);
+ goto out;
+ }
+
+ ASSERT_EQ(fd, -EINVAL, "err");
+ ASSERT_STREQ(log_buf, msg, "log_buf");
+ ASSERT_EQ(log_opts.true_size, strlen(msg) + 1, "log_size");
+
+out:
+ fentry_recursive__destroy(skel);
+ xdp_dummy__destroy(target);
+}
+
+static void test_log_invalid_attach_type(void)
+{
+ const char *msg = "Invalid attach_type.\n";
+
+ test_fentry_link_create_log(0, 0, 0, BPF_XDP, false, msg);
+}
+
+static void test_log_tgt_prog_fd_only(void)
+{
+ const char *msg = "tgt_prog_fd and btf_id must be both set or both unset.\n";
+
+ test_fentry_link_create_log(-1, 0, 1, BPF_TRACE_FENTRY, false, msg);
+}
+
+static void test_log_tgt_prog_fd_disallowed(void)
+{
+ const char *msg = "tgt_prog_fd is only allowed for freplace prog.\n";
+
+ test_fentry_link_create_log(-1, 1, 0, BPF_TRACE_FENTRY, false, msg);
+}
+
+static void test_log_invalid_attach_btf(void)
+{
+ const char *msg = "Invalid attach_btf.\n";
+
+ test_fentry_link_create_log(0, 0, 1, BPF_TRACE_FENTRY, true, msg);
+}
+
+static void test_freplace_link_create_log(int target_fd, __u32 target_btf_id, int err_exp,
+ bool reattach, const char *msg)
+{
+ struct priv_freplace_prog *skel = NULL;
+ char log_buf[256] = {};
+ struct xdp_dummy *xdp;
+ int err, fd, prog_fd;
+ LIBBPF_OPTS(bpf_log_opts, log_opts,
+ .buf = log_buf,
+ .size = sizeof(log_buf),
+ .level = 1,
+ );
+ LIBBPF_OPTS(bpf_link_create_opts, opts,
+ .log_opts = &log_opts,
+ .target_btf_id = target_btf_id,
+ );
+
+ xdp = xdp_dummy__open_and_load();
+ if (!ASSERT_OK_PTR(xdp, "xdp_dummy__open_and_load"))
+ return;
+
+ skel = priv_freplace_prog__open();
+ if (!ASSERT_OK_PTR(skel, "priv_freplace_prog__open"))
+ goto out;
+
+ err = bpf_program__set_attach_target(skel->progs.new_xdp_prog2,
+ bpf_program__fd(xdp->progs.xdp_dummy_prog),
+ "xdp_dummy_prog");
+ if (!ASSERT_OK(err, "bpf_program__set_attach_target"))
+ goto out;
+
+ err = priv_freplace_prog__load(skel);
+ if (!ASSERT_OK(err, "priv_freplace_prog__load"))
+ goto out;
+
+ prog_fd = bpf_program__fd(skel->progs.new_xdp_prog2);
+ if (reattach) {
+ /* Consume the initial target saved when the program was loaded. */
+ fd = bpf_link_create(prog_fd, 0, 0, &opts);
+ if (!ASSERT_GE(fd, 0, "bpf_link_create first"))
+ goto out;
+
+ close(fd);
+ memset(log_buf, 0, sizeof(log_buf));
+ log_opts.true_size = 0;
+ }
+
+ if (!target_fd && target_btf_id)
+ target_fd = bpf_program__fd(xdp->progs.xdp_dummy_prog);
+
+ fd = bpf_link_create(prog_fd, target_fd, 0, &opts);
+ if (!ASSERT_LT(fd, 0, "bpf_link_create")) {
+ close(fd);
+ goto out;
+ }
+
+ ASSERT_EQ(fd, err_exp, "err");
+ ASSERT_STREQ(log_buf, msg, "log_buf");
+ ASSERT_EQ(log_opts.true_size, strlen(msg) + 1, "log_size");
+
+out:
+ priv_freplace_prog__destroy(skel);
+ xdp_dummy__destroy(xdp);
+}
+
+static void test_log_invalid_target_fd(void)
+{
+ const char *msg = "Invalid tgt_prog_fd.\n";
+
+ test_freplace_link_create_log(-1, 1, -EBADF, false, msg);
+}
+
+static void test_log_reattach_without_target(void)
+{
+ const char *msg = "Only allow re-attach for tracing and lsm progs.\n";
+
+ test_freplace_link_create_log(0, 0, -EINVAL, true, msg);
+}
+
+static void test_log_invalid_target_btf_id(void)
+{
+ const char *msg = "attach_btf_id 4294967295 is invalid\n";
+
+ test_freplace_link_create_log(0, ~0U, -EINVAL, false, msg);
+}
+
+#ifdef __x86_64__
+static void test_log_invalid_kprobe_write_ctx(void)
+{
+ struct kprobe_write_ctx *skel_kprobe, *skel_freplace = NULL;
+ struct bpf_program *prog_kprobe, *prog_freplace;
+ const char *msg = "Invalid kprobe_write_ctx.\n";
+ int err, prog_fd, btf_id, fd;
+ char log_buf[128] = {};
+ LIBBPF_OPTS(bpf_log_opts, log_opts,
+ .buf = log_buf,
+ .size = sizeof(log_buf),
+ .level = 1,
+ );
+ LIBBPF_OPTS(bpf_link_create_opts, link_opts,
+ .log_opts = &log_opts,
+ );
+
+ skel_kprobe = kprobe_write_ctx__open();
+ if (!ASSERT_OK_PTR(skel_kprobe, "kprobe_write_ctx__open kprobe"))
+ return;
+
+ prog_kprobe = skel_kprobe->progs.kprobe_dummy;
+ bpf_program__set_autoload(prog_kprobe, true);
+
+ err = kprobe_write_ctx__load(skel_kprobe);
+ if (!ASSERT_OK(err, "kprobe_write_ctx__load kprobe"))
+ goto out;
+
+ skel_freplace = kprobe_write_ctx__open();
+ if (!ASSERT_OK_PTR(skel_freplace, "kprobe_write_ctx__open freplace"))
+ goto out;
+
+ prog_freplace = skel_freplace->progs.freplace_kprobe;
+ bpf_program__set_autoload(prog_freplace, true);
+
+ prog_fd = bpf_program__fd(skel_kprobe->progs.kprobe_write_ctx);
+ bpf_program__set_attach_target(prog_freplace, prog_fd, "kprobe_write_ctx");
+
+ err = kprobe_write_ctx__load(skel_freplace);
+ if (!ASSERT_OK(err, "kprobe_write_ctx__load freplace"))
+ goto out;
+
+ prog_fd = bpf_program__fd(prog_kprobe);
+ btf_id = btf__find_by_name_kind(bpf_object__btf(skel_kprobe->obj), "kprobe_dummy",
+ BTF_KIND_FUNC);
+ if (!ASSERT_GT(btf_id, 0, "btf__find_by_name_kind"))
+ goto out;
+
+ link_opts.target_btf_id = btf_id;
+ fd = bpf_link_create(bpf_program__fd(prog_freplace), prog_fd, 0, &link_opts);
+ if (!ASSERT_LT(fd, 0, "bpf_link_create")) {
+ close(fd);
+ goto out;
+ }
+
+ ASSERT_EQ(fd, -EINVAL, "err");
+ ASSERT_STREQ(log_buf, msg, "log_buf");
+ ASSERT_EQ(log_opts.true_size, strlen(msg) + 1, "log_size");
+
+out:
+ kprobe_write_ctx__destroy(skel_freplace);
+ kprobe_write_ctx__destroy(skel_kprobe);
+}
+#else
+static void test_log_invalid_kprobe_write_ctx(void)
+{
+ test__skip();
+}
+#endif
+
+void test_tracing_link_log(void)
+{
+ if (test__start_subtest("invalid_attach_type"))
+ test_log_invalid_attach_type();
+ if (test__start_subtest("tgt_prog_fd_only"))
+ test_log_tgt_prog_fd_only();
+ if (test__start_subtest("tgt_prog_fd_disallowed"))
+ test_log_tgt_prog_fd_disallowed();
+ if (test__start_subtest("invalid_attach_btf"))
+ test_log_invalid_attach_btf();
+ if (test__start_subtest("invalid_target_fd"))
+ test_log_invalid_target_fd();
+ if (test__start_subtest("reattach_without_target"))
+ test_log_reattach_without_target();
+ if (test__start_subtest("invalid_target_btf_id"))
+ test_log_invalid_target_btf_id();
+ if (test__start_subtest("invalid_kprobe_write_ctx"))
+ test_log_invalid_kprobe_write_ctx();
+}
--
2.55.0
^ permalink raw reply [flat|nested] 14+ messages in thread* Re: [PATCH bpf-next 6/9] selftests/bpf: Verify tracing link_create logs
2026-11-09 14:08 ` [PATCH bpf-next 6/9] selftests/bpf: Verify tracing link_create logs Leon Hwang
@ 2026-11-09 14:47 ` bot+bpf-ci
0 siblings, 0 replies; 14+ messages in thread
From: bot+bpf-ci @ 2026-11-09 14:47 UTC (permalink / raw)
To: leon.hwang, bpf
Cc: ast, daniel, john.fastabend, andrii, eddyz87, memxor, martin.lau,
song, yonghong.song, jolsa, emil, ihor.solodrai, rostedt,
mhiramat, mathieu.desnoyers, kpsingh, matt, shuah, leon.hwang,
linux-kernel, linux-trace-kernel, linux-kselftest, netdev, ast,
andrii, daniel, martin.lau, eddyz87, yonghong.song, mason,
ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 2016 bytes --]
> selftests/bpf: Verify tracing link_create logs
TL;DR: Test issue: the invalid attach_type subtest in tracing_link.c may fail
earlier in the generic check, so it may not exercise the new tracing log.
Using BPF_TRACE_FEXIT on the fentry program might fix it.
> diff --git a/tools/testing/selftests/bpf/prog_tests/tracing_link.c b/tools/testing/selftests/bpf/prog_tests/tracing_link.c
> new file mode 100644
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/tracing_link.c
[ ... ]
> +static void test_log_invalid_attach_type(void)
> +{
> + const char *msg = "Invalid attach_type.\n";
> +
> + test_fentry_link_create_log(0, 0, 0, BPF_XDP, false, msg);
> +}
Does this subtest reach the tracing-specific log added by "bpf: Add log
support for tracing link_create"?
With BPF_XDP on the fentry program, link_create() fails earlier in the
generic check:
ret = bpf_prog_attach_check_attach_type(prog, attr->link_create.attach_type);
if (ret) {
bpf_log(log, "Invalid attach_type.\n");
goto out;
}
For BPF_PROG_TYPE_TRACING, attach_type_to_prog_type(BPF_XDP) returns
BPF_PROG_TYPE_XDP, so bpf_prog_attach_check_attach_type() returns -EINVAL
and the case below is never reached:
case BPF_PROG_TYPE_TRACING:
if (attr->link_create.attach_type != prog->expected_attach_type) {
bpf_log(log, "Invalid attach_type.\n");
Both messages are identical, so this subtest would still pass if the
tracing-branch bpf_log() were removed. The generic check is already
covered by the invalid_attach_type subtest in xdp_link.c.
Would passing an attach type that maps to BPF_PROG_TYPE_TRACING but
differs from the program's expected_attach_type, for example
BPF_TRACE_FEXIT for this fentry program, exercise the new log instead?
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/42633880300
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH bpf-next 7/9] selftests/bpf: Verify tracing_multi link_create logs
2026-11-09 14:08 [PATCH bpf-next 0/9] bpf: Add log support for tracing link_create Leon Hwang
` (5 preceding siblings ...)
2026-11-09 14:08 ` [PATCH bpf-next 6/9] selftests/bpf: Verify tracing link_create logs Leon Hwang
@ 2026-11-09 14:08 ` Leon Hwang
2026-11-09 14:08 ` [PATCH bpf-next 8/9] selftests/bpf: Verify iter " Leon Hwang
2026-11-09 14:08 ` [PATCH bpf-next 9/9] selftests/bpf: Verify cgroup prog " Leon Hwang
8 siblings, 0 replies; 14+ messages in thread
From: Leon Hwang @ 2026-11-09 14:08 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, Ihor Solodrai, Steven Rostedt, Masami Hiramatsu,
Mathieu Desnoyers, KP Singh, Matt Bobrowski, Shuah Khan,
Leon Hwang, linux-kernel, linux-trace-kernel, linux-kselftest,
netdev
Check the error code, log message, and reported log size for a zero or
excessive count, missing IDs, invalid flags, and a nonzero target FD.
Signed-off-by: Leon Hwang <leon.hwang@linux.dev>
---
.../selftests/bpf/prog_tests/tracing_link.c | 102 ++++++++++++++++++
1 file changed, 102 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/tracing_link.c b/tools/testing/selftests/bpf/prog_tests/tracing_link.c
index 1b64cb3e9983..6f22a21be946 100644
--- a/tools/testing/selftests/bpf/prog_tests/tracing_link.c
+++ b/tools/testing/selftests/bpf/prog_tests/tracing_link.c
@@ -5,6 +5,7 @@
#include "priv_freplace_prog.skel.h"
#include "xdp_dummy.skel.h"
#include "kprobe_write_ctx.skel.h"
+#include "tracing_multi_fail.skel.h"
static void test_fentry_link_create_log(int target_fd, __u32 target_btf_id, int cookie,
enum bpf_attach_type attach_type,
@@ -273,3 +274,104 @@ void test_tracing_link_log(void)
if (test__start_subtest("invalid_kprobe_write_ctx"))
test_log_invalid_kprobe_write_ctx();
}
+
+static void test_tracing_multi_link_create_log(int target_fd, __u32 flags, const __u32 *ids,
+ __u32 cnt, int err_exp, const char *msg)
+{
+ struct tracing_multi_fail *skel;
+ char log_buf[128] = {};
+ int err, fd, prog_fd;
+ LIBBPF_OPTS(bpf_log_opts, log_opts,
+ .buf = log_buf,
+ .size = sizeof(log_buf),
+ .level = 1,
+ );
+ LIBBPF_OPTS(bpf_link_create_opts, opts,
+ .log_opts = &log_opts,
+ .flags = flags,
+ .tracing_multi.ids = ids,
+ .tracing_multi.cnt = cnt,
+ );
+
+ skel = tracing_multi_fail__open();
+ if (!ASSERT_OK_PTR(skel, "tracing_multi_fail__open"))
+ return;
+
+ bpf_program__set_autoload(skel->progs.test_fentry_s, false);
+ err = tracing_multi_fail__load(skel);
+ if (!ASSERT_OK(err, "tracing_multi_fail__load"))
+ goto out;
+
+ prog_fd = bpf_program__fd(skel->progs.test_fentry);
+ fd = bpf_link_create(prog_fd, target_fd, BPF_TRACE_FENTRY_MULTI, &opts);
+ if (!ASSERT_LT(fd, 0, "bpf_link_create")) {
+ close(fd);
+ goto out;
+ }
+
+ ASSERT_EQ(fd, err_exp, "err");
+ ASSERT_STREQ(log_buf, msg, "log_buf");
+ ASSERT_EQ(log_opts.true_size, strlen(msg) + 1, "log_size");
+
+out:
+ tracing_multi_fail__destroy(skel);
+}
+
+static void test_multi_log_zero_count(void)
+{
+ const char *msg = "tracing_multi.cnt cannot be zero.\n";
+ __u32 id = 0;
+
+ test_tracing_multi_link_create_log(0, 0, &id, 0, -EINVAL, msg);
+}
+
+static void test_multi_log_missing_ids(void)
+{
+ const char *msg = "tracing_multi.ids must be set.\n";
+
+ test_tracing_multi_link_create_log(0, 0, NULL, 1, -EINVAL, msg);
+}
+
+static void test_multi_log_excessive_count(void)
+{
+ const char *msg = "tracing_multi.cnt is too large.\n";
+ __u32 id = 0;
+
+ test_tracing_multi_link_create_log(0, 0, &id, INT_MAX, -E2BIG, msg);
+}
+
+static void test_multi_log_invalid_flags(void)
+{
+ const char *msg = "Invalid flags.\n";
+ __u32 id = 0;
+
+ test_tracing_multi_link_create_log(0, 1, &id, 1, -EINVAL, msg);
+}
+
+static void test_multi_log_invalid_target_fd(void)
+{
+ const char *msg = "Invalid target_fd.\n";
+ __u32 id = 0;
+
+ test_tracing_multi_link_create_log(-1, 0, &id, 1, -EINVAL, msg);
+}
+
+void test_tracing_multi_link_log(void)
+{
+#ifndef __x86_64__
+ test__skip();
+ return;
+#endif
+
+ if (test__start_subtest("zero_count"))
+ test_multi_log_zero_count();
+ if (test__start_subtest("missing_ids"))
+ test_multi_log_missing_ids();
+ if (test__start_subtest("excessive_count"))
+ test_multi_log_excessive_count();
+ if (test__start_subtest("invalid_flags"))
+ test_multi_log_invalid_flags();
+ if (test__start_subtest("invalid_target_fd"))
+ test_multi_log_invalid_target_fd();
+}
+
--
2.55.0
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH bpf-next 8/9] selftests/bpf: Verify iter link_create logs
2026-11-09 14:08 [PATCH bpf-next 0/9] bpf: Add log support for tracing link_create Leon Hwang
` (6 preceding siblings ...)
2026-11-09 14:08 ` [PATCH bpf-next 7/9] selftests/bpf: Verify tracing_multi " Leon Hwang
@ 2026-11-09 14:08 ` Leon Hwang
2026-11-09 14:08 ` [PATCH bpf-next 9/9] selftests/bpf: Verify cgroup prog " Leon Hwang
8 siblings, 0 replies; 14+ messages in thread
From: Leon Hwang @ 2026-11-09 14:08 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, Ihor Solodrai, Steven Rostedt, Masami Hiramatsu,
Mathieu Desnoyers, KP Singh, Matt Bobrowski, Shuah Khan,
Leon Hwang, linux-kernel, linux-trace-kernel, linux-kselftest,
netdev
Check the error code, log message, and reported log size for invalid target
FDs, flags, and iterator information. Also cover attaching a sleepable
program to an iterator that does not support it.
Signed-off-by: Leon Hwang <leon.hwang@linux.dev>
---
.../selftests/bpf/prog_tests/iter_link.c | 132 ++++++++++++++++++
1 file changed, 132 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/iter_link.c
diff --git a/tools/testing/selftests/bpf/prog_tests/iter_link.c b/tools/testing/selftests/bpf/prog_tests/iter_link.c
new file mode 100644
index 000000000000..c684a109091a
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/iter_link.c
@@ -0,0 +1,132 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <test_progs.h>
+#include "bpf_iter_bpf_map.skel.h"
+
+static void test_iter_link_create_log(int target_fd, struct bpf_link_create_opts *opts,
+ __u64 flags, int err_exp, const char *msg)
+{
+ struct bpf_iter_bpf_map *skel;
+ char log_buf[128] = {};
+ int err, fd, prog_fd;
+ LIBBPF_OPTS(bpf_log_opts, log_opts,
+ .buf = log_buf,
+ .size = sizeof(log_buf),
+ .level = 1,
+ );
+
+ skel = bpf_iter_bpf_map__open();
+ if (!ASSERT_OK_PTR(skel, "bpf_iter_bpf_map__open"))
+ return;
+
+ err = bpf_program__set_flags(skel->progs.dump_bpf_map, flags);
+ if (!ASSERT_OK(err, "bpf_program__set_flags"))
+ goto out;
+
+ err = bpf_iter_bpf_map__load(skel);
+ if (!ASSERT_OK(err, "bpf_iter_bpf_map__load"))
+ goto out;
+
+ opts->log_opts = &log_opts;
+ prog_fd = bpf_program__fd(skel->progs.dump_bpf_map);
+ fd = bpf_link_create(prog_fd, target_fd, BPF_TRACE_ITER, opts);
+ if (!ASSERT_LT(fd, 0, "bpf_link_create")) {
+ close(fd);
+ goto out;
+ }
+
+ ASSERT_EQ(fd, err_exp, "err");
+ ASSERT_STREQ(log_buf, msg, "log_buf");
+ ASSERT_EQ(log_opts.true_size, strlen(msg) + 1, "log_size");
+
+out:
+ bpf_iter_bpf_map__destroy(skel);
+}
+
+static void test_log_invalid_target_fd(void)
+{
+ const char *msg = "Invalid target_fd.\n";
+ LIBBPF_OPTS(bpf_link_create_opts, opts);
+
+ test_iter_link_create_log(-1, &opts, 0, -EINVAL, msg);
+}
+
+static void test_log_invalid_flags(void)
+{
+ const char *msg = "Invalid flags.\n";
+ LIBBPF_OPTS(bpf_link_create_opts, opts,
+ .flags = 1,
+ );
+
+ test_iter_link_create_log(0, &opts, 0, -EINVAL, msg);
+}
+
+static void test_log_iter_info_only(void)
+{
+ const char *msg = "iter_info and iter_info_len must be both set or both unset.\n";
+ union bpf_iter_link_info info = {};
+ LIBBPF_OPTS(bpf_link_create_opts, opts,
+ .iter.info = &info,
+ );
+
+ test_iter_link_create_log(0, &opts, 0, -EINVAL, msg);
+}
+
+static void test_log_iter_info_len_only(void)
+{
+ const char *msg = "iter_info and iter_info_len must be both set or both unset.\n";
+ LIBBPF_OPTS(bpf_link_create_opts, opts,
+ .iter.len = sizeof(union bpf_iter_link_info),
+ );
+
+ test_iter_link_create_log(0, &opts, 0, -EINVAL, msg);
+}
+
+static void test_log_invalid_iter_info(void)
+{
+ unsigned char info[sizeof(union bpf_iter_link_info) + 1] = {};
+ const char *msg = "Invalid iter_info.\n";
+ LIBBPF_OPTS(bpf_link_create_opts, opts,
+ .iter.info = (union bpf_iter_link_info *)info,
+ .iter.len = sizeof(info),
+ );
+
+ info[sizeof(info) - 1] = 1;
+ test_iter_link_create_log(0, &opts, 0, -E2BIG, msg);
+}
+
+static void test_log_invalid_iter_info_len(void)
+{
+ const char *msg = "Invalid iter_info_len.\n";
+ LIBBPF_OPTS(bpf_link_create_opts, opts,
+ .iter.info = (union bpf_iter_link_info *)1,
+ .iter.len = sizeof(union bpf_iter_link_info),
+ );
+
+ test_iter_link_create_log(0, &opts, 0, -EFAULT, msg);
+}
+
+static void test_log_invalid_prog(void)
+{
+ const char *msg = "The iter target does not support the bpf prog.\n";
+ LIBBPF_OPTS(bpf_link_create_opts, opts);
+
+ test_iter_link_create_log(0, &opts, BPF_F_SLEEPABLE, -EINVAL, msg);
+}
+
+void test_iter_link_log(void)
+{
+ if (test__start_subtest("invalid_target_fd"))
+ test_log_invalid_target_fd();
+ if (test__start_subtest("invalid_flags"))
+ test_log_invalid_flags();
+ if (test__start_subtest("iter_info_only"))
+ test_log_iter_info_only();
+ if (test__start_subtest("iter_info_len_only"))
+ test_log_iter_info_len_only();
+ if (test__start_subtest("invalid_iter_info"))
+ test_log_invalid_iter_info();
+ if (test__start_subtest("invalid_iter_info_len"))
+ test_log_invalid_iter_info_len();
+ if (test__start_subtest("invalid_prog"))
+ test_log_invalid_prog();
+}
--
2.55.0
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH bpf-next 9/9] selftests/bpf: Verify cgroup prog link_create logs
2026-11-09 14:08 [PATCH bpf-next 0/9] bpf: Add log support for tracing link_create Leon Hwang
` (7 preceding siblings ...)
2026-11-09 14:08 ` [PATCH bpf-next 8/9] selftests/bpf: Verify iter " Leon Hwang
@ 2026-11-09 14:08 ` Leon Hwang
8 siblings, 0 replies; 14+ messages in thread
From: Leon Hwang @ 2026-11-09 14:08 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, Ihor Solodrai, Steven Rostedt, Masami Hiramatsu,
Mathieu Desnoyers, KP Singh, Matt Bobrowski, Shuah Khan,
Leon Hwang, linux-kernel, linux-trace-kernel, linux-kselftest,
netdev
Check the error code, log message, and reported log size for unsupported
flags, invalid target FDs, and relative attachment errors. Cover missing
or conflicting ordering flags, a missing BPF_F_LINK flag, invalid link FDs
and IDs, preorder mismatches, and anchors outside the target cgroup.
Also cover revision mismatches, conflicting attachment flags, cgroup
hierarchy restrictions, and the per-cgroup program limit.
Signed-off-by: Leon Hwang <leon.hwang@linux.dev>
---
.../selftests/bpf/prog_tests/cgroup_link.c | 324 ++++++++++++++++++
1 file changed, 324 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/cgroup_link.c b/tools/testing/selftests/bpf/prog_tests/cgroup_link.c
index 15093a69510e..5a27d3a6a26c 100644
--- a/tools/testing/selftests/bpf/prog_tests/cgroup_link.c
+++ b/tools/testing/selftests/bpf/prog_tests/cgroup_link.c
@@ -253,3 +253,327 @@ void serial_test_cgroup_link(void)
}
cleanup_cgroup_environment();
}
+
+static void check_cgroup_link_create_log(int prog_fd, int target_fd,
+ struct bpf_link_create_opts *opts,
+ int err_exp, const char *msg)
+{
+ char log_buf[128] = {};
+ LIBBPF_OPTS(bpf_log_opts, log_opts,
+ .buf = log_buf,
+ .size = sizeof(log_buf),
+ .level = 1,
+ );
+ int fd;
+
+ opts->log_opts = &log_opts;
+ fd = bpf_link_create(prog_fd, target_fd, BPF_CGROUP_INET_EGRESS, opts);
+ if (!ASSERT_LT(fd, 0, "bpf_link_create")) {
+ close(fd);
+ return;
+ }
+
+ ASSERT_EQ(fd, err_exp, "err");
+ ASSERT_STREQ(log_buf, msg, "log_buf");
+ ASSERT_EQ(log_opts.true_size, strlen(msg) + 1, "log_size");
+}
+
+static void test_cgroup_link_create_log(int target_fd, __u32 flags, int err_exp, const char *msg)
+{
+ LIBBPF_OPTS(bpf_link_create_opts, opts,
+ .flags = flags,
+ );
+ struct test_cgroup_link *skel;
+
+ skel = test_cgroup_link__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "test_cgroup_link__open_and_load"))
+ return;
+
+ check_cgroup_link_create_log(bpf_program__fd(skel->progs.egress), target_fd, &opts,
+ err_exp, msg);
+
+ test_cgroup_link__destroy(skel);
+}
+
+static void test_log_invalid_flags(void)
+{
+ const char *msg = "Invalid flags.\n";
+
+ test_cgroup_link_create_log(-1, BPF_F_ALLOW_OVERRIDE, -EINVAL, msg);
+}
+
+static void test_log_invalid_target_fd(void)
+{
+ const char *msg = "Invalid target_fd.\n";
+
+ test_cgroup_link_create_log(-1, 0, -EBADF, msg);
+}
+
+static void test_log_cgroup_attach(struct bpf_link_create_opts *opts,
+ int err_exp, const char *msg)
+{
+ struct test_cgroup_link *skel;
+ int cg;
+
+ cg = test__join_cgroup("/cgroup_link_log_attach");
+ if (!ASSERT_GE(cg, 0, "join_cgroup"))
+ return;
+
+ skel = test_cgroup_link__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "test_cgroup_link__open_and_load"))
+ goto out;
+
+ check_cgroup_link_create_log(bpf_program__fd(skel->progs.egress), cg, opts, err_exp, msg);
+ test_cgroup_link__destroy(skel);
+
+out:
+ close(cg);
+}
+
+static void test_log_relative_without_order(void)
+{
+ const char *msg = "Specify exactly one of BPF_F_BEFORE or BPF_F_AFTER.\n";
+ LIBBPF_OPTS(bpf_link_create_opts, opts,
+ .flags = BPF_F_LINK,
+ );
+
+ test_log_cgroup_attach(&opts, -EINVAL, msg);
+}
+
+static void test_log_relative_both_orders(void)
+{
+ const char *msg = "Specify exactly one of BPF_F_BEFORE or BPF_F_AFTER.\n";
+ LIBBPF_OPTS(bpf_link_create_opts, opts,
+ .flags = BPF_F_LINK | BPF_F_BEFORE | BPF_F_AFTER,
+ );
+
+ test_log_cgroup_attach(&opts, -EINVAL, msg);
+}
+
+static void test_log_relative_type_mismatch(void)
+{
+ const char *msg = "BPF_F_LINK is required for relative link attachment.\n";
+ LIBBPF_OPTS(bpf_link_create_opts, opts,
+ .flags = BPF_F_BEFORE,
+ .cgroup.relative_fd = -1,
+ );
+
+ test_log_cgroup_attach(&opts, -EINVAL, msg);
+}
+
+static void test_log_invalid_relative_link_fd(void)
+{
+ const char *msg = "Invalid relative link FD.\n";
+ LIBBPF_OPTS(bpf_link_create_opts, opts,
+ .flags = BPF_F_LINK | BPF_F_BEFORE,
+ .cgroup.relative_fd = -1,
+ );
+
+ test_log_cgroup_attach(&opts, -EBADF, msg);
+}
+
+static void test_log_invalid_relative_link_id(void)
+{
+ const char *msg = "Invalid relative link ID.\n";
+ /* Link ID zero cannot identify a link. */
+ LIBBPF_OPTS(bpf_link_create_opts, opts,
+ .flags = BPF_F_LINK | BPF_F_ID | BPF_F_BEFORE,
+ );
+
+ test_log_cgroup_attach(&opts, -ENOENT, msg);
+}
+
+static void test_log_relative_anchor(__u32 flags, bool other_cgroup, int err_exp, const char *msg)
+{
+ int cg, other_cg = -1, anchor_fd = -1, prog_fd;
+ LIBBPF_OPTS(bpf_link_create_opts, opts,
+ .flags = flags,
+ );
+ struct test_cgroup_link *skel;
+
+ cg = test__join_cgroup("/cgroup_link_log_relative");
+ if (!ASSERT_GE(cg, 0, "join_cgroup"))
+ return;
+
+ if (other_cgroup) {
+ other_cg = test__join_cgroup("/cgroup_link_log_other");
+ if (!ASSERT_GE(other_cg, 0, "join_cgroup other"))
+ goto cleanup;
+ }
+
+ skel = test_cgroup_link__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "test_cgroup_link__open_and_load"))
+ goto cleanup;
+
+ prog_fd = bpf_program__fd(skel->progs.egress);
+ anchor_fd = bpf_link_create(prog_fd, cg, BPF_CGROUP_INET_EGRESS, NULL);
+ if (!ASSERT_GE(anchor_fd, 0, "bpf_link_create"))
+ goto destroy;
+
+ if (flags & BPF_F_LINK)
+ opts.cgroup.relative_fd = anchor_fd;
+ check_cgroup_link_create_log(prog_fd, other_cgroup ? other_cg : cg, &opts, err_exp, msg);
+
+ close(anchor_fd);
+
+destroy:
+ test_cgroup_link__destroy(skel);
+cleanup:
+ if (other_cg >= 0)
+ close(other_cg);
+ close(cg);
+}
+
+static void test_log_both_orders(void)
+{
+ const char *msg = "BPF_F_BEFORE and BPF_F_AFTER are mutually exclusive.\n";
+
+ test_log_relative_anchor(BPF_F_BEFORE | BPF_F_AFTER, false, -EINVAL, msg);
+}
+
+static void test_log_relative_preorder_mismatch(void)
+{
+ const char *msg = "Relative attachment BPF_F_PREORDER mismatch.\n";
+
+ test_log_relative_anchor(BPF_F_LINK | BPF_F_BEFORE | BPF_F_PREORDER, false, -EINVAL, msg);
+}
+
+static void test_log_relative_not_in_cgroup(void)
+{
+ const char *msg = "Relative attachment not found in target cgroup.\n";
+
+ test_log_relative_anchor(BPF_F_LINK | BPF_F_BEFORE, true, -ENOENT, msg);
+}
+
+static void test_log_revision_mismatch(void)
+{
+ const char *msg = "Cgroup revision mismatch: expected 2, actual 1.\n";
+ LIBBPF_OPTS(bpf_link_create_opts, opts,
+ .cgroup.expected_revision = 2,
+ );
+
+ test_log_cgroup_attach(&opts, -ESTALE, msg);
+}
+
+static void test_log_exclusive_attach(bool child, const char *msg)
+{
+ LIBBPF_OPTS(bpf_link_create_opts, opts);
+ int cg, child_cg = -1, prog_fd, err;
+ struct test_cgroup_link *skel;
+
+ cg = test__join_cgroup("/cgroup_link_log_exclusive");
+ if (!ASSERT_GE(cg, 0, "join_cgroup"))
+ return;
+
+ if (child) {
+ child_cg = test__join_cgroup("/cgroup_link_log_exclusive/child");
+ if (!ASSERT_GE(child_cg, 0, "join_child_cgroup"))
+ goto cleanup;
+ }
+
+ skel = test_cgroup_link__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "test_cgroup_link__open_and_load"))
+ goto cleanup;
+
+ prog_fd = bpf_program__fd(skel->progs.egress);
+
+ err = bpf_prog_attach(prog_fd, cg, BPF_CGROUP_INET_EGRESS, 0);
+ if (!ASSERT_OK(err, "bpf_prog_attach"))
+ goto destroy;
+
+ check_cgroup_link_create_log(prog_fd, child ? child_cg : cg, &opts, -EPERM, msg);
+
+ err = bpf_prog_detach2(prog_fd, cg, BPF_CGROUP_INET_EGRESS);
+ ASSERT_OK(err, "bpf_prog_detach2");
+
+destroy:
+ test_cgroup_link__destroy(skel);
+cleanup:
+ if (child_cg >= 0)
+ close(child_cg);
+ close(cg);
+}
+
+static void test_log_attachment_flags_conflict(void)
+{
+ const char *msg = "Attachment flags conflict with existing flags.\n";
+
+ test_log_exclusive_attach(false, msg);
+}
+
+static void test_log_hierarchy_disallows_attach(void)
+{
+ const char *msg = "Cgroup hierarchy does not allow attachment.\n";
+
+ test_log_exclusive_attach(true, msg);
+}
+
+#define BPF_CGROUP_MAX_PROGS 64
+
+static void test_log_too_many_programs(void)
+{
+ const char *msg = "Too many programs attached to cgroup (limit 64).\n";
+ LIBBPF_OPTS(bpf_link_create_opts, opts);
+ int cg, prog_fd, err, i, nr_links = 0;
+ int link_fds[BPF_CGROUP_MAX_PROGS];
+ struct test_cgroup_link *skel;
+
+ cg = test__join_cgroup("/cgroup_link_log_limit");
+ if (!ASSERT_GE(cg, 0, "join_cgroup"))
+ return;
+
+ skel = test_cgroup_link__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "test_cgroup_link__open_and_load"))
+ goto cleanup;
+
+ prog_fd = bpf_program__fd(skel->progs.egress);
+
+ for (i = 0; i < ARRAY_SIZE(link_fds); i++) {
+ err = bpf_link_create(prog_fd, cg, BPF_CGROUP_INET_EGRESS, NULL);
+ if (!ASSERT_GE(err, 0, "bpf_link_create"))
+ goto destroy;
+
+ link_fds[nr_links++] = err;
+ }
+
+ check_cgroup_link_create_log(prog_fd, cg, &opts, -E2BIG, msg);
+
+destroy:
+ for (i = 0; i < nr_links; i++)
+ close(link_fds[i]);
+ test_cgroup_link__destroy(skel);
+cleanup:
+ close(cg);
+}
+
+void test_cgroup_link_log(void)
+{
+ if (test__start_subtest("invalid_flags"))
+ test_log_invalid_flags();
+ if (test__start_subtest("invalid_target_fd"))
+ test_log_invalid_target_fd();
+ if (test__start_subtest("relative_without_order"))
+ test_log_relative_without_order();
+ if (test__start_subtest("relative_both_orders"))
+ test_log_relative_both_orders();
+ if (test__start_subtest("relative_type_mismatch"))
+ test_log_relative_type_mismatch();
+ if (test__start_subtest("invalid_relative_link_fd"))
+ test_log_invalid_relative_link_fd();
+ if (test__start_subtest("invalid_relative_link_id"))
+ test_log_invalid_relative_link_id();
+ if (test__start_subtest("both_orders"))
+ test_log_both_orders();
+ if (test__start_subtest("relative_preorder_mismatch"))
+ test_log_relative_preorder_mismatch();
+ if (test__start_subtest("relative_not_in_cgroup"))
+ test_log_relative_not_in_cgroup();
+ if (test__start_subtest("revision_mismatch"))
+ test_log_revision_mismatch();
+ if (test__start_subtest("attachment_flags_conflict"))
+ test_log_attachment_flags_conflict();
+ if (test__start_subtest("hierarchy_disallows_attach"))
+ test_log_hierarchy_disallows_attach();
+ if (test__start_subtest("too_many_programs"))
+ test_log_too_many_programs();
+}
--
2.55.0
^ permalink raw reply [flat|nested] 14+ messages in thread