Skip to content

Commit 7574883

Browse files
Alexei Starovoitovborkmann
Alexei Starovoitov
authored andcommitted
bpf: Propagate scalar ranges through register assignments.
The llvm register allocator may use two different registers representing the same virtual register. In such case the following pattern can be observed: 1047: (bf) r9 = r6 1048: (a5) if r6 < 0x1000 goto pc+1 1050: ... 1051: (a5) if r9 < 0x2 goto pc+66 1052: ... 1053: (bf) r2 = r9 /* r2 needs to have upper and lower bounds */ This is normal behavior of greedy register allocator. The slides 137+ explain why regalloc introduces such register copy: http://llvm.org/devmtg/2018-04/slides/Yatsina-LLVM%20Greedy%20Register%20Allocator.pdf There is no way to tell llvm 'not to do this'. Hence the verifier has to recognize such patterns. In order to track this information without backtracking allocate ID for scalars in a similar way as it's done for find_good_pkt_pointers(). When the verifier encounters r9 = r6 assignment it will assign the same ID to both registers. Later if either register range is narrowed via conditional jump propagate the register state into the other register. Clear register ID in adjust_reg_min_max_vals() for any alu instruction. The register ID is ignored for scalars in regsafe() and doesn't affect state pruning. mark_reg_unknown() clears the ID. It's used to process call, endian and other instructions. Hence ID is explicitly cleared only in adjust_reg_min_max_vals() and in 32-bit mov. Signed-off-by: Alexei Starovoitov <[email protected]> Signed-off-by: Daniel Borkmann <[email protected]> Acked-by: Andrii Nakryiko <[email protected]> Acked-by: John Fastabend <[email protected]> Link: https://lore.kernel.org/bpf/[email protected]
1 parent eca43ee commit 7574883

File tree

3 files changed

+59
-9
lines changed

3 files changed

+59
-9
lines changed

