Skip to content

Commit ac53a0d

Browse files
committed
Merge branch 'bpf-llvm-reg-alloc-patterns'
Alexei Starovoitov says: ==================== Make two verifier improvements: - The llvm register allocator may use two different registers representing the same virtual register. Teach the verifier to recognize that. - Track bounded scalar spill/fill. The profiler[123] test in patch 3 will fail to load without patches 1 and 2. The profiler[23] test may fail to load on older llvm due to speculative code motion nd instruction combining optimizations that are fixed in https://reviews.llvm.org/D85570 v1 -> v2: - fixed 32-bit mov issue spotted by John. - allowed r2=r1; r3=r2; sequence as suggested by John. - added comments, acks, more tests. ==================== Signed-off-by: Daniel Borkmann <[email protected]>
2 parents eca43ee + 54fada4 commit ac53a0d

File tree

11 files changed

+1591
-10
lines changed

11 files changed

+1591
-10
lines changed

kernel/bpf/verifier.c

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2227,6 +2227,20 @@ static bool register_is_const(struct bpf_reg_state *reg)
22272227
return reg->type == SCALAR_VALUE && tnum_is_const(reg->var_off);
22282228
}
22292229

2230+
static bool __is_scalar_unbounded(struct bpf_reg_state *reg)
2231+
{
2232+
return tnum_is_unknown(reg->var_off) &&
2233+
reg->smin_value == S64_MIN && reg->smax_value == S64_MAX &&
2234+
reg->umin_value == 0 && reg->umax_value == U64_MAX &&
2235+
reg->s32_min_value == S32_MIN && reg->s32_max_value == S32_MAX &&
2236+
reg->u32_min_value == 0 && reg->u32_max_value == U32_MAX;
2237+
}
2238+
2239+
static bool register_is_bounded(struct bpf_reg_state *reg)
2240+
{
2241+
return reg->type == SCALAR_VALUE && !__is_scalar_unbounded(reg);
2242+
}
2243+
22302244
static bool __is_pointer_value(bool allow_ptr_leaks,
22312245
const struct bpf_reg_state *reg)
22322246
{
@@ -2278,7 +2292,7 @@ static int check_stack_write(struct bpf_verifier_env *env,
22782292
if (value_regno >= 0)
22792293
reg = &cur->regs[value_regno];
22802294

2281-
if (reg && size == BPF_REG_SIZE && register_is_const(reg) &&
2295+
if (reg && size == BPF_REG_SIZE && register_is_bounded(reg) &&
22822296
!register_is_null(reg) && env->bpf_capable) {
22832297
if (dst_reg != BPF_REG_FP) {
22842298
/* The backtracking logic can only recognize explicit
@@ -6436,6 +6450,11 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,
64366450
src_reg = NULL;
64376451
if (dst_reg->type != SCALAR_VALUE)
64386452
ptr_reg = dst_reg;
6453+
else
6454+
/* Make sure ID is cleared otherwise dst_reg min/max could be
6455+
* incorrectly propagated into other registers by find_equal_scalars()
6456+
*/
6457+
dst_reg->id = 0;
64396458
if (BPF_SRC(insn->code) == BPF_X) {
64406459
src_reg = &regs[insn->src_reg];
64416460
if (src_reg->type != SCALAR_VALUE) {
@@ -6569,6 +6588,12 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
65696588
/* case: R1 = R2
65706589
* copy register state to dest reg
65716590
*/
6591+
if (src_reg->type == SCALAR_VALUE && !src_reg->id)
6592+
/* Assign src and dst registers the same ID
6593+
* that will be used by find_equal_scalars()
6594+
* to propagate min/max range.
6595+
*/
6596+
src_reg->id = ++env->id_gen;
65726597
*dst_reg = *src_reg;
65736598
dst_reg->live |= REG_LIVE_WRITTEN;
65746599
dst_reg->subreg_def = DEF_NOT_SUBREG;
@@ -6581,6 +6606,11 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
65816606
return -EACCES;
65826607
} else if (src_reg->type == SCALAR_VALUE) {
65836608
*dst_reg = *src_reg;
6609+
/* Make sure ID is cleared otherwise
6610+
* dst_reg min/max could be incorrectly
6611+
* propagated into src_reg by find_equal_scalars()
6612+
*/
6613+
dst_reg->id = 0;
65846614
dst_reg->live |= REG_LIVE_WRITTEN;
65856615
dst_reg->subreg_def = env->insn_idx + 1;
65866616
} else {
@@ -7369,6 +7399,30 @@ static bool try_match_pkt_pointers(const struct bpf_insn *insn,
73697399
return true;
73707400
}
73717401

7402+
static void find_equal_scalars(struct bpf_verifier_state *vstate,
7403+
struct bpf_reg_state *known_reg)
7404+
{
7405+
struct bpf_func_state *state;
7406+
struct bpf_reg_state *reg;
7407+
int i, j;
7408+
7409+
for (i = 0; i <= vstate->curframe; i++) {
7410+
state = vstate->frame[i];
7411+
for (j = 0; j < MAX_BPF_REG; j++) {
7412+
reg = &state->regs[j];
7413+
if (reg->type == SCALAR_VALUE && reg->id == known_reg->id)
7414+
*reg = *known_reg;
7415+
}
7416+
7417+
bpf_for_each_spilled_reg(j, state, reg) {
7418+
if (!reg)
7419+
continue;
7420+
if (reg->type == SCALAR_VALUE && reg->id == known_reg->id)
7421+
*reg = *known_reg;
7422+
}
7423+
}
7424+
}
7425+
73727426
static int check_cond_jmp_op(struct bpf_verifier_env *env,
73737427
struct bpf_insn *insn, int *insn_idx)
73747428
{
@@ -7497,13 +7551,23 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env,
74977551
reg_combine_min_max(&other_branch_regs[insn->src_reg],
74987552
&other_branch_regs[insn->dst_reg],
74997553
src_reg, dst_reg, opcode);
7554+
if (src_reg->id) {
7555+
find_equal_scalars(this_branch, src_reg);
7556+
find_equal_scalars(other_branch, &other_branch_regs[insn->src_reg]);
7557+
}
7558+
75007559
}
75017560
} else if (dst_reg->type == SCALAR_VALUE) {
75027561
reg_set_min_max(&other_branch_regs[insn->dst_reg],
75037562
dst_reg, insn->imm, (u32)insn->imm,
75047563
opcode, is_jmp32);
75057564
}
75067565

7566+
if (dst_reg->type == SCALAR_VALUE && dst_reg->id) {
7567+
find_equal_scalars(this_branch, dst_reg);
7568+
find_equal_scalars(other_branch, &other_branch_regs[insn->dst_reg]);
7569+
}
7570+
75077571
/* detect if R == 0 where R is returned from bpf_map_lookup_elem().
75087572
* NOTE: these optimizations below are related with pointer comparison
75097573
* which will never be JMP32.

tools/testing/selftests/bpf/README.rst

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,44 @@ General instructions on running selftests can be found in
77
Additional information about selftest failures are
88
documented here.
99

10+
profiler[23] test failures with clang/llvm <12.0.0
11+
==================================================
12+
13+
With clang/llvm <12.0.0, the profiler[23] test may fail.
14+
The symptom looks like
15+
16+
.. code-block:: c
17+
18+
// r9 is a pointer to map_value
19+
// r7 is a scalar
20+
17: bf 96 00 00 00 00 00 00 r6 = r9
21+
18: 0f 76 00 00 00 00 00 00 r6 += r7
22+
math between map_value pointer and register with unbounded min value is not allowed
23+
24+
// the instructions below will not be seen in the verifier log
25+
19: a5 07 01 00 01 01 00 00 if r7 < 257 goto +1
26+
20: bf 96 00 00 00 00 00 00 r6 = r9
27+
// r6 is used here
28+
29+
The verifier will reject such code with above error.
30+
At insn 18 the r7 is indeed unbounded. The later insn 19 checks the bounds and
31+
the insn 20 undoes map_value addition. It is currently impossible for the
32+
verifier to understand such speculative pointer arithmetic.
33+
Hence
34+
https://reviews.llvm.org/D85570
35+
addresses it on the compiler side. It was committed on llvm 12.
36+
37+
The corresponding C code
38+
.. code-block:: c
39+
40+
for (int i = 0; i < MAX_CGROUPS_PATH_DEPTH; i++) {
41+
filepart_length = bpf_probe_read_str(payload, ...);
42+
if (filepart_length <= MAX_PATH) {
43+
barrier_var(filepart_length); // workaround
44+
payload += filepart_length;
45+
}
46+
}
47+
1048
bpf_iter test failures with clang/llvm 10.0.0
1149
=============================================
1250

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
};
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/* Copyright (c) 2020 Facebook */
3+
#include <test_progs.h>
4+
#include "progs/profiler.h"
5+
#include "profiler1.skel.h"
6+
#include "profiler2.skel.h"
7+
#include "profiler3.skel.h"
8+
9+
static int sanity_run(struct bpf_program *prog)
10+
{
11+
struct bpf_prog_test_run_attr test_attr = {};
12+
__u64 args[] = {1, 2, 3};
13+
__u32 duration = 0;
14+
int err, prog_fd;
15+
16+
prog_fd = bpf_program__fd(prog);
17+
test_attr.prog_fd = prog_fd;
18+
test_attr.ctx_in = args;
19+
test_attr.ctx_size_in = sizeof(args);
20+
err = bpf_prog_test_run_xattr(&test_attr);
21+
if (CHECK(err || test_attr.retval, "test_run",
22+
"err %d errno %d retval %d duration %d\n",
23+
err, errno, test_attr.retval, duration))
24+
return -1;
25+
return 0;
26+
}
27+
28+
void test_test_profiler(void)
29+
{
30+
struct profiler1 *profiler1_skel = NULL;
31+
struct profiler2 *profiler2_skel = NULL;
32+
struct profiler3 *profiler3_skel = NULL;
33+
__u32 duration = 0;
34+
int err;
35+
36+
profiler1_skel = profiler1__open_and_load();
37+
if (CHECK(!profiler1_skel, "profiler1_skel_load", "profiler1 skeleton failed\n"))
38+
goto cleanup;
39+
40+
err = profiler1__attach(profiler1_skel);
41+
if (CHECK(err, "profiler1_attach", "profiler1 attach failed: %d\n", err))
42+
goto cleanup;
43+
44+
if (sanity_run(profiler1_skel->progs.raw_tracepoint__sched_process_exec))
45+
goto cleanup;
46+
47+
profiler2_skel = profiler2__open_and_load();
48+
if (CHECK(!profiler2_skel, "profiler2_skel_load", "profiler2 skeleton failed\n"))
49+
goto cleanup;
50+
51+
err = profiler2__attach(profiler2_skel);
52+
if (CHECK(err, "profiler2_attach", "profiler2 attach failed: %d\n", err))
53+
goto cleanup;
54+
55+
if (sanity_run(profiler2_skel->progs.raw_tracepoint__sched_process_exec))
56+
goto cleanup;
57+
58+
profiler3_skel = profiler3__open_and_load();
59+
if (CHECK(!profiler3_skel, "profiler3_skel_load", "profiler3 skeleton failed\n"))
60+
goto cleanup;
61+
62+
err = profiler3__attach(profiler3_skel);
63+
if (CHECK(err, "profiler3_attach", "profiler3 attach failed: %d\n", err))
64+
goto cleanup;
65+
66+
if (sanity_run(profiler3_skel->progs.raw_tracepoint__sched_process_exec))
67+
goto cleanup;
68+
cleanup:
69+
profiler1__destroy(profiler1_skel);
70+
profiler2__destroy(profiler2_skel);
71+
profiler3__destroy(profiler3_skel);
72+
}

0 commit comments

Comments
 (0)