mirror of
https://github.com/torvalds/linux.git
synced 2026-06-01 19:13:47 +02:00
bpf: Move insn if/else into do_check_insn()
This is required to catch the errors later and fall back to a nospec if on a speculative path. Eliminate the regs variable as it is only used once and insn_idx is not modified in-between the definition and usage. Do not pass insn but compute it in the function itself. As Eduard points out [1], insn is assumed to correspond to env->insn_idx in many places (e.g, __check_reg_arg()). Move code into do_check_insn(), replace * "continue" with "return 0" after modifying insn_idx * "goto process_bpf_exit" with "return PROCESS_BPF_EXIT" * "goto process_bpf_exit_full" with "return process_bpf_exit_full()" * "do_print_state = " with "*do_print_state = " [1] https://lore.kernel.org/all/293dbe3950a782b8eb3b87b71d7a967e120191fd.camel@gmail.com/ Signed-off-by: Luis Gerhorst <luis.gerhorst@fau.de> Acked-by: Kumar Kartikeya Dwivedi <memxor@gmail.com> Acked-by: Henriette Herzog <henriette.herzog@rub.de> Cc: Maximilian Ott <ott@cs.fau.de> Cc: Milan Stephan <milan.stephan@fau.de> Link: https://lore.kernel.org/r/20250603205800.334980-2-luis.gerhorst@fau.de Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
parent
2bc0575fec
commit
8b7df50fd4
|
|
@ -19430,20 +19430,223 @@ static int save_aux_ptr_type(struct bpf_verifier_env *env, enum bpf_reg_type typ
|
|||
return 0;
|
||||
}
|
||||
|
||||
enum {
|
||||
PROCESS_BPF_EXIT = 1
|
||||
};
|
||||
|
||||
static int process_bpf_exit_full(struct bpf_verifier_env *env,
|
||||
bool *do_print_state,
|
||||
bool exception_exit)
|
||||
{
|
||||
/* We must do check_reference_leak here before
|
||||
* prepare_func_exit to handle the case when
|
||||
* state->curframe > 0, it may be a callback function,
|
||||
* for which reference_state must match caller reference
|
||||
* state when it exits.
|
||||
*/
|
||||
int err = check_resource_leak(env, exception_exit,
|
||||
!env->cur_state->curframe,
|
||||
"BPF_EXIT instruction in main prog");
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* The side effect of the prepare_func_exit which is
|
||||
* being skipped is that it frees bpf_func_state.
|
||||
* Typically, process_bpf_exit will only be hit with
|
||||
* outermost exit. copy_verifier_state in pop_stack will
|
||||
* handle freeing of any extra bpf_func_state left over
|
||||
* from not processing all nested function exits. We
|
||||
* also skip return code checks as they are not needed
|
||||
* for exceptional exits.
|
||||
*/
|
||||
if (exception_exit)
|
||||
return PROCESS_BPF_EXIT;
|
||||
|
||||
if (env->cur_state->curframe) {
|
||||
/* exit from nested function */
|
||||
err = prepare_func_exit(env, &env->insn_idx);
|
||||
if (err)
|
||||
return err;
|
||||
*do_print_state = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
err = check_return_code(env, BPF_REG_0, "R0");
|
||||
if (err)
|
||||
return err;
|
||||
return PROCESS_BPF_EXIT;
|
||||
}
|
||||
|
||||
static int do_check_insn(struct bpf_verifier_env *env, bool *do_print_state)
|
||||
{
|
||||
int err;
|
||||
struct bpf_insn *insn = &env->prog->insnsi[env->insn_idx];
|
||||
u8 class = BPF_CLASS(insn->code);
|
||||
|
||||
if (class == BPF_ALU || class == BPF_ALU64) {
|
||||
err = check_alu_op(env, insn);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
} else if (class == BPF_LDX) {
|
||||
bool is_ldsx = BPF_MODE(insn->code) == BPF_MEMSX;
|
||||
|
||||
/* Check for reserved fields is already done in
|
||||
* resolve_pseudo_ldimm64().
|
||||
*/
|
||||
err = check_load_mem(env, insn, false, is_ldsx, true, "ldx");
|
||||
if (err)
|
||||
return err;
|
||||
} else if (class == BPF_STX) {
|
||||
if (BPF_MODE(insn->code) == BPF_ATOMIC) {
|
||||
err = check_atomic(env, insn);
|
||||
if (err)
|
||||
return err;
|
||||
env->insn_idx++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (BPF_MODE(insn->code) != BPF_MEM || insn->imm != 0) {
|
||||
verbose(env, "BPF_STX uses reserved fields\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
err = check_store_reg(env, insn, false);
|
||||
if (err)
|
||||
return err;
|
||||
} else if (class == BPF_ST) {
|
||||
enum bpf_reg_type dst_reg_type;
|
||||
|
||||
if (BPF_MODE(insn->code) != BPF_MEM ||
|
||||
insn->src_reg != BPF_REG_0) {
|
||||
verbose(env, "BPF_ST uses reserved fields\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
/* check src operand */
|
||||
err = check_reg_arg(env, insn->dst_reg, SRC_OP);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
dst_reg_type = cur_regs(env)[insn->dst_reg].type;
|
||||
|
||||
/* check that memory (dst_reg + off) is writeable */
|
||||
err = check_mem_access(env, env->insn_idx, insn->dst_reg,
|
||||
insn->off, BPF_SIZE(insn->code),
|
||||
BPF_WRITE, -1, false, false);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = save_aux_ptr_type(env, dst_reg_type, false);
|
||||
if (err)
|
||||
return err;
|
||||
} else if (class == BPF_JMP || class == BPF_JMP32) {
|
||||
u8 opcode = BPF_OP(insn->code);
|
||||
|
||||
env->jmps_processed++;
|
||||
if (opcode == BPF_CALL) {
|
||||
if (BPF_SRC(insn->code) != BPF_K ||
|
||||
(insn->src_reg != BPF_PSEUDO_KFUNC_CALL &&
|
||||
insn->off != 0) ||
|
||||
(insn->src_reg != BPF_REG_0 &&
|
||||
insn->src_reg != BPF_PSEUDO_CALL &&
|
||||
insn->src_reg != BPF_PSEUDO_KFUNC_CALL) ||
|
||||
insn->dst_reg != BPF_REG_0 || class == BPF_JMP32) {
|
||||
verbose(env, "BPF_CALL uses reserved fields\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (env->cur_state->active_locks) {
|
||||
if ((insn->src_reg == BPF_REG_0 &&
|
||||
insn->imm != BPF_FUNC_spin_unlock) ||
|
||||
(insn->src_reg == BPF_PSEUDO_KFUNC_CALL &&
|
||||
(insn->off != 0 || !kfunc_spin_allowed(insn->imm)))) {
|
||||
verbose(env,
|
||||
"function calls are not allowed while holding a lock\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
if (insn->src_reg == BPF_PSEUDO_CALL) {
|
||||
err = check_func_call(env, insn, &env->insn_idx);
|
||||
} else if (insn->src_reg == BPF_PSEUDO_KFUNC_CALL) {
|
||||
err = check_kfunc_call(env, insn, &env->insn_idx);
|
||||
if (!err && is_bpf_throw_kfunc(insn))
|
||||
return process_bpf_exit_full(env, do_print_state, true);
|
||||
} else {
|
||||
err = check_helper_call(env, insn, &env->insn_idx);
|
||||
}
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
mark_reg_scratched(env, BPF_REG_0);
|
||||
} else if (opcode == BPF_JA) {
|
||||
if (BPF_SRC(insn->code) != BPF_K ||
|
||||
insn->src_reg != BPF_REG_0 ||
|
||||
insn->dst_reg != BPF_REG_0 ||
|
||||
(class == BPF_JMP && insn->imm != 0) ||
|
||||
(class == BPF_JMP32 && insn->off != 0)) {
|
||||
verbose(env, "BPF_JA uses reserved fields\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (class == BPF_JMP)
|
||||
env->insn_idx += insn->off + 1;
|
||||
else
|
||||
env->insn_idx += insn->imm + 1;
|
||||
return 0;
|
||||
} else if (opcode == BPF_EXIT) {
|
||||
if (BPF_SRC(insn->code) != BPF_K ||
|
||||
insn->imm != 0 ||
|
||||
insn->src_reg != BPF_REG_0 ||
|
||||
insn->dst_reg != BPF_REG_0 ||
|
||||
class == BPF_JMP32) {
|
||||
verbose(env, "BPF_EXIT uses reserved fields\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
return process_bpf_exit_full(env, do_print_state, false);
|
||||
} else {
|
||||
err = check_cond_jmp_op(env, insn, &env->insn_idx);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
} else if (class == BPF_LD) {
|
||||
u8 mode = BPF_MODE(insn->code);
|
||||
|
||||
if (mode == BPF_ABS || mode == BPF_IND) {
|
||||
err = check_ld_abs(env, insn);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
} else if (mode == BPF_IMM) {
|
||||
err = check_ld_imm(env, insn);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
env->insn_idx++;
|
||||
sanitize_mark_insn_seen(env);
|
||||
} else {
|
||||
verbose(env, "invalid BPF_LD mode\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
} else {
|
||||
verbose(env, "unknown insn class %d\n", class);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
env->insn_idx++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_check(struct bpf_verifier_env *env)
|
||||
{
|
||||
bool pop_log = !(env->log.level & BPF_LOG_LEVEL2);
|
||||
struct bpf_verifier_state *state = env->cur_state;
|
||||
struct bpf_insn *insns = env->prog->insnsi;
|
||||
struct bpf_reg_state *regs;
|
||||
int insn_cnt = env->prog->len;
|
||||
bool do_print_state = false;
|
||||
int prev_insn_idx = -1;
|
||||
|
||||
for (;;) {
|
||||
bool exception_exit = false;
|
||||
struct bpf_insn *insn;
|
||||
u8 class;
|
||||
int err;
|
||||
|
||||
/* reset current history entry on each new instruction */
|
||||
|
|
@ -19457,7 +19660,6 @@ static int do_check(struct bpf_verifier_env *env)
|
|||
}
|
||||
|
||||
insn = &insns[env->insn_idx];
|
||||
class = BPF_CLASS(insn->code);
|
||||
|
||||
if (++env->insn_processed > BPF_COMPLEXITY_LIMIT_INSNS) {
|
||||
verbose(env,
|
||||
|
|
@ -19527,215 +19729,31 @@ static int do_check(struct bpf_verifier_env *env)
|
|||
return err;
|
||||
}
|
||||
|
||||
regs = cur_regs(env);
|
||||
sanitize_mark_insn_seen(env);
|
||||
prev_insn_idx = env->insn_idx;
|
||||
|
||||
if (class == BPF_ALU || class == BPF_ALU64) {
|
||||
err = check_alu_op(env, insn);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
} else if (class == BPF_LDX) {
|
||||
bool is_ldsx = BPF_MODE(insn->code) == BPF_MEMSX;
|
||||
|
||||
/* Check for reserved fields is already done in
|
||||
* resolve_pseudo_ldimm64().
|
||||
*/
|
||||
err = check_load_mem(env, insn, false, is_ldsx, true,
|
||||
"ldx");
|
||||
if (err)
|
||||
return err;
|
||||
} else if (class == BPF_STX) {
|
||||
if (BPF_MODE(insn->code) == BPF_ATOMIC) {
|
||||
err = check_atomic(env, insn);
|
||||
if (err)
|
||||
return err;
|
||||
env->insn_idx++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (BPF_MODE(insn->code) != BPF_MEM || insn->imm != 0) {
|
||||
verbose(env, "BPF_STX uses reserved fields\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
err = check_store_reg(env, insn, false);
|
||||
if (err)
|
||||
return err;
|
||||
} else if (class == BPF_ST) {
|
||||
enum bpf_reg_type dst_reg_type;
|
||||
|
||||
if (BPF_MODE(insn->code) != BPF_MEM ||
|
||||
insn->src_reg != BPF_REG_0) {
|
||||
verbose(env, "BPF_ST uses reserved fields\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
/* check src operand */
|
||||
err = check_reg_arg(env, insn->dst_reg, SRC_OP);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
dst_reg_type = regs[insn->dst_reg].type;
|
||||
|
||||
/* check that memory (dst_reg + off) is writeable */
|
||||
err = check_mem_access(env, env->insn_idx, insn->dst_reg,
|
||||
insn->off, BPF_SIZE(insn->code),
|
||||
BPF_WRITE, -1, false, false);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = save_aux_ptr_type(env, dst_reg_type, false);
|
||||
if (err)
|
||||
return err;
|
||||
} else if (class == BPF_JMP || class == BPF_JMP32) {
|
||||
u8 opcode = BPF_OP(insn->code);
|
||||
|
||||
env->jmps_processed++;
|
||||
if (opcode == BPF_CALL) {
|
||||
if (BPF_SRC(insn->code) != BPF_K ||
|
||||
(insn->src_reg != BPF_PSEUDO_KFUNC_CALL
|
||||
&& insn->off != 0) ||
|
||||
(insn->src_reg != BPF_REG_0 &&
|
||||
insn->src_reg != BPF_PSEUDO_CALL &&
|
||||
insn->src_reg != BPF_PSEUDO_KFUNC_CALL) ||
|
||||
insn->dst_reg != BPF_REG_0 ||
|
||||
class == BPF_JMP32) {
|
||||
verbose(env, "BPF_CALL uses reserved fields\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (env->cur_state->active_locks) {
|
||||
if ((insn->src_reg == BPF_REG_0 && insn->imm != BPF_FUNC_spin_unlock) ||
|
||||
(insn->src_reg == BPF_PSEUDO_KFUNC_CALL &&
|
||||
(insn->off != 0 || !kfunc_spin_allowed(insn->imm)))) {
|
||||
verbose(env, "function calls are not allowed while holding a lock\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
if (insn->src_reg == BPF_PSEUDO_CALL) {
|
||||
err = check_func_call(env, insn, &env->insn_idx);
|
||||
} else if (insn->src_reg == BPF_PSEUDO_KFUNC_CALL) {
|
||||
err = check_kfunc_call(env, insn, &env->insn_idx);
|
||||
if (!err && is_bpf_throw_kfunc(insn)) {
|
||||
exception_exit = true;
|
||||
goto process_bpf_exit_full;
|
||||
}
|
||||
} else {
|
||||
err = check_helper_call(env, insn, &env->insn_idx);
|
||||
}
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
mark_reg_scratched(env, BPF_REG_0);
|
||||
} else if (opcode == BPF_JA) {
|
||||
if (BPF_SRC(insn->code) != BPF_K ||
|
||||
insn->src_reg != BPF_REG_0 ||
|
||||
insn->dst_reg != BPF_REG_0 ||
|
||||
(class == BPF_JMP && insn->imm != 0) ||
|
||||
(class == BPF_JMP32 && insn->off != 0)) {
|
||||
verbose(env, "BPF_JA uses reserved fields\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (class == BPF_JMP)
|
||||
env->insn_idx += insn->off + 1;
|
||||
else
|
||||
env->insn_idx += insn->imm + 1;
|
||||
continue;
|
||||
|
||||
} else if (opcode == BPF_EXIT) {
|
||||
if (BPF_SRC(insn->code) != BPF_K ||
|
||||
insn->imm != 0 ||
|
||||
insn->src_reg != BPF_REG_0 ||
|
||||
insn->dst_reg != BPF_REG_0 ||
|
||||
class == BPF_JMP32) {
|
||||
verbose(env, "BPF_EXIT uses reserved fields\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
process_bpf_exit_full:
|
||||
/* We must do check_reference_leak here before
|
||||
* prepare_func_exit to handle the case when
|
||||
* state->curframe > 0, it may be a callback
|
||||
* function, for which reference_state must
|
||||
* match caller reference state when it exits.
|
||||
*/
|
||||
err = check_resource_leak(env, exception_exit, !env->cur_state->curframe,
|
||||
"BPF_EXIT instruction in main prog");
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* The side effect of the prepare_func_exit
|
||||
* which is being skipped is that it frees
|
||||
* bpf_func_state. Typically, process_bpf_exit
|
||||
* will only be hit with outermost exit.
|
||||
* copy_verifier_state in pop_stack will handle
|
||||
* freeing of any extra bpf_func_state left over
|
||||
* from not processing all nested function
|
||||
* exits. We also skip return code checks as
|
||||
* they are not needed for exceptional exits.
|
||||
*/
|
||||
if (exception_exit)
|
||||
goto process_bpf_exit;
|
||||
|
||||
if (state->curframe) {
|
||||
/* exit from nested function */
|
||||
err = prepare_func_exit(env, &env->insn_idx);
|
||||
if (err)
|
||||
return err;
|
||||
do_print_state = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
err = check_return_code(env, BPF_REG_0, "R0");
|
||||
if (err)
|
||||
return err;
|
||||
err = do_check_insn(env, &do_print_state);
|
||||
if (err < 0) {
|
||||
return err;
|
||||
} else if (err == PROCESS_BPF_EXIT) {
|
||||
process_bpf_exit:
|
||||
mark_verifier_state_scratched(env);
|
||||
update_branch_counts(env, env->cur_state);
|
||||
err = pop_stack(env, &prev_insn_idx,
|
||||
&env->insn_idx, pop_log);
|
||||
if (err < 0) {
|
||||
if (err != -ENOENT)
|
||||
return err;
|
||||
break;
|
||||
} else {
|
||||
if (verifier_bug_if(env->cur_state->loop_entry, env,
|
||||
"broken loop detection"))
|
||||
return -EFAULT;
|
||||
do_print_state = true;
|
||||
continue;
|
||||
}
|
||||
mark_verifier_state_scratched(env);
|
||||
update_branch_counts(env, env->cur_state);
|
||||
err = pop_stack(env, &prev_insn_idx, &env->insn_idx,
|
||||
pop_log);
|
||||
if (err < 0) {
|
||||
if (err != -ENOENT)
|
||||
return err;
|
||||
break;
|
||||
} else {
|
||||
err = check_cond_jmp_op(env, insn, &env->insn_idx);
|
||||
if (err)
|
||||
return err;
|
||||
if (verifier_bug_if(env->cur_state->loop_entry, env,
|
||||
"broken loop detection"))
|
||||
return -EFAULT;
|
||||
do_print_state = true;
|
||||
continue;
|
||||
}
|
||||
} else if (class == BPF_LD) {
|
||||
u8 mode = BPF_MODE(insn->code);
|
||||
|
||||
if (mode == BPF_ABS || mode == BPF_IND) {
|
||||
err = check_ld_abs(env, insn);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
} else if (mode == BPF_IMM) {
|
||||
err = check_ld_imm(env, insn);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
env->insn_idx++;
|
||||
sanitize_mark_insn_seen(env);
|
||||
} else {
|
||||
verbose(env, "invalid BPF_LD mode\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
} else {
|
||||
verbose(env, "unknown insn class %d\n", class);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
env->insn_idx++;
|
||||
WARN_ON_ONCE(err);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user