kernel/bpf/verifier.c

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6436,6 +6436,11 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,
64366436
src_reg = NULL;
64376437
if (dst_reg->type != SCALAR_VALUE)
64386438
ptr_reg = dst_reg;
6439+
else
6440+
/* Make sure ID is cleared otherwise dst_reg min/max could be
6441+
* incorrectly propagated into other registers by find_equal_scalars()
6442+
*/
6443+
dst_reg->id = 0;
64396444
if (BPF_SRC(insn->code) == BPF_X) {
64406445
src_reg = &regs[insn->src_reg];
64416446
if (src_reg->type != SCALAR_VALUE) {
@@ -6569,6 +6574,12 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
65696574
/* case: R1 = R2
65706575
* copy register state to dest reg
65716576
*/
6577+
if (src_reg->type == SCALAR_VALUE && !src_reg->id)
6578+
/* Assign src and dst registers the same ID
6579+
* that will be used by find_equal_scalars()
6580+
* to propagate min/max range.
6581+
*/
6582+
src_reg->id = ++env->id_gen;
65726583
*dst_reg = *src_reg;
65736584
dst_reg->live |= REG_LIVE_WRITTEN;
65746585
dst_reg->subreg_def = DEF_NOT_SUBREG;
@@ -6581,6 +6592,11 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
65816592
return -EACCES;
65826593
} else if (src_reg->type == SCALAR_VALUE) {
65836594
*dst_reg = *src_reg;
6595+
/* Make sure ID is cleared otherwise
6596+
* dst_reg min/max could be incorrectly
6597+
* propagated into src_reg by find_equal_scalars()
6598+
*/
6599+
dst_reg->id = 0;
65846600
dst_reg->live |= REG_LIVE_WRITTEN;
65856601
dst_reg->subreg_def = env->insn_idx + 1;
65866602
} else {
@@ -7369,6 +7385,30 @@ static bool try_match_pkt_pointers(const struct bpf_insn *insn,
73697385
return true;
73707386
}
73717387

7388+
static void find_equal_scalars(struct bpf_verifier_state *vstate,
7389+
struct bpf_reg_state *known_reg)
7390+
{
7391+
struct bpf_func_state *state;
7392+
struct bpf_reg_state *reg;
7393+
int i, j;
7394+
7395+
for (i = 0; i <= vstate->curframe; i++) {
7396+
state = vstate->frame[i];
7397+
for (j = 0; j < MAX_BPF_REG; j++) {
7398+
reg = &state->regs[j];
7399+
if (reg->type == SCALAR_VALUE && reg->id == known_reg->id)
7400+
*reg = *known_reg;
7401+
}
7402+
7403+
bpf_for_each_spilled_reg(j, state, reg) {
7404+
if (!reg)
7405+
continue;
7406+
if (reg->type == SCALAR_VALUE && reg->id == known_reg->id)
7407+
*reg = *known_reg;
7408+
}
7409+
}
7410+
}
7411+
73727412
static int check_cond_jmp_op(struct bpf_verifier_env *env,
73737413
struct bpf_insn *insn, int *insn_idx)
73747414
{
@@ -7497,13 +7537,23 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env,
74977537
reg_combine_min_max(&other_branch_regs[insn->src_reg],
74987538
&other_branch_regs[insn->dst_reg],
74997539
src_reg, dst_reg, opcode);
7540+
if (src_reg->id) {
7541+
find_equal_scalars(this_branch, src_reg);
7542+
find_equal_scalars(other_branch, &other_branch_regs[insn->src_reg]);
7543+
}
7544+
75007545
}
75017546
} else if (dst_reg->type == SCALAR_VALUE) {
75027547
reg_set_min_max(&other_branch_regs[insn->dst_reg],
75037548
dst_reg, insn->imm, (u32)insn->imm,
75047549
opcode, is_jmp32);
75057550
}
75067551

7552+
if (dst_reg->type == SCALAR_VALUE && dst_reg->id) {
7553+
find_equal_scalars(this_branch, dst_reg);
7554+
find_equal_scalars(other_branch, &other_branch_regs[insn->dst_reg]);
7555+
}
7556+
75077557
/* detect if R == 0 where R is returned from bpf_map_lookup_elem().
75087558
* NOTE: these optimizations below are related with pointer comparison
75097559
* which will never be JMP32.

tools/testing/selftests/bpf/prog_tests/align.c

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -195,13 +195,13 @@ static struct bpf_align_test tests[] = {
195195
.prog_type = BPF_PROG_TYPE_SCHED_CLS,
196196
.matches = {
197197
{7, "R3_w=inv(id=0,umax_value=255,var_off=(0x0; 0xff))"},
198-
{8, "R4_w=inv(id=0,umax_value=255,var_off=(0x0; 0xff))"},
198+
{8, "R4_w=inv(id=1,umax_value=255,var_off=(0x0; 0xff))"},
199199
{9, "R4_w=inv(id=0,umax_value=255,var_off=(0x0; 0xff))"},
200-
{10, "R4_w=inv(id=0,umax_value=255,var_off=(0x0; 0xff))"},
200+
{10, "R4_w=inv(id=1,umax_value=255,var_off=(0x0; 0xff))"},
201201
{11, "R4_w=inv(id=0,umax_value=510,var_off=(0x0; 0x1fe))"},
202-
{12, "R4_w=inv(id=0,umax_value=255,var_off=(0x0; 0xff))"},
202+
{12, "R4_w=inv(id=1,umax_value=255,var_off=(0x0; 0xff))"},
203203
{13, "R4_w=inv(id=0,umax_value=1020,var_off=(0x0; 0x3fc))"},
204-
{14, "R4_w=inv(id=0,umax_value=255,var_off=(0x0; 0xff))"},
204+
{14, "R4_w=inv(id=1,umax_value=255,var_off=(0x0; 0xff))"},
205205
{15, "R4_w=inv(id=0,umax_value=2040,var_off=(0x0; 0x7f8))"},
206206
{16, "R4_w=inv(id=0,umax_value=4080,var_off=(0x0; 0xff0))"},
207207
},
@@ -518,7 +518,7 @@ static struct bpf_align_test tests[] = {
518518
* the total offset is 4-byte aligned and meets the
519519
* load's requirements.
520520
*/
521-
{20, "R5=pkt(id=1,off=0,r=4,umin_value=2,umax_value=1034,var_off=(0x2; 0x7fc)"},
521+
{20, "R5=pkt(id=2,off=0,r=4,umin_value=2,umax_value=1034,var_off=(0x2; 0x7fc)"},
522522

523523
},
524524
},
@@ -561,18 +561,18 @@ static struct bpf_align_test tests[] = {
561561
/* Adding 14 makes R6 be (4n+2) */
562562
{11, "R6_w=inv(id=0,umin_value=14,umax_value=74,var_off=(0x2; 0x7c))"},
563563
/* Subtracting from packet pointer overflows ubounds */
564-
{13, "R5_w=pkt(id=1,off=0,r=8,umin_value=18446744073709551542,umax_value=18446744073709551602,var_off=(0xffffffffffffff82; 0x7c)"},
564+
{13, "R5_w=pkt(id=2,off=0,r=8,umin_value=18446744073709551542,umax_value=18446744073709551602,var_off=(0xffffffffffffff82; 0x7c)"},
565565
/* New unknown value in R7 is (4n), >= 76 */
566566
{15, "R7_w=inv(id=0,umin_value=76,umax_value=1096,var_off=(0x0; 0x7fc))"},
567567
/* Adding it to packet pointer gives nice bounds again */
568-
{16, "R5_w=pkt(id=2,off=0,r=0,umin_value=2,umax_value=1082,var_off=(0x2; 0xfffffffc)"},
568+
{16, "R5_w=pkt(id=3,off=0,r=0,umin_value=2,umax_value=1082,var_off=(0x2; 0xfffffffc)"},
569569
/* At the time the word size load is performed from R5,
570570
* its total fixed offset is NET_IP_ALIGN + reg->off (0)
571571
* which is 2. Then the variable offset is (4n+2), so
572572
* the total offset is 4-byte aligned and meets the
573573
* load's requirements.
574574
*/
575-
{20, "R5=pkt(id=2,off=0,r=4,umin_value=2,umax_value=1082,var_off=(0x2; 0xfffffffc)"},
575+
{20, "R5=pkt(id=3,off=0,r=4,umin_value=2,umax_value=1082,var_off=(0x2; 0xfffffffc)"},
576576
},
577577
},
578578
};

tools/testing/selftests/bpf/verifier/direct_packet_access.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,7 @@
529529
},
530530
.prog_type = BPF_PROG_TYPE_SCHED_CLS,
531531
.result = REJECT,
532-
.errstr = "invalid access to packet, off=0 size=8, R5(id=1,off=0,r=0)",
532+
.errstr = "invalid access to packet, off=0 size=8, R5(id=2,off=0,r=0)",
533533
.flags = F_NEEDS_EFFICIENT_UNALIGNED_ACCESS,
534534
},
535535
{

0 commit comments

Comments
 (0)