bpf: Fix u32/s32 bounds when ranges cross min/max boundary

Same as in __reg64_deduce_bounds(), refine s32/u32 ranges
in __reg32_deduce_bounds() in the following situations:

- s32 range crosses U32_MAX/0 boundary, positive part of the s32 range
  overlaps with u32 range:

  0                                                   U32_MAX
  |  [xxxxxxxxxxxxxx u32 range xxxxxxxxxxxxxx]              |
  |----------------------------|----------------------------|
  |xxxxx s32 range xxxxxxxxx]                       [xxxxxxx|
  0                     S32_MAX S32_MIN                    -1

- s32 range crosses U32_MAX/0 boundary, negative part of the s32 range
  overlaps with u32 range:

  0                                                   U32_MAX
  |              [xxxxxxxxxxxxxx u32 range xxxxxxxxxxxxxx]  |
  |----------------------------|----------------------------|
  |xxxxxxxxx]                       [xxxxxxxxxxxx s32 range |
  0                     S32_MAX S32_MIN                    -1

- No refinement if ranges overlap in two intervals.

This helps for e.g. consider the following program:

   call %[bpf_get_prandom_u32];
   w0 &= 0xffffffff;
   if w0 < 0x3 goto 1f;    // on fall-through u32 range [3..U32_MAX]
   if w0 s> 0x1 goto 1f;   // on fall-through s32 range [S32_MIN..1]
   if w0 s< 0x0 goto 1f;   // range can be narrowed to  [S32_MIN..-1]
   r10 = 0;
1: ...;

The reg_bounds.c selftest is updated to incorporate identical logic,
refinement based on non-overflowing range halves:

  ((x ∩ [0, smax]) ∩ (y ∩ [0, smax])) ∪
  ((x ∩ [smin,-1]) ∩ (y ∩ [smin,-1]))

Reported-by: Andrea Righi <arighi@nvidia.com>
Reported-by: Emil Tsalapatis <emil@etsalapatis.com>
Closes: https://lore.kernel.org/bpf/aakqucg4vcujVwif@gpd4/T/
Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
Acked-by: Shung-Hsi Yu <shung-hsi.yu@suse.com>
Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
Link: https://lore.kernel.org/r/20260306-bpf-32-bit-range-overflow-v3-1-f7f67e060a6b@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
Eduard Zingerman 2026-03-06 16:54:24 -08:00 committed by Alexei Starovoitov
parent 56145d2373
commit fbc7aef517
2 changed files with 82 additions and 4 deletions

View file

@ -2511,6 +2511,30 @@ static void __reg32_deduce_bounds(struct bpf_reg_state *reg)
if ((u32)reg->s32_min_value <= (u32)reg->s32_max_value) {
reg->u32_min_value = max_t(u32, reg->s32_min_value, reg->u32_min_value);
reg->u32_max_value = min_t(u32, reg->s32_max_value, reg->u32_max_value);
} else {
if (reg->u32_max_value < (u32)reg->s32_min_value) {
/* See __reg64_deduce_bounds() for detailed explanation.
* Refine ranges in the following situation:
*
* 0 U32_MAX
* | [xxxxxxxxxxxxxx u32 range xxxxxxxxxxxxxx] |
* |----------------------------|----------------------------|
* |xxxxx s32 range xxxxxxxxx] [xxxxxxx|
* 0 S32_MAX S32_MIN -1
*/
reg->s32_min_value = (s32)reg->u32_min_value;
reg->u32_max_value = min_t(u32, reg->u32_max_value, reg->s32_max_value);
} else if ((u32)reg->s32_max_value < reg->u32_min_value) {
/*
* 0 U32_MAX
* | [xxxxxxxxxxxxxx u32 range xxxxxxxxxxxxxx] |
* |----------------------------|----------------------------|
* |xxxxxxxxx] [xxxxxxxxxxxx s32 range |
* 0 S32_MAX S32_MIN -1
*/
reg->s32_max_value = (s32)reg->u32_max_value;
reg->u32_min_value = max_t(u32, reg->u32_min_value, reg->s32_min_value);
}
}
}