Similarly as b02709587ea3 ("bpf: Fix propagation of 32-bit signed bounds from 64-bit bounds."), we also need to fix the propagation of 32 bit unsigned bounds from 64 bit counterparts. That is, really only set the u32_{min,max}_value when /both/ {umin,umax}_value safely fit in 32 bit space. For example, the register with a umin_value == 1 does /not/ imply that u32_min_value is also equal to 1, since umax_value could be much larger than 32 bit subregister can hold, and thus u32_min_value is in the interval [0,1] instead. Before fix, invalid tracking result of R2_w=inv1: [...] 5: R0_w=inv1337 R1=ctx(id=0,off=0,imm=0) R2_w=inv(id=0) R10=fp0 5: (35) if r2 >= 0x1 goto pc+1 [...] // goto path 7: R0=inv1337 R1=ctx(id=0,off=0,imm=0) R2=inv(id=0,umin_value=1) R10=fp0 7: (b6) if w2 <= 0x1 goto pc+1 [...] // goto path 9: R0=inv1337 R1=ctx(id=0,off=0,imm=0) R2=inv(id=0,smin_value=-9223372036854775807,smax_value=9223372032559808513,umin_value=1,umax_value=18446744069414584321,var_off=(0x1; 0xffffffff00000000),s32_min_value=1,s32_max_value=1,u32_max_value=1) R10=fp0 9: (bc) w2 = w2 10: R0=inv1337 R1=ctx(id=0,off=0,imm=0) R2_w=inv1 R10=fp0 [...] After fix, correct tracking result of R2_w=inv(id=0,umax_value=1,var_off=(0x0; 0x1)): [...] 5: R0_w=inv1337 R1=ctx(id=0,off=0,imm=0) R2_w=inv(id=0) R10=fp0 5: (35) if r2 >= 0x1 goto pc+1 [...] // goto path 7: R0=inv1337 R1=ctx(id=0,off=0,imm=0) R2=inv(id=0,umin_value=1) R10=fp0 7: (b6) if w2 <= 0x1 goto pc+1 [...] // goto path 9: R0=inv1337 R1=ctx(id=0,off=0,imm=0) R2=inv(id=0,smax_value=9223372032559808513,umax_value=18446744069414584321,var_off=(0x0; 0xffffffff00000001),s32_min_value=0,s32_max_value=1,u32_max_value=1) R10=fp0 9: (bc) w2 = w2 10: R0=inv1337 R1=ctx(id=0,off=0,imm=0) R2_w=inv(id=0,umax_value=1,var_off=(0x0; 0x1)) R10=fp0 [...] Thus, same issue as in b02709587ea3 holds for unsigned subregister tracking. Also, align __reg64_bound_u32() similarly to __reg64_bound_s32() as done in b02709587ea3 to make them uniform again.
Unified · Split
| @@ -1398,9 +1398,7 @@ static bool __reg64_bound_s32(s64 a) · 1397 unmodified lines | ||
| 1398 | 1398 | |
| 1399 | 1399 | static bool __reg64_bound_u32(u64 a) |
| 1400 | 1400 | { |
| 1401 | − if (a > U32_MIN && a < U32_MAX) | |
| 1402 | − return true; | |
| 1403 | − return false; | |
| 1401 | + return a > U32_MIN && a < U32_MAX; | |
| 1404 | 1402 | } |
| 1405 | 1403 | |
| 1406 | 1404 | static void __reg_combine_64_into_32(struct bpf_reg_state *reg) |
| @@ -1411,10 +1409,10 @@ static void __reg_combine_64_into_32(struct bpf_reg_state *reg) · 4 unmodified lines | ||
| 1411 | 1409 | reg->s32_min_value = (s32)reg->smin_value; |
| 1412 | 1410 | reg->s32_max_value = (s32)reg->smax_value; |
| 1413 | 1411 | } |
| 1414 | − if (__reg64_bound_u32(reg->umin_value)) | |
| 1412 | + if (__reg64_bound_u32(reg->umin_value) && __reg64_bound_u32(reg->umax_value)) { | |
| 1415 | 1413 | reg->u32_min_value = (u32)reg->umin_value; |
| 1416 | − if (__reg64_bound_u32(reg->umax_value)) | |
| 1417 | 1414 | reg->u32_max_value = (u32)reg->umax_value; |
| 1415 | + } | |
| 1418 | 1416 | |
| 1419 | 1417 | /* Intersecting with the old var_off might have improved our bounds |
| 1420 | 1418 | * slightly. e.g. if umax was 0x7f...f and var_off was (0; 0xf...fc), |
| @@ -186,7 +186,7 @@ · 185 unmodified lines | ||
| 186 | 186 | }, |
| 187 | 187 | .fixup_map_hash_48b = { 3 }, |
| 188 | 188 | .errstr_unpriv = "R0 leaks addr", |
| 189 | − .errstr = "invalid access to map value, value_size=48 off=44 size=8", | |
| 189 | + .errstr = "R0 unbounded memory access", | |
| 190 | 190 | .result_unpriv = REJECT, |
| 191 | 191 | .result = REJECT, |
| 192 | 192 | .flags = F_NEEDS_EFFICIENT_UNALIGNED_ACCESS, |