bpf: Prevent variable arena/non-arena register contents

The verifier marks ALU instructions that include at least
one arena operand with needs_zext: These instructions are
fixed up after verification to be ALU32 instructions to
ensure that the result is a valid offset into an arena.
However, different code paths may provide two non-arena
64-bit arguments to the same instruction. The result of
the operation in that code path is wrong, since it is
now unexpectedly truncated to 32 bits and zero-extended.

Add logic to the verifier to ensure every instruction either
always has at least one PTR_TO_ARENA argument, or never does.
Since needs_zext already tracks the first scenario, add a
prevent_zext field in bpf_insn_aux to track the latter.
Reject instructions that use arena arguments and have prevent_zext
set, or do not have arena arguments and have needs_zext set.

Fixes: 6082b6c328 ("bpf: Recognize addr_space_cast instruction in the verifier.")
Reported-by: Nicholas Carlini <nicholas@carlini.com>
Suggested-by: Nicholas Carlini <nicholas@carlini.com>
Signed-off-by: Emil Tsalapatis <emil@etsalapatis.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Link: https://patch.msgid.link/20260922172028.6269-8-emil@etsalapatis.com
This commit is contained in:
Emil Tsalapatis 2026-09-22 17:20:24 +00:00 committed by Alexei Starovoitov
parent 1ed69a54d3
commit f85f5917aa
No known key found for this signature in database
2 changed files with 24 additions and 1 deletions

View File

@ -680,6 +680,7 @@ struct bpf_insn_aux_data {
bool nospec_result; /* result is unsafe under speculation, nospec must follow */
bool zext_dst; /* this insn zero extends dst reg */
bool needs_zext; /* alu op needs to clear upper bits */
bool prevent_zext; /* alu op cannot be zext (already used with 64-bit scalars) */
bool non_sleepable; /* helper/kfunc may be called from non-sleepable context */
bool is_iter_next; /* bpf_iter_<type>_next() kfunc call */
bool call_with_percpu_alloc_ptr; /* {this,per}_cpu_ptr() with prog percpu alloc */

View File

@ -15741,6 +15741,7 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,
struct bpf_reg_state *regs = state->regs, *dst_reg, *src_reg;
struct bpf_reg_state *ptr_reg = NULL, off_reg = {0};
bool alu32 = (BPF_CLASS(insn->code) != BPF_ALU64);
struct bpf_insn_aux_data *aux = cur_aux(env);
u8 opcode = BPF_OP(insn->code);
int err;
@ -15752,12 +15753,23 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,
/* Case where at least one operand is an arena. */
if (dst_reg->type == PTR_TO_ARENA || (src_reg && src_reg->type == PTR_TO_ARENA)) {
struct bpf_insn_aux_data *aux = cur_aux(env);
if (dst_reg->type != PTR_TO_ARENA)
*dst_reg = *src_reg;
if (BPF_CLASS(insn->code) == BPF_ALU64) {
/*
* Only arena pointers set needs_zext, but doing so
* modifies the instruction at fixup time to an ALU32
* and makes it unsuitable for 64-bit scalar args. We
* prevent zext from being set if the instruction has
* been previously called with non-arena registers.
*/
if (aux->prevent_zext) {
verbose(env, "same insn cannot be used with and without arena pointer\n");
return -EINVAL;
}
/*
* 32-bit operations zero upper bits automatically.
* 64-bit operations need to be converted to 32.
@ -15770,6 +15782,16 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,
return 0;
}
/* Prevent the instruction from being used with arena pointers (see above). */
if (env->prog->aux->arena && BPF_CLASS(insn->code) == BPF_ALU64) {
if (aux->needs_zext) {
verbose(env, "same insn cannot be used with and without arena pointer\n");
return -EINVAL;
}
aux->prevent_zext = true;
}
if (dst_reg->type != SCALAR_VALUE)
ptr_reg = dst_reg;