summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexei Starovoitov <ast@kernel.org>2026-01-28 18:41:57 -0800
committerAlexei Starovoitov <ast@kernel.org>2026-01-28 18:41:57 -0800
commit95dbe214b910fc80f0627e1760305cc0f472ff9f (patch)
tree3e2944caeb45a9e6c70d3f68647df63cfff452ea
parent08a7491843224f8b96518fbe70d9e48163046054 (diff)
parent60d2c438c1bb705cdbf74ce8f12e6e141a4719b0 (diff)
Merge branch 'bpf-fix-verifier_bug_if-to-account-for-bpf_call'
Luis Gerhorst says: ==================== bpf: Fix verifier_bug_if to account for BPF_CALL This fixes the verifier_bug_if() that runs on nospec_result to not trigger for BPF_CALL (bug reported by Hu, Mei, and Mu). See patch 1 for a full description and patch 2 for a test (based on the PoC from the report). While working on this I noticed two other problems: - nospec_result is currently ignored for BPF_CALL during patching, but it may be required if we assume the CPU may speculate into/out of functions. - Both the instruction patching for nospec and nospec_result erases the instruction aux information even thought it might be better to keep that. For nospec_result it may be fine as it is only applied to store instructions currently (except for when we decide to change the thing from above), but nospec may be set for arbitrary instructions and if these require rewrites they break. I assume these issues are better fixed separately, thus I decided to exclude them from this series. ==================== Link: https://patch.msgid.link/20260127115912.3026761-1-luis.gerhorst@fau.de Signed-off-by: Alexei Starovoitov <ast@kernel.org>
-rw-r--r--kernel/bpf/verifier.c14
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_unpriv.c22
2 files changed, 30 insertions, 6 deletions
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index c2f2650db9fd..e7ff8394e0da 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -21065,17 +21065,19 @@ static int do_check(struct bpf_verifier_env *env)
* may skip a nospec patched-in after the jump. This can
* currently never happen because nospec_result is only
* used for the write-ops
- * `*(size*)(dst_reg+off)=src_reg|imm32` which must
- * never skip the following insn. Still, add a warning
- * to document this in case nospec_result is used
- * elsewhere in the future.
+ * `*(size*)(dst_reg+off)=src_reg|imm32` and helper
+ * calls. These must never skip the following insn
+ * (i.e., bpf_insn_successors()'s opcode_info.can_jump
+ * is false). Still, add a warning to document this in
+ * case nospec_result is used elsewhere in the future.
*
* All non-branch instructions have a single
* fall-through edge. For these, nospec_result should
* already work.
*/
- if (verifier_bug_if(BPF_CLASS(insn->code) == BPF_JMP ||
- BPF_CLASS(insn->code) == BPF_JMP32, env,
+ if (verifier_bug_if((BPF_CLASS(insn->code) == BPF_JMP ||
+ BPF_CLASS(insn->code) == BPF_JMP32) &&
+ BPF_OP(insn->code) != BPF_CALL, env,
"speculation barrier after jump instruction may not have the desired effect"))
return -EFAULT;
process_bpf_exit:
diff --git a/tools/testing/selftests/bpf/progs/verifier_unpriv.c b/tools/testing/selftests/bpf/progs/verifier_unpriv.c
index 28b4f7035ceb..8ee1243e62a8 100644
--- a/tools/testing/selftests/bpf/progs/verifier_unpriv.c
+++ b/tools/testing/selftests/bpf/progs/verifier_unpriv.c
@@ -950,4 +950,26 @@ l3_%=: r0 = 0; \
" ::: __clobber_all);
}
+SEC("socket")
+__description("unpriv: nospec after dead stack write in helper")
+__success __success_unpriv
+__retval(0)
+/* Dead code sanitizer rewrites the call to `goto -1`. */
+__naked void unpriv_dead_helper_stack_write_nospec_result(void)
+{
+ asm volatile (" \
+ r0 = 0; \
+ if r0 != 1 goto l0_%=; \
+ r2 = 0; \
+ r3 = r10; \
+ r3 += -16; \
+ r4 = 4; \
+ r5 = 0; \
+ call %[bpf_skb_load_bytes_relative]; \
+l0_%=: exit; \
+" :
+ : __imm(bpf_skb_load_bytes_relative)
+ : __clobber_all);
+}
+
char _license[] SEC("license") = "GPL";