Merge branch 'enable-static-subprog-calls-in-spin-lock-critical-sections'

Kumar Kartikeya Dwivedi says:

====================
Enable static subprog calls in spin lock critical sections

This set allows a BPF program to make a call to a static subprog within
a bpf_spin_lock critical section. This problem has been hit in sched-ext
and ghOSt [0] as well, and is mostly an annoyance which is worked around
by inling the static subprog into the critical section.

In case of sched-ext, there are a lot of other helper/kfunc calls that
need to be allow listed for the support to be complete, but a separate
follow up will deal with that.

Unlike static subprogs, global subprogs cannot be allowed yet as the
verifier will not explore their body when encountering a call
instruction for them. Therefore, we would need an alternative approach
(some sort of function summarization to ensure a lock is never taken
from a global subprog and all its callees).

 [0]: https://lore.kernel.org/bpf/bd173bf2-dea6-3e0e-4176-4a9256a9a056@google.com

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

 * Indicate global function call in verifier error string (Yonghong, David)
 * Add Acks from Yonghong, David
====================

Link: https://lore.kernel.org/r/20240204222349.938118-1-memxor@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
Alexei Starovoitov 2024-02-05 19:58:47 -08:00
commit 8244ab509f
5 changed files with 120 additions and 4 deletions

View file

@ -9493,6 +9493,13 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
if (subprog_is_global(env, subprog)) {
const char *sub_name = subprog_name(env, subprog);
/* Only global subprogs cannot be called with a lock held. */
if (env->cur_state->active_lock.ptr) {
verbose(env, "global function calls are not allowed while holding a lock,\n"
"use static function instead\n");
return -EINVAL;
}
if (err) {
verbose(env, "Caller passes invalid args into func#%d ('%s')\n",
subprog, sub_name);
@ -17644,7 +17651,6 @@ static int do_check(struct bpf_verifier_env *env)
if (env->cur_state->active_lock.ptr) {
if ((insn->src_reg == BPF_REG_0 && insn->imm != BPF_FUNC_spin_unlock) ||
(insn->src_reg == BPF_PSEUDO_CALL) ||
(insn->src_reg == BPF_PSEUDO_KFUNC_CALL &&
(insn->off != 0 || !is_bpf_graph_api_kfunc(insn->imm)))) {
verbose(env, "function calls are not allowed while holding a lock\n");
@ -17692,8 +17698,7 @@ static int do_check(struct bpf_verifier_env *env)
return -EINVAL;
}
process_bpf_exit_full:
if (env->cur_state->active_lock.ptr &&
!in_rbtree_lock_required_cb(env)) {
if (env->cur_state->active_lock.ptr && !env->cur_state->curframe) {
verbose(env, "bpf_spin_unlock is missing\n");
return -EINVAL;
}

View file

@ -48,6 +48,8 @@ static struct {
{ "lock_id_mismatch_innermapval_kptr", "bpf_spin_unlock of different lock" },
{ "lock_id_mismatch_innermapval_global", "bpf_spin_unlock of different lock" },
{ "lock_id_mismatch_innermapval_mapval", "bpf_spin_unlock of different lock" },
{ "lock_global_subprog_call1", "global function calls are not allowed while holding a lock" },
{ "lock_global_subprog_call2", "global function calls are not allowed while holding a lock" },
};
static int match_regex(const char *pattern, const char *string)

View file

@ -101,4 +101,69 @@ int bpf_spin_lock_test(struct __sk_buff *skb)
err:
return err;
}
struct bpf_spin_lock lockA __hidden SEC(".data.A");
__noinline
static int static_subprog(struct __sk_buff *ctx)
{
volatile int ret = 0;
if (ctx->protocol)
return ret;
return ret + ctx->len;
}
__noinline
static int static_subprog_lock(struct __sk_buff *ctx)
{
volatile int ret = 0;
ret = static_subprog(ctx);
bpf_spin_lock(&lockA);
return ret + ctx->len;
}
__noinline
static int static_subprog_unlock(struct __sk_buff *ctx)
{
volatile int ret = 0;
ret = static_subprog(ctx);
bpf_spin_unlock(&lockA);
return ret + ctx->len;
}
SEC("tc")
int lock_static_subprog_call(struct __sk_buff *ctx)
{
int ret = 0;
bpf_spin_lock(&lockA);
if (ctx->mark == 42)
ret = static_subprog(ctx);
bpf_spin_unlock(&lockA);
return ret;
}
SEC("tc")
int lock_static_subprog_lock(struct __sk_buff *ctx)
{
int ret = 0;
ret = static_subprog_lock(ctx);
bpf_spin_unlock(&lockA);
return ret;
}
SEC("tc")
int lock_static_subprog_unlock(struct __sk_buff *ctx)
{
int ret = 0;
bpf_spin_lock(&lockA);
ret = static_subprog_unlock(ctx);
return ret;
}
char _license[] SEC("license") = "GPL";

View file

@ -201,4 +201,48 @@ CHECK(innermapval_mapval, &iv->lock, &v->lock);
#undef CHECK
__noinline
int global_subprog(struct __sk_buff *ctx)
{
volatile int ret = 0;
if (ctx->protocol)
ret += ctx->protocol;
return ret + ctx->mark;
}
__noinline
static int static_subprog_call_global(struct __sk_buff *ctx)
{
volatile int ret = 0;
if (ctx->protocol)
return ret;
return ret + ctx->len + global_subprog(ctx);
}
SEC("?tc")
int lock_global_subprog_call1(struct __sk_buff *ctx)
{
int ret = 0;
bpf_spin_lock(&lockA);
if (ctx->mark == 42)
ret = global_subprog(ctx);
bpf_spin_unlock(&lockA);
return ret;
}
SEC("?tc")
int lock_global_subprog_call2(struct __sk_buff *ctx)
{
int ret = 0;
bpf_spin_lock(&lockA);
if (ctx->mark == 42)
ret = static_subprog_call_global(ctx);
bpf_spin_unlock(&lockA);
return ret;
}
char _license[] SEC("license") = "GPL";

View file

@ -330,7 +330,7 @@ l1_%=: r7 = r0; \
SEC("cgroup/skb")
__description("spin_lock: test10 lock in subprog without unlock")
__failure __msg("unlock is missing")
__success
__failure_unpriv __msg_unpriv("")
__naked void lock_in_subprog_without_unlock(void)
{