bpf: Reject negative const offsets for buffer pointers
Subsystems (5)
v5 cover bpf: Reject negative const offsets for buffer pointers
Reject negative effective offsets for PTR_TO_TP_BUFFER and PTR_TO_BUF accesses. Calculate the effective access start using signed arithmetic to prevent unsigned access-end accounting from wrapping, and cover both load-time rejection and the raw tracepoint writable attach-time path.
---
Changes in v5:
- Simplify __check_buffer_access() to reject a negative effective start after confirming that var_off is constant. Validate the combined offset instead of rejecting negative instruction offsets separately. Drop the duplicate BPF_MAX_VAR_OFF check because pointer arithmetic already bounds constant offsets, and remove the redundant size < 0 check.
- Switch the raw tracepoint writable attach tests from nbd_send_request to bpf_testmod_test_writable_bare_tp, avoiding the NBD configuration dependency and its false-pass condition.
- Split the attach coverage into named subtests and require bpf_raw_tracepoint_open() to return -EINVAL.
- Add verifier coverage for a negative constant PTR_TO_BUF offset.
Changes in v4:
- Correct the Fixes tag to point to 022ac0750883, where pointer offsets were folded into reg->var_off.
- Drop the end > U32_MAX check, which is unreachable after bounding const var_off with BPF_MAX_VAR_OFF while keeping instruction offsets and access sizes bounded.
Changes in v3:
- Check constant var_off against +/-BPF_MAX_VAR_OFF before computing the effective access range, matching the existing verifier pointer offset convention.
- Keep explicit rejection of negative instruction offsets and keep bounded negative constant var_off valid when the effective offset is non-negative.
Changes in v2:
- Split the kernel fix and selftests into separate patches.
- Add an attach-time raw tracepoint writable test that exercises max_tp_access against nbd_send_request's writable size.
- Adjust selftest formatting to use the 100 character line width.
Tested:
- ./test_progs -v -t verifier_raw_tp_writable
- ./test_progs -v -t verifier_ptr_to_buf
- ./test_progs -v -t raw_tp_writable_reject_bad_access
- ./test_progs -v -t raw_tp_writable_test_run
v4: https://lore.kernel.org/bpf/[email protected]/
v3: https://lore.kernel.org/bpf/[email protected]/
v2: https://lore.kernel.org/bpf/[email protected]/
v1: https://lore.kernel.org/bpf/[email protected]/
Sun Jian (2):
bpf: Reject negative const offsets for buffer pointers selftests/bpf: Cover negative buffer pointer offsets
kernel/bpf/verifier.c | 31 ++++++---- .../raw_tp_writable_reject_bad_access.c | 57 +++++++++++++++++++ .../raw_tp_writable_reject_nbd_invalid.c | 43 -------------- .../selftests/bpf/prog_tests/verifier.c | 2 + .../selftests/bpf/progs/verifier_ptr_to_buf.c | 27 +++++++++ .../bpf/progs/verifier_raw_tp_writable.c | 16 ++++++ 6 files changed, 121 insertions(+), 55 deletions(-) create mode 100644 tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_bad_access.c delete mode 100644 tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_nbd_invalid.c create mode 100644 tools/testing/selftests/bpf/progs/verifier_ptr_to_buf.c
base-commit: 7cbd0c4cebe4c9f678d15e6b9ba975e1155a107f
Signature · 1 line
2.43.0
PATCH v5 1/2 bpf: Reject negative const offsets for buffer pointers
The verifier rejects variable offsets for PTR_TO_TP_BUFFER and PTR_TO_BUF accesses, but it currently accepts a constant negative offset produced by pointer arithmetic.
Commit 022ac0750883 ("bpf: use reg->var_off instead of reg->off for pointers") moved constant pointer offsets from reg->off to reg->var_off. However, __check_buffer_access() continued to check only the instruction offset. An access with reg->var_off equal to -8 and an instruction offset of zero therefore passes verification.
For writable raw tracepoints, the access end is also calculated from the unsigned reg->var_off.value. An eight-byte access starting at -8 wraps the calculated end to zero, allowing the program to load and attach without increasing max_tp_access.
After ensuring that reg->var_off is constant, calculate the effective access start using signed arithmetic and reject it when it is negative. Use the validated start to calculate the access end for both PTR_TO_TP_BUFFER and PTR_TO_BUF.
--- kernel/bpf/verifier.c | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-)
kernel/bpf/verifier.c +19 -12
@@ -5326,14 +5326,11 @@ static int check_max_stack_depth(struct bpf_verifier_env *env)static int __check_buffer_access (struct bpf_verifier_env *env,const char *buf_info,const struct bpf_reg_state *reg, -argno_t argno,int off,int size) +argno_t argno,int off,int size, +u32 *access_end) { -if (off <0 ) { -verbose (env, -"%s invalid %s buffer access: off=%d, size=%d\n" , -reg_arg_name (env, argno), buf_info, off, size); -return -EACCES ; - } +s64 start; +if (!tnum_is_const (reg->var_off)) {char tn_buf[48 ];
@@ -5344,6 +5341,15 @@ static int __check_buffer_access(struct bpf_verifier_env *env,return -EACCES ; } + start = (s64 )reg->var_off.value + off; +if (start <0 ) { +verbose (env, +"%s invalid negative %s buffer offset: off=%d, var_off=%lld\n" , +reg_arg_name (env, argno), buf_info, off, (s64 )reg->var_off.value); +return -EACCES ; + } + + *access_end = start + size;return 0 ; }
@@ -5351,14 +5357,14 @@ static int check_tp_buffer_access(struct bpf_verifier_env *env,const struct bpf_reg_state *reg, argno_t argno, int off, int size) { + u32 access_end;int err; - err =__check_buffer_access (env,"tracepoint" , reg, argno, off, size); + err =__check_buffer_access (env,"tracepoint" , reg, argno, off, size, &access_end);if (err)return err; - env->prog->aux->max_tp_access =max (reg->var_off.value + off + size, - env->prog->aux->max_tp_access); + env->prog->aux->max_tp_access =max (access_end, env->prog->aux->max_tp_access);return 0 ; }
@@ -5370,13 +5376,14 @@ static int check_buffer_access(struct bpf_verifier_env *env, u32 *max_access) {const char *buf_info =type_is_rdonly_mem (reg->type) ?"rdonly" :"rdwr" ; +u32 access_end;int err; - err =__check_buffer_access (env, buf_info, reg, argno, off, size); + err =__check_buffer_access (env, buf_info, reg, argno, off, size, &access_end);if (err)return err; - *max_access =max (reg->var_off.value + off + size, *max_access); + *max_access =max (access_end, *max_access);return 0 ; }
Signature · 1 line
2.43.0
PATCH v5 2/2 selftests/bpf: Cover negative buffer pointer offsets
In reply to Sun JianAdd verifier coverage for constant negative offsets on PTR_TO_TP_BUFFER and PTR_TO_BUF pointers. Both programs adjust the buffer pointer by -8 and access it at offset zero, so the negative effective start must be rejected at load time.
Switch the raw tracepoint writable attach checks from nbd_send_request to bpf_testmod_test_writable_bare_tp, avoiding a dependency on the NBD tracepoint. Keep the existing past-end case and add a case with a negative var_off compensated by a positive instruction offset. The effective start remains non-negative, so the program loads, but its access end exceeds the writable context size and bpf_raw_tracepoint_open() must return -EINVAL.
--- .../raw_tp_writable_reject_bad_access.c | 57 +++++++++++++++++++ .../raw_tp_writable_reject_nbd_invalid.c | 43 -------------- .../selftests/bpf/prog_tests/verifier.c | 2 + .../selftests/bpf/progs/verifier_ptr_to_buf.c | 27 +++++++++ .../bpf/progs/verifier_raw_tp_writable.c | 16 ++++++ 5 files changed, 102 insertions(+), 43 deletions(-) create mode 100644 tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_bad_access.c delete mode 100644 tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_nbd_invalid.c create mode 100644 tools/testing/selftests/bpf/progs/verifier_ptr_to_buf.c
tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_bad_access.c new file +57 -0
@@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <test_progs.h> +#include "test_kmods/bpf_testmod.h" +#include "bpf_util.h" + +static void check_attach_reject (const struct bpf_insn *program,size_t prog_len) +{ +LIBBPF_OPTS (bpf_prog_load_opts, opts); +char error[4096 ]; +int bpf_fd, tp_fd; + + opts.log_level =2 ; + opts.log_buf = error; + opts.log_size =sizeof (error); + + bpf_fd =bpf_prog_load (BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE ,NULL ,"GPL v2" , + program, prog_len, &opts); +if (!ASSERT_GE (bpf_fd,0 ,"prog_load" )) +return ; + + tp_fd =bpf_raw_tracepoint_open ("bpf_testmod_test_writable_bare_tp" , bpf_fd); +ASSERT_EQ (tp_fd, -EINVAL ,"bpf_raw_tracepoint_open" ); +if (tp_fd >=0 ) +close (tp_fd); + +close (bpf_fd); +} + +void test_raw_tp_writable_reject_bad_access (void ) +{ +const struct bpf_insn program[] = { +/* r6 is our tp buffer */ +BPF_LDX_MEM (BPF_DW ,BPF_REG_6 ,BPF_REG_1 ,0 ), +/* one byte beyond the end of the writable context */ +BPF_LDX_MEM (BPF_B ,BPF_REG_0 ,BPF_REG_6 , +sizeof (struct bpf_testmod_test_writable_ctx )), +BPF_EXIT_INSN (), + }; + +const struct bpf_insn negative_var_off_program[] = { +BPF_LDX_MEM (BPF_DW ,BPF_REG_6 ,BPF_REG_1 ,0 ), +/* make var_off negative, but keep the effective access offset non-negative */ +BPF_ALU64_IMM (BPF_ADD ,BPF_REG_6 ,-8 ), +/* one byte beyond the end of the writable context */ +BPF_LDX_MEM (BPF_B ,BPF_REG_0 ,BPF_REG_6 , +sizeof (struct bpf_testmod_test_writable_ctx ) +8 ), +BPF_EXIT_INSN (), + }; + +if (test__start_subtest ("past_end" )) +check_attach_reject (program,ARRAY_SIZE (program)); + +if (test__start_subtest ("negative_var_off_past_end" )) +check_attach_reject (negative_var_off_program, +ARRAY_SIZE (negative_var_off_program)); +}
tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_nbd_invalid.c deleted +0 -43
43 lines not on this page · raw message
tools/testing/selftests/bpf/prog_tests/verifier.c +2 -0
@@ -78,6 +78,7 @@#include "verifier_precision.skel.h" #include "verifier_prevent_map_lookup.skel.h" #include "verifier_private_stack.skel.h" +#include "verifier_ptr_to_buf.skel.h" #include "verifier_raw_stack.skel.h" #include "verifier_raw_tp_writable.skel.h" #include "verifier_reg_equal.skel.h"
@@ -230,6 +231,7 @@ void test_verifier_or_jmp32_k(void) { RUN(verifier_or_jmp32_k); }void test_verifier_precision (void ) {RUN (verifier_precision); }void test_verifier_prevent_map_lookup (void ) {RUN (verifier_prevent_map_lookup); }void test_verifier_private_stack (void ) {RUN (verifier_private_stack); } +void test_verifier_ptr_to_buf (void ) {RUN (verifier_ptr_to_buf); }void test_verifier_raw_stack (void ) {RUN (verifier_raw_stack); }void test_verifier_raw_tp_writable (void ) {RUN (verifier_raw_tp_writable); }void test_verifier_reg_equal (void ) {RUN (verifier_reg_equal); }
tools/testing/selftests/bpf/progs/verifier_ptr_to_buf.c new file +27 -0
@@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <vmlinux.h> +#include <bpf/bpf_helpers.h> +#include "bpf_misc.h" + +SEC("iter/bpf_map_elem") +__description("PTR_TO_BUF: reject negative const offset") +__failure +__msg("invalid negative rdwr buffer offset") +__naked void ptr_to_buf_reject_negative_const_offset(void) +{ + asmvolatile ("r0 = 0; \ +r2 = *(u64 *)(r1 + %[value_off]); \ +if r2 == 0 goto l0_%=; \ +r2 += -8; \ +r0 = *(u64 *)(r2 + 0); \ +l0_%=: \ +exit; \ +" + : + : __imm_const(value_off, + offsetof(struct bpf_iter__bpf_map_elem, value)) + : __clobber_all); +} + +char _license[] SEC("license") = "GPL";
tools/testing/selftests/bpf/progs/verifier_raw_tp_writable.c +16 -0
@@ -47,4 +47,20 @@ l0_%=: /* shift the buffer pointer to a variable location */\ : __clobber_all); } +SEC("raw_tracepoint.w") +__description("raw_tracepoint_writable: reject negative const offset") +__failure +__msg("invalid negative tracepoint buffer offset") +__naked void tracepoint_writable_reject_negative_const_offset(void) +{ + asmvolatile (" \ +r6 = *(u64 *)(r1 + 0); \ +r6 += -8; \ +r0 = *(u64 *)(r6 + 0); \ +exit; \ +" : + : + : __clobber_all); +} + char _license[] SEC("license") = "GPL";
Signature · 1 line
2.43.0
Re: [PATCH bpf v5 2/2] selftests/bpf: Cover negative buffer pointer offsets
Quoted 13 lines · Sun Jian
Add verifier coverage for constant negative offsets on
PTR_TO_TP_BUFFERandPTR_TO_BUFpointers. Both programs adjust the buffer pointer by -8 and access it at offset zero, so the negative effective start must be rejected at load time.Switch the raw tracepoint writable attach checks from
nbd_send_requesttobpf_testmod_test_writable_bare_tp, avoiding a dependency on the NBD tracepoint. Keep the existing past-end case and add a case with a negativevar_offcompensated by a positive instruction offset. The effective start remains non-negative, so the program loads, but its access end exceeds the writable context size andbpf_raw_tracepoint_open()must return-EINVAL.
Quoted 3 lines
Reported-by Shung-Hsi Yu <[email protected]>Cc [email protected] # 5.2.0
...
IMO it is slightly better to split this into two. First part would be
[1] suggested by Eduard, and the second part would be the addition of negative offset test.
That said, I checked that with v5.2 we do have PTR_TO_TP_BUFFER and PTR_TO_BUF support already (latter as PTR_TO_RDONLY_BUF and PTR_TO_RDWR_BUF), so there shouldn't be a problem having these tests back in stable.
1: https://lore.kernel.org/bpf/[email protected]/2-tp-test.diff
Re: [PATCH bpf v5 2/2] selftests/bpf: Cover negative buffer pointer offsets
In reply to Sun JianQuoted 4 lines · Sun Jian
Add verifier coverage for constant negative offsets on
PTR_TO_TP_BUFFERandPTR_TO_BUFpointers. Both programs adjust the buffer pointer by -8 and access it at offset zero, so the negative effective start must be rejected at load time.
[...]
tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_bad_access.c line 49
Quoted 9 lines
tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_bad_access.c new file +9
-0+const struct bpf_insn negative_var_off_program[] = { +BPF_LDX_MEM (BPF_DW ,BPF_REG_6 ,BPF_REG_1 ,0 ), +/* make var_off negative, but keep the effective access offset non-negative */ +BPF_ALU64_IMM (BPF_ADD ,BPF_REG_6 ,-8 ), +/* one byte beyond the end of the writable context */ +BPF_LDX_MEM (BPF_B ,BPF_REG_0 ,BPF_REG_6 , +sizeof (struct bpf_testmod_test_writable_ctx ) +8 ), +BPF_EXIT_INSN (), + };
Come to think of it, perhaps we can add another one that test one byte *before* the start of the writable context?
I understand that it won't even reach the attachment phase because after your 1st patch is applied, access to effective negative offset of will be rejected at load time, but the one that tried to access one byte before the start of writable context was what that triggered KASAN, and would be useful to have it as a regression test.
Or alternatively simply change negative_var_off_program[] to be the one that test access *before* the start of context. I am not even sure if the compiler generate such pattern; if it doesn't, then this test would make future refactoring harder without much benefit.
[...]
Re: [PATCH bpf v5 2/2] selftests/bpf: Cover negative buffer pointer offsets
Quoted 19 lines · Shung-Hsi Yu
Quoted 4 lines · Sun Jian
Add verifier coverage for constant negative offsets on
PTR_TO_TP_BUFFERandPTR_TO_BUFpointers. Both programs adjust the buffer pointer by -8 and access it at offset zero, so the negative effective start must be rejected at load time.[...]
tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_bad_access.c line 49
Quoted 9 lines
tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_bad_access.c new file +9
-0+const struct bpf_insn negative_var_off_program[] = { +BPF_LDX_MEM (BPF_DW ,BPF_REG_6 ,BPF_REG_1 ,0 ), +/* make var_off negative, but keep the effective access offset non-negative */ +BPF_ALU64_IMM (BPF_ADD ,BPF_REG_6 ,-8 ), +/* one byte beyond the end of the writable context */ +BPF_LDX_MEM (BPF_B ,BPF_REG_0 ,BPF_REG_6 , +sizeof (struct bpf_testmod_test_writable_ctx ) +8 ), +BPF_EXIT_INSN (), + };Come to think of it, perhaps we can add another one that test one byte *before* the start of the writable context?
Quoted 5 lines
I understand that it won't even reach the attachment phase because after your 1st patch is applied, access to effective negative offset of will be rejected at load time, but the one that tried to access one byte before the start of writable context was what that triggered KASAN, and would be useful to have it as a regression test.
I really should proof-read more before I send...
Since the "effective access offset non-negative" should be rejected, it would not make refactoring harder, sorry. What I said below in the last email is wrong.
Anyway, still recommend adding a regression test that test access to one byte before the start of writable context.
Quoted 6 lines
Or alternatively simply change negative_var_off_program[] to be the one that test access *before* the start of context. I am not even sure if the compiler generate such pattern; if it doesn't, then this test would make future refactoring harder without much benefit.
[...]
Re: [PATCH bpf v5 2/2] selftests/bpf: Cover negative buffer pointer offsets
Quoted 27 lines · Shung-Hsi Yu
Quoted 16 lines · Shung-Hsi Yu
Quoted 4 lines · Sun Jian
Add verifier coverage for constant negative offsets on
PTR_TO_TP_BUFFERandPTR_TO_BUFpointers. Both programs adjust the buffer pointer by -8 and access it at offset zero, so the negative effective start must be rejected at load time.[...]
tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_bad_access.c line 49
Quoted 9 lines
tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_bad_access.c new file +9
-0+const struct bpf_insn negative_var_off_program[] = { +BPF_LDX_MEM (BPF_DW ,BPF_REG_6 ,BPF_REG_1 ,0 ), +/* make var_off negative, but keep the effective access offset non-negative */ +BPF_ALU64_IMM (BPF_ADD ,BPF_REG_6 ,-8 ), +/* one byte beyond the end of the writable context */ +BPF_LDX_MEM (BPF_B ,BPF_REG_0 ,BPF_REG_6 , +sizeof (struct bpf_testmod_test_writable_ctx ) +8 ), +BPF_EXIT_INSN (), + };Quoted 8 lines
Come to think of it, perhaps we can add another one that test one byte *before* the start of the writable context?
I understand that it won't even reach the attachment phase because after your 1st patch is applied, access to effective negative offset of will be rejected at load time, but the one that tried to access one byte before the start of writable context was what that triggered KASAN, and would be useful to have it as a regression test.
Quoted 9 lines
I really should proof-read more before I send...
Since the "effective access offset non-negative" should be rejected, it would not make refactoring harder, sorry. What I said below in the last email is wrong.
Anyway, still recommend adding a regression test that test access to one byte before the start of writable context.
The new tracepoint_writable_reject_negative_const_offset verifier case already covers an access before the start of the writable context:
r6 = *(u64 *)(r1 + 0);
r6 += -8;
r0 = *(u64 *)(r6 + 0);Its effective access range is [-8, 0), so it is rejected at load time. This is the direct regression test for the negative-start case.
Quoted 6 lines
Quoted 6 lines
Or alternatively simply change negative_var_off_program[] to be the one that test access *before* the start of context. I am not even sure if the compiler generate such pattern; if it doesn't, then this test would make future refactoring harder without much benefit.
[...]
Eduard Zingerman
maintainerRe: [PATCH bpf v5 2/2] selftests/bpf: Cover negative buffer pointer offsets
On Wed, 2026-07-15 at 13:26 +0800, sun jian wrote:
[...]
Quoted 7 lines
Quoted 3 lines
Anyway, still recommend adding a regression test that test access to one byte before the start of writable context.
The new
tracepoint_writable_reject_negative_const_offsetverifier case already covers an access before the start of the writable context:
Quoted 6 lines
r6 = *(u64 *)(r1 + 0);
r6 += -8;
r0 = *(u64 *)(r6 + 0);Its effective access range is [-8, 0), so it is rejected at load time. This is the direct regression test for the negative-start case.
The existing test seem to be sufficient, why would it matter is at offset -8 or -1 given that the read is appropriately sized?
The added test in tools/testing/selftests/bpf/progs/verifier_raw_tp_writable.c is redundant given the changes in raw_tp_writable_reject_bad_access.c but I was going to apply anyway.
Re: [PATCH bpf v5 2/2] selftests/bpf: Cover negative buffer pointer offsets
Quoted 11 lines · Eduard Zingerman
Quoted 10 lines · sun jian
Quoted 2 lines
Anyway, still recommend adding a regression test that test access to one byte before the start of writable context.
The new
tracepoint_writable_reject_negative_const_offsetverifier case already covers an access before the start of the writable context:r6 = *(u64 *)(r1 + 0);
r6 += -8;
r0 = *(u64 *)(r6 + 0);
Quoted 5 lines
Quoted 2 lines
Its effective access range is [-8, 0), so it is rejected at load time. This is the direct regression test for the negative-start case.
The existing test seem to be sufficient, why would it matter is at offset -8 or -1 given that the read is appropriately sized?
Right it doesn't. Let's just, pretend I haven't suggest that.
[...]
This series was applied to bpf/bpf.git (master) by Eduard Zingerman <[email protected]>:
Hello:
This series was applied to bpf/bpf.git (master)
by Eduard Zingerman <[email protected]>:
Quoted 8 lines · On Tue, 14 Jul 2026 02:38:44 -0700 you wrote:
Reject negative effective offsets for
PTR_TO_TP_BUFFERandPTR_TO_BUFaccesses. Calculate the effective access start using signed arithmetic to prevent unsigned access-end accounting from wrapping, and cover both load-time rejection and the raw tracepoint writable attach-time path.---
[...]
Here is the summary with links:
- [bpf,v5,1/2] bpf: Reject negative const offsets for buffer pointers
https://git.kernel.org/bpf/bpf/c/fd4cfa8c8f9a - [bpf,v5,2/2] selftests/bpf: Cover negative buffer pointer offsets https://git.kernel.org/bpf/bpf/c/6f59deb32efa
You are awesome, thank you!
Signature · 2 lines
Deet-doot-dot, I am a bot. https://korg.docs.kernel.org/patchwork/pwbot.html