Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 94079b64 authored by Daniel Borkmann's avatar Daniel Borkmann
Browse files

Merge branch 'bpf-bounded-loops'



Alexei Starovoitov says:

====================
v2->v3: fixed issues in backtracking pointed out by Andrii.
The next step is to add a lot more tests for backtracking.

v1->v2: addressed Andrii's feedback.

this patch set introduces verifier support for bounded loops and
adds several other improvements.
Ideally they would be introduced one at a time,
but to support bounded loop the verifier needs to 'step back'
in the patch 1. That patch introduces tracking of spill/fill
of constants through the stack. Though it's a useful feature
it hurts cilium tests.
Patch 3 introduces another feature by extending is_branch_taken
logic to 'if rX op rY' conditions. This feature is also
necessary to support bounded loops.
Then patch 4 adds support for the loops while adding
key heuristics with jmp_processed.
Introduction of parentage chain of verifier states in patch 4
allows patch 9 to add backtracking of precise scalar registers
which finally resolves degradation from patch 1.

The end result is much faster verifier for existing programs
and new support for loops.
See patch 8 for many kinds of loops that are now validated.
Patch 9 is the most tricky one and could be rewritten with
a different algorithm in the future.
====================

Signed-off-by: default avatarDaniel Borkmann <daniel@iogearbox.net>
parents a324aae3 b5dc0163
Loading
Loading
Loading
Loading
+68 −1
Original line number Diff line number Diff line
@@ -139,6 +139,8 @@ struct bpf_reg_state {
	 */
	s32 subreg_def;
	enum bpf_reg_liveness live;
	/* if (!precise && SCALAR_VALUE) min/max/tnum don't affect safety */
	bool precise;
};

enum bpf_stack_slot_type {
@@ -190,14 +192,77 @@ struct bpf_func_state {
	struct bpf_stack_state *stack;
};

struct bpf_idx_pair {
	u32 prev_idx;
	u32 idx;
};

#define MAX_CALL_FRAMES 8
struct bpf_verifier_state {
	/* call stack tracking */
	struct bpf_func_state *frame[MAX_CALL_FRAMES];
	struct bpf_verifier_state *parent;
	/*
	 * 'branches' field is the number of branches left to explore:
	 * 0 - all possible paths from this state reached bpf_exit or
	 * were safely pruned
	 * 1 - at least one path is being explored.
	 * This state hasn't reached bpf_exit
	 * 2 - at least two paths are being explored.
	 * This state is an immediate parent of two children.
	 * One is fallthrough branch with branches==1 and another
	 * state is pushed into stack (to be explored later) also with
	 * branches==1. The parent of this state has branches==1.
	 * The verifier state tree connected via 'parent' pointer looks like:
	 * 1
	 * 1
	 * 2 -> 1 (first 'if' pushed into stack)
	 * 1
	 * 2 -> 1 (second 'if' pushed into stack)
	 * 1
	 * 1
	 * 1 bpf_exit.
	 *
	 * Once do_check() reaches bpf_exit, it calls update_branch_counts()
	 * and the verifier state tree will look:
	 * 1
	 * 1
	 * 2 -> 1 (first 'if' pushed into stack)
	 * 1
	 * 1 -> 1 (second 'if' pushed into stack)
	 * 0
	 * 0
	 * 0 bpf_exit.
	 * After pop_stack() the do_check() will resume at second 'if'.
	 *
	 * If is_state_visited() sees a state with branches > 0 it means
	 * there is a loop. If such state is exactly equal to the current state
	 * it's an infinite loop. Note states_equal() checks for states
	 * equvalency, so two states being 'states_equal' does not mean
	 * infinite loop. The exact comparison is provided by
	 * states_maybe_looping() function. It's a stronger pre-check and
	 * much faster than states_equal().
	 *
	 * This algorithm may not find all possible infinite loops or
	 * loop iteration count may be too high.
	 * In such cases BPF_COMPLEXITY_LIMIT_INSNS limit kicks in.
	 */
	u32 branches;
	u32 insn_idx;
	u32 curframe;
	u32 active_spin_lock;
	bool speculative;

	/* first and last insn idx of this verifier state */
	u32 first_insn_idx;
	u32 last_insn_idx;
	/* jmp history recorded from first to last.
	 * backtracking is using it to go from last to first.
	 * For most states jmp_history_cnt is [0-3].
	 * For loops can go up to ~40.
	 */
	struct bpf_idx_pair *jmp_history;
	u32 jmp_history_cnt;
};

#define bpf_get_spilled_reg(slot, frame)				\
@@ -312,7 +377,9 @@ struct bpf_verifier_env {
	} cfg;
	u32 subprog_cnt;
	/* number of instructions analyzed by the verifier */
	u32 insn_processed;
	u32 prev_insn_processed, insn_processed;
	/* number of jmps, calls, exits analyzed so far */
	u32 prev_jmps_processed, jmps_processed;
	/* total verification time */
	u64 verification_time;
	/* maximum number of verifier states kept in 'branching' instructions */
+700 −67

File changed.

Preview size limit exceeded, changes collapsed.

+56 −11
Original line number Diff line number Diff line
@@ -5,7 +5,7 @@ static int libbpf_debug_print(enum libbpf_print_level level,
			      const char *format, va_list args)
{
	if (level != LIBBPF_DEBUG)
		return 0;
		return vfprintf(stderr, format, args);

	if (!strstr(format, "verifier log"))
		return 0;
@@ -32,24 +32,69 @@ static int check_load(const char *file, enum bpf_prog_type type)

void test_bpf_verif_scale(void)
{
	const char *scale[] = {
		"./test_verif_scale1.o", "./test_verif_scale2.o", "./test_verif_scale3.o"
	const char *sched_cls[] = {
		"./test_verif_scale1.o", "./test_verif_scale2.o", "./test_verif_scale3.o",
	};
	const char *pyperf[] = {
		"./pyperf50.o",	"./pyperf100.o", "./pyperf180.o"
	const char *raw_tp[] = {
		/* full unroll by llvm */
		"./pyperf50.o",	"./pyperf100.o", "./pyperf180.o",

		/* partial unroll. llvm will unroll loop ~150 times.
		 * C loop count -> 600.
		 * Asm loop count -> 4.
		 * 16k insns in loop body.
		 * Total of 5 such loops. Total program size ~82k insns.
		 */
		"./pyperf600.o",

		/* no unroll at all.
		 * C loop count -> 600.
		 * ASM loop count -> 600.
		 * ~110 insns in loop body.
		 * Total of 5 such loops. Total program size ~1500 insns.
		 */
		"./pyperf600_nounroll.o",

		"./loop1.o", "./loop2.o",

		/* partial unroll. 19k insn in a loop.
		 * Total program size 20.8k insn.
		 * ~350k processed_insns
		 */
		"./strobemeta.o",

		/* no unroll, tiny loops */
		"./strobemeta_nounroll1.o",
		"./strobemeta_nounroll2.o",
	};
	const char *cg_sysctl[] = {
		"./test_sysctl_loop1.o", "./test_sysctl_loop2.o",
	};
	int err, i;

	if (verifier_stats)
		libbpf_set_print(libbpf_debug_print);

	for (i = 0; i < ARRAY_SIZE(scale); i++) {
		err = check_load(scale[i], BPF_PROG_TYPE_SCHED_CLS);
		printf("test_scale:%s:%s\n", scale[i], err ? "FAIL" : "OK");
	err = check_load("./loop3.o", BPF_PROG_TYPE_RAW_TRACEPOINT);
	printf("test_scale:loop3:%s\n", err ? (error_cnt--, "OK") : "FAIL");

	for (i = 0; i < ARRAY_SIZE(sched_cls); i++) {
		err = check_load(sched_cls[i], BPF_PROG_TYPE_SCHED_CLS);
		printf("test_scale:%s:%s\n", sched_cls[i], err ? "FAIL" : "OK");
	}

	for (i = 0; i < ARRAY_SIZE(pyperf); i++) {
		err = check_load(pyperf[i], BPF_PROG_TYPE_RAW_TRACEPOINT);
		printf("test_scale:%s:%s\n", pyperf[i], err ? "FAIL" : "OK");
	for (i = 0; i < ARRAY_SIZE(raw_tp); i++) {
		err = check_load(raw_tp[i], BPF_PROG_TYPE_RAW_TRACEPOINT);
		printf("test_scale:%s:%s\n", raw_tp[i], err ? "FAIL" : "OK");
	}

	for (i = 0; i < ARRAY_SIZE(cg_sysctl); i++) {
		err = check_load(cg_sysctl[i], BPF_PROG_TYPE_CGROUP_SYSCTL);
		printf("test_scale:%s:%s\n", cg_sysctl[i], err ? "FAIL" : "OK");
	}
	err = check_load("./test_xdp_loop.o", BPF_PROG_TYPE_XDP);
	printf("test_scale:test_xdp_loop:%s\n", err ? "FAIL" : "OK");

	err = check_load("./test_seg6_loop.o", BPF_PROG_TYPE_LWT_SEG6LOCAL);
	printf("test_scale:test_seg6_loop:%s\n", err ? "FAIL" : "OK");
}
+28 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2019 Facebook
#include <linux/sched.h>
#include <linux/ptrace.h>
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <linux/bpf.h>
#include "bpf_helpers.h"

char _license[] SEC("license") = "GPL";

SEC("raw_tracepoint/kfree_skb")
int nested_loops(volatile struct pt_regs* ctx)
{
	int i, j, sum = 0, m;

	for (j = 0; j < 300; j++)
		for (i = 0; i < j; i++) {
			if (j & 1)
				m = ctx->rax;
			else
				m = j;
			sum += i * m;
		}

	return sum;
}
+28 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2019 Facebook
#include <linux/sched.h>
#include <linux/ptrace.h>
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <linux/bpf.h>
#include "bpf_helpers.h"

char _license[] SEC("license") = "GPL";

SEC("raw_tracepoint/consume_skb")
int while_true(volatile struct pt_regs* ctx)
{
	int i = 0;

	while (true) {
		if (ctx->rax & 1)
			i += 3;
		else
			i += 7;
		if (i > 40)
			break;
	}

	return i;
}
Loading