920 lines · 26.1 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */#include <linux/bpf.h>#include <linux/bpf_verifier.h>#include <linux/filter.h>#include <linux/sort.h> #include "diagnostics.h" #define verbose(env, fmt, args...) bpf_verifier_log_write(env, fmt, ##args) /* non-recursive DFS pseudo code * 1  procedure DFS-iterative(G,v): * 2      label v as discovered * 3      let S be a stack * 4      S.push(v) * 5      while S is not empty * 6            t <- S.peek() * 7            if t is what we're looking for: * 8                return t * 9            for all edges e in G.adjacentEdges(t) do * 10               if edge e is already labelled * 11                   continue with the next edge * 12               w <- G.adjacentVertex(t,e) * 13               if vertex w is not discovered and not explored * 14                   label e as tree-edge * 15                   label w as discovered * 16                   S.push(w) * 17                   continue at 5 * 18               else if vertex w is discovered * 19                   label e as back-edge * 20               else * 21                   // vertex w is explored * 22                   label e as forward- or cross-edge * 23           label t as explored * 24           S.pop() * * convention: * 0x10 - discovered * 0x11 - discovered and fall-through edge labelled * 0x12 - discovered and fall-through and branch edges labelled * 0x20 - explored */ enum {	DISCOVERED = 0x10,	EXPLORED = 0x20,	FALLTHROUGH = 1,	BRANCH = 2,}; static void mark_subprog_changes_pkt_data(struct bpf_verifier_env *env, int off){	struct bpf_subprog_info *subprog; 	subprog = bpf_find_containing_subprog(env, off);	subprog->changes_pkt_data = true;} static void mark_subprog_might_sleep(struct bpf_verifier_env *env, int off){	struct bpf_subprog_info *subprog; 	subprog = bpf_find_containing_subprog(env, off);	subprog->might_sleep = true;} static void mark_subprog_might_throw(struct bpf_verifier_env *env, int off){	struct bpf_subprog_info *subprog; 	subprog = bpf_find_containing_subprog(env, off);	subprog->might_throw = true;} /* 't' is an index of a call-site. * 'w' is a callee entry point. * Eventually this function would be called when env->cfg.insn_state[w] == EXPLORED. * Rely on DFS traversal order and absence of recursive calls to guarantee that * callee's effect marks would be correct at that moment. */static void merge_callee_effects(struct bpf_verifier_env *env, int t, int w){	struct bpf_subprog_info *caller, *callee; 	caller = bpf_find_containing_subprog(env, t);	callee = bpf_find_containing_subprog(env, w);	caller->changes_pkt_data |= callee->changes_pkt_data;	caller->might_sleep |= callee->might_sleep;	caller->might_throw |= callee->might_throw;} enum {	DONE_EXPLORING = 0,	KEEP_EXPLORING = 1,}; /* t, w, e - match pseudo-code above: * t - index of current instruction * w - next instruction * e - edge */static int push_insn(int t, int w, int e, struct bpf_verifier_env *env){	int *insn_stack = env->cfg.insn_stack;	int *insn_state = env->cfg.insn_state; 	if (e == FALLTHROUGH && insn_state[t] >= (DISCOVERED | FALLTHROUGH))		return DONE_EXPLORING; 	if (e == BRANCH && insn_state[t] >= (DISCOVERED | BRANCH))		return DONE_EXPLORING; 	if (w < 0 || w >= env->prog->len) {		verbose_linfo(env, t, "%d: ", t);		verbose(env, "jump out of range from insn %d to %d\n", t, w);		bpf_diag_program_structure(			env, t, "jump out of range", "Keep branch targets inside the program.",			"Instruction %d jumps to instruction %d, but the program only contains instructions 0 through %d.",			t, w, env->prog->len - 1);		return -EINVAL;	} 	if (e == BRANCH) {		/* mark branch target for state pruning */		mark_prune_point(env, w);		mark_jmp_point(env, w);		mark_jump_target(env, w);	} 	if (insn_state[w] == 0) {		/* tree-edge */		insn_state[t] = DISCOVERED | e;		insn_state[w] = DISCOVERED;		if (env->cfg.cur_stack >= env->prog->len)			return -E2BIG;		insn_stack[env->cfg.cur_stack++] = w;		return KEEP_EXPLORING;	} else if ((insn_state[w] & 0xF0) == DISCOVERED) {		if (env->bpf_capable)			return DONE_EXPLORING;		verbose_linfo(env, t, "%d: ", t);		verbose_linfo(env, w, "%d: ", w);		verbose(env, "back-edge from insn %d to %d\n", t, w);		bpf_diag_program_structure(			env, t, "back-edge is not allowed",			"Load with privileges that allow this back-edge, or rewrite the control flow so it does not branch backward.",			"Instruction %d branches back to instruction %d. This program is being rejected without the privilege needed for this back-edge.",			t, w);		return -EINVAL;	} else if (insn_state[w] == EXPLORED) {		/* forward- or cross-edge */		insn_state[t] = DISCOVERED | e;	} else {		verifier_bug(env, "insn state internal bug");		return -EFAULT;	}	return DONE_EXPLORING;} static int visit_func_call_insn(int t, struct bpf_insn *insns,				struct bpf_verifier_env *env,				bool visit_callee){	int ret, insn_sz;	int w; 	insn_sz = bpf_is_ldimm64(&insns[t]) ? 2 : 1;	ret = push_insn(t, t + insn_sz, FALLTHROUGH, env);	if (ret)		return ret; 	mark_prune_point(env, t + insn_sz);	/* when we exit from subprog, we need to record non-linear history */	mark_jmp_point(env, t + insn_sz); 	if (visit_callee) {		w = t + insns[t].imm + 1;		mark_prune_point(env, t);		merge_callee_effects(env, t, w);		ret = push_insn(t, w, BRANCH, env);	}	return ret;} struct bpf_iarray *bpf_iarray_realloc(struct bpf_iarray *old, size_t n_elem){	size_t new_size = sizeof(struct bpf_iarray) + n_elem * sizeof(old->items[0]);	struct bpf_iarray *new; 	new = kvrealloc(old, new_size, GFP_KERNEL_ACCOUNT);	if (!new) {		/* this is what callers always want, so simplify the call site */		kvfree(old);		return NULL;	} 	new->cnt = n_elem;	return new;} static int copy_insn_array(struct bpf_map *map, u32 start, u32 end, u32 *items){	struct bpf_insn_array_value *value;	u32 i; 	for (i = start; i <= end; i++) {		value = map->ops->map_lookup_elem(map, &i);		/*		 * map_lookup_elem of an array map will never return an error,		 * but not checking it makes some static analysers to worry		 */		if (IS_ERR(value))			return PTR_ERR(value);		else if (!value)			return -EINVAL;		items[i - start] = value->xlated_off;	}	return 0;} static int cmp_ptr_to_u32(const void *a, const void *b){	return *(u32 *)a - *(u32 *)b;} static int sort_insn_array_uniq(u32 *items, int cnt){	int unique = 1;	int i; 	sort(items, cnt, sizeof(items[0]), cmp_ptr_to_u32, NULL); 	for (i = 1; i < cnt; i++)		if (items[i] != items[unique - 1])			items[unique++] = items[i]; 	return unique;} /* * sort_unique({map[start], ..., map[end]}) into off */int bpf_copy_insn_array_uniq(struct bpf_map *map, u32 start, u32 end, u32 *off){	u32 n = end - start + 1;	int err; 	err = copy_insn_array(map, start, end, off);	if (err)		return err; 	return sort_insn_array_uniq(off, n);} /* * Copy all unique offsets from the map */static struct bpf_iarray *jt_from_map(struct bpf_map *map){	struct bpf_iarray *jt;	int err;	int n; 	jt = bpf_iarray_realloc(NULL, map->max_entries);	if (!jt)		return ERR_PTR(-ENOMEM); 	n = bpf_copy_insn_array_uniq(map, 0, map->max_entries - 1, jt->items);	if (n < 0) {		err = n;		goto err_free;	}	if (n == 0) {		err = -EINVAL;		goto err_free;	}	jt->cnt = n;	return jt; err_free:	kvfree(jt);	return ERR_PTR(err);} /* * Find and collect all maps which fit in the subprog. Return the result as one * combined jump table in jt->items (allocated with kvcalloc) */static struct bpf_iarray *jt_from_subprog(struct bpf_verifier_env *env,					  int subprog_start, int subprog_end){	struct bpf_iarray *jt = NULL;	struct bpf_map *map;	struct bpf_iarray *jt_cur;	int i; 	for (i = 0; i < env->insn_array_map_cnt; i++) {		/*		 * TODO (when needed): collect only jump tables, not static keys		 * or maps for indirect calls		 */		map = env->insn_array_maps[i]; 		jt_cur = jt_from_map(map);		if (IS_ERR(jt_cur)) {			kvfree(jt);			return jt_cur;		} 		/*		 * This is enough to check one element. The full table is		 * checked to fit inside the subprog later in create_jt()		 */		if (jt_cur->items[0] >= subprog_start && jt_cur->items[0] < subprog_end) {			u32 old_cnt = jt ? jt->cnt : 0;			jt = bpf_iarray_realloc(jt, old_cnt + jt_cur->cnt);			if (!jt) {				kvfree(jt_cur);				return ERR_PTR(-ENOMEM);			}			memcpy(jt->items + old_cnt, jt_cur->items, jt_cur->cnt << 2);		} 		kvfree(jt_cur);	} 	if (!jt) {		verbose(env, "no jump tables found for subprog starting at %u\n", subprog_start);		bpf_diag_program_structure(			env, subprog_start, "missing jump table",			"Make sure subprograms containing gotox instructions are accompanied by jump tables referencing these subprograms.",			"No jump table was found for the subprogram that starts at instruction %u.",			subprog_start);		return ERR_PTR(-EINVAL);	} 	jt->cnt = sort_insn_array_uniq(jt->items, jt->cnt);	return jt;} static struct bpf_iarray *create_jt(int t, struct bpf_verifier_env *env){	struct bpf_subprog_info *subprog;	int subprog_start, subprog_end;	struct bpf_iarray *jt;	int i; 	subprog = bpf_find_containing_subprog(env, t);	subprog_start = subprog->start;	subprog_end = (subprog + 1)->start;	jt = jt_from_subprog(env, subprog_start, subprog_end);	if (IS_ERR(jt))		return jt; 	/* Check that the every element of the jump table fits within the given subprogram */	for (i = 0; i < jt->cnt; i++) {		if (jt->items[i] < subprog_start || jt->items[i] >= subprog_end) {			verbose(env, "jump table for insn %d points outside of the subprog [%u,%u]\n",					t, subprog_start, subprog_end);			bpf_diag_program_structure(				env, t, "jump table target out of range",				"Keep every jump-table target inside the same subprogram.",				"The jump table for instruction %d points outside subprogram range [%u,%u).",				t, subprog_start, subprog_end);			kvfree(jt);			return ERR_PTR(-EINVAL);		}	} 	return jt;} /* "conditional jump with N edges" */static int visit_gotox_insn(int t, struct bpf_verifier_env *env){	int *insn_stack = env->cfg.insn_stack;	int *insn_state = env->cfg.insn_state;	bool keep_exploring = false;	struct bpf_iarray *jt;	int i, w; 	jt = env->insn_aux_data[t].jt;	if (!jt) {		jt = create_jt(t, env);		if (IS_ERR(jt))			return PTR_ERR(jt); 		env->insn_aux_data[t].jt = jt;	} 	mark_prune_point(env, t);	for (i = 0; i < jt->cnt; i++) {		w = jt->items[i];		if (w < 0 || w >= env->prog->len) {			verbose(env, "indirect jump out of range from insn %d to %d\n", t, w);			bpf_diag_program_structure(				env, t, "indirect jump out of range",				"Keep indirect jump targets inside the program.",				"Instruction %d can jump indirectly to instruction %d, but the program only contains instructions 0 through %d.",				t, w, env->prog->len - 1);			return -EINVAL;		} 		mark_jmp_point(env, w);		mark_jump_target(env, w); 		/* EXPLORED || DISCOVERED */		if (insn_state[w])			continue; 		if (env->cfg.cur_stack >= env->prog->len)			return -E2BIG; 		insn_stack[env->cfg.cur_stack++] = w;		insn_state[w] |= DISCOVERED;		keep_exploring = true;	} 	return keep_exploring ? KEEP_EXPLORING : DONE_EXPLORING;} /* * Instructions that can abnormally return from a subprog (tail_call * upon success, ld_{abs,ind} upon load failure) have a hidden exit * that the verifier must account for. */static int visit_abnormal_return_insn(struct bpf_verifier_env *env, int t){	struct bpf_subprog_info *subprog;	struct bpf_iarray *jt; 	if (env->insn_aux_data[t].jt)		return 0; 	jt = bpf_iarray_realloc(NULL, 2);	if (!jt)		return -ENOMEM; 	subprog = bpf_find_containing_subprog(env, t);	jt->items[0] = t + 1;	jt->items[1] = subprog->exit_idx;	env->insn_aux_data[t].jt = jt;	return 0;} /* Visits the instruction at index t and returns one of the following: *  < 0 - an error occurred *  DONE_EXPLORING - the instruction was fully explored *  KEEP_EXPLORING - there is still work to be done before it is fully explored */static int visit_insn(int t, struct bpf_verifier_env *env){	struct bpf_insn *insns = env->prog->insnsi, *insn = &insns[t];	int ret, off, insn_sz; 	if (bpf_pseudo_func(insn))		return visit_func_call_insn(t, insns, env, true); 	/* All non-branch instructions have a single fall-through edge. */	if (BPF_CLASS(insn->code) != BPF_JMP &&	    BPF_CLASS(insn->code) != BPF_JMP32) {		if (BPF_CLASS(insn->code) == BPF_LD &&		    (BPF_MODE(insn->code) == BPF_ABS ||		     BPF_MODE(insn->code) == BPF_IND)) {			ret = visit_abnormal_return_insn(env, t);			if (ret)				return ret;		}		insn_sz = bpf_is_ldimm64(insn) ? 2 : 1;		return push_insn(t, t + insn_sz, FALLTHROUGH, env);	} 	switch (BPF_OP(insn->code)) {	case BPF_EXIT:		return DONE_EXPLORING; 	case BPF_CALL:		if (bpf_is_async_callback_calling_insn(insn))			/* Mark this call insn as a prune point to trigger			 * is_state_visited() check before call itself is			 * processed by __check_func_call(). Otherwise new			 * async state will be pushed for further exploration.			 */			mark_prune_point(env, t);		/* For functions that invoke callbacks it is not known how many times		 * callback would be called. Verifier models callback calling functions		 * by repeatedly visiting callback bodies and returning to origin call		 * instruction.		 * In order to stop such iteration verifier needs to identify when a		 * state identical some state from a previous iteration is reached.		 * Check below forces creation of checkpoint before callback calling		 * instruction to allow search for such identical states.		 */		if (bpf_is_sync_callback_calling_insn(insn)) {			mark_calls_callback(env, t);			mark_force_checkpoint(env, t);			mark_prune_point(env, t);			mark_jmp_point(env, t);

Lines 1–500 of 920.