> 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: 9df1c28bb752 ("bpf: add writable context for raw tracepoints")
> Signed-off-by: Sun Jian <[email protected]>

The Fixes: tag points at 9df1c28bb752 ("bpf: add writable context for
raw tracepoints"), but does that commit actually have this bug?

The code it introduced rejected any non-zero offset with:

        if (!tnum_is_const(reg->var_off) || reg->var_off.value)

and a negative constant offset from pointer arithmetic was tracked in
reg->off and caught by the off < 0 check.

The constant negative offset became accepted later when
022ac0750883 ("bpf: use reg->var_off instead of reg->off for pointers")
folded static and varying offsets into reg->var_off and dropped the
|| reg->var_off.value guard, leaving:

        if (!tnum_is_const(reg->var_off))

so a constant negative offset now stored in reg->var_off.value is no
longer rejected. Should the tag instead be:

  Fixes: 022ac0750883 ("bpf: use reg->var_off instead of reg->off for pointers")

> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 6515d4d3c003..7bcbcd43cc37 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -5326,14 +5326,19 @@ 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;
> +     u64 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;
>       }
> +
>       if (!tnum_is_const(reg->var_off)) {
>               char tn_buf[48];
>
> @@ -5344,6 +5349,36 @@ 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;
> +     }
> +
> +     end = (u64)start + (u64)size;
> +     if (end > U32_MAX) {
> +             verbose(env, "%s invalid %s buffer access: off=%lld, size=%d\n",
> +                     reg_arg_name(env, argno), buf_info, start, size);
> +             return -EACCES;
> +     }

Now that var_off is bounded within +/-BPF_MAX_VAR_OFF and start is
checked to be non-negative, can end > U32_MAX ever be true here?

With off capped at S16_MAX and size at 8, start + size stays well below
U32_MAX, so is the end > U32_MAX check reachable?  This is the same
point raised in review of an earlier version, and the BPF_MAX_VAR_OFF
bound was added while this overflow check was kept.

> +
> +     *access_end = (u32)end;
> +
>       return 0;
>  }

[ ... ]

---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/28916895865

Reply via email to