bpf: don't rewrite bpf_fastcall patterns entered by a jump
Subsystems (4)
Eduard Zingerman
maintainerPATCH 1/2 bpf: don't rewrite bpf_fastcall patterns entered by a jump
mark_fastcall_pattern_for_call() must ensure that matched "spill; call; fill" instruction series is not interrupted by a jump. Otherwise the rewrite applied by bpf_remove_fastcall_spills_fills() is not sound.
Record the instructions targeted by jumps in insn_aux_data[*].jump_target when the CFG is built and use this flag to stop growing a pattern at such an instruction. Jumps to the first spill are fine.
Note that existing insn_aux_data[*].jmp_point field can't be reused, as it marks subprogram return instructions.
--- include/linux/bpf_verifier.h | 12 ++++++++++++ kernel/bpf/cfg.c | 3 +++ kernel/bpf/verifier.c | 8 ++++++++ 3 files changed, 23 insertions(+)
include/linux/bpf_verifier.h +12 -0
@@ -706,6 +706,8 @@ struct bpf_insn_aux_data {*/ u32 calls_callback:1; u32 indirect_target:1; /* if it is an indirect jump target */ +/* true if some jump or call instruction targets this instruction */ + u32 jump_target:1;/* * CFG strongly connected component this instruction belongs to, * zero if it is a singleton SCC.
@@ -1142,6 +1144,16 @@ static inline void mark_jmp_point(struct bpf_verifier_env *env, int idx) env->insn_aux_data[idx].jmp_point = true; } +static inline void mark_jump_target (struct bpf_verifier_env *env,int idx) +{ + env->insn_aux_data[idx].jump_target = true; +} + +static inline bool bpf_is_jump_target (struct bpf_verifier_env *env,int insn_idx) +{ +return env->insn_aux_data[insn_idx].jump_target; +} +static inline struct bpf_func_state *cur_func (struct bpf_verifier_env *env) {struct bpf_verifier_state *cur = env->cur_state;
kernel/bpf/cfg.c +3 -0
@@ -125,6 +125,7 @@ static int push_insn(int t, int w, int e, struct bpf_verifier_env *env)/* 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 ) {
@@ -403,6 +404,7 @@ static int visit_gotox_insn(int t, struct bpf_verifier_env *env) }mark_jmp_point (env, w); +mark_jump_target (env, w);/* EXPLORED || DISCOVERED */ if (insn_state[w])
@@ -564,6 +566,7 @@ static int visit_insn(int t, struct bpf_verifier_env *env)mark_prune_point (env, t + off +1 );mark_jmp_point (env, t + off +1 ); +mark_jump_target (env, t + off +1 );return ret;
kernel/bpf/verifier.c +8 -0
@@ -17631,6 +17631,10 @@ bool bpf_get_call_summary(struct bpf_verifier_env *env, struct bpf_insn *call,* r0 = *(u64 *)(r10 - 8); r0 += r1; * r0 += r1; exit; * exit; +* +* Both uses of the marks assume that a pattern is entered at its first +* spill and thus executes as a unit, hence a pattern is not grown past +* an instruction targeted by a jump. */ static void mark_fastcall_pattern_for_call(struct bpf_verifier_env *env, struct bpf_subprog_info *subprog,
@@ -17669,6 +17673,10 @@ static void mark_fastcall_pattern_for_call(struct bpf_verifier_env *env,for (i =1 , off = lowest_off; i <=ARRAY_SIZE (caller_saved); ++i, off +=BPF_REG_SIZE ) {if (insn_idx - i <0 || insn_idx + i >= env->prog->len)break ; +/* stx/ldx/call must not be a jump targets, a jump to the first stx is fine */ +if (bpf_is_jump_target (env, insn_idx - i +1 ) || +bpf_is_jump_target (env, insn_idx + i)) +break ; stx = &insns[insn_idx - i]; ldx = &insns[insn_idx + i];/* must be a stack spill/fill pair */
Signature · 1 line
2.55.0
Eduard Zingerman
maintainerPATCH 2/2 selftests/bpf: bpf_fastcall patterns entered by a jump
Check bpf_fastcall pattern detection when the pattern is entered at an instruction other than the first spill:
- a jump to the first spill allows the rewrite;
- conditional/unconditional a jump to the call or to the fill does not allow the rewrite.
--- .../bpf/progs/verifier_bpf_fastcall.c | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+)
tools/testing/selftests/bpf/progs/verifier_bpf_fastcall.c +110 -0
@@ -621,6 +621,116 @@ __naked void helper_call_does_not_prevent_bpf_fastcall(void) : __clobber_all); } +/* A jump to the first spill executes the whole pattern, rewrite is safe. */ +SEC("raw_tp") +__arch_x86_64 +__log_level(4) +__msg("subprog 0 (jump_to_first_spill) main {{.*}} stack 0") +__xlated("2: if r0 == 0x2a goto pc+0") +__xlated("3: r0 = ") +__xlated("4: r0 = &(void __percpu *)(r0)") +__success +__naked void jump_to_first_spill (void ) +{ + asmvolatile ( +"call %[bpf_get_prandom_u32];" +"r1 = 1;" +"if r0 == 42 goto l0_%=;" +"l0_%=:" +"*(u64 *)(r10 - 8) = r1;" +"call %[bpf_get_smp_processor_id];" +"r1 = *(u64 *)(r10 - 8);" +"exit;" + : + : __imm(bpf_get_prandom_u32), +__imm (bpf_get_smp_processor_id) + : __clobber_all); +} + +/* A jump to the call skips the spill, the pattern must be kept. */ +SEC("raw_tp") +__arch_x86_64 +__log_level(4) +__msg("subprog 0 (jump_to_call) main {{.*}} stack 8") +__xlated("2: if r0 == 0x2a goto pc+1") +__xlated("3: *(u64 *)(r10 -8) = r1") +__xlated("...") +__xlated("7: r1 = *(u64 *)(r10 -8)") +__success +__naked void jump_to_call (void ) +{ + asmvolatile ( +"call %[bpf_get_prandom_u32];" +"r1 = 1;" +"if r0 == 42 goto l0_%=;" +"*(u64 *)(r10 - 8) = r1;" +"l0_%=:" +"call %[bpf_get_smp_processor_id];" +"r1 = *(u64 *)(r10 - 8);" +"exit;" + : + : __imm(bpf_get_prandom_u32), +__imm (bpf_get_smp_processor_id) + : __clobber_all); +} + +/* A jump to the fill skips the spill, the pattern must be kept. */ +SEC("raw_tp") +__arch_x86_64 +__log_level(4) +__msg("subprog 0 (jump_to_fill) main {{.*}} stack 8") +__xlated("2: if r0 == 0x2a goto pc+4") +__xlated("3: *(u64 *)(r10 -8) = r1") +__xlated("...") +__xlated("7: r1 = *(u64 *)(r10 -8)") +__success +__naked void jump_to_fill (void ) +{ + asmvolatile ( +"call %[bpf_get_prandom_u32];" +"r1 = 1;" +"if r0 == 42 goto l0_%=;" +"*(u64 *)(r10 - 8) = r1;" +"call %[bpf_get_smp_processor_id];" +"l0_%=:" +"r1 = *(u64 *)(r10 - 8);" +"exit;" + : + : __imm(bpf_get_prandom_u32), +__imm (bpf_get_smp_processor_id) + : __clobber_all); +} + +/* Same as above, but the fill is entered by an unconditional jump. */ +SEC("raw_tp") +__arch_x86_64 +__log_level(4) +__msg("subprog 0 (unconditional_jump_to_fill) main {{.*}} stack 8") +__xlated("3: *(u64 *)(r10 -8) = r1") +__xlated("...") +__xlated("7: r1 = *(u64 *)(r10 -8)") +__xlated("8: exit") +__xlated("9: goto pc-3") +__success +__naked void unconditional_jump_to_fill (void ) +{ + asmvolatile ( +"call %[bpf_get_prandom_u32];" +"r1 = 1;" +"if r0 == 42 goto l1_%=;" +"*(u64 *)(r10 - 8) = r1;" +"call %[bpf_get_smp_processor_id];" +"l0_%=:" +"r1 = *(u64 *)(r10 - 8);" +"exit;" +"l1_%=:" +"goto l0_%=;" + : + : __imm(bpf_get_prandom_u32), +__imm (bpf_get_smp_processor_id) + : __clobber_all); +} + SEC("raw_tp") __arch_x86_64__log_level (4 )
Signature · 1 line
2.55.0
This series was applied to bpf/bpf.git (master) by Alexei Starovoitov <[email protected]>:
Hello:
This series was applied to bpf/bpf.git (master)
by Alexei Starovoitov <[email protected]>:
Quoted 11 lines · On Thu, 3 Sep 2026 13:58:19 -0700 you wrote:
mark_fastcall_pattern_for_call()must ensure that matched "spill; call; fill" instruction series is not interrupted by a jump. Otherwise the rewrite applied bybpf_remove_fastcall_spills_fills()is not sound.Record the instructions targeted by jumps in insn_aux_data[*].jump_target when the CFG is built and use this flag to stop growing a pattern at such an instruction. Jumps to the first spill are fine.
[...]
Here is the summary with links:
- [bpf,1/2] bpf: don't rewrite bpf_fastcall patterns entered by a jump
https://git.kernel.org/bpf/bpf/c/0b1c83dc3c44 - [bpf,2/2] selftests/bpf: bpf_fastcall patterns entered by a jump https://git.kernel.org/bpf/bpf/c/65b1518c995c
You are awesome, thank you!
Signature · 2 lines
Deet-doot-dot, I am a bot. https://korg.docs.kernel.org/patchwork/pwbot.html