Merge branch 'follow-ups-for-verifier-errors-set'

Kumar Kartikeya Dwivedi says:

====================
Follow ups for verifier errors set

Some follow up changes based on comments from Eduard, Sashiko, and BPF
CI Bot. See commits for details.

Patches 1-4 of v3 were applied to bpf-next. This is the remainder,
rebased on the current bpf-next/master.

Changelog:
----------
v3 -> v4
v3: https://lore.kernel.org/bpf/20260924170646.2366016-1-memxor@gmail.com

 * Drop v3 patches 1-4, which are already applied.
 * Rebase on the current bpf-next/master.
 * State in the Program Structure patch that CO-RE relocations stay where
   c26e97721b put them and only func_info and line_info validation
   moves ahead of the layout checks. (Alexei)

v2 -> v3
v2: https://lore.kernel.org/bpf/20260924092941.3174809-1-memxor@gmail.com

 * Rebase on bpf-next/master, which now applies CO-RE relocations before
   subprogram discovery, and only move func_info and line_info validation
   ahead of the layout checks.
 * Drop the truncated final LD_IMM64 relocation guard and its test, as
   bpf_check() now rejects that form up front and core_reloc_raw covers it.

v1 -> v2
v1: https://lore.kernel.org/bpf/20260816015746.2632990-1-memxor@gmail.com

 * Drop v1 patches 3, 5-7, 11, and 13-14 because they are already present
   in bpf-next.
 * Use instruction-neutral wording for variable-offset stack accesses
   rather than inferring an atomic operation from value_regno == -1. (Eduard)
 * Update the variable-offset assertions and cover the retained
   uninitialized stack-read diagnostic through the existing CAP_PERFMON-less
   read inside the allocated stack. (BPF CI)
 * Keep v1 patch 4 unchanged after confirming that the write filter is
   reachable for lineage-preserving ALU operations with unchanged
   diagnostic snapshots; only its spill arm is impossible. Say so in the
   commit message. (Eduard)
 * Consider active critical sections only for sleepable programs and let
   non-sleepable programs reach the existing no-active-context fallback
   instead of adding an early branch. A non-sleepable program can still
   hold RCU, preempt, IRQ, or lock state, so the fallback alone would
   keep blaming the region. Keep the "non-sleepable prog" description.
   (Eduard, BPF CI)
 * Select the helper and global-function suggestions by cause, matching
   the kfunc site. (BPF CI)
 * Split the kfunc coverage into a non-sleepable program case and a
   sleepable program inside an RCU read-side critical section. (Eduard)
 * Scan subprogram properties with nested loops and drop the partial
   recursive-edge details together with their assertion. (Eduard)
 * Reject CO-RE relocations targeting a truncated final LD_IMM64 before
   applying them, since BTF validation now runs before the subprogram
   layout check, and add raw CO-RE coverage for the rejection. Add the
   Fixes tag. (BPF CI)
 * Rebase on the current bpf-next/master and let the new property scan
   also set the callx marker that check_subprogs() collects upstream.
====================

Link: https://patch.msgid.link/20260926133048.2962553-1-memxor@gmail.com
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
This commit is contained in:
Daniel Borkmann 2026-09-30 14:02:04 +02:00
commit 7d2fa34fd3
5 changed files with 106 additions and 18 deletions

View File

