mirror of
https://github.com/torvalds/linux.git
synced 2026-09-28 20:12:01 +02:00
bpf: Verify global subprogs in each sleepability context
Global subprograms are verified independently with a fresh verifier root. do_check_common() currently seeds that root's in_sleepable state from the program, even though a global subprogram can also run from callbacks whose execution context differs from the program's main entry point. In particular, workqueue and task-work callbacks are sleepable even when the containing program is not. A global subprogram of that program is therefore verified as non-sleepable, making in_rcu_cs() true and allowing loads of RCU-protected kptrs to produce trusted MEM_RCU pointers. The same subprogram can then be called from a sleepable callback without a classic RCU reader. It can retain such a pointer while the object is freed and use it after free. The verifier's execution-context predicates are complementary. A state is sleepable only when in_sleepable is set and no RCU, preemption, IRQ, or lock region is active. Each condition which prevents sleeping also provides RCU protection, while in_rcu_cs() treats a non-sleepable state as implicitly protected. Use this relationship to represent a global subprogram caller with only the result of in_sleepable_context(). A protected sleepable caller is normalized to in_sleepable=false at the independent verification root. This both prevents sleepable operations and makes in_rcu_cs() true without copying caller-owned lock state. Track only the contexts in which each global subprogram is actually reached. Verify it once if all reachable calls use the same context, and twice only if both sleepable and non-sleepable calls reach it. Calls found while verifying globals or asynchronous callbacks mark further contexts for checking. Repeat the existing subprogram walk until all called contexts have been verified; unreachable global calls remain unchecked. Accumulate instruction counts over those verification passes. Preserve the total recorded before each pass, since path accounting has already added this pass's synchronous instructions and its root total must also include asynchronous subprograms. This makes an unprotected callback verify the global subprogram as sleepable, turning its RCU-protected kptr load into an untrusted pointer. Protected callers and global subprograms which do not depend on implicit RCU protection remain valid. Fixes:81f1d7a583("bpf: wq: add bpf_wq_set_callback_impl") Fixes:38aa7003e3("bpf: task work scheduling kfuncs") Reported-by: Nicholas Carlini <npc@anthropic.com> Suggested-by: Nicholas Carlini <npc@anthropic.com> Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com> Acked-by: Eduard Zingerman <eddyz87@gmail.com> Link: https://patch.msgid.link/20260914131923.2544250-2-memxor@gmail.com Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
This commit is contained in:
parent
ee363e0558
commit
40c2096961
|
|
@ -1651,8 +1651,9 @@ static inline void bpf_trampoline_set_flags(struct bpf_trampoline *tr, u32 flags
|
|||
struct bpf_func_info_aux {
|
||||
u16 linkage;
|
||||
bool unreliable;
|
||||
bool called : 1;
|
||||
bool verified : 1;
|
||||
/* Indexed by in_sleepable. */
|
||||
bool called[2];
|
||||
bool verified[2];
|
||||
};
|
||||
|
||||
enum bpf_jit_poke_reason {
|
||||
|
|
|
|||
|
|
@ -9931,6 +9931,7 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
|
|||
if (err == -EFAULT)
|
||||
return err;
|
||||
if (bpf_subprog_is_global(env, subprog)) {
|
||||
struct bpf_func_info_aux *sub_aux = subprog_aux(env, subprog);
|
||||
const char *sub_name = bpf_subprog_name(env, subprog);
|
||||
const char *operation;
|
||||
bool returns_void;
|
||||
|
|
@ -9962,11 +9963,10 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
|
|||
if (env->log.level & BPF_LOG_LEVEL)
|
||||
verbose(env, "Func#%d ('%s') is global and assumed valid.\n",
|
||||
subprog, sub_name);
|
||||
sub_aux->called[in_sleepable_context(env)] = true;
|
||||
returns_void = subprog_returns_void(env, subprog);
|
||||
if (env->subprog_info[subprog].changes_pkt_data)
|
||||
clear_all_pkt_pointers(env);
|
||||
/* mark global subprog for verifying after main prog */
|
||||
subprog_aux(env, subprog)->called = true;
|
||||
if (returns_void)
|
||||
bpf_diag_record_scrub(env, &caller->regs[BPF_REG_0], BPF_DIAG_MOD_CALLER_SAVED);
|
||||
else
|
||||
|
|
@ -10784,11 +10784,7 @@ int bpf_get_helper_proto(struct bpf_verifier_env *env, int func_id,
|
|||
/* Check if we're in a sleepable context. */
|
||||
static inline bool in_sleepable_context(struct bpf_verifier_env *env)
|
||||
{
|
||||
return !env->cur_state->active_rcu_locks &&
|
||||
!env->cur_state->active_preempt_locks &&
|
||||
!env->cur_state->active_locks &&
|
||||
!env->cur_state->active_irq_id &&
|
||||
in_sleepable(env);
|
||||
return !in_rcu_cs(env);
|
||||
}
|
||||
|
||||
static const char *non_sleepable_context_description(struct bpf_verifier_env *env)
|
||||
|
|
@ -19447,13 +19443,14 @@ static void free_states(struct bpf_verifier_env *env)
|
|||
}
|
||||
}
|
||||
|
||||
static int do_check_common(struct bpf_verifier_env *env, int subprog)
|
||||
static int do_check_common(struct bpf_verifier_env *env, int subprog, bool is_sleepable)
|
||||
{
|
||||
bool pop_log = !(env->log.level & BPF_LOG_LEVEL2);
|
||||
struct bpf_subprog_info *sub = subprog_info(env, subprog);
|
||||
struct bpf_prog_aux *aux = env->prog->aux;
|
||||
struct bpf_verifier_state *state;
|
||||
struct bpf_reg_state *regs;
|
||||
u32 old_insns_total = sub->insns_total;
|
||||
u32 insn_processed = env->insn_processed;
|
||||
int ret, i;
|
||||
|
||||
|
|
@ -19466,7 +19463,7 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog)
|
|||
state->curframe = 0;
|
||||
state->speculative = false;
|
||||
state->branches = 1;
|
||||
state->in_sleepable = env->prog->sleepable;
|
||||
state->in_sleepable = is_sleepable;
|
||||
state->frame[0] = kzalloc_obj(struct bpf_func_state, GFP_KERNEL_ACCOUNT);
|
||||
if (!state->frame[0]) {
|
||||
kfree(state);
|
||||
|
|
@ -19607,8 +19604,10 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog)
|
|||
* not accounted as callees by account_current_path().
|
||||
* Accumulate their total counts as total counts of the main or
|
||||
* global subprog hosting the async call.
|
||||
* Start from the saved total of earlier contexts: adding to the current
|
||||
* total would count this pass's synchronous paths twice.
|
||||
*/
|
||||
env->subprog_info[subprog].insns_total = env->insn_processed - insn_processed;
|
||||
sub->insns_total = old_insns_total + (env->insn_processed - insn_processed);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -19636,14 +19635,19 @@ static int do_check_subprogs(struct bpf_verifier_env *env)
|
|||
{
|
||||
struct bpf_prog_aux *aux = env->prog->aux;
|
||||
struct bpf_func_info_aux *sub_aux;
|
||||
int i, ret, new_cnt;
|
||||
int context, i, ret, new_cnt;
|
||||
|
||||
if (!aux->func_info)
|
||||
return 0;
|
||||
|
||||
/* exception callback is presumed to be always called */
|
||||
if (env->exception_callback_subprog)
|
||||
subprog_aux(env, env->exception_callback_subprog)->called = true;
|
||||
/*
|
||||
* Callbacks cannot throw, so the exception callback always runs in the
|
||||
* main program's context. It is presumed to be always called.
|
||||
*/
|
||||
if (env->exception_callback_subprog) {
|
||||
sub_aux = subprog_aux(env, env->exception_callback_subprog);
|
||||
sub_aux->called[env->prog->sleepable] = true;
|
||||
}
|
||||
|
||||
again:
|
||||
new_cnt = 0;
|
||||
|
|
@ -19652,29 +19656,28 @@ static int do_check_subprogs(struct bpf_verifier_env *env)
|
|||
continue;
|
||||
|
||||
sub_aux = subprog_aux(env, i);
|
||||
if (!sub_aux->called || sub_aux->verified)
|
||||
continue;
|
||||
for (context = 0; context < ARRAY_SIZE(sub_aux->called); context++) {
|
||||
if (!sub_aux->called[context] || sub_aux->verified[context])
|
||||
continue;
|
||||
|
||||
env->insn_idx = env->subprog_info[i].start;
|
||||
WARN_ON_ONCE(env->insn_idx == 0);
|
||||
ret = do_check_common(env, i);
|
||||
if (ret) {
|
||||
return ret;
|
||||
} else if (env->log.level & BPF_LOG_LEVEL) {
|
||||
verbose(env, "Func#%d ('%s') is safe for any args that match its prototype\n",
|
||||
i, bpf_subprog_name(env, i));
|
||||
env->insn_idx = env->subprog_info[i].start;
|
||||
WARN_ON_ONCE(env->insn_idx == 0);
|
||||
ret = do_check_common(env, i, context);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (env->log.level & BPF_LOG_LEVEL)
|
||||
verbose(env, "Func#%d ('%s') is safe for any args "
|
||||
"that match its prototype\n",
|
||||
i, bpf_subprog_name(env, i));
|
||||
|
||||
sub_aux->verified[context] = true;
|
||||
new_cnt++;
|
||||
}
|
||||
|
||||
/* We verified new global subprog, it might have called some
|
||||
* more global subprogs that we haven't verified yet, so we
|
||||
* need to do another pass over subprogs to verify those.
|
||||
*/
|
||||
sub_aux->verified = true;
|
||||
new_cnt++;
|
||||
}
|
||||
|
||||
/* We can't loop forever as we verify at least one global subprog on
|
||||
* each pass.
|
||||
/*
|
||||
* We can't loop forever as each pass verifies at least one new context,
|
||||
* and there are only two contexts per global subprog.
|
||||
*/
|
||||
if (new_cnt)
|
||||
goto again;
|
||||
|
|
@ -19687,7 +19690,7 @@ static int do_check_main(struct bpf_verifier_env *env)
|
|||
int ret;
|
||||
|
||||
env->insn_idx = 0;
|
||||
ret = do_check_common(env, 0);
|
||||
ret = do_check_common(env, 0, env->prog->sleepable);
|
||||
if (!ret)
|
||||
env->prog->aux->stack_depth = env->subprog_info[0].stack_depth;
|
||||
return ret;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user