On Fri, Jul 10, 2026 at 2:21 AM Eduard Zingerman <[email protected]> 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 > [...]