@ -393,7 +393,7 @@ subprog_jt(int t, struct bpf_verifier_env *env)
if (!subprog->jt) {
verbose(env, "no jump tables found for subprog starting at %u\n", subprog_start);
bpf_diag_program_structure(
env, subprog_start, "missing jump table",
env, t, "missing jump table",
"Make sure subprograms containing gotox instructions are accompanied by jump tables referencing these subprograms.",
"No jump table was found for the subprogram that starts at instruction %u.",
subprog_start);

View File

@ -3107,6 +3107,34 @@ static int add_kfuncs(struct bpf_verifier_env *env)
return 0;
}
static void find_subprog_properties(struct bpf_verifier_env *env)
{
struct bpf_subprog_info *subprog = env->subprog_info;
struct bpf_insn *insn = env->prog->insnsi;
int cur_subprog;
for (cur_subprog = 0; cur_subprog < env->subprog_cnt; cur_subprog++) {
int i;
for (i = subprog[cur_subprog].start;
i < subprog[cur_subprog + 1].start; i++) {
u8 code = insn[i].code;
if (code == (BPF_JMP | BPF_CALL) &&
insn[i].src_reg == 0 &&
insn[i].imm == BPF_FUNC_tail_call) {
subprog[cur_subprog].has_tail_call = true;
subprog[cur_subprog].tail_call_reachable = true;
}
if (BPF_CLASS(code) == BPF_LD &&
(BPF_MODE(code) == BPF_ABS || BPF_MODE(code) == BPF_IND))
subprog[cur_subprog].has_ld_abs = true;
if (bpf_is_callx(&insn[i]))
env->has_callx = true;
}
}
}
static int check_subprogs(struct bpf_verifier_env *env)
{
int i, subprog_start, subprog_end, off, cur_subprog = 0;
@ -3120,17 +3148,6 @@ static int check_subprogs(struct bpf_verifier_env *env)
for (i = 0; i < insn_cnt; i++) {
u8 code = insn[i].code;
if (code == (BPF_JMP | BPF_CALL) &&
insn[i].src_reg == 0 &&
insn[i].imm == BPF_FUNC_tail_call) {
subprog[cur_subprog].has_tail_call = true;
subprog[cur_subprog].tail_call_reachable = true;
}
if (BPF_CLASS(code) == BPF_LD &&
(BPF_MODE(code) == BPF_ABS || BPF_MODE(code) == BPF_IND))
subprog[cur_subprog].has_ld_abs = true;
if (bpf_is_callx(&insn[i]))
env->has_callx = true;
if (BPF_CLASS(code) != BPF_JMP && BPF_CLASS(code) != BPF_JMP32)
goto next;
if (BPF_OP(code) == BPF_CALL)
@ -3154,9 +3171,10 @@ static int check_subprogs(struct bpf_verifier_env *env)
}
next:
if (i == subprog_end - 1) {
/* to avoid fall-through from one subprog into another
/*
* To avoid fall-through from one subprog into another,
* the last insn of the subprog should be either exit
* or unconditional jump back or bpf_throw call
* or unconditional jump back or bpf_throw call.
*/
if (code != (BPF_JMP | BPF_EXIT) &&
code != (BPF_JMP32 | BPF_JA) &&
@ -22511,17 +22529,19 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
if (ret < 0)
goto skip_full_check;
/* Discover all subprograms before validating their layout and BTF. */
/* Discover all subprograms and collect the properties needed by BTF validation. */
ret = add_subprogs(env);
if (ret < 0)
goto skip_full_check;
ret = check_subprogs(env);
find_subprog_properties(env);
/* Validate BTF before reporting subprogram layout errors. */
ret = bpf_check_btf_info(env, attr, uattr);
if (ret < 0)
goto skip_full_check;
/* Validate BTF against the complete subprogram layout. */
ret = bpf_check_btf_info(env, attr, uattr);
ret = check_subprogs(env);
if (ret < 0)
goto skip_full_check;

View File

@ -6,6 +6,8 @@
#include "bpf_experimental.h"
extern int bpf_copy_from_user_str(void *dst, u32 dst__sz, const void *unsafe_ptr__ign, u64 flags) __weak __ksym;
extern void bpf_rcu_read_lock(void) __ksym;
extern void bpf_rcu_read_unlock(void) __ksym;
SEC("?tc")
__failure __msg("BPF_EXIT instruction in main prog cannot be used inside bpf_preempt_disable-ed region")
@ -179,6 +181,30 @@ int preempt_sleepable_kfunc(void *ctx)
return 0;
}
SEC("?fentry/" SYS_PREFIX "sys_getpgid")
__failure __msg("program must be sleepable to call sleepable kfunc bpf_copy_from_user_str")
__msg("cannot be used in non-sleepable program")
int non_sleepable_kfunc(void *ctx)
{
u32 data;
bpf_copy_from_user_str(&data, sizeof(data), NULL, 0);
return 0;
}
SEC("?fentry.s/" SYS_PREFIX "sys_getpgid")
__failure __msg("kernel func bpf_copy_from_user_str is sleepable within rcu_read_lock region")
__msg("cannot be used in RCU read lock region")
int sleepable_kfunc_in_rcu(void *ctx)
{
u32 data;
bpf_rcu_read_lock();
bpf_copy_from_user_str(&data, sizeof(data), NULL, 0);
bpf_rcu_read_unlock();
return 0;
}
int __noinline preempt_global_subprog(void)
{
preempt_balance_subprog();

View File

@ -56,6 +56,46 @@ __naked void out_of_range_jump2(void)
" ::: __clobber_all);
}
static __naked __noinline __used int cross_subprog_target(void)
{
asm volatile (" \
r0 = 0; \
exit; \
" ::: __clobber_all);
}
SEC("socket")
__description("jump across subprogram boundary")
__failure __msg("jump out of range from insn 1")
__msg("jump_across_subprog_boundary @ verifier_cfg.c")
__naked void jump_across_subprog_boundary(void)
{
asm volatile (" \
call cross_subprog_target; \
goto +1; \
exit; \
" ::: __clobber_all);
}
static __naked __noinline __used int fallthrough_subprog(void)
{
asm volatile (" \
r0 = 0; \
" ::: __clobber_all);
}
SEC("socket")
__description("subprogram fallthrough")
__failure __msg("last insn is not an exit or jmp")
__msg("fallthrough_subprog @ verifier_cfg.c")
__naked void subprog_fallthrough(void)
{
asm volatile (" \
call fallthrough_subprog; \
exit; \
" ::: __clobber_all);
}
SEC("socket")
__description("invalid DW LDSX instruction in diagnostics")
__failure __msg("BUG_ldx_99")

View File

@ -101,9 +101,11 @@ __naked void jump_table_terminal_gotox_subprog(void)
*/
SEC("socket")
__failure __msg("no jump tables found for subprog starting at 0")
__msg(">>> 1 | (0d) gotox r0")
__naked void jump_table_no_jump_table(void)
{
asm volatile (" \
r0 = 0; \
.8byte %[gotox_r0]; \
r0 = 1; \
exit; \