* [PATCH bpf v5 0/2] bpf: Reject negative const offsets for buffer pointers
@ 2026-07-14 9:38 Sun Jian
2026-07-14 9:38 ` [PATCH bpf v5 1/2] " Sun Jian
2026-07-14 9:38 ` [PATCH bpf v5 2/2] selftests/bpf: Cover negative buffer pointer offsets Sun Jian
0 siblings, 2 replies; 3+ messages in thread
From: Sun Jian @ 2026-07-14 9:38 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Emil Tsalapatis, Jiri Olsa, John Fastabend,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Shuah Khan, Song Liu,
Yonghong Song, Shung-Hsi Yu, Matt Mullins, linux-kernel,
linux-kselftest, Sun Jian
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/20260708090151.151729-1-sun.jian.kdev@gmail.com/
v3: https://lore.kernel.org/bpf/20260708040715.116680-1-sun.jian.kdev@gmail.com/
v2: https://lore.kernel.org/bpf/20260707060804.93561-1-sun.jian.kdev@gmail.com/
v1: https://lore.kernel.org/bpf/20260703035137.109608-1-sun.jian.kdev@gmail.com/
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
--
2.43.0
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH bpf v5 1/2] bpf: Reject negative const offsets for buffer pointers
2026-07-14 9:38 [PATCH bpf v5 0/2] bpf: Reject negative const offsets for buffer pointers Sun Jian
@ 2026-07-14 9:38 ` Sun Jian
2026-07-14 9:38 ` [PATCH bpf v5 2/2] selftests/bpf: Cover negative buffer pointer offsets Sun Jian
1 sibling, 0 replies; 3+ messages in thread
From: Sun Jian @ 2026-07-14 9:38 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Emil Tsalapatis, Jiri Olsa, John Fastabend,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Shuah Khan, Song Liu,
Yonghong Song, Shung-Hsi Yu, Matt Mullins, linux-kernel,
linux-kselftest, Sun Jian, stable
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.
Fixes: 022ac0750883 ("bpf: use reg->var_off instead of reg->off for pointers")
Acked-by: Shung-Hsi Yu <shung-hsi.yu@suse.com>
Cc: stable@vger.kernel.org # 5.2.0
Signed-off-by: Sun Jian <sun.jian.kdev@gmail.com>
---
kernel/bpf/verifier.c | 31 +++++++++++++++++++------------
1 file changed, 19 insertions(+), 12 deletions(-)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 6515d4d3c003..9f1333676365 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -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;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 3+ messages in thread
* [PATCH bpf v5 2/2] selftests/bpf: Cover negative buffer pointer offsets
2026-07-14 9:38 [PATCH bpf v5 0/2] bpf: Reject negative const offsets for buffer pointers Sun Jian
2026-07-14 9:38 ` [PATCH bpf v5 1/2] " Sun Jian
@ 2026-07-14 9:38 ` Sun Jian
1 sibling, 0 replies; 3+ messages in thread
From: Sun Jian @ 2026-07-14 9:38 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Emil Tsalapatis, Jiri Olsa, John Fastabend,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Shuah Khan, Song Liu,
Yonghong Song, Shung-Hsi Yu, Matt Mullins, linux-kernel,
linux-kselftest, Sun Jian, stable
Add 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.
Reported-by: Shung-Hsi Yu <shung-hsi.yu@suse.com>
Link: https://lore.kernel.org/bpf/alRtilWhKw4zzMkI@u94a
Cc: stable@vger.kernel.org # 5.2.0
Signed-off-by: Sun Jian <sun.jian.kdev@gmail.com>
---
.../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
diff --git a/tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_bad_access.c b/tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_bad_access.c
new file mode 100644
index 000000000000..b8538fc4fc3f
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_bad_access.c
@@ -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));
+}
diff --git a/tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_nbd_invalid.c b/tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_nbd_invalid.c
deleted file mode 100644
index 216b0dfac0fe..000000000000
--- a/tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_nbd_invalid.c
+++ /dev/null
@@ -1,43 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-
-#include <test_progs.h>
-#include <linux/nbd.h>
-#include "bpf_util.h"
-
-void test_raw_tp_writable_reject_nbd_invalid(void)
-{
- __u32 duration = 0;
- char error[4096];
- int bpf_fd = -1, tp_fd = -1;
-
- 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 nbd_request struct */
- BPF_LDX_MEM(BPF_B, BPF_REG_0, BPF_REG_6,
- sizeof(struct nbd_request)),
- BPF_EXIT_INSN(),
- };
-
- LIBBPF_OPTS(bpf_prog_load_opts, opts,
- .log_level = 2,
- .log_buf = error,
- .log_size = sizeof(error),
- );
-
- bpf_fd = bpf_prog_load(BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE, NULL, "GPL v2",
- program, ARRAY_SIZE(program),
- &opts);
- if (CHECK(bpf_fd < 0, "bpf_raw_tracepoint_writable load",
- "failed: %d errno %d\n", bpf_fd, errno))
- return;
-
- tp_fd = bpf_raw_tracepoint_open("nbd_send_request", bpf_fd);
- if (CHECK(tp_fd >= 0, "bpf_raw_tracepoint_writable open",
- "erroneously succeeded\n"))
- goto out_bpffd;
-
- close(tp_fd);
-out_bpffd:
- close(bpf_fd);
-}
diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c
index 8a3d69e2453c..be97f6887f0e 100644
--- a/tools/testing/selftests/bpf/prog_tests/verifier.c
+++ b/tools/testing/selftests/bpf/prog_tests/verifier.c
@@ -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); }
diff --git a/tools/testing/selftests/bpf/progs/verifier_ptr_to_buf.c b/tools/testing/selftests/bpf/progs/verifier_ptr_to_buf.c
new file mode 100644
index 000000000000..12cf24db46a0
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_ptr_to_buf.c
@@ -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)
+{
+ asm volatile ("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";
diff --git a/tools/testing/selftests/bpf/progs/verifier_raw_tp_writable.c b/tools/testing/selftests/bpf/progs/verifier_raw_tp_writable.c
index 14a0172e2141..4055a6443bc2 100644
--- a/tools/testing/selftests/bpf/progs/verifier_raw_tp_writable.c
+++ b/tools/testing/selftests/bpf/progs/verifier_raw_tp_writable.c
@@ -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)
+{
+ asm volatile (" \
+ r6 = *(u64 *)(r1 + 0); \
+ r6 += -8; \
+ r0 = *(u64 *)(r6 + 0); \
+ exit; \
+" :
+ :
+ : __clobber_all);
+}
+
char _license[] SEC("license") = "GPL";
--
2.43.0
^ permalink raw reply related [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-07-14 9:39 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-14 9:38 [PATCH bpf v5 0/2] bpf: Reject negative const offsets for buffer pointers Sun Jian
2026-07-14 9:38 ` [PATCH bpf v5 1/2] " Sun Jian
2026-07-14 9:38 ` [PATCH bpf v5 2/2] selftests/bpf: Cover negative buffer pointer offsets Sun Jian
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.