On Thu, 2025-05-01 at 09:35 +0200, Luis Gerhorst wrote:
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.
Still pass insn simply to match the other check_*() functions. 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"
- "do_print_state = " with "*do_print_state = "
[1] https://lore.kernel.org/all/293dbe3950a782b8eb3b87b71d7a967e120191fd.camel@g...
Signed-off-by: Luis Gerhorst luis.gerhorst@fau.de Acked-by: Henriette Herzog henriette.herzog@rub.de Cc: Maximilian Ott ott@cs.fau.de Cc: Milan Stephan milan.stephan@fau.de
Except two notes below, I think this patch looks good. Thank you, this is a good refactoring.
[...]
+static int do_check_insn(struct bpf_verifier_env *env, struct bpf_insn *insn,
bool *do_print_state)
+{
[...]
- } 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;
Implicitly relying on `insn == &env->prog->insnsi[env->cur_idx]` is weird. Still think that `insn` parameter should be dropped and computed inside this function instead.
/* 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) {
[...]
} 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:
Nit: since we are refactoring I'd extract this as a function instead of goto.
/* 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)
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;
[...]