* [PATCH bpf 1/2] bpf: Reject >8 byte return values on return-reading trampoline paths
@ 2026-07-10 14:44 Yonghong Song
2026-07-10 14:44 ` [PATCH bpf 2/2] selftests/bpf: Add test for >8 byte return value on fexit attach Yonghong Song
` (3 more replies)
0 siblings, 4 replies; 10+ messages in thread
From: Yonghong Song @ 2026-07-10 14:44 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, kernel-team, Leon Hwang
btf_distill_func_proto() builds the function model used for the
fentry/fexit/fmod_ret/fsession trampolines and struct_ops. It has
accepted a 16-byte __int128 return value since the trampoline was
introduced: __get_type_size() returns the integer's type size, and the
return-type check only rejected ret < 0.
But the BPF trampoline preserves only 8 bytes of the return value (RAX on
x86, i.e. R0). For an attach type that reads the target's return value the
second half (RDX / R3) is neither saved nor restored, so a program
attached to a function returning a 16-byte value corrupts the value seen
by the real caller and itself observes only half of it. struct_ops
trampolines have the same limitation.
This affects the attach types that read the target's return value: fexit,
fmod_ret and fsession (plus the _multi variants of fexit and fsession),
and struct_ops. fentry/fentry_multi run before the target returns and are
unaffected.
Reject a >8 byte return value for these attach types in
bpf_check_attach_target() and bpf_check_attach_btf_id_multi(), and for
struct_ops in bpf_struct_ops_desc_init().
Fixes: fec56f5890d9 ("bpf: Introduce BPF trampoline")
Cc: Leon Hwang <leon.hwang@linux.dev>
Reviewed-by: Eduard Zingerman <eddyz87@gmail.com>
Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
---
kernel/bpf/bpf_struct_ops.c | 12 ++++++++++++
kernel/bpf/verifier.c | 25 +++++++++++++++++++++++++
2 files changed, 37 insertions(+)
diff --git a/kernel/bpf/bpf_struct_ops.c b/kernel/bpf/bpf_struct_ops.c
index 51b16e5f5534..bbb44ef8f87a 100644
--- a/kernel/bpf/bpf_struct_ops.c
+++ b/kernel/bpf/bpf_struct_ops.c
@@ -445,6 +445,18 @@ int bpf_struct_ops_desc_init(struct bpf_struct_ops_desc *st_ops_desc,
goto errout;
}
+ /*
+ * A >8 byte return value is passed back in the R0:R2 register
+ * pair, which the struct_ops trampoline does not preserve (only
+ * 8 bytes of the return value are saved and restored).
+ */
+ if (st_ops->func_models[i].ret_size > 8) {
+ pr_warn("func ptr %s in struct %s has a >8 byte return value, which is not supported\n",
+ mname, st_ops->name);
+ err = -EOPNOTSUPP;
+ goto errout;
+ }
+
stub_func_addr = *(void **)(st_ops->cfi_stubs + moff);
err = prepare_arg_info(btf, st_ops->name, mname,
func_proto, stub_func_addr,
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 6515d4d3c003..26d281b77a6e 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -18873,6 +18873,20 @@ static int btf_id_allow_sleepable(u32 btf_id, unsigned long addr, const struct b
return -EINVAL;
}
+static bool attach_uses_trampoline_retval(enum bpf_attach_type type)
+{
+ switch (type) {
+ case BPF_MODIFY_RETURN:
+ case BPF_TRACE_FEXIT:
+ case BPF_TRACE_FEXIT_MULTI:
+ case BPF_TRACE_FSESSION:
+ case BPF_TRACE_FSESSION_MULTI:
+ return true;
+ default:
+ return false;
+ }
+}
+
int bpf_check_attach_target(struct bpf_verifier_log *log,
const struct bpf_prog *prog,
const struct bpf_prog *tgt_prog,
@@ -19137,6 +19151,14 @@ int bpf_check_attach_target(struct bpf_verifier_log *log,
if (ret < 0)
return ret;
+ if (tgt_info->fmodel.ret_size > 8 &&
+ attach_uses_trampoline_retval(prog->expected_attach_type)) {
+ bpf_log(log,
+ "Attach to function %s with a >8 byte return value is not supported for this attach type\n",
+ tname);
+ return -EOPNOTSUPP;
+ }
+
/*
* *.multi programs don't need an address during program
* verification, we just take the module ref if needed.
@@ -19413,6 +19435,9 @@ int bpf_check_attach_btf_id_multi(struct btf *btf, struct bpf_prog *prog, u32 bt
err = btf_distill_func_proto(NULL, btf, t, tname, &tgt_info->fmodel);
if (err < 0)
return err;
+ if (tgt_info->fmodel.ret_size > 8 &&
+ attach_uses_trampoline_retval(prog->expected_attach_type))
+ return -EOPNOTSUPP;
if (btf_is_module(btf)) {
/* The bpf program already holds reference to module. */
if (WARN_ON_ONCE(!prog->aux->mod))
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH bpf 2/2] selftests/bpf: Add test for >8 byte return value on fexit attach
2026-07-10 14:44 [PATCH bpf 1/2] bpf: Reject >8 byte return values on return-reading trampoline paths Yonghong Song
@ 2026-07-10 14:44 ` Yonghong Song
2026-07-10 15:29 ` Leon Hwang
2026-07-10 14:58 ` [PATCH bpf 1/2] bpf: Reject >8 byte return values on return-reading trampoline paths sashiko-bot
` (2 subsequent siblings)
3 siblings, 1 reply; 10+ messages in thread
From: Yonghong Song @ 2026-07-10 14:44 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, kernel-team
The BPF trampoline preserves only 8 bytes of the target's return value
(R0), so attaching an fexit/fmod_ret/fsession program to a function that
returns a >8 byte value is now rejected by the verifier.
Add a bpf_testmod function returning __int128 and an fexit program that
targets it. The program is expected to fail to load with the
"with a >8 byte return value is not supported for this attach type"
message.
__int128 is only available on 64-bit targets (where the compiler defines
__SIZEOF_INT128__). Guard the testmod function and the test with
__SIZEOF_INT128__ so a 32-bit build still compiles; on such builds
the subtest is simply not run.
Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
---
.../selftests/bpf/prog_tests/tracing_failure.c | 12 ++++++++++++
tools/testing/selftests/bpf/progs/tracing_failure.c | 6 ++++++
.../testing/selftests/bpf/test_kmods/bpf_testmod.c | 13 +++++++++++++
3 files changed, 31 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/tracing_failure.c b/tools/testing/selftests/bpf/prog_tests/tracing_failure.c
index f9f9e1cb87bf..6c199f5ff4db 100644
--- a/tools/testing/selftests/bpf/prog_tests/tracing_failure.c
+++ b/tools/testing/selftests/bpf/prog_tests/tracing_failure.c
@@ -76,6 +76,14 @@ static void test_fexit_noreturns(void)
"Attaching fexit/fsession/fmod_ret to __noreturn function 'do_exit' is rejected.");
}
+#ifdef __SIZEOF_INT128__
+static void test_fexit_int128_ret(void)
+{
+ test_tracing_fail_prog("fexit_int128_ret",
+ "with a >8 byte return value is not supported for this attach type");
+}
+#endif
+
void test_tracing_failure(void)
{
if (test__start_subtest("bpf_spin_lock"))
@@ -86,4 +94,8 @@ void test_tracing_failure(void)
test_tracing_deny();
if (test__start_subtest("fexit_noreturns"))
test_fexit_noreturns();
+#ifdef __SIZEOF_INT128__
+ if (test__start_subtest("fexit_int128_ret"))
+ test_fexit_int128_ret();
+#endif
}
diff --git a/tools/testing/selftests/bpf/progs/tracing_failure.c b/tools/testing/selftests/bpf/progs/tracing_failure.c
index 65e485c4468c..f7a095767679 100644
--- a/tools/testing/selftests/bpf/progs/tracing_failure.c
+++ b/tools/testing/selftests/bpf/progs/tracing_failure.c
@@ -30,3 +30,9 @@ int BPF_PROG(fexit_noreturns)
{
return 0;
}
+
+SEC("?fexit/bpf_testmod_test_int128_ret")
+int BPF_PROG(fexit_int128_ret)
+{
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
index 30f1cd23093c..902dbf658250 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
@@ -161,6 +161,15 @@ bpf_testmod_test_arg_ptr_to_struct(struct bpf_testmod_struct_arg_1 *a) {
return bpf_testmod_test_struct_arg_result;
}
+#ifdef __SIZEOF_INT128__
+noinline __int128
+bpf_testmod_test_int128_ret(int a)
+{
+ bpf_testmod_test_struct_arg_result = a;
+ return (__int128)a;
+}
+#endif
+
__weak noinline void bpf_testmod_looooooooooooooooooooooooooooooong_name(void)
{
}
@@ -514,6 +523,10 @@ bpf_testmod_test_read(struct file *file, struct kobject *kobj,
(void)bpf_testmod_test_arg_ptr_to_struct(&struct_arg1_2);
+#ifdef __SIZEOF_INT128__
+ (void)bpf_testmod_test_int128_ret(i);
+#endif
+
(void)trace_bpf_testmod_test_raw_tp_null_tp(NULL);
bpf_testmod_test_struct_ops3();
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH bpf 1/2] bpf: Reject >8 byte return values on return-reading trampoline paths
2026-07-10 14:44 [PATCH bpf 1/2] bpf: Reject >8 byte return values on return-reading trampoline paths Yonghong Song
2026-07-10 14:44 ` [PATCH bpf 2/2] selftests/bpf: Add test for >8 byte return value on fexit attach Yonghong Song
@ 2026-07-10 14:58 ` sashiko-bot
2026-07-10 15:45 ` Yonghong Song
2026-07-10 15:45 ` bot+bpf-ci
2026-07-10 16:37 ` Leon Hwang
3 siblings, 1 reply; 10+ messages in thread
From: sashiko-bot @ 2026-07-10 14:58 UTC (permalink / raw)
To: Yonghong Song; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] The x86 BPF trampoline generation under-allocates stack space for 16-byte scalar arguments (e.g., __int128), leading to an out-of-bounds stack write that overwrites the saved RBP and return address.
--
--- Patch [1]: [PATCH bpf 1/2] bpf: Reject >8 byte return values on return-reading trampoline paths ---
commit 8947b8106804bc7e7328c045366441fc0d165459
Author: Yonghong Song <yonghong.song@linux.dev>
bpf: Reject >8 byte return values on return-reading trampoline paths
This patch rejects BPF trampoline attachments for types like fexit, fmod_ret,
and struct_ops if the target function has a return value larger than 8 bytes.
Previously, the trampoline accepted 16-byte return values (e.g., __int128) but
only saved and restored 8 bytes, which corrupted the return value seen by the
caller.
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 6515d4d3c0032..26d281b77a6e7 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
[ ... ]
> @@ -19137,6 +19151,14 @@ int bpf_check_attach_target(struct bpf_verifier_log *log,
> if (ret < 0)
> return ret;
>
> + if (tgt_info->fmodel.ret_size > 8 &&
> + attach_uses_trampoline_retval(prog->expected_attach_type)) {
[Severity: High]
This is a pre-existing issue, but while this patch addresses >8 byte return
values, does the BPF trampoline also have a similar problem with >8 byte
arguments?
In kernel/bpf/btf.c:btf_distill_func_proto(), 16-byte arguments like __int128
are accepted, but __get_type_fmodel_flags() does not set BTF_FMODEL_STRUCT_ARG
for them because they are BTF_KIND_INT, not structs.
In arch/x86/net/bpf_jit_comp.c:__arch_prepare_bpf_trampoline(), the stack
size calculation relies on this flag:
if (m->arg_flags[i] & BTF_FMODEL_STRUCT_ARG)
nr_regs += (m->arg_size[i] + 7) / 8 - 1;
Because the flag isn't set for __int128, it is incorrectly counted as
1 register, under-allocating the stack space by 8 bytes per 16-byte
scalar argument.
Later during code generation, save_args() computes
arg_regs = (m->arg_size[i] + 7) / 8, which evaluates to 2. It performs
two 8-byte stack stores, which will underflow the allocated boundary and
overwrite the saved RBP and potentially the return address.
Could this lead to out-of-bounds stack writes for fentry/fexit programs
attached to functions with 16-byte scalar arguments?
> + bpf_log(log,
> + "Attach to function %s with a >8 byte return value is not supported for this attach type\n",
> + tname);
> + return -EOPNOTSUPP;
> + }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260710144404.2579671-1-yonghong.song@linux.dev?part=1
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH bpf 2/2] selftests/bpf: Add test for >8 byte return value on fexit attach
2026-07-10 14:44 ` [PATCH bpf 2/2] selftests/bpf: Add test for >8 byte return value on fexit attach Yonghong Song
@ 2026-07-10 15:29 ` Leon Hwang
2026-07-10 15:43 ` Yonghong Song
0 siblings, 1 reply; 10+ messages in thread
From: Leon Hwang @ 2026-07-10 15:29 UTC (permalink / raw)
To: Yonghong Song, bpf
Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, kernel-team
On 2026/7/10 22:44, Yonghong Song wrote:
> The BPF trampoline preserves only 8 bytes of the target's return value
> (R0), so attaching an fexit/fmod_ret/fsession program to a function that
> returns a >8 byte value is now rejected by the verifier.
>
> Add a bpf_testmod function returning __int128 and an fexit program that
> targets it. The program is expected to fail to load with the
> "with a >8 byte return value is not supported for this attach type"
> message.
>
> __int128 is only available on 64-bit targets (where the compiler defines
> __SIZEOF_INT128__). Guard the testmod function and the test with
> __SIZEOF_INT128__ so a 32-bit build still compiles; on such builds
> the subtest is simply not run.
>
> Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
> ---
> .../selftests/bpf/prog_tests/tracing_failure.c | 12 ++++++++++++
> tools/testing/selftests/bpf/progs/tracing_failure.c | 6 ++++++
> .../testing/selftests/bpf/test_kmods/bpf_testmod.c | 13 +++++++++++++
> 3 files changed, 31 insertions(+)
>
> diff --git a/tools/testing/selftests/bpf/prog_tests/tracing_failure.c b/tools/testing/selftests/bpf/prog_tests/tracing_failure.c
> index f9f9e1cb87bf..6c199f5ff4db 100644
> --- a/tools/testing/selftests/bpf/prog_tests/tracing_failure.c
> +++ b/tools/testing/selftests/bpf/prog_tests/tracing_failure.c
> @@ -76,6 +76,14 @@ static void test_fexit_noreturns(void)
> "Attaching fexit/fsession/fmod_ret to __noreturn function 'do_exit' is rejected.");
> }
>
> +#ifdef __SIZEOF_INT128__
> +static void test_fexit_int128_ret(void)
> +{
> + test_tracing_fail_prog("fexit_int128_ret",
> + "with a >8 byte return value is not supported for this attach type");
> +}
Seems that this subtest can be simplified to
static void test_fexit_int128_ret(void)
{
#ifdef __SIZEOF_INT128__
test_tracing_fail_prog("fexit_int128_ret",
"with a >8 byte return value is not supported for this attach
type");
#else
test__skip();
#endif
}
Then, no '#ifdef' below.
Thanks,
Leon
> +#endif
> +
> void test_tracing_failure(void)
> {
> if (test__start_subtest("bpf_spin_lock"))
> @@ -86,4 +94,8 @@ void test_tracing_failure(void)
> test_tracing_deny();
> if (test__start_subtest("fexit_noreturns"))
> test_fexit_noreturns();
> +#ifdef __SIZEOF_INT128__
> + if (test__start_subtest("fexit_int128_ret"))
> + test_fexit_int128_ret();
> +#endif
> }
> diff --git a/tools/testing/selftests/bpf/progs/tracing_failure.c b/tools/testing/selftests/bpf/progs/tracing_failure.c
> index 65e485c4468c..f7a095767679 100644
> --- a/tools/testing/selftests/bpf/progs/tracing_failure.c
> +++ b/tools/testing/selftests/bpf/progs/tracing_failure.c
> @@ -30,3 +30,9 @@ int BPF_PROG(fexit_noreturns)
> {
> return 0;
> }
> +
> +SEC("?fexit/bpf_testmod_test_int128_ret")
> +int BPF_PROG(fexit_int128_ret)
> +{
> + return 0;
> +}
> diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
> index 30f1cd23093c..902dbf658250 100644
> --- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
> +++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
> @@ -161,6 +161,15 @@ bpf_testmod_test_arg_ptr_to_struct(struct bpf_testmod_struct_arg_1 *a) {
> return bpf_testmod_test_struct_arg_result;
> }
>
> +#ifdef __SIZEOF_INT128__
> +noinline __int128
> +bpf_testmod_test_int128_ret(int a)
> +{
> + bpf_testmod_test_struct_arg_result = a;
> + return (__int128)a;
> +}
> +#endif
> +
> __weak noinline void bpf_testmod_looooooooooooooooooooooooooooooong_name(void)
> {
> }
> @@ -514,6 +523,10 @@ bpf_testmod_test_read(struct file *file, struct kobject *kobj,
>
> (void)bpf_testmod_test_arg_ptr_to_struct(&struct_arg1_2);
>
> +#ifdef __SIZEOF_INT128__
> + (void)bpf_testmod_test_int128_ret(i);
> +#endif
> +
> (void)trace_bpf_testmod_test_raw_tp_null_tp(NULL);
>
> bpf_testmod_test_struct_ops3();
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH bpf 2/2] selftests/bpf: Add test for >8 byte return value on fexit attach
2026-07-10 15:29 ` Leon Hwang
@ 2026-07-10 15:43 ` Yonghong Song
0 siblings, 0 replies; 10+ messages in thread
From: Yonghong Song @ 2026-07-10 15:43 UTC (permalink / raw)
To: Leon Hwang, bpf
Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, kernel-team
On 7/10/26 8:29 AM, Leon Hwang wrote:
> On 2026/7/10 22:44, Yonghong Song wrote:
>> The BPF trampoline preserves only 8 bytes of the target's return value
>> (R0), so attaching an fexit/fmod_ret/fsession program to a function that
>> returns a >8 byte value is now rejected by the verifier.
>>
>> Add a bpf_testmod function returning __int128 and an fexit program that
>> targets it. The program is expected to fail to load with the
>> "with a >8 byte return value is not supported for this attach type"
>> message.
>>
>> __int128 is only available on 64-bit targets (where the compiler defines
>> __SIZEOF_INT128__). Guard the testmod function and the test with
>> __SIZEOF_INT128__ so a 32-bit build still compiles; on such builds
>> the subtest is simply not run.
>>
>> Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
>> ---
>> .../selftests/bpf/prog_tests/tracing_failure.c | 12 ++++++++++++
>> tools/testing/selftests/bpf/progs/tracing_failure.c | 6 ++++++
>> .../testing/selftests/bpf/test_kmods/bpf_testmod.c | 13 +++++++++++++
>> 3 files changed, 31 insertions(+)
>>
>> diff --git a/tools/testing/selftests/bpf/prog_tests/tracing_failure.c b/tools/testing/selftests/bpf/prog_tests/tracing_failure.c
>> index f9f9e1cb87bf..6c199f5ff4db 100644
>> --- a/tools/testing/selftests/bpf/prog_tests/tracing_failure.c
>> +++ b/tools/testing/selftests/bpf/prog_tests/tracing_failure.c
>> @@ -76,6 +76,14 @@ static void test_fexit_noreturns(void)
>> "Attaching fexit/fsession/fmod_ret to __noreturn function 'do_exit' is rejected.");
>> }
>>
>> +#ifdef __SIZEOF_INT128__
>> +static void test_fexit_int128_ret(void)
>> +{
>> + test_tracing_fail_prog("fexit_int128_ret",
>> + "with a >8 byte return value is not supported for this attach type");
>> +}
>
> Seems that this subtest can be simplified to
>
> static void test_fexit_int128_ret(void)
> {
> #ifdef __SIZEOF_INT128__
> test_tracing_fail_prog("fexit_int128_ret",
> "with a >8 byte return value is not supported for this attach
> type");
> #else
> test__skip();
> #endif
> }
>
> Then, no '#ifdef' below.
Thanks. Let me test this.
>
> Thanks,
> Leon
>
>> +#endif
>> +
>> void test_tracing_failure(void)
>> {
>> if (test__start_subtest("bpf_spin_lock"))
>> @@ -86,4 +94,8 @@ void test_tracing_failure(void)
>> test_tracing_deny();
>> if (test__start_subtest("fexit_noreturns"))
>> test_fexit_noreturns();
>> +#ifdef __SIZEOF_INT128__
>> + if (test__start_subtest("fexit_int128_ret"))
>> + test_fexit_int128_ret();
>> +#endif
>> }
>> diff --git a/tools/testing/selftests/bpf/progs/tracing_failure.c b/tools/testing/selftests/bpf/progs/tracing_failure.c
>> index 65e485c4468c..f7a095767679 100644
>> --- a/tools/testing/selftests/bpf/progs/tracing_failure.c
>> +++ b/tools/testing/selftests/bpf/progs/tracing_failure.c
>> @@ -30,3 +30,9 @@ int BPF_PROG(fexit_noreturns)
>> {
>> return 0;
>> }
>> +
>> +SEC("?fexit/bpf_testmod_test_int128_ret")
>> +int BPF_PROG(fexit_int128_ret)
>> +{
>> + return 0;
>> +}
>> diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
>> index 30f1cd23093c..902dbf658250 100644
>> --- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
>> +++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
>> @@ -161,6 +161,15 @@ bpf_testmod_test_arg_ptr_to_struct(struct bpf_testmod_struct_arg_1 *a) {
>> return bpf_testmod_test_struct_arg_result;
>> }
>>
>> +#ifdef __SIZEOF_INT128__
>> +noinline __int128
>> +bpf_testmod_test_int128_ret(int a)
>> +{
>> + bpf_testmod_test_struct_arg_result = a;
>> + return (__int128)a;
>> +}
>> +#endif
>> +
>> __weak noinline void bpf_testmod_looooooooooooooooooooooooooooooong_name(void)
>> {
>> }
>> @@ -514,6 +523,10 @@ bpf_testmod_test_read(struct file *file, struct kobject *kobj,
>>
>> (void)bpf_testmod_test_arg_ptr_to_struct(&struct_arg1_2);
>>
>> +#ifdef __SIZEOF_INT128__
>> + (void)bpf_testmod_test_int128_ret(i);
>> +#endif
>> +
>> (void)trace_bpf_testmod_test_raw_tp_null_tp(NULL);
>>
>> bpf_testmod_test_struct_ops3();
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH bpf 1/2] bpf: Reject >8 byte return values on return-reading trampoline paths
2026-07-10 14:58 ` [PATCH bpf 1/2] bpf: Reject >8 byte return values on return-reading trampoline paths sashiko-bot
@ 2026-07-10 15:45 ` Yonghong Song
0 siblings, 0 replies; 10+ messages in thread
From: Yonghong Song @ 2026-07-10 15:45 UTC (permalink / raw)
To: sashiko-reviews; +Cc: bpf
On 7/10/26 7:58 AM, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
>
> Pre-existing issues:
> - [High] The x86 BPF trampoline generation under-allocates stack space for 16-byte scalar arguments (e.g., __int128), leading to an out-of-bounds stack write that overwrites the saved RBP and return address.
> --
>
> --- Patch [1]: [PATCH bpf 1/2] bpf: Reject >8 byte return values on return-reading trampoline paths ---
> commit 8947b8106804bc7e7328c045366441fc0d165459
> Author: Yonghong Song <yonghong.song@linux.dev>
>
> bpf: Reject >8 byte return values on return-reading trampoline paths
>
> This patch rejects BPF trampoline attachments for types like fexit, fmod_ret,
> and struct_ops if the target function has a return value larger than 8 bytes.
> Previously, the trampoline accepted 16-byte return values (e.g., __int128) but
> only saved and restored 8 bytes, which corrupted the return value seen by the
> caller.
>
>> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
>> index 6515d4d3c0032..26d281b77a6e7 100644
>> --- a/kernel/bpf/verifier.c
>> +++ b/kernel/bpf/verifier.c
> [ ... ]
>
>> @@ -19137,6 +19151,14 @@ int bpf_check_attach_target(struct bpf_verifier_log *log,
>> if (ret < 0)
>> return ret;
>>
>> + if (tgt_info->fmodel.ret_size > 8 &&
>> + attach_uses_trampoline_retval(prog->expected_attach_type)) {
> [Severity: High]
> This is a pre-existing issue, but while this patch addresses >8 byte return
> values, does the BPF trampoline also have a similar problem with >8 byte
> arguments?
>
> In kernel/bpf/btf.c:btf_distill_func_proto(), 16-byte arguments like __int128
> are accepted, but __get_type_fmodel_flags() does not set BTF_FMODEL_STRUCT_ARG
> for them because they are BTF_KIND_INT, not structs.
>
> In arch/x86/net/bpf_jit_comp.c:__arch_prepare_bpf_trampoline(), the stack
> size calculation relies on this flag:
>
> if (m->arg_flags[i] & BTF_FMODEL_STRUCT_ARG)
> nr_regs += (m->arg_size[i] + 7) / 8 - 1;
>
> Because the flag isn't set for __int128, it is incorrectly counted as
> 1 register, under-allocating the stack space by 8 bytes per 16-byte
> scalar argument.
>
> Later during code generation, save_args() computes
> arg_regs = (m->arg_size[i] + 7) / 8, which evaluates to 2. It performs
> two 8-byte stack stores, which will underflow the allocated boundary and
> overwrite the saved RBP and potentially the return address.
>
> Could this lead to out-of-bounds stack writes for fentry/fexit programs
> attached to functions with 16-byte scalar arguments?
Looks like this indeed an issue for x86.
Since the __int128 issue exists for both return value and argument,
I will send v2 to address both of them together.
>
>> + bpf_log(log,
>> + "Attach to function %s with a >8 byte return value is not supported for this attach type\n",
>> + tname);
>> + return -EOPNOTSUPP;
>> + }
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH bpf 1/2] bpf: Reject >8 byte return values on return-reading trampoline paths
2026-07-10 14:44 [PATCH bpf 1/2] bpf: Reject >8 byte return values on return-reading trampoline paths Yonghong Song
2026-07-10 14:44 ` [PATCH bpf 2/2] selftests/bpf: Add test for >8 byte return value on fexit attach Yonghong Song
2026-07-10 14:58 ` [PATCH bpf 1/2] bpf: Reject >8 byte return values on return-reading trampoline paths sashiko-bot
@ 2026-07-10 15:45 ` bot+bpf-ci
2026-07-10 15:47 ` Yonghong Song
2026-07-10 16:37 ` Leon Hwang
3 siblings, 1 reply; 10+ messages in thread
From: bot+bpf-ci @ 2026-07-10 15:45 UTC (permalink / raw)
To: yonghong.song, bpf
Cc: ast, andrii, daniel, eddyz87, kernel-team, leon.hwang, ast,
andrii, daniel, martin.lau, eddyz87, yonghong.song, clm,
ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 1566 bytes --]
> diff --git a/kernel/bpf/bpf_struct_ops.c b/kernel/bpf/bpf_struct_ops.c
> index 51b16e5f5534..bbb44ef8f87a 100644
> --- a/kernel/bpf/bpf_struct_ops.c
> +++ b/kernel/bpf/bpf_struct_ops.c
> @@ -445,6 +445,18 @@ int bpf_struct_ops_desc_init(struct bpf_struct_ops_desc *st_ops_desc,
> goto errout;
> }
>
> + /*
> + * A >8 byte return value is passed back in the R0:R2 register
^^^^
Does this comment have the correct register pair? Looking at the commit
message, it states the second half of the return value is "RDX / R3", and
in arch/x86/net/bpf_jit_comp.c reg2hex[] shows BPF_REG_0 = RAX and
BPF_REG_3 = RDX, while BPF_REG_2 = RSI (an argument register).
Should this be R0:R3 instead of R0:R2 to match the x86-64 ABI return pair?
> + * pair, which the struct_ops trampoline does not preserve (only
> + * 8 bytes of the return value are saved and restored).
> + */
> + if (st_ops->func_models[i].ret_size > 8) {
> + pr_warn("func ptr %s in struct %s has a >8 byte return value, which is not supported\n",
> + mname, st_ops->name);
> + err = -EOPNOTSUPP;
> + goto errout;
> + }
> +
> stub_func_addr = *(void **)(st_ops->cfi_stubs + moff);
> err = prepare_arg_info(btf, st_ops->name, mname,
> func_proto, stub_func_addr,
[ ... ]
---
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/29101893933
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH bpf 1/2] bpf: Reject >8 byte return values on return-reading trampoline paths
2026-07-10 15:45 ` bot+bpf-ci
@ 2026-07-10 15:47 ` Yonghong Song
0 siblings, 0 replies; 10+ messages in thread
From: Yonghong Song @ 2026-07-10 15:47 UTC (permalink / raw)
To: bot+bpf-ci, bpf
Cc: ast, andrii, daniel, eddyz87, kernel-team, leon.hwang, martin.lau,
clm, ihor.solodrai
On 7/10/26 8:45 AM, bot+bpf-ci@kernel.org wrote:
>> diff --git a/kernel/bpf/bpf_struct_ops.c b/kernel/bpf/bpf_struct_ops.c
>> index 51b16e5f5534..bbb44ef8f87a 100644
>> --- a/kernel/bpf/bpf_struct_ops.c
>> +++ b/kernel/bpf/bpf_struct_ops.c
>> @@ -445,6 +445,18 @@ int bpf_struct_ops_desc_init(struct bpf_struct_ops_desc *st_ops_desc,
>> goto errout;
>> }
>>
>> + /*
>> + * A >8 byte return value is passed back in the R0:R2 register
> ^^^^
>
> Does this comment have the correct register pair? Looking at the commit
> message, it states the second half of the return value is "RDX / R3", and
> in arch/x86/net/bpf_jit_comp.c reg2hex[] shows BPF_REG_0 = RAX and
> BPF_REG_3 = RDX, while BPF_REG_2 = RSI (an argument register).
>
> Should this be R0:R3 instead of R0:R2 to match the x86-64 ABI return pair?
It should be R0:R3. My commit message is correct, but I forgot to update
the comments. Will fix the comment.
>
>> + * pair, which the struct_ops trampoline does not preserve (only
>> + * 8 bytes of the return value are saved and restored).
>> + */
>> + if (st_ops->func_models[i].ret_size > 8) {
>> + pr_warn("func ptr %s in struct %s has a >8 byte return value, which is not supported\n",
>> + mname, st_ops->name);
>> + err = -EOPNOTSUPP;
>> + goto errout;
>> + }
>> +
>> stub_func_addr = *(void **)(st_ops->cfi_stubs + moff);
>> err = prepare_arg_info(btf, st_ops->name, mname,
>> func_proto, stub_func_addr,
> [ ... ]
>
>
> ---
> 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/29101893933
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH bpf 1/2] bpf: Reject >8 byte return values on return-reading trampoline paths
2026-07-10 14:44 [PATCH bpf 1/2] bpf: Reject >8 byte return values on return-reading trampoline paths Yonghong Song
` (2 preceding siblings ...)
2026-07-10 15:45 ` bot+bpf-ci
@ 2026-07-10 16:37 ` Leon Hwang
2026-07-10 17:45 ` Yonghong Song
3 siblings, 1 reply; 10+ messages in thread
From: Leon Hwang @ 2026-07-10 16:37 UTC (permalink / raw)
To: Yonghong Song, bpf
Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, kernel-team
On 2026/7/10 22:44, Yonghong Song wrote:
[...]
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 6515d4d3c003..26d281b77a6e 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -18873,6 +18873,20 @@ static int btf_id_allow_sleepable(u32 btf_id, unsigned long addr, const struct b
> return -EINVAL;
> }
>
> +static bool attach_uses_trampoline_retval(enum bpf_attach_type type)
> +{
> + switch (type) {
> + case BPF_MODIFY_RETURN:
> + case BPF_TRACE_FEXIT:
> + case BPF_TRACE_FEXIT_MULTI:
> + case BPF_TRACE_FSESSION:
> + case BPF_TRACE_FSESSION_MULTI:
> + return true;
> + default:
> + return false;
> + }
> +}
> +
> int bpf_check_attach_target(struct bpf_verifier_log *log,
> const struct bpf_prog *prog,
> const struct bpf_prog *tgt_prog,
> @@ -19137,6 +19151,14 @@ int bpf_check_attach_target(struct bpf_verifier_log *log,
> if (ret < 0)
> return ret;
>
> + if (tgt_info->fmodel.ret_size > 8 &&
> + attach_uses_trampoline_retval(prog->expected_attach_type)) {
> + bpf_log(log,
> + "Attach to function %s with a >8 byte return value is not supported for this attach type\n",
> + tname);
> + return -EOPNOTSUPP;
> + }
> +
> /*
> * *.multi programs don't need an address during program
> * verification, we just take the module ref if needed.
> @@ -19413,6 +19435,9 @@ int bpf_check_attach_btf_id_multi(struct btf *btf, struct bpf_prog *prog, u32 bt
> err = btf_distill_func_proto(NULL, btf, t, tname, &tgt_info->fmodel);
> if (err < 0)
> return err;
> + if (tgt_info->fmodel.ret_size > 8 &&
> + attach_uses_trampoline_retval(prog->expected_attach_type))
> + return -EOPNOTSUPP;
> if (btf_is_module(btf)) {
> /* The bpf program already holds reference to module. */
> if (WARN_ON_ONCE(!prog->aux->mod))
I think there's no need to introduce the helper
attach_uses_trampoline_retval().
See this untested diff:
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 03e2202cca13..899d5b129b54 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -19023,6 +19023,7 @@ int bpf_check_attach_target(struct
bpf_verifier_log *log,
const char prefix[] = "btf_trace_";
struct bpf_raw_event_map *btp;
int ret = 0, subprog = -1, i;
+ bool attach_retval = false;
const struct btf_type *t;
bool conservative = true;
const char *tname, *fname;
@@ -19232,19 +19233,21 @@ int bpf_check_attach_target(struct
bpf_verifier_log *log,
if (ret)
return ret;
break;
+ case BPF_MODIFY_RETURN:
+ case BPF_TRACE_FEXIT:
+ case BPF_TRACE_FEXIT_MULTI:
+ case BPF_TRACE_FSESSION:
+ case BPF_TRACE_FSESSION_MULTI:
+ attach_retval = true;
+ fallthrough;
default:
- if (!prog_extension)
+ if (!prog_extension && !attach_retval)
return -EINVAL;
fallthrough;
- case BPF_MODIFY_RETURN:
case BPF_LSM_MAC:
case BPF_LSM_CGROUP:
case BPF_TRACE_FENTRY:
- case BPF_TRACE_FEXIT:
- case BPF_TRACE_FSESSION:
- case BPF_TRACE_FSESSION_MULTI:
case BPF_TRACE_FENTRY_MULTI:
- case BPF_TRACE_FEXIT_MULTI:
if ((prog->expected_attach_type == BPF_TRACE_FSESSION ||
prog->expected_attach_type == BPF_TRACE_FSESSION_MULTI) &&
!bpf_jit_supports_fsession()) {
@@ -19275,6 +19278,13 @@ int bpf_check_attach_target(struct
bpf_verifier_log *log,
if (ret < 0)
return ret;
+ if (tgt_info->fmodel.ret_size > 8 && attach_retval) {
+ bpf_log(log,
+ "Attach to function %s with a >8 byte return value is not supported
for this attach type\n",
+ tname);
+ return -EOPNOTSUPP;
+ }
+
/*
* *.multi programs don't need an address during program
* verification, we just take the module ref if needed.
@@ -19551,6 +19561,13 @@ int bpf_check_attach_btf_id_multi(struct btf
*btf, struct bpf_prog *prog, u32 bt
err = btf_distill_func_proto(NULL, btf, t, tname, &tgt_info->fmodel);
if (err < 0)
return err;
+ switch (prog->expected_attach_type) {
+ case BPF_TRACE_FEXIT_MULTI:
+ case BPF_TRACE_FSESSION_MULTI:
+ if (tgt_info->fmodel.ret_size > 8)
+ return -EOPNOTSUPP;
+ break;
+ }
if (btf_is_module(btf)) {
/* The bpf program already holds reference to module. */
if (WARN_ON_ONCE(!prog->aux->mod))
WDYT?
Thanks,
Leon
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH bpf 1/2] bpf: Reject >8 byte return values on return-reading trampoline paths
2026-07-10 16:37 ` Leon Hwang
@ 2026-07-10 17:45 ` Yonghong Song
0 siblings, 0 replies; 10+ messages in thread
From: Yonghong Song @ 2026-07-10 17:45 UTC (permalink / raw)
To: Leon Hwang, bpf
Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, kernel-team
On 7/10/26 9:37 AM, Leon Hwang wrote:
> On 2026/7/10 22:44, Yonghong Song wrote:
>
> [...]
>
>> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
>> index 6515d4d3c003..26d281b77a6e 100644
>> --- a/kernel/bpf/verifier.c
>> +++ b/kernel/bpf/verifier.c
>> @@ -18873,6 +18873,20 @@ static int btf_id_allow_sleepable(u32 btf_id, unsigned long addr, const struct b
>> return -EINVAL;
>> }
>>
>> +static bool attach_uses_trampoline_retval(enum bpf_attach_type type)
>> +{
>> + switch (type) {
>> + case BPF_MODIFY_RETURN:
>> + case BPF_TRACE_FEXIT:
>> + case BPF_TRACE_FEXIT_MULTI:
>> + case BPF_TRACE_FSESSION:
>> + case BPF_TRACE_FSESSION_MULTI:
>> + return true;
>> + default:
>> + return false;
>> + }
>> +}
>> +
>> int bpf_check_attach_target(struct bpf_verifier_log *log,
>> const struct bpf_prog *prog,
>> const struct bpf_prog *tgt_prog,
>> @@ -19137,6 +19151,14 @@ int bpf_check_attach_target(struct bpf_verifier_log *log,
>> if (ret < 0)
>> return ret;
>>
>> + if (tgt_info->fmodel.ret_size > 8 &&
>> + attach_uses_trampoline_retval(prog->expected_attach_type)) {
>> + bpf_log(log,
>> + "Attach to function %s with a >8 byte return value is not supported for this attach type\n",
>> + tname);
>> + return -EOPNOTSUPP;
>> + }
>> +
>> /*
>> * *.multi programs don't need an address during program
>> * verification, we just take the module ref if needed.
>> @@ -19413,6 +19435,9 @@ int bpf_check_attach_btf_id_multi(struct btf *btf, struct bpf_prog *prog, u32 bt
>> err = btf_distill_func_proto(NULL, btf, t, tname, &tgt_info->fmodel);
>> if (err < 0)
>> return err;
>> + if (tgt_info->fmodel.ret_size > 8 &&
>> + attach_uses_trampoline_retval(prog->expected_attach_type))
>> + return -EOPNOTSUPP;
>> if (btf_is_module(btf)) {
>> /* The bpf program already holds reference to module. */
>> if (WARN_ON_ONCE(!prog->aux->mod))
>
> I think there's no need to introduce the helper
> attach_uses_trampoline_retval().
>
> See this untested diff:
>
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 03e2202cca13..899d5b129b54 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -19023,6 +19023,7 @@ int bpf_check_attach_target(struct
> bpf_verifier_log *log,
> const char prefix[] = "btf_trace_";
> struct bpf_raw_event_map *btp;
> int ret = 0, subprog = -1, i;
> + bool attach_retval = false;
> const struct btf_type *t;
> bool conservative = true;
> const char *tname, *fname;
> @@ -19232,19 +19233,21 @@ int bpf_check_attach_target(struct
> bpf_verifier_log *log,
> if (ret)
> return ret;
> break;
> + case BPF_MODIFY_RETURN:
> + case BPF_TRACE_FEXIT:
> + case BPF_TRACE_FEXIT_MULTI:
> + case BPF_TRACE_FSESSION:
> + case BPF_TRACE_FSESSION_MULTI:
> + attach_retval = true;
> + fallthrough;
> default:
> - if (!prog_extension)
> + if (!prog_extension && !attach_retval)
> return -EINVAL;
> fallthrough;
> - case BPF_MODIFY_RETURN:
> case BPF_LSM_MAC:
> case BPF_LSM_CGROUP:
> case BPF_TRACE_FENTRY:
> - case BPF_TRACE_FEXIT:
> - case BPF_TRACE_FSESSION:
> - case BPF_TRACE_FSESSION_MULTI:
> case BPF_TRACE_FENTRY_MULTI:
> - case BPF_TRACE_FEXIT_MULTI:
> if ((prog->expected_attach_type == BPF_TRACE_FSESSION ||
> prog->expected_attach_type == BPF_TRACE_FSESSION_MULTI) &&
> !bpf_jit_supports_fsession()) {
> @@ -19275,6 +19278,13 @@ int bpf_check_attach_target(struct
Thanks for the suggestion. I still prefers the helper approach. It provides
a single place in verifier.c to handle this case (tgt_info->fmodel.ret_size > 8).
The above switch already very subtle and the change makes it even more subtle.
> bpf_verifier_log *log,
> if (ret < 0)
> return ret;
>
> + if (tgt_info->fmodel.ret_size > 8 && attach_retval) {
> + bpf_log(log,
> + "Attach to function %s with a >8 byte return value is not supported
> for this attach type\n",
> + tname);
> + return -EOPNOTSUPP;
> + }
> +
> /*
> * *.multi programs don't need an address during program
> * verification, we just take the module ref if needed.
> @@ -19551,6 +19561,13 @@ int bpf_check_attach_btf_id_multi(struct btf
> *btf, struct bpf_prog *prog, u32 bt
> err = btf_distill_func_proto(NULL, btf, t, tname, &tgt_info->fmodel);
> if (err < 0)
> return err;
> + switch (prog->expected_attach_type) {
> + case BPF_TRACE_FEXIT_MULTI:
> + case BPF_TRACE_FSESSION_MULTI:
> + if (tgt_info->fmodel.ret_size > 8)
> + return -EOPNOTSUPP;
> + break;
> + }
> if (btf_is_module(btf)) {
> /* The bpf program already holds reference to module. */
> if (WARN_ON_ONCE(!prog->aux->mod))
>
>
> WDYT?
>
> Thanks,
> Leon
>
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-07-10 17:45 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-10 14:44 [PATCH bpf 1/2] bpf: Reject >8 byte return values on return-reading trampoline paths Yonghong Song
2026-07-10 14:44 ` [PATCH bpf 2/2] selftests/bpf: Add test for >8 byte return value on fexit attach Yonghong Song
2026-07-10 15:29 ` Leon Hwang
2026-07-10 15:43 ` Yonghong Song
2026-07-10 14:58 ` [PATCH bpf 1/2] bpf: Reject >8 byte return values on return-reading trampoline paths sashiko-bot
2026-07-10 15:45 ` Yonghong Song
2026-07-10 15:45 ` bot+bpf-ci
2026-07-10 15:47 ` Yonghong Song
2026-07-10 16:37 ` Leon Hwang
2026-07-10 17:45 ` Yonghong Song
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox