* [PATCH bpf v4 0/2] bpf: Reject negative const offsets for buffer pointers @ 2026-07-08 9:01 Sun Jian 2026-07-08 9:01 ` [PATCH bpf v4 1/2] " Sun Jian 2026-07-08 9:01 ` [PATCH bpf v4 2/2] selftests/bpf: Cover negative raw_tp writable buffer offsets Sun Jian 0 siblings, 2 replies; 16+ messages in thread From: Sun Jian @ 2026-07-08 9:01 UTC (permalink / raw) To: bpf Cc: ast, daniel, john.fastabend, andrii, eddyz87, memxor, martin.lau, song, yonghong.song, jolsa, emil, shuah, mmullins, shung-hsi.yu, linux-kernel, linux-kselftest, Sun Jian Reject negative effective offsets for PTR_TO_TP_BUFFER and PTR_TO_BUF buffer accesses, and add raw tracepoint writable coverage for both load-time rejection and the attach-time max_tp_access path. --- 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 -t verifier_raw_tp_writable - ./test_progs -t raw_tp_writable_reject_nbd_invalid -v - ./test_progs -t raw_tp_writable_test_run 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 raw_tp writable buffer offsets kernel/bpf/verifier.c | 40 +++++++++++-- .../raw_tp_writable_reject_nbd_invalid.c | 58 +++++++++++-------- .../bpf/progs/verifier_raw_tp_writable.c | 16 +++++ 3 files changed, 85 insertions(+), 29 deletions(-) base-commit: 12091470c6b4c1c14b2de12dcbae2ada6cb6d20b -- 2.43.0 ^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH bpf v4 1/2] bpf: Reject negative const offsets for buffer pointers 2026-07-08 9:01 [PATCH bpf v4 0/2] bpf: Reject negative const offsets for buffer pointers Sun Jian @ 2026-07-08 9:01 ` Sun Jian 2026-07-08 13:13 ` Shung-Hsi Yu 2026-07-08 9:01 ` [PATCH bpf v4 2/2] selftests/bpf: Cover negative raw_tp writable buffer offsets Sun Jian 1 sibling, 1 reply; 16+ messages in thread From: Sun Jian @ 2026-07-08 9:01 UTC (permalink / raw) To: bpf Cc: ast, daniel, john.fastabend, andrii, eddyz87, memxor, martin.lau, song, yonghong.song, jolsa, emil, shuah, mmullins, shung-hsi.yu, linux-kernel, linux-kselftest, Sun Jian 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. For example, a raw tracepoint writable program can load ctx[0] as a PTR_TO_TP_BUFFER, move it by -8, and then access the adjusted pointer. The access is before the tracepoint writable buffer base and should be rejected. Keep rejecting negative instruction offsets explicitly. Once the register offset is known to be constant, reject const var_off values outside the same +/-BPF_MAX_VAR_OFF range used by other verifier pointer offset checks before computing the effective access range. Fixes: 022ac0750883 ("bpf: use reg->var_off instead of reg->off for pointers") Signed-off-by: Sun Jian <sun.jian.kdev@gmail.com> --- kernel/bpf/verifier.c | 40 ++++++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 21a365d436a5..6fe4fcf93e5f 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -5326,14 +5326,18 @@ 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) { + s64 start, var_off; + 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; } + if (!tnum_is_const(reg->var_off)) { char tn_buf[48]; @@ -5344,6 +5348,29 @@ static int __check_buffer_access(struct bpf_verifier_env *env, return -EACCES; } + var_off = (s64)reg->var_off.value; + if (var_off >= BPF_MAX_VAR_OFF || var_off <= -BPF_MAX_VAR_OFF) { + verbose(env, "%s %s buffer offset %lld is not allowed\n", + reg_arg_name(env, argno), buf_info, var_off); + return -EACCES; + } + + start = var_off + 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, var_off); + return -EACCES; + } + + if (size < 0) { + verbose(env, "%s invalid %s buffer access: off=%lld, size=%d\n", + reg_arg_name(env, argno), buf_info, start, size); + return -EACCES; + } + + *access_end = (u32)start + (u32)size; + return 0; } @@ -5351,14 +5378,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 +5397,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] 16+ messages in thread
* Re: [PATCH bpf v4 1/2] bpf: Reject negative const offsets for buffer pointers 2026-07-08 9:01 ` [PATCH bpf v4 1/2] " Sun Jian @ 2026-07-08 13:13 ` Shung-Hsi Yu 2026-07-08 14:11 ` sun jian 0 siblings, 1 reply; 16+ messages in thread From: Shung-Hsi Yu @ 2026-07-08 13:13 UTC (permalink / raw) To: Sun Jian Cc: bpf, ast, daniel, john.fastabend, andrii, eddyz87, memxor, martin.lau, song, yonghong.song, jolsa, emil, shuah, mmullins, linux-kernel, linux-kselftest On Wed, Jul 08, 2026 at 02:01:50AM -0700, Sun Jian wrote: [...] > Fixes: 022ac0750883 ("bpf: use reg->var_off instead of reg->off for pointers") Agree that above is the commit that introduced the issue. More comments below. [...] > @@ -5326,14 +5326,18 @@ 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) > { > + s64 start, var_off; > + > 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; > } > + > if (!tnum_is_const(reg->var_off)) { > char tn_buf[48]; > > @@ -5344,6 +5348,29 @@ static int __check_buffer_access(struct bpf_verifier_env *env, > return -EACCES; > } > > + var_off = (s64)reg->var_off.value; > + if (var_off >= BPF_MAX_VAR_OFF || var_off <= -BPF_MAX_VAR_OFF) { > + verbose(env, "%s %s buffer offset %lld is not allowed\n", > + reg_arg_name(env, argno), buf_info, var_off); > + return -EACCES; > + } > + > + start = var_off + 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, var_off); > + return -EACCES; > + } I was thinking of suggest to just do a single unsigned check var_off = reg->var_off.value; if (var_off >= BPF_MAX_VAR_OFF) { ... But looking at the code before 022ac0750883, what you have is closer aligned to the previous behavior, let's stick to this. > + > + if (size < 0) { > + verbose(env, "%s invalid %s buffer access: off=%lld, size=%d\n", > + reg_arg_name(env, argno), buf_info, start, size); > + return -EACCES; > + } `size` comes from bpf_size_to_bytes(bpf_size) in check_mem_access(), and is already checked to be not negative; plus other check_* helpers does not check for this, so I think it can be dropped. Otherwise, LGTM: Acked-by: Shung-Hsi Yu <shung-hsi.yu@suse.com> > + > + *access_end = (u32)start + (u32)size; If you want, this could use a quick one-line comments regarding the fact that it won't overflow, or even verifier_bug_if(). > + > return 0; > } > [...] ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH bpf v4 1/2] bpf: Reject negative const offsets for buffer pointers 2026-07-08 13:13 ` Shung-Hsi Yu @ 2026-07-08 14:11 ` sun jian 2026-07-09 6:47 ` Shung-Hsi Yu 0 siblings, 1 reply; 16+ messages in thread From: sun jian @ 2026-07-08 14:11 UTC (permalink / raw) To: Shung-Hsi Yu Cc: bpf, ast, daniel, john.fastabend, andrii, eddyz87, memxor, martin.lau, song, yonghong.song, jolsa, emil, shuah, mmullins, linux-kernel, linux-kselftest On Wed, Jul 8, 2026 at 9:13 PM Shung-Hsi Yu <shung-hsi.yu@suse.com> wrote: > > On Wed, Jul 08, 2026 at 02:01:50AM -0700, Sun Jian wrote: > [...] > > Fixes: 022ac0750883 ("bpf: use reg->var_off instead of reg->off for pointers") > > Agree that above is the commit that introduced the issue. > > More comments below. > > [...] > > @@ -5326,14 +5326,18 @@ 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) > > { > > + s64 start, var_off; > > + > > 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; > > } > > + > > if (!tnum_is_const(reg->var_off)) { > > char tn_buf[48]; > > > > @@ -5344,6 +5348,29 @@ static int __check_buffer_access(struct bpf_verifier_env *env, > > return -EACCES; > > } > > > > + var_off = (s64)reg->var_off.value; > > + if (var_off >= BPF_MAX_VAR_OFF || var_off <= -BPF_MAX_VAR_OFF) { > > + verbose(env, "%s %s buffer offset %lld is not allowed\n", > > + reg_arg_name(env, argno), buf_info, var_off); > > + return -EACCES; > > + } > > + > > + start = var_off + 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, var_off); > > + return -EACCES; > > + } > > I was thinking of suggest to just do a single unsigned check > > var_off = reg->var_off.value; > if (var_off >= BPF_MAX_VAR_OFF) { > ... > > But looking at the code before 022ac0750883, what you have is closer > aligned to the previous behavior, let's stick to this. > > > + > > + if (size < 0) { > > + verbose(env, "%s invalid %s buffer access: off=%lld, size=%d\n", > > + reg_arg_name(env, argno), buf_info, start, size); > > + return -EACCES; > > + } > > `size` comes from bpf_size_to_bytes(bpf_size) in check_mem_access(), and > is already checked to be not negative; plus other check_* helpers does > not check for this, so I think it can be dropped. > > Otherwise, LGTM: > > Acked-by: Shung-Hsi Yu <shung-hsi.yu@suse.com> > > > + > > + *access_end = (u32)start + (u32)size; > > If you want, this could use a quick one-line comments regarding the fact > that it won't overflow, or even verifier_bug_if(). > > > + > > return 0; > > } > > > [...] Thanks for taking another look, and thanks for the Ack. I agree that the size check is redundant given the current call path. I’ll leave v4 as-is for now to avoid another respin unless maintainers prefer dropping it or adding a short comment around the access_end calculation. Best Regards Sun Jian ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH bpf v4 1/2] bpf: Reject negative const offsets for buffer pointers 2026-07-08 14:11 ` sun jian @ 2026-07-09 6:47 ` Shung-Hsi Yu 2026-07-09 12:47 ` sun jian 2026-07-09 18:21 ` Eduard Zingerman 0 siblings, 2 replies; 16+ messages in thread From: Shung-Hsi Yu @ 2026-07-09 6:47 UTC (permalink / raw) To: sun jian Cc: bpf, ast, daniel, john.fastabend, andrii, eddyz87, memxor, martin.lau, song, yonghong.song, jolsa, emil, shuah, mmullins, linux-kernel, linux-kselftest On Wed, Jul 08, 2026 at 10:11:01PM +0800, sun jian wrote: [...] > > > @@ -5326,14 +5326,18 @@ 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) > > > { > > > + s64 start, var_off; > > > + > > > 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; > > > } > > > + > > > if (!tnum_is_const(reg->var_off)) { > > > char tn_buf[48]; > > > > > > @@ -5344,6 +5348,29 @@ static int __check_buffer_access(struct bpf_verifier_env *env, > > > return -EACCES; > > > } > > > > > > + var_off = (s64)reg->var_off.value; > > > + if (var_off >= BPF_MAX_VAR_OFF || var_off <= -BPF_MAX_VAR_OFF) { > > > + verbose(env, "%s %s buffer offset %lld is not allowed\n", > > > + reg_arg_name(env, argno), buf_info, var_off); > > > + return -EACCES; > > > + } > > > + > > > + start = var_off + 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, var_off); > > > + return -EACCES; > > > + } > > > > I was thinking of suggest to just do a single unsigned check > > > > var_off = reg->var_off.value; > > if (var_off >= BPF_MAX_VAR_OFF) { > > ... > > > > But looking at the code before 022ac0750883, what you have is closer > > aligned to the previous behavior, let's stick to this. Actually looking again at 022ac0750883, moving the `off < 0` after tnum_is_const() and bringing back the `off += reg->off` removed from check_mem_access() is perhaps the more faithful restoration of the original behavior. Though reg->off no longer exists, we have to use reg->var_off.value instead. IIUC any register of pointer type should already have its var_off bounded to +-BPF_MAX_VAR_OFF by adjust_ptr_min_max_vals() in theory, and thus shouldn't overflow `int off`. See the diff below. [...] > I agree that the size check is redundant given the current call path. I’ll leave > v4 as-is for now to avoid another respin unless maintainers prefer dropping it > or adding a short comment around the access_end calculation. Agree and make sense. Let's see what @Eduard thinks. --- diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index d46f7db20d8f..e116b33ad83e 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -5359,12 +5359,6 @@ static int __check_buffer_access(struct bpf_verifier_env *env, const struct bpf_reg_state *reg, argno_t argno, int off, int size) { - 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; - } if (!tnum_is_const(reg->var_off)) { char tn_buf[48]; @@ -5375,6 +5369,14 @@ static int __check_buffer_access(struct bpf_verifier_env *env, return -EACCES; } + off += reg->var_off.value; + 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; + } + return 0; } ^ permalink raw reply related [flat|nested] 16+ messages in thread
* Re: [PATCH bpf v4 1/2] bpf: Reject negative const offsets for buffer pointers 2026-07-09 6:47 ` Shung-Hsi Yu @ 2026-07-09 12:47 ` sun jian 2026-07-09 18:21 ` Eduard Zingerman 1 sibling, 0 replies; 16+ messages in thread From: sun jian @ 2026-07-09 12:47 UTC (permalink / raw) To: Shung-Hsi Yu Cc: bpf, ast, daniel, john.fastabend, andrii, eddyz87, memxor, martin.lau, song, yonghong.song, jolsa, emil, shuah, mmullins, linux-kernel, linux-kselftest On Thu, Jul 9, 2026 at 2:47 PM Shung-Hsi Yu <shung-hsi.yu@suse.com> wrote: > > On Wed, Jul 08, 2026 at 10:11:01PM +0800, sun jian wrote: > [...] > > > > @@ -5326,14 +5326,18 @@ 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) > > > > { > > > > + s64 start, var_off; > > > > + > > > > 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; > > > > } > > > > + > > > > if (!tnum_is_const(reg->var_off)) { > > > > char tn_buf[48]; > > > > > > > > @@ -5344,6 +5348,29 @@ static int __check_buffer_access(struct bpf_verifier_env *env, > > > > return -EACCES; > > > > } > > > > > > > > + var_off = (s64)reg->var_off.value; > > > > + if (var_off >= BPF_MAX_VAR_OFF || var_off <= -BPF_MAX_VAR_OFF) { > > > > + verbose(env, "%s %s buffer offset %lld is not allowed\n", > > > > + reg_arg_name(env, argno), buf_info, var_off); > > > > + return -EACCES; > > > > + } > > > > + > > > > + start = var_off + 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, var_off); > > > > + return -EACCES; > > > > + } > > > > > > I was thinking of suggest to just do a single unsigned check > > > > > > var_off = reg->var_off.value; > > > if (var_off >= BPF_MAX_VAR_OFF) { > > > ... > > > > > > But looking at the code before 022ac0750883, what you have is closer > > > aligned to the previous behavior, let's stick to this. > > Actually looking again at 022ac0750883, moving the `off < 0` after > tnum_is_const() and bringing back the `off += reg->off` removed from > check_mem_access() is perhaps the more faithful restoration of the > original behavior. > > Though reg->off no longer exists, we have to use reg->var_off.value > instead. IIUC any register of pointer type should already have its > var_off bounded to +-BPF_MAX_VAR_OFF by adjust_ptr_min_max_vals() in > theory, and thus shouldn't overflow `int off`. > Thanks for the additional analysis. I checked the related verifier code paths and the following provenance items: 1. The source and initial state of PTR_TO_TP_BUFFER on the raw_tp writable path. 2. The shared PTR_TO_TP_BUFFER/PTR_TO_BUF buffer access checker path. 3. The pointer arithmetic path through adjust_ptr_min_max_vals() and the BPF_MAX_VAR_OFF bounds. 4. The source and range guarantees of the access size. 5. The source and range of the instruction offset passed to buffer access checks. 6.The accounting path from effective offset calculation to access_end and max_tp_access/max_access. Based on these checks, folding the constant var_off back into the access offset is consistent with the current pointer offset tracking model and is also closer to the pre-022ac0750883 behavior. > See the diff below. > > [...] > > > I agree that the size check is redundant given the current call path. I’ll leave > > v4 as-is for now to avoid another respin unless maintainers prefer dropping it > > or adding a short comment around the access_end calculation. > > Agree and make sense. Let's see what @Eduard thinks. > > --- > One remaining question is the intended semantics after restoring the offset-folding behavior. For example: r6 += 16; r0 = *(u64 *)(r6 - 8); > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > index d46f7db20d8f..e116b33ad83e 100644 > --- a/kernel/bpf/verifier.c > +++ b/kernel/bpf/verifier.c > @@ -5359,12 +5359,6 @@ static int __check_buffer_access(struct bpf_verifier_env *env, > const struct bpf_reg_state *reg, > argno_t argno, int off, int size) > { > - 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; > - } The current v4 approach rejects this because the instruction offset is negative. > if (!tnum_is_const(reg->var_off)) { > char tn_buf[48]; > > @@ -5375,6 +5369,14 @@ static int __check_buffer_access(struct bpf_verifier_env *env, > return -EACCES; > } > > + off += reg->var_off.value; > + 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; > + } > + > return 0; > } > The folded-offset approach would accept it because the final effective offset is non-negative. So this would change the accepted program set if we choose to restore the previous offset-folding behavior. It would be good to clarify whether the effective-offset behavior or the current instruction-offset restriction is the desired convention. I'll wait for the maintainers' preference. Thanks, Sun Jian ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH bpf v4 1/2] bpf: Reject negative const offsets for buffer pointers 2026-07-09 6:47 ` Shung-Hsi Yu 2026-07-09 12:47 ` sun jian @ 2026-07-09 18:21 ` Eduard Zingerman 2026-07-10 5:52 ` sun jian 1 sibling, 1 reply; 16+ messages in thread From: Eduard Zingerman @ 2026-07-09 18:21 UTC (permalink / raw) To: Shung-Hsi Yu, sun jian Cc: bpf, ast, daniel, john.fastabend, andrii, memxor, martin.lau, song, yonghong.song, jolsa, emil, shuah, mmullins, linux-kernel, linux-kselftest On Thu, 2026-07-09 at 14:47 +0800, Shung-Hsi Yu wrote: > On Wed, Jul 08, 2026 at 10:11:01PM +0800, sun jian wrote: > [...] > > > > @@ -5326,14 +5326,18 @@ 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) > > > > { > > > > + s64 start, var_off; > > > > + > > > > 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; > > > > } > > > > + > > > > if (!tnum_is_const(reg->var_off)) { > > > > char tn_buf[48]; > > > > > > > > @@ -5344,6 +5348,29 @@ static int __check_buffer_access(struct bpf_verifier_env *env, > > > > return -EACCES; > > > > } > > > > > > > > + var_off = (s64)reg->var_off.value; > > > > + if (var_off >= BPF_MAX_VAR_OFF || var_off <= -BPF_MAX_VAR_OFF) { > > > > + verbose(env, "%s %s buffer offset %lld is not allowed\n", > > > > + reg_arg_name(env, argno), buf_info, var_off); > > > > + return -EACCES; > > > > + } > > > > + > > > > + start = var_off + 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, var_off); > > > > + return -EACCES; > > > > + } > > > > > > I was thinking of suggest to just do a single unsigned check > > > > > > var_off = reg->var_off.value; > > > if (var_off >= BPF_MAX_VAR_OFF) { > > > ... > > > > > > But looking at the code before 022ac0750883, what you have is closer > > > aligned to the previous behavior, let's stick to this. > > Actually looking again at 022ac0750883, moving the `off < 0` after > tnum_is_const() and bringing back the `off += reg->off` removed from > check_mem_access() is perhaps the more faithful restoration of the > original behavior. > > Though reg->off no longer exists, we have to use reg->var_off.value > instead. IIUC any register of pointer type should already have its > var_off bounded to +-BPF_MAX_VAR_OFF by adjust_ptr_min_max_vals() in > theory, and thus shouldn't overflow `int off`. > > See the diff below. > > [...] > > > I agree that the size check is redundant given the current call path. I’ll leave > > v4 as-is for now to avoid another respin unless maintainers prefer dropping it > > or adding a short comment around the access_end calculation. > > Agree and make sense. Let's see what @Eduard thinks. I don't understand what this patch is attempting to fix. If you run the selftests from patch #2 against current bpf-next both would be rejected. If you extend these test cases to exercise a truly negative offset, that would be rejected as well. And this does not rely on UB. Consider the current code: env->prog->aux->max_tp_access = max(reg->var_off.value + off + size, env->prog->aux->max_tp_access); The types of the expressions involved: reg->var_off.value + off + size u64 int int The promotion/conversion rules: u64 + (u64)(s64)int -> u64 In other words, 'off' and 'size' would be sign extended to s64 and then treated as u64. Hence any negative offset would be represented as a large unsigned value in max_tp_access. Now, the real head scratcher is whether u64 -> u32 truncation on the assignment can bite here (mask higher bits of a big positive or negative value). Given the bounds on the values involved: - reg->var_off.value is -2**29..2**29 - off is -2**16+1..2**16-1 - size is 1, 2, 4 or 8 It cannot. If one wants to be truly paranoid, one can add a truncation check before the assignment, just to avoid the mental gymnastics. But that won't be a fix for any observable behaviour. [...] ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH bpf v4 1/2] bpf: Reject negative const offsets for buffer pointers 2026-07-09 18:21 ` Eduard Zingerman @ 2026-07-10 5:52 ` sun jian 2026-07-10 6:23 ` Eduard Zingerman 2026-07-10 8:00 ` Shung-Hsi Yu 0 siblings, 2 replies; 16+ messages in thread From: sun jian @ 2026-07-10 5:52 UTC (permalink / raw) To: Eduard Zingerman Cc: Shung-Hsi Yu, bpf, ast, daniel, john.fastabend, andrii, memxor, martin.lau, song, yonghong.song, jolsa, emil, shuah, mmullins, linux-kernel, linux-kselftest On Fri, Jul 10, 2026 at 2:21 AM Eduard Zingerman <eddyz87@gmail.com> wrote: > > On Thu, 2026-07-09 at 14:47 +0800, Shung-Hsi Yu wrote: > > On Wed, Jul 08, 2026 at 10:11:01PM +0800, sun jian wrote: > > [...] > > > > > @@ -5326,14 +5326,18 @@ 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) > > > > > { > > > > > + s64 start, var_off; > > > > > + > > > > > 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; > > > > > } > > > > > + > > > > > if (!tnum_is_const(reg->var_off)) { > > > > > char tn_buf[48]; > > > > > > > > > > @@ -5344,6 +5348,29 @@ static int __check_buffer_access(struct bpf_verifier_env *env, > > > > > return -EACCES; > > > > > } > > > > > > > > > > + var_off = (s64)reg->var_off.value; > > > > > + if (var_off >= BPF_MAX_VAR_OFF || var_off <= -BPF_MAX_VAR_OFF) { > > > > > + verbose(env, "%s %s buffer offset %lld is not allowed\n", > > > > > + reg_arg_name(env, argno), buf_info, var_off); > > > > > + return -EACCES; > > > > > + } > > > > > + > > > > > + start = var_off + 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, var_off); > > > > > + return -EACCES; > > > > > + } > > > > > > > > I was thinking of suggest to just do a single unsigned check > > > > > > > > var_off = reg->var_off.value; > > > > if (var_off >= BPF_MAX_VAR_OFF) { > > > > ... > > > > > > > > But looking at the code before 022ac0750883, what you have is closer > > > > aligned to the previous behavior, let's stick to this. > > > > Actually looking again at 022ac0750883, moving the `off < 0` after > > tnum_is_const() and bringing back the `off += reg->off` removed from > > check_mem_access() is perhaps the more faithful restoration of the > > original behavior. > > > > Though reg->off no longer exists, we have to use reg->var_off.value > > instead. IIUC any register of pointer type should already have its > > var_off bounded to +-BPF_MAX_VAR_OFF by adjust_ptr_min_max_vals() in > > theory, and thus shouldn't overflow `int off`. > > > > See the diff below. > > > > [...] > > > > > I agree that the size check is redundant given the current call path. I’ll leave > > > v4 as-is for now to avoid another respin unless maintainers prefer dropping it > > > or adding a short comment around the access_end calculation. > > > > Agree and make sense. Let's see what @Eduard thinks. > > I don't understand what this patch is attempting to fix. > If you run the selftests from patch #2 against current bpf-next both > would be rejected. If you extend these test cases to exercise a truly > negative offset, that would be rejected as well. > Thanks for pushing on this. I rechecked the issue more carefully. This series targets the bpf tree, with base 12091470c6b4. On that base, with only the selftest change applied, the negative-offset verifier case is not rejected at load time. The test fails with an unexpected load success: #664/2 verifier_raw_tp_writable/raw_tracepoint_writable: reject negative const offset:FAIL run_subtest:FAIL:unexpected_load_success unexpected success: 0 It is possible that current bpf-next rejects this earlier through another path, but on the target bpf base it does not. > And this does not rely on UB. > Consider the current code: > > env->prog->aux->max_tp_access = max(reg->var_off.value + off + size, > env->prog->aux->max_tp_access); > > > The types of the expressions involved: > > reg->var_off.value + off + size > u64 int int > > The promotion/conversion rules: > > u64 + (u64)(s64)int -> u64 > > In other words, 'off' and 'size' would be sign extended to s64 and > then treated as u64. Hence any negative offset would be represented > as a large unsigned value in max_tp_access. > Agreed, this is not a u64-to-u32 truncation issue. For the problematic case the access range is [-8, 0), so the end offset is 0. Since max_tp_access only tracks the upper bound, the attach-time writable_size check cannot catch the negative start offset. The missing piece is the lower-bound check. > Now, the real head scratcher is whether u64 -> u32 truncation on the > assignment can bite here (mask higher bits of a big positive or > negative value). Given the bounds on the values involved: > - reg->var_off.value is -2**29..2**29 > - off is -2**16+1..2**16-1 > - size is 1, 2, 4 or 8 > > It cannot. > > If one wants to be truly paranoid, one can add a truncation check > before the assignment, just to avoid the mental gymnastics. > But that won't be a fix for any observable behaviour. > There is an observable effect on the target bpf base. I tested it with a temporary raw_tp writable test_run reproducer, not part of the series. It attaches a raw_tp writable program to bpf_test_finish and triggers it through BPF_PROG_TEST_RUN. The program does: r6 = *(u64 *)(r1 + 0) r6 += -8 *(u64 *)(r6 + 0) = 0 With KASAN enabled and the BPF JIT disabled, on the bpf base without the verifier fix, the program loads, attaches, executes, and KASAN reports: BUG: KASAN: stack-out-of-bounds in ___bpf_prog_run Write of size 8 with the relevant call trace: ___bpf_prog_run __bpf_prog_run32 bpf_trace_run1 bpf_test_finish bpf_prog_test_run_skb bpf_prog_test_run __sys_bpf The report points to the bpf_test_finish stack frame, which matches the raw_tp writable program being attached to bpf_test_finish. With this series applied, the same negative-offset access is rejected at load time: R6 invalid negative tracepoint buffer offset: off=0, var_off=-8 Rerunning the same reproducer against the fixed kernel, the negative program is rejected by bpf_prog_load() and the KASAN log stays clean: NEGATIVE_RUNTIME_REPRO: negative_bpf_load rejected at load time: fd=-13 errno=13 So the observable behavior is the missing lower-bound check: on the target bpf base, the verifier can accept and execute an access before the writable tracepoint buffer base. The patch prevents that by rejecting negative effective buffer offsets at verification time. Thanks, Sun Jian > [...] ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH bpf v4 1/2] bpf: Reject negative const offsets for buffer pointers 2026-07-10 5:52 ` sun jian @ 2026-07-10 6:23 ` Eduard Zingerman 2026-07-10 7:25 ` sun jian 2026-07-10 8:00 ` Shung-Hsi Yu 1 sibling, 1 reply; 16+ messages in thread From: Eduard Zingerman @ 2026-07-10 6:23 UTC (permalink / raw) To: sun jian Cc: Shung-Hsi Yu, bpf, ast, daniel, john.fastabend, andrii, memxor, martin.lau, song, yonghong.song, jolsa, emil, shuah, mmullins, linux-kernel, linux-kselftest On Fri, 2026-07-10 at 13:52 +0800, sun jian wrote: [...] > There is an observable effect on the target bpf base. I tested it with a > temporary raw_tp writable test_run reproducer, not part of the series. It > attaches a raw_tp writable program to bpf_test_finish and triggers it > through BPF_PROG_TEST_RUN. The program does: > > r6 = *(u64 *)(r1 + 0) > r6 += -8 > *(u64 *)(r6 + 0) = 0 > > With KASAN enabled and the BPF JIT disabled, on the bpf base without the > verifier fix, the program loads, attaches, executes, and KASAN reports: Could you please identify why the behaviour differs between bpf and bpf-next? [...] ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH bpf v4 1/2] bpf: Reject negative const offsets for buffer pointers 2026-07-10 6:23 ` Eduard Zingerman @ 2026-07-10 7:25 ` sun jian 2026-07-10 7:47 ` Eduard Zingerman 0 siblings, 1 reply; 16+ messages in thread From: sun jian @ 2026-07-10 7:25 UTC (permalink / raw) To: Eduard Zingerman Cc: Shung-Hsi Yu, bpf, ast, daniel, john.fastabend, andrii, memxor, martin.lau, song, yonghong.song, jolsa, emil, shuah, mmullins, linux-kernel, linux-kselftest On Fri, Jul 10, 2026 at 2:23 PM Eduard Zingerman <eddyz87@gmail.com> wrote: > > On Fri, 2026-07-10 at 13:52 +0800, sun jian wrote: > > [...] > > > There is an observable effect on the target bpf base. I tested it with a > > temporary raw_tp writable test_run reproducer, not part of the series. It > > attaches a raw_tp writable program to bpf_test_finish and triggers it > > through BPF_PROG_TEST_RUN. The program does: > > > > r6 = *(u64 *)(r1 + 0) > > r6 += -8 > > *(u64 *)(r6 + 0) = 0 > > > > With KASAN enabled and the BPF JIT disabled, on the bpf base without the > > verifier fix, the program loads, attaches, executes, and KASAN reports: > > Could you please identify why the behaviour differs between bpf and bpf-next? I checked the current bpf-next, but I could not reproduce the difference. Both the public cgit page and my local bpf-next/master and bpf-next/for-next point to: a4553044d1af ("Merge branch 'bpf-bound-rdonly-rdwr_buf_size-kfunc-return-size'") I tested that tree with only the selftest patch applied. The staged diff only contains: tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_nbd_invalid.c tools/testing/selftests/bpf/progs/verifier_raw_tp_writable.c The negative const-offset case is still accepted at load time: #637/2 verifier_raw_tp_writable/raw_tracepoint_writable: reject negative const offset:FAIL run_subtest:FAIL:unexpected_load_success unexpected success: 0 and the verifier log only shows: processed 4 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0 The current __check_buffer_access() in that tree still only rejects a negative instruction offset and a non-constant var_off. It does not check the folded effective offset, so this case still has off=0 and var_off=-8 and is accepted. So, at least on a4553044d1af, I do not see bpf-next rejecting this case. If you have a bpf-next commit or verifier log where this case is rejected, I'd be glad to take a look and reconcile; I may well be missing something in my setup. Thanks, Sun Jian > > [...] ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH bpf v4 1/2] bpf: Reject negative const offsets for buffer pointers 2026-07-10 7:25 ` sun jian @ 2026-07-10 7:47 ` Eduard Zingerman 0 siblings, 0 replies; 16+ messages in thread From: Eduard Zingerman @ 2026-07-10 7:47 UTC (permalink / raw) To: sun jian Cc: Shung-Hsi Yu, bpf, ast, daniel, john.fastabend, andrii, memxor, martin.lau, song, yonghong.song, jolsa, emil, shuah, mmullins, linux-kernel, linux-kselftest On Fri, 2026-07-10 at 15:25 +0800, sun jian wrote: > On Fri, Jul 10, 2026 at 2:23 PM Eduard Zingerman <eddyz87@gmail.com> wrote: > > > > On Fri, 2026-07-10 at 13:52 +0800, sun jian wrote: > > > > [...] > > > > > There is an observable effect on the target bpf base. I tested it with a > > > temporary raw_tp writable test_run reproducer, not part of the series. It > > > attaches a raw_tp writable program to bpf_test_finish and triggers it > > > through BPF_PROG_TEST_RUN. The program does: > > > > > > r6 = *(u64 *)(r1 + 0) > > > r6 += -8 > > > *(u64 *)(r6 + 0) = 0 > > > > > > With KASAN enabled and the BPF JIT disabled, on the bpf base without the > > > verifier fix, the program loads, attaches, executes, and KASAN reports: > > > > Could you please identify why the behaviour differs between bpf and bpf-next? > > I checked the current bpf-next, but I could not reproduce the difference. > > Both the public cgit page and my local bpf-next/master and bpf-next/for-next > point to: > a4553044d1af ("Merge branch 'bpf-bound-rdonly-rdwr_buf_size-kfunc-return-size'") > > I tested that tree with only the selftest patch applied. The staged diff > only contains: > > tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_nbd_invalid.c > tools/testing/selftests/bpf/progs/verifier_raw_tp_writable.c > > The negative const-offset case is still accepted at load time: > > #637/2 verifier_raw_tp_writable/raw_tracepoint_writable: reject > negative const offset:FAIL > run_subtest:FAIL:unexpected_load_success unexpected success: 0 > > and the verifier log only shows: > > processed 4 insns (limit 1000000) max_states_per_insn 0 total_states 0 > peak_states 0 mark_read 0 Here is the branch: https://github.com/eddyz87/bpf/tree/tp-buffer-negative-offset It is the current bpf-next HEAD [1] plus patches #1,2 and a revert of patch #1. I have KASAN enabled, the test is passing: #317 raw_tp_writable_reject_nbd_invalid:OK [1] c3d5ef291a2a ("selftests/bpf: Add test for scalar id on sign-extending stack fill") > The current __check_buffer_access() in that tree still only rejects a > negative instruction offset and a non-constant var_off. It does not check > the folded effective offset, so this case still has off=0 and var_off=-8 and > is accepted. > > So, at least on a4553044d1af, I do not see bpf-next rejecting this case. If > you have a bpf-next commit or verifier log where this case is rejected, I'd > be glad to take a look and reconcile; I may well be missing something in my > setup. > > Thanks, > Sun Jian > > > > [...] ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH bpf v4 1/2] bpf: Reject negative const offsets for buffer pointers 2026-07-10 5:52 ` sun jian 2026-07-10 6:23 ` Eduard Zingerman @ 2026-07-10 8:00 ` Shung-Hsi Yu 2026-07-10 8:10 ` Shung-Hsi Yu 2026-07-10 10:27 ` sun jian 1 sibling, 2 replies; 16+ messages in thread From: Shung-Hsi Yu @ 2026-07-10 8:00 UTC (permalink / raw) To: sun jian, Eduard Zingerman Cc: bpf, ast, daniel, john.fastabend, andrii, memxor, martin.lau, song, yonghong.song, jolsa, emil, shuah, mmullins, linux-kernel, linux-kselftest On Fri, Jul 10, 2026 at 01:52:26PM +0800, sun jian wrote: > On Fri, Jul 10, 2026 at 2:21 AM Eduard Zingerman <eddyz87@gmail.com> wrote: > > On Thu, 2026-07-09 at 14:47 +0800, Shung-Hsi Yu wrote: > > > On Wed, Jul 08, 2026 at 10:11:01PM +0800, sun jian wrote: [...] > > > Actually looking again at 022ac0750883, moving the `off < 0` after > > > tnum_is_const() and bringing back the `off += reg->off` removed from > > > check_mem_access() is perhaps the more faithful restoration of the > > > original behavior. > > > > > > Though reg->off no longer exists, we have to use reg->var_off.value > > > instead. IIUC any register of pointer type should already have its > > > var_off bounded to +-BPF_MAX_VAR_OFF by adjust_ptr_min_max_vals() in > > > theory, and thus shouldn't overflow `int off`. > > > > > > See the diff below. [...] > > > > I don't understand what this patch is attempting to fix. > > If you run the selftests from patch #2 against current bpf-next both > > would be rejected. If you extend these test cases to exercise a truly > > negative offset, that would be rejected as well. I tried to exercise a truly negative offset, and it was indeed rejected on bpf-next. I had thought it would pass. But more below. > Thanks for pushing on this. I rechecked the issue more carefully. > > This series targets the bpf tree, with base 12091470c6b4. On that base, > with only the selftest change applied, the negative-offset verifier case > is not rejected at load time. The test fails with an unexpected load > success: > > #664/2 verifier_raw_tp_writable/raw_tracepoint_writable: reject > negative const offset:FAIL > run_subtest:FAIL:unexpected_load_success unexpected success: 0 > > It is possible that current bpf-next rejects this earlier through another > path, but on the target bpf base it does not. So I think I now have a better picture of what's going on. Before 022ac0750883, negative const offset is rejected at *load* time because the const offset is accumulated into `off` before the negative check: // check_mem_access() off += reg->off // __check_buffer_access() if (off < 0) return return -EACCES; if (!tnum_is_const(reg->var_off) || reg->var_off.value) return -EACCES; After 022ac0750883 the const offset accumulation does not happen, and thus now negative const offset is no longer rejected at load time. // __check_buffer_access() if (off < 0) return return -EACCES; if (!tnum_is_const(reg->var_off)) return -EACCES; > > And this does not rely on UB. > > Consider the current code: > > > > env->prog->aux->max_tp_access = max(reg->var_off.value + off + size, > > env->prog->aux->max_tp_access); > > > > > > The types of the expressions involved: > > > > reg->var_off.value + off + size > > u64 int int > > > > The promotion/conversion rules: > > > > u64 + (u64)(s64)int -> u64 > > > > In other words, 'off' and 'size' would be sign extended to s64 and > > then treated as u64. Hence any negative offset would be represented > > as a large unsigned value in max_tp_access. I entirely missed that, yes, the attached-time check does prevented the negative offset going through. So there isn't a bug, but the behavior has changed, and it seems better to restore to the one where we straightout reject negative offset during load time. That can be done by reordering `off < 0` check after tnum_is_const(), similar to how check_ptr_to_btf_access() was update in commit 022ac0750883. [...] ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH bpf v4 1/2] bpf: Reject negative const offsets for buffer pointers 2026-07-10 8:00 ` Shung-Hsi Yu @ 2026-07-10 8:10 ` Shung-Hsi Yu 2026-07-10 10:27 ` sun jian 1 sibling, 0 replies; 16+ messages in thread From: Shung-Hsi Yu @ 2026-07-10 8:10 UTC (permalink / raw) To: sun jian, Eduard Zingerman Cc: bpf, ast, daniel, john.fastabend, andrii, memxor, martin.lau, song, yonghong.song, jolsa, emil, shuah, mmullins, linux-kernel, linux-kselftest Forgot to include the verifier log when running verifier_raw_tp_writable/raw_tracepoint_writable as evidence. On Fri, Jul 10, 2026 at 04:00:27PM +0800, Shung-Hsi Yu wrote: > So I think I now have a better picture of what's going on. Before > 022ac0750883, negative const offset is rejected at *load* time because > the const offset is accumulated into `off` before the negative check: > > // check_mem_access() > off += reg->off > // __check_buffer_access() > if (off < 0) return return -EACCES; > if (!tnum_is_const(reg->var_off) || reg->var_off.value) return -EACCES; On 136114e0abf0 ("Merge tag 'mm-nonmm-stable-2026-02-12-10-48'...") I get: libbpf: prog 'tracepoint_writable_reject_negative_const_offset': BPF program load failed: -EACCES ... 0: R1=ctx() R10=fp0 ; asm volatile (" \ @ verifier_raw_tp_writable.c:56 0: (79) r6 = *(u64 *)(r1 +0) ; R1=ctx() R6=tp_buffer() 1: (07) r6 += -8 ; R6=tp_buffer(off=-8) 2: (79) r0 = *(u64 *)(r6 +0) R6 invalid tracepoint buffer access: off=-8, size=8 processed 3 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0 > After 022ac0750883 the const offset accumulation does not happen, and > thus now negative const offset is no longer rejected at load time. > > // __check_buffer_access() > if (off < 0) return return -EACCES; > if (!tnum_is_const(reg->var_off)) return -EACCES; On a4553044d1af ("Merge branch 'bpf-bound-rdonly-rdwr_buf_size-kfunc-return-size'") I get 0: R1=ctx() R10=fp0 ; asm volatile (" \ @ verifier_raw_tp_writable.c:56 0: (79) r6 = *(u64 *)(r1 +0) ; R1=ctx() R6=tp_buffer() 1: (07) r6 += -8 ; R6=tp_buffer(imm=-8) 2: (79) r0 = *(u64 *)(r6 +0) ; R0=scalar() R6=tp_buffer(imm=-8) 3: (95) exit processed 4 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0 > [...] ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH bpf v4 1/2] bpf: Reject negative const offsets for buffer pointers 2026-07-10 8:00 ` Shung-Hsi Yu 2026-07-10 8:10 ` Shung-Hsi Yu @ 2026-07-10 10:27 ` sun jian 1 sibling, 0 replies; 16+ messages in thread From: sun jian @ 2026-07-10 10:27 UTC (permalink / raw) To: Shung-Hsi Yu Cc: Eduard Zingerman, bpf, ast, daniel, john.fastabend, andrii, memxor, martin.lau, song, yonghong.song, jolsa, emil, shuah, mmullins, linux-kernel, linux-kselftest On Fri, Jul 10, 2026 at 4:00 PM Shung-Hsi Yu <shung-hsi.yu@suse.com> wrote: > > On Fri, Jul 10, 2026 at 01:52:26PM +0800, sun jian wrote: > > On Fri, Jul 10, 2026 at 2:21 AM Eduard Zingerman <eddyz87@gmail.com> wrote: > > > On Thu, 2026-07-09 at 14:47 +0800, Shung-Hsi Yu wrote: > > > > On Wed, Jul 08, 2026 at 10:11:01PM +0800, sun jian wrote: > [...] > > > > Actually looking again at 022ac0750883, moving the `off < 0` after > > > > tnum_is_const() and bringing back the `off += reg->off` removed from > > > > check_mem_access() is perhaps the more faithful restoration of the > > > > original behavior. > > > > > > > > Though reg->off no longer exists, we have to use reg->var_off.value > > > > instead. IIUC any register of pointer type should already have its > > > > var_off bounded to +-BPF_MAX_VAR_OFF by adjust_ptr_min_max_vals() in > > > > theory, and thus shouldn't overflow `int off`. > > > > > > > > See the diff below. > [...] > > > > > > I don't understand what this patch is attempting to fix. > > > If you run the selftests from patch #2 against current bpf-next both > > > would be rejected. If you extend these test cases to exercise a truly > > > negative offset, that would be rejected as well. > > I tried to exercise a truly negative offset, and it was indeed rejected > on bpf-next. I had thought it would pass. > > But more below. > > > Thanks for pushing on this. I rechecked the issue more carefully. > > > > This series targets the bpf tree, with base 12091470c6b4. On that base, > > with only the selftest change applied, the negative-offset verifier case > > is not rejected at load time. The test fails with an unexpected load > > success: > > > > #664/2 verifier_raw_tp_writable/raw_tracepoint_writable: reject > > negative const offset:FAIL > > run_subtest:FAIL:unexpected_load_success unexpected success: 0 > > > > It is possible that current bpf-next rejects this earlier through another > > path, but on the target bpf base it does not. > > So I think I now have a better picture of what's going on. Before > 022ac0750883, negative const offset is rejected at *load* time because > the const offset is accumulated into `off` before the negative check: > > // check_mem_access() > off += reg->off > // __check_buffer_access() > if (off < 0) return return -EACCES; > if (!tnum_is_const(reg->var_off) || reg->var_off.value) return -EACCES; > > After 022ac0750883 the const offset accumulation does not happen, and > thus now negative const offset is no longer rejected at load time. > > // __check_buffer_access() > if (off < 0) return return -EACCES; > if (!tnum_is_const(reg->var_off)) return -EACCES; > > > > And this does not rely on UB. > > > Consider the current code: > > > > > > env->prog->aux->max_tp_access = max(reg->var_off.value + off + size, > > > env->prog->aux->max_tp_access); > > > > > > > > > The types of the expressions involved: > > > > > > reg->var_off.value + off + size > > > u64 int int > > > > > > The promotion/conversion rules: > > > > > > u64 + (u64)(s64)int -> u64 > > > > > > In other words, 'off' and 'size' would be sign extended to s64 and > > > then treated as u64. Hence any negative offset would be represented > > > as a large unsigned value in max_tp_access. > Thanks for checking this and for the verifier logs. The before/after logs match what I see as well. Before 022ac0750883, the constant pointer offset was folded into off, so this case was rejected at load time with: R6 invalid tracepoint buffer access: off=-8, size=8 After 022ac0750883, the same constant offset remains in reg->var_off.value, and the program is accepted at load time with: R6=tp_buffer(imm=-8) processed 4 insns So I agree that the behavior change comes from 022ac0750883, and that the right direction is to restore the load-time rejection of negative effective buffer offsets. > I entirely missed that, yes, the attached-time check does prevented the > negative offset going through. So there isn't a bug, but the behavior > has changed, and it seems better to restore to the one where we > straightout reject negative offset during load time. > One detail about the attach-time check: I agree that some negative-offset constructions can still be rejected later if the max_tp_access accounting produces a large value. But the runtime reproducer I used exercises this specific access: r6 = *(u64 *)(r1 + 0) r6 += -8 *(u64 *)(r6 + 0) = 0 Here reg->var_off.value is (u64)-8, off is 0, and size is 8. With the current accounting, reg->var_off.value + off + size wraps to 0. So max_tp_access records 0 and the attach-time writable_size check does not reject it, even though the effective access starts at -8. On the bpf base without the verifier fix, the temporary reproducer did load and attach successfully: negative_bpf_load: PASS negative_raw_tp_open: PASS negative_test_run: PASS and KASAN reported the stack-out-of-bounds write in ___bpf_prog_run. So for this particular range, this looks like more than a load-time policy difference: the missing lower-bound check leaves the access executable on the bpf tree. In any case, I agree that v5 should restore the previous load-time rejection semantics. For v5, I'm inclined to follow the direction you suggested: fold the constant reg->var_off.value into off before the negative check, similar to the 022ac0750883 handling in check_ptr_to_btf_access(), rather than keeping the v4 explicit access_end form. I'll hold off on respinning until you and Eduard are settled on the approach. > That can be done by reordering `off < 0` check after tnum_is_const(), > similar to how check_ptr_to_btf_access() was update in commit > 022ac0750883. > > [...] ^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH bpf v4 2/2] selftests/bpf: Cover negative raw_tp writable buffer offsets 2026-07-08 9:01 [PATCH bpf v4 0/2] bpf: Reject negative const offsets for buffer pointers Sun Jian 2026-07-08 9:01 ` [PATCH bpf v4 1/2] " Sun Jian @ 2026-07-08 9:01 ` Sun Jian 2026-07-09 16:59 ` Eduard Zingerman 1 sibling, 1 reply; 16+ messages in thread From: Sun Jian @ 2026-07-08 9:01 UTC (permalink / raw) To: bpf Cc: ast, daniel, john.fastabend, andrii, eddyz87, memxor, martin.lau, song, yonghong.song, jolsa, emil, shuah, mmullins, shung-hsi.yu, linux-kernel, linux-kselftest, Sun Jian Add raw tracepoint writable coverage for buffer accesses involving negative constant pointer adjustments. The verifier case checks that a negative effective offset is rejected. The attach-time case adds incremental coverage beyond the existing nbd test and the verifier rejection case: it uses a negative var_off and a positive instruction offset whose effective offset remains non-negative. This exercises the checked access_end accounting path and verifies that the resulting max_tp_access is still checked against nbd_send_request's writable size at attach time. Signed-off-by: Sun Jian <sun.jian.kdev@gmail.com> --- .../raw_tp_writable_reject_nbd_invalid.c | 58 +++++++++++-------- .../bpf/progs/verifier_raw_tp_writable.c | 16 +++++ 2 files changed, 51 insertions(+), 23 deletions(-) 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 index 216b0dfac0fe..efe4cad47b28 100644 --- 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 @@ -4,12 +4,31 @@ #include <linux/nbd.h> #include "bpf_util.h" -void test_raw_tp_writable_reject_nbd_invalid(void) +static void check_nbd_attach_reject(const char *name, + const struct bpf_insn *program, size_t prog_len) { - __u32 duration = 0; + LIBBPF_OPTS(bpf_prog_load_opts, opts); char error[4096]; - int bpf_fd = -1, tp_fd = -1; + 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("nbd_send_request", bpf_fd); + if (!ASSERT_LT(tp_fd, 0, name)) + close(tp_fd); + + close(bpf_fd); +} +void test_raw_tp_writable_reject_nbd_invalid(void) +{ const struct bpf_insn program[] = { /* r6 is our tp buffer */ BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_1, 0), @@ -19,25 +38,18 @@ void test_raw_tp_writable_reject_nbd_invalid(void) 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; + 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 nbd_request struct */ + BPF_LDX_MEM(BPF_B, BPF_REG_0, BPF_REG_6, + sizeof(struct nbd_request) + 8), + BPF_EXIT_INSN(), + }; - close(tp_fd); -out_bpffd: - close(bpf_fd); + check_nbd_attach_reject("nbd_invalid", program, ARRAY_SIZE(program)); + check_nbd_attach_reject("nbd_invalid_negative_var_off", + negative_var_off_program, + ARRAY_SIZE(negative_var_off_program)); } 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] 16+ messages in thread
* Re: [PATCH bpf v4 2/2] selftests/bpf: Cover negative raw_tp writable buffer offsets 2026-07-08 9:01 ` [PATCH bpf v4 2/2] selftests/bpf: Cover negative raw_tp writable buffer offsets Sun Jian @ 2026-07-09 16:59 ` Eduard Zingerman 0 siblings, 0 replies; 16+ messages in thread From: Eduard Zingerman @ 2026-07-09 16:59 UTC (permalink / raw) To: Sun Jian, bpf Cc: ast, daniel, john.fastabend, andrii, memxor, martin.lau, song, yonghong.song, jolsa, emil, shuah, mmullins, shung-hsi.yu, linux-kernel, linux-kselftest On Wed, 2026-07-08 at 02:01 -0700, Sun Jian wrote: > Add raw tracepoint writable coverage for buffer accesses involving > negative constant pointer adjustments. > > The verifier case checks that a negative effective offset is rejected. > The attach-time case adds incremental coverage beyond the existing nbd > test and the verifier rejection case: it uses a negative var_off and a > positive instruction offset whose effective offset remains non-negative. > This exercises the checked access_end accounting path and verifies that > the resulting max_tp_access is still checked against nbd_send_request's > writable size at attach time. > > Signed-off-by: Sun Jian <sun.jian.kdev@gmail.com> > --- > .../raw_tp_writable_reject_nbd_invalid.c | 58 +++++++++++-------- > .../bpf/progs/verifier_raw_tp_writable.c | 16 +++++ > 2 files changed, 51 insertions(+), 23 deletions(-) > > 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 > index 216b0dfac0fe..efe4cad47b28 100644 > --- 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 > @@ -4,12 +4,31 @@ > #include <linux/nbd.h> > #include "bpf_util.h" > > -void test_raw_tp_writable_reject_nbd_invalid(void) > +static void check_nbd_attach_reject(const char *name, > + const struct bpf_insn *program, size_t prog_len) > { > - __u32 duration = 0; > + LIBBPF_OPTS(bpf_prog_load_opts, opts); > char error[4096]; > - int bpf_fd = -1, tp_fd = -1; > + 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("nbd_send_request", bpf_fd); > + if (!ASSERT_LT(tp_fd, 0, name)) > + close(tp_fd); > + > + close(bpf_fd); > +} > > +void test_raw_tp_writable_reject_nbd_invalid(void) > +{ > const struct bpf_insn program[] = { > /* r6 is our tp buffer */ > BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_1, 0), > @@ -19,25 +38,18 @@ void test_raw_tp_writable_reject_nbd_invalid(void) > 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; > + 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 nbd_request struct */ > + BPF_LDX_MEM(BPF_B, BPF_REG_0, BPF_REG_6, > + sizeof(struct nbd_request) + 8), > + BPF_EXIT_INSN(), > + }; > > - close(tp_fd); > -out_bpffd: > - close(bpf_fd); > + check_nbd_attach_reject("nbd_invalid", program, ARRAY_SIZE(program)); > + check_nbd_attach_reject("nbd_invalid_negative_var_off", > + negative_var_off_program, > + ARRAY_SIZE(negative_var_off_program)); > } Let's use subtests here: static void check_nbd_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("nbd_send_request", bpf_fd); ASSERT_ERR_FD(tp_fd, "bpf_raw_tracepoint_open"); close(tp_fd); close(bpf_fd); } void test_raw_tp_writable_reject_nbd_invalid(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 nbd_request struct */ BPF_LDX_MEM(BPF_B, BPF_REG_0, BPF_REG_6, sizeof(struct nbd_request)), 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 nbd_request struct */ BPF_LDX_MEM(BPF_B, BPF_REG_0, BPF_REG_6, sizeof(struct nbd_request) + 8), BPF_EXIT_INSN(), }; if (test__start_subtest("nbd_invalid")) check_nbd_attach_reject(program, ARRAY_SIZE(program)); if (test__start_subtest("nbd_invalid_negative_var_off")) check_nbd_attach_reject(negative_var_off_program, ARRAY_SIZE(negative_var_off_program)); } [...] ^ permalink raw reply [flat|nested] 16+ messages in thread
end of thread, other threads:[~2026-07-10 10:27 UTC | newest] Thread overview: 16+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-07-08 9:01 [PATCH bpf v4 0/2] bpf: Reject negative const offsets for buffer pointers Sun Jian 2026-07-08 9:01 ` [PATCH bpf v4 1/2] " Sun Jian 2026-07-08 13:13 ` Shung-Hsi Yu 2026-07-08 14:11 ` sun jian 2026-07-09 6:47 ` Shung-Hsi Yu 2026-07-09 12:47 ` sun jian 2026-07-09 18:21 ` Eduard Zingerman 2026-07-10 5:52 ` sun jian 2026-07-10 6:23 ` Eduard Zingerman 2026-07-10 7:25 ` sun jian 2026-07-10 7:47 ` Eduard Zingerman 2026-07-10 8:00 ` Shung-Hsi Yu 2026-07-10 8:10 ` Shung-Hsi Yu 2026-07-10 10:27 ` sun jian 2026-07-08 9:01 ` [PATCH bpf v4 2/2] selftests/bpf: Cover negative raw_tp writable buffer offsets Sun Jian 2026-07-09 16:59 ` Eduard Zingerman
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox