]> www.infradead.org Git - nvme.git/commitdiff
bpf: Fix truncation bug in coerce_reg_to_size_sx()
authorDimitar Kanaliev <dimitar.kanaliev@siteground.com>
Mon, 14 Oct 2024 12:11:53 +0000 (15:11 +0300)
committerAlexei Starovoitov <ast@kernel.org>
Tue, 15 Oct 2024 18:16:24 +0000 (11:16 -0700)
coerce_reg_to_size_sx() updates the register state after a sign-extension
operation. However, there's a bug in the assignment order of the unsigned
min/max values, leading to incorrect truncation:

  0: (85) call bpf_get_prandom_u32#7    ; R0_w=scalar()
  1: (57) r0 &= 1                       ; R0_w=scalar(smin=smin32=0,smax=umax=smax32=umax32=1,var_off=(0x0; 0x1))
  2: (07) r0 += 254                     ; R0_w=scalar(smin=umin=smin32=umin32=254,smax=umax=smax32=umax32=255,var_off=(0xfe; 0x1))
  3: (bf) r0 = (s8)r0                   ; R0_w=scalar(smin=smin32=-2,smax=smax32=-1,umin=umin32=0xfffffffe,umax=0xffffffff,var_off=(0xfffffffffffffffe; 0x1))

In the current implementation, the unsigned 32-bit min/max values
(u32_min_value and u32_max_value) are assigned directly from the 64-bit
signed min/max values (s64_min and s64_max):

  reg->umin_value = reg->u32_min_value = s64_min;
  reg->umax_value = reg->u32_max_value = s64_max;

Due to the chain assigmnent, this is equivalent to:

  reg->u32_min_value = s64_min;  // Unintended truncation
  reg->umin_value = reg->u32_min_value;
  reg->u32_max_value = s64_max;  // Unintended truncation
  reg->umax_value = reg->u32_max_value;

Fixes: 1f9a1ea821ff ("bpf: Support new sign-extension load insns")
Reported-by: Shung-Hsi Yu <shung-hsi.yu@suse.com>
Reported-by: Zac Ecob <zacecob@protonmail.com>
Signed-off-by: Dimitar Kanaliev <dimitar.kanaliev@siteground.com>
Acked-by: Yonghong Song <yonghong.song@linux.dev>
Reviewed-by: Shung-Hsi Yu <shung-hsi.yu@suse.com>
Link: https://lore.kernel.org/r/20241014121155.92887-2-dimitar.kanaliev@siteground.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
kernel/bpf/verifier.c

index bf9996ea34fe1d80cef1c1ff3bbdb1030a976710..a8a0b6e4110ea579213800bd8cef70018333254d 100644 (file)
@@ -6339,10 +6339,10 @@ static void coerce_reg_to_size_sx(struct bpf_reg_state *reg, int size)
 
        /* both of s64_max/s64_min positive or negative */
        if ((s64_max >= 0) == (s64_min >= 0)) {
-               reg->smin_value = reg->s32_min_value = s64_min;
-               reg->smax_value = reg->s32_max_value = s64_max;
-               reg->umin_value = reg->u32_min_value = s64_min;
-               reg->umax_value = reg->u32_max_value = s64_max;
+               reg->s32_min_value = reg->smin_value = s64_min;
+               reg->s32_max_value = reg->smax_value = s64_max;
+               reg->u32_min_value = reg->umin_value = s64_min;
+               reg->u32_max_value = reg->umax_value = s64_max;
                reg->var_off = tnum_range(s64_min, s64_max);
                return;
        }