From: Alexei Starovoitov After a prog compares 'pkt + 54' with pkt_end, the verifier knows that 54 bytes are in the packet. It keeps this number in the pointer as the range (r=54 in the log). The range stays when the prog adds a constant to the pointer. But the verifier clears the range when the prog adds a scalar that is not a constant, although the scalar has an upper bound: if (pkt + 54 > pkt_end || off > 38) return 0; *(u64 *)(pkt + off) rejected: pkt has r=54, the sum has r=0 A prog with 'off = c ? 38 : 22' loads only because the verifier follows the two paths separately, and each path adds a constant. "bpf: Add join of verifier states" makes one state from the two, with off=[22,38]. Then the verifier rejects this prog too. Keep the range when the scalar cannot be negative. The verifier accepts a load if umax + insn->off + size <= range, where umax is the largest offset of the pointer. Thus it accepts range - umax bytes after the pointer. A scalar in [0, N] adds N to umax, and it moves the pointer by N at most: pkt pkt + off pkt + 38 pkt + 54 | | | | the sum its umax its range |<-- 16 bytes ->| accepted |<------- 54 - off bytes -------->| in the packet The verifier accepts 54 - 38 bytes after the sum. 54 - off bytes are in the packet, and off <= 38. This is correct only if umax of the sum is exactly the sum of the two umax. reg_bounds_sync() can make umax smaller because of var_off. Commit b88822d2584f ("bpf: Fix packet range of pointers sharing an id") skips reg_bounds_sync() for a packet pointer that has a range, thus also for the sum. The sum gets a new id, as before. Thus a comparison of the sum with pkt_end does not change the range of pkt. Signed-off-by: Alexei Starovoitov --- kernel/bpf/verifier.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 4752a4619035..8f5aa77e4ecc 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -16546,12 +16546,16 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env, struct bpf_insn if (!known) dst_reg->id = ++env->id_gen; /* - * Clear range for unknown addends since we can't know - * where the pkt pointer ended up. Also clear AT_PKT_END / - * BEYOND_PKT_END from prior comparison as any pointer - * arithmetic invalidates them. + * The range is what umax of the pointer may get to, see + * check_packet_access(). umax of the addend was added to it + * and the pointer moved by that much at most, so the range + * holds. Clear it if the addend may be negative: umax says + * nothing then, or if nothing is left of it. Also clear + * AT_PKT_END / BEYOND_PKT_END from prior comparison as any + * pointer arithmetic invalidates them. */ - if (!known || dst_reg->range < 0) + if (dst_reg->range < 0 || + (!known && (smin_val < 0 || reg_umax(dst_reg) >= dst_reg->range))) memset(&dst_reg->raw, 0, sizeof(dst_reg->raw)); } break; -- 2.55.0