]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
bpf: Clear delta when clearing reg id for non-{add,sub} ops
authorDaniel Borkmann <daniel@iogearbox.net>
Tue, 7 Apr 2026 19:24:19 +0000 (21:24 +0200)
committerAlexei Starovoitov <ast@kernel.org>
Wed, 8 Apr 2026 01:15:42 +0000 (18:15 -0700)
When a non-{add,sub} alu op such as xor is performed on a scalar
register that previously had a BPF_ADD_CONST delta, the else path
in adjust_reg_min_max_vals() only clears dst_reg->id but leaves
dst_reg->delta unchanged.

This stale delta can propagate via assign_scalar_id_before_mov()
when the register is later used in a mov. It gets a fresh id but
keeps the stale delta from the old (now-cleared) BPF_ADD_CONST.
This stale delta can later propagate leading to a verifier-vs-
runtime value mismatch.

The clear_id label already correctly clears both delta and id.
Make the else path consistent by also zeroing the delta when id
is cleared. More generally, this introduces a helper clear_scalar_id()
which internally takes care of zeroing. There are various other
locations in the verifier where only the id is cleared. By using
the helper we catch all current and future locations.

Fixes: 98d7ca374ba4 ("bpf: Track delta between "linked" registers.")
Reported-by: STAR Labs SG <info@starlabs.sg>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Link: https://lore.kernel.org/r/20260407192421.508817-2-daniel@iogearbox.net
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
kernel/bpf/verifier.c

index d83e17cccd384d3179b3402bafc44f445a594bd9..40584ab48fb49238048beacf326cb3a5341c9744 100644 (file)
@@ -5257,27 +5257,30 @@ static bool __is_pointer_value(bool allow_ptr_leaks,
        return reg->type != SCALAR_VALUE;
 }
 
+static void clear_scalar_id(struct bpf_reg_state *reg)
+{
+       reg->id = 0;
+       reg->delta = 0;
+}
+
 static void assign_scalar_id_before_mov(struct bpf_verifier_env *env,
                                        struct bpf_reg_state *src_reg)
 {
        if (src_reg->type != SCALAR_VALUE)
                return;
-
-       if (src_reg->id & BPF_ADD_CONST) {
-               /*
-                * The verifier is processing rX = rY insn and
-                * rY->id has special linked register already.
-                * Cleared it, since multiple rX += const are not supported.
-                */
-               src_reg->id = 0;
-               src_reg->delta = 0;
-       }
-
+       /*
+        * The verifier is processing rX = rY insn and
+        * rY->id has special linked register already.
+        * Cleared it, since multiple rX += const are not supported.
+        */
+       if (src_reg->id & BPF_ADD_CONST)
+               clear_scalar_id(src_reg);
+       /*
+        * Ensure that src_reg has a valid ID that will be copied to
+        * dst_reg and then will be used by sync_linked_regs() to
+        * propagate min/max range.
+        */
        if (!src_reg->id && !tnum_is_const(src_reg->var_off))
-               /* Ensure that src_reg has a valid ID that will be copied to
-                * dst_reg and then will be used by sync_linked_regs() to
-                * propagate min/max range.
-                */
                src_reg->id = ++env->id_gen;
 }
 
@@ -5715,7 +5718,7 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env,
                                 * coerce_reg_to_size will adjust the boundaries.
                                 */
                                if (get_reg_width(reg) > size * BITS_PER_BYTE)
-                                       state->regs[dst_regno].id = 0;
+                                       clear_scalar_id(&state->regs[dst_regno]);
                        } else {
                                int spill_cnt = 0, zero_cnt = 0;
 
@@ -16346,7 +16349,7 @@ static void scalar_byte_swap(struct bpf_reg_state *dst_reg, struct bpf_insn *ins
         * any existing ties and avoid incorrect bounds propagation.
         */
        if (need_bswap || insn->imm == 16 || insn->imm == 32)
-               dst_reg->id = 0;
+               clear_scalar_id(dst_reg);
 
        if (need_bswap) {
                if (insn->imm == 16)
@@ -16748,8 +16751,7 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,
                         * we cannot accumulate another val into rx->off.
                         */
 clear_id:
-                       dst_reg->delta = 0;
-                       dst_reg->id = 0;
+                       clear_scalar_id(dst_reg);
                } else {
                        if (alu32)
                                dst_reg->id |= BPF_ADD_CONST32;
@@ -16762,7 +16764,7 @@ clear_id:
                 * Make sure ID is cleared otherwise dst_reg min/max could be
                 * incorrectly propagated into other registers by sync_linked_regs()
                 */
-               dst_reg->id = 0;
+               clear_scalar_id(dst_reg);
        }
        return 0;
 }
@@ -16893,7 +16895,7 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
                                                        assign_scalar_id_before_mov(env, src_reg);
                                                copy_register_state(dst_reg, src_reg);
                                                if (!no_sext)
-                                                       dst_reg->id = 0;
+                                                       clear_scalar_id(dst_reg);
                                                coerce_reg_to_size_sx(dst_reg, insn->off >> 3);
                                                dst_reg->subreg_def = DEF_NOT_SUBREG;
                                        } else {
@@ -16919,7 +16921,7 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
                                                 * propagated into src_reg by sync_linked_regs()
                                                 */
                                                if (!is_src_reg_u32)
-                                                       dst_reg->id = 0;
+                                                       clear_scalar_id(dst_reg);
                                                dst_reg->subreg_def = env->insn_idx + 1;
                                        } else {
                                                /* case: W1 = (s8, s16)W2 */
@@ -16929,7 +16931,7 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
                                                        assign_scalar_id_before_mov(env, src_reg);
                                                copy_register_state(dst_reg, src_reg);
                                                if (!no_sext)
-                                                       dst_reg->id = 0;
+                                                       clear_scalar_id(dst_reg);
                                                dst_reg->subreg_def = env->insn_idx + 1;
                                                coerce_subreg_to_size_sx(dst_reg, insn->off >> 3);
                                        }
@@ -17788,7 +17790,7 @@ static void __collect_linked_regs(struct linked_regs *reg_set, struct bpf_reg_st
                e->is_reg = is_reg;
                e->regno = spi_or_reg;
        } else {
-               reg->id = 0;
+               clear_scalar_id(reg);
        }
 }
 
@@ -20220,10 +20222,8 @@ static void clear_singular_ids(struct bpf_verifier_env *env,
                        continue;
                if (!reg->id)
                        continue;
-               if (idset_cnt_get(idset, reg->id & ~BPF_ADD_CONST) == 1) {
-                       reg->id = 0;
-                       reg->delta = 0;
-               }
+               if (idset_cnt_get(idset, reg->id & ~BPF_ADD_CONST) == 1)
+                       clear_scalar_id(reg);
        }));
 }