diff mbox series

[bpf-next,v1,1/2] bpf: Allow calling static subprogs while holding a bpf_spin_lock

Message ID 20240204120206.796412-2-memxor@gmail.com (mailing list archive)
State Superseded
Delegated to: BPF
Headers show
Series Enable static subprog calls in spin lock critical sections | expand

Checks

Context Check Description
bpf/vmtest-bpf-next-PR success PR summary
bpf/vmtest-bpf-next-VM_Test-0 success Logs for Lint
bpf/vmtest-bpf-next-VM_Test-1 success Logs for ShellCheck
bpf/vmtest-bpf-next-VM_Test-2 success Logs for Unittests
bpf/vmtest-bpf-next-VM_Test-3 success Logs for Validate matrix.py
bpf/vmtest-bpf-next-VM_Test-5 success Logs for aarch64-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-4 success Logs for aarch64-gcc / build / build for aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-10 success Logs for aarch64-gcc / veristat
bpf/vmtest-bpf-next-VM_Test-12 success Logs for s390x-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-9 success Logs for aarch64-gcc / test (test_verifier, false, 360) / test_verifier on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-6 success Logs for aarch64-gcc / test (test_maps, false, 360) / test_maps on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-11 success Logs for s390x-gcc / build / build for s390x with gcc
bpf/vmtest-bpf-next-VM_Test-8 success Logs for aarch64-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-24 success Logs for x86_64-gcc / test (test_progs_no_alu32_parallel, true, 30) / test_progs_no_alu32_parallel on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-26 success Logs for x86_64-gcc / test (test_verifier, false, 360) / test_verifier on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-22 success Logs for x86_64-gcc / test (test_progs, false, 360) / test_progs on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-7 success Logs for aarch64-gcc / test (test_progs, false, 360) / test_progs on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-21 success Logs for x86_64-gcc / test (test_maps, false, 360) / test_maps on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-18 success Logs for set-matrix
bpf/vmtest-bpf-next-VM_Test-20 success Logs for x86_64-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-30 success Logs for x86_64-llvm-17 / test (test_maps, false, 360) / test_maps on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-35 success Logs for x86_64-llvm-18 / build / build for x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-38 success Logs for x86_64-llvm-18 / test (test_progs, false, 360) / test_progs on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-16 success Logs for s390x-gcc / test (test_verifier, false, 360) / test_verifier on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-27 success Logs for x86_64-gcc / veristat / veristat on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-17 success Logs for s390x-gcc / veristat
bpf/vmtest-bpf-next-VM_Test-33 success Logs for x86_64-llvm-17 / test (test_verifier, false, 360) / test_verifier on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-28 success Logs for x86_64-llvm-17 / build / build for x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-32 success Logs for x86_64-llvm-17 / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-39 success Logs for x86_64-llvm-18 / test (test_progs_cpuv4, false, 360) / test_progs_cpuv4 on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-23 success Logs for x86_64-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-40 success Logs for x86_64-llvm-18 / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-31 success Logs for x86_64-llvm-17 / test (test_progs, false, 360) / test_progs on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-42 success Logs for x86_64-llvm-18 / veristat
bpf/vmtest-bpf-next-VM_Test-41 success Logs for x86_64-llvm-18 / test (test_verifier, false, 360) / test_verifier on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-37 success Logs for x86_64-llvm-18 / test (test_maps, false, 360) / test_maps on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-25 success Logs for x86_64-gcc / test (test_progs_parallel, true, 30) / test_progs_parallel on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-19 success Logs for x86_64-gcc / build / build for x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-34 success Logs for x86_64-llvm-17 / veristat
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Clearly marked for bpf-next
netdev/ynl success Generated files up to date; no warnings/errors; no diff in generated;
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 1060 this patch: 1060
netdev/build_tools success Errors and warnings before: 1 this patch: 0
netdev/cc_maintainers success CCed 0 of 0 maintainers
netdev/build_clang success Errors and warnings before: 1066 this patch: 1066
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 1077 this patch: 1077
netdev/checkpatch warning WARNING: 'atleast' may be misspelled - perhaps 'at least'? WARNING: line length of 94 exceeds 80 columns WARNING: line length of 99 exceeds 80 columns
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0
bpf/vmtest-bpf-next-VM_Test-36 success Logs for x86_64-llvm-18 / build-release / build for x86_64 with llvm-18 and -O2 optimization
bpf/vmtest-bpf-next-VM_Test-29 success Logs for x86_64-llvm-17 / build-release / build for x86_64 with llvm-17 and -O2 optimization
bpf/vmtest-bpf-next-VM_Test-14 success Logs for s390x-gcc / test (test_progs, false, 360) / test_progs on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-15 success Logs for s390x-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-13 success Logs for s390x-gcc / test (test_maps, false, 360) / test_maps on s390x with gcc

Commit Message

Kumar Kartikeya Dwivedi Feb. 4, 2024, 12:02 p.m. UTC
Currently, calling any helpers, kfuncs, or subprogs except the graph
data structure (lists, rbtrees) API kfuncs while holding a bpf_spin_lock
is not allowed. One of the original motivations of this decision was to
force the BPF programmer's hand into keeping the bpf_spin_lock critical
section small, and to ensure the execution time of the program does not
increase due to lock waiting times. In addition to this, some of the
helpers and kfuncs may be unsafe to call while holding a bpf_spin_lock.

However, when it comes to subprog calls, atleast for static subprogs,
the verifier is able to explore their instructions during verification.
Therefore, it is similar in effect to having the same code inlined into
the critical section. Hence, not allowing static subprog calls in the
bpf_spin_lock critical section is mostly an annoyance that needs to be
worked around, without providing any tangible benefit.

Unlike static subprog calls, global subprog calls are not safe to permit
within the critical section, as the verifier does not explore them
during verification, therefore whether the same lock will be taken
again, or unlocked, cannot be ascertained.

Therefore, allow calling static subprogs within a bpf_spin_lock critical
section, and only reject it in case the subprog linkage is global.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 kernel/bpf/verifier.c                                  | 10 +++++++---
 tools/testing/selftests/bpf/progs/verifier_spin_lock.c |  2 +-
 2 files changed, 8 insertions(+), 4 deletions(-)

Comments

Yonghong Song Feb. 4, 2024, 9:23 p.m. UTC | #1
On 2/4/24 4:02 AM, Kumar Kartikeya Dwivedi wrote:
> Currently, calling any helpers, kfuncs, or subprogs except the graph
> data structure (lists, rbtrees) API kfuncs while holding a bpf_spin_lock
> is not allowed. One of the original motivations of this decision was to
> force the BPF programmer's hand into keeping the bpf_spin_lock critical
> section small, and to ensure the execution time of the program does not
> increase due to lock waiting times. In addition to this, some of the
> helpers and kfuncs may be unsafe to call while holding a bpf_spin_lock.
>
> However, when it comes to subprog calls, atleast for static subprogs,
> the verifier is able to explore their instructions during verification.
> Therefore, it is similar in effect to having the same code inlined into
> the critical section. Hence, not allowing static subprog calls in the
> bpf_spin_lock critical section is mostly an annoyance that needs to be
> worked around, without providing any tangible benefit.
>
> Unlike static subprog calls, global subprog calls are not safe to permit
> within the critical section, as the verifier does not explore them
> during verification, therefore whether the same lock will be taken
> again, or unlocked, cannot be ascertained.
>
> Therefore, allow calling static subprogs within a bpf_spin_lock critical
> section, and only reject it in case the subprog linkage is global.
>
> Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>

SGTM with a small nit below.

Acked-by: Yonghong Song <yonghong.song@linux.dev>

> ---
>   kernel/bpf/verifier.c                                  | 10 +++++++---
>   tools/testing/selftests/bpf/progs/verifier_spin_lock.c |  2 +-
>   2 files changed, 8 insertions(+), 4 deletions(-)
>
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 64fa188d00ad..f858c959753b 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -9493,6 +9493,12 @@ 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, "function calls are not allowed while holding a lock\n");

Maybe explicit to mention "global function calls are not allowed ..."?

> +			return -EINVAL;
> +		}
> +
>   		if (err) {
>   			verbose(env, "Caller passes invalid args into func#%d ('%s')\n",
>   				subprog, sub_name);
> @@ -17644,7 +17650,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 +17697,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;
>   				}

[...]
David Vernet Feb. 4, 2024, 9:33 p.m. UTC | #2
On Sun, Feb 04, 2024 at 12:02:05PM +0000, Kumar Kartikeya Dwivedi wrote:
> Currently, calling any helpers, kfuncs, or subprogs except the graph
> data structure (lists, rbtrees) API kfuncs while holding a bpf_spin_lock
> is not allowed. One of the original motivations of this decision was to
> force the BPF programmer's hand into keeping the bpf_spin_lock critical
> section small, and to ensure the execution time of the program does not
> increase due to lock waiting times. In addition to this, some of the
> helpers and kfuncs may be unsafe to call while holding a bpf_spin_lock.
> 
> However, when it comes to subprog calls, atleast for static subprogs,
> the verifier is able to explore their instructions during verification.
> Therefore, it is similar in effect to having the same code inlined into
> the critical section. Hence, not allowing static subprog calls in the
> bpf_spin_lock critical section is mostly an annoyance that needs to be
> worked around, without providing any tangible benefit.
> 
> Unlike static subprog calls, global subprog calls are not safe to permit
> within the critical section, as the verifier does not explore them
> during verification, therefore whether the same lock will be taken
> again, or unlocked, cannot be ascertained.
> 
> Therefore, allow calling static subprogs within a bpf_spin_lock critical
> section, and only reject it in case the subprog linkage is global.
> 
> Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>

Looks good, thanks for this improvement. I had the same suggestion as
Yonghong in [0], and also left a question below.

[0]: https://lore.kernel.org/all/2e008ab1-44b8-4d1b-a86d-1f347d7630e6@linux.dev/

Acked-by: David Vernet <void@manifault.com>

> ---
>  kernel/bpf/verifier.c                                  | 10 +++++++---
>  tools/testing/selftests/bpf/progs/verifier_spin_lock.c |  2 +-
>  2 files changed, 8 insertions(+), 4 deletions(-)
> 
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 64fa188d00ad..f858c959753b 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -9493,6 +9493,12 @@ 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, "function calls are not allowed while holding a lock\n");
> +			return -EINVAL;
> +		}
> +
>  		if (err) {
>  			verbose(env, "Caller passes invalid args into func#%d ('%s')\n",
>  				subprog, sub_name);
> @@ -17644,7 +17650,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 +17697,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) {

Can we do the same thing here for the RCU check below? It seems like the
exact same issue, as we're already allowed to call subprogs from within
an RCU read region, but the verifier will get confused and think we
haven't unlocked by the time we return to the caller.

Assuming that's the case, we can take care of it in a separate patch
set.

>  					verbose(env, "bpf_spin_unlock is missing\n");
>  					return -EINVAL;
>  				}
> diff --git a/tools/testing/selftests/bpf/progs/verifier_spin_lock.c b/tools/testing/selftests/bpf/progs/verifier_spin_lock.c
> index 9c1aa69650f8..fb316c080c84 100644
> --- a/tools/testing/selftests/bpf/progs/verifier_spin_lock.c
> +++ b/tools/testing/selftests/bpf/progs/verifier_spin_lock.c
> @@ -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)
>  {
> -- 
> 2.40.1
>
Kumar Kartikeya Dwivedi Feb. 4, 2024, 10:09 p.m. UTC | #3
On Sun, 4 Feb 2024 at 22:23, Yonghong Song <yonghong.song@linux.dev> wrote:
>
>
> On 2/4/24 4:02 AM, Kumar Kartikeya Dwivedi wrote:
> > Currently, calling any helpers, kfuncs, or subprogs except the graph
> > data structure (lists, rbtrees) API kfuncs while holding a bpf_spin_lock
> > is not allowed. One of the original motivations of this decision was to
> > force the BPF programmer's hand into keeping the bpf_spin_lock critical
> > section small, and to ensure the execution time of the program does not
> > increase due to lock waiting times. In addition to this, some of the
> > helpers and kfuncs may be unsafe to call while holding a bpf_spin_lock.
> >
> > However, when it comes to subprog calls, atleast for static subprogs,
> > the verifier is able to explore their instructions during verification.
> > Therefore, it is similar in effect to having the same code inlined into
> > the critical section. Hence, not allowing static subprog calls in the
> > bpf_spin_lock critical section is mostly an annoyance that needs to be
> > worked around, without providing any tangible benefit.
> >
> > Unlike static subprog calls, global subprog calls are not safe to permit
> > within the critical section, as the verifier does not explore them
> > during verification, therefore whether the same lock will be taken
> > again, or unlocked, cannot be ascertained.
> >
> > Therefore, allow calling static subprogs within a bpf_spin_lock critical
> > section, and only reject it in case the subprog linkage is global.
> >
> > Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
>
> SGTM with a small nit below.
>
> Acked-by: Yonghong Song <yonghong.song@linux.dev>
>
> > ---
> >   kernel/bpf/verifier.c                                  | 10 +++++++---
> >   tools/testing/selftests/bpf/progs/verifier_spin_lock.c |  2 +-
> >   2 files changed, 8 insertions(+), 4 deletions(-)
> >
> > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> > index 64fa188d00ad..f858c959753b 100644
> > --- a/kernel/bpf/verifier.c
> > +++ b/kernel/bpf/verifier.c
> > @@ -9493,6 +9493,12 @@ 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, "function calls are not allowed while holding a lock\n");
>
> Maybe explicit to mention "global function calls are not allowed ..."?
>

Ack, I made this change, and also extended the last part with "use a
static function instead" to make it more helpful.
Thanks for the review.

> > +                     return -EINVAL;
> > +             }
> > +
> >               if (err) {
> >                       verbose(env, "Caller passes invalid args into func#%d ('%s')\n",
> >                               subprog, sub_name);
> > @@ -17644,7 +17650,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 +17697,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;
> >                               }
>
> [...]
>
Kumar Kartikeya Dwivedi Feb. 4, 2024, 10:10 p.m. UTC | #4
On Sun, 4 Feb 2024 at 22:33, David Vernet <void@manifault.com> wrote:
>
> On Sun, Feb 04, 2024 at 12:02:05PM +0000, Kumar Kartikeya Dwivedi wrote:
> > Currently, calling any helpers, kfuncs, or subprogs except the graph
> > data structure (lists, rbtrees) API kfuncs while holding a bpf_spin_lock
> > is not allowed. One of the original motivations of this decision was to
> > force the BPF programmer's hand into keeping the bpf_spin_lock critical
> > section small, and to ensure the execution time of the program does not
> > increase due to lock waiting times. In addition to this, some of the
> > helpers and kfuncs may be unsafe to call while holding a bpf_spin_lock.
> >
> > However, when it comes to subprog calls, atleast for static subprogs,
> > the verifier is able to explore their instructions during verification.
> > Therefore, it is similar in effect to having the same code inlined into
> > the critical section. Hence, not allowing static subprog calls in the
> > bpf_spin_lock critical section is mostly an annoyance that needs to be
> > worked around, without providing any tangible benefit.
> >
> > Unlike static subprog calls, global subprog calls are not safe to permit
> > within the critical section, as the verifier does not explore them
> > during verification, therefore whether the same lock will be taken
> > again, or unlocked, cannot be ascertained.
> >
> > Therefore, allow calling static subprogs within a bpf_spin_lock critical
> > section, and only reject it in case the subprog linkage is global.
> >
> > Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
>
> Looks good, thanks for this improvement. I had the same suggestion as
> Yonghong in [0], and also left a question below.
>
> [0]: https://lore.kernel.org/all/2e008ab1-44b8-4d1b-a86d-1f347d7630e6@linux.dev/
>
> Acked-by: David Vernet <void@manifault.com>
>
> > ---
> >  kernel/bpf/verifier.c                                  | 10 +++++++---
> >  tools/testing/selftests/bpf/progs/verifier_spin_lock.c |  2 +-
> >  2 files changed, 8 insertions(+), 4 deletions(-)
> >
> > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> > index 64fa188d00ad..f858c959753b 100644
> > --- a/kernel/bpf/verifier.c
> > +++ b/kernel/bpf/verifier.c
> > @@ -9493,6 +9493,12 @@ 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, "function calls are not allowed while holding a lock\n");
> > +                     return -EINVAL;
> > +             }
> > +
> >               if (err) {
> >                       verbose(env, "Caller passes invalid args into func#%d ('%s')\n",
> >                               subprog, sub_name);
> > @@ -17644,7 +17650,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 +17697,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) {
>
> Can we do the same thing here for the RCU check below? It seems like the
> exact same issue, as we're already allowed to call subprogs from within
> an RCU read region, but the verifier will get confused and think we
> haven't unlocked by the time we return to the caller.
>
> Assuming that's the case, we can take care of it in a separate patch
> set.

Makes sense, I'll send a separate patch for the RCU fix.
Thanks for the review.
Yonghong Song Feb. 4, 2024, 11:55 p.m. UTC | #5
On 2/4/24 2:10 PM, Kumar Kartikeya Dwivedi wrote:
> On Sun, 4 Feb 2024 at 22:33, David Vernet <void@manifault.com> wrote:
>> On Sun, Feb 04, 2024 at 12:02:05PM +0000, Kumar Kartikeya Dwivedi wrote:
>>> Currently, calling any helpers, kfuncs, or subprogs except the graph
>>> data structure (lists, rbtrees) API kfuncs while holding a bpf_spin_lock
>>> is not allowed. One of the original motivations of this decision was to
>>> force the BPF programmer's hand into keeping the bpf_spin_lock critical
>>> section small, and to ensure the execution time of the program does not
>>> increase due to lock waiting times. In addition to this, some of the
>>> helpers and kfuncs may be unsafe to call while holding a bpf_spin_lock.
>>>
>>> However, when it comes to subprog calls, atleast for static subprogs,
>>> the verifier is able to explore their instructions during verification.
>>> Therefore, it is similar in effect to having the same code inlined into
>>> the critical section. Hence, not allowing static subprog calls in the
>>> bpf_spin_lock critical section is mostly an annoyance that needs to be
>>> worked around, without providing any tangible benefit.
>>>
>>> Unlike static subprog calls, global subprog calls are not safe to permit
>>> within the critical section, as the verifier does not explore them
>>> during verification, therefore whether the same lock will be taken
>>> again, or unlocked, cannot be ascertained.
>>>
>>> Therefore, allow calling static subprogs within a bpf_spin_lock critical
>>> section, and only reject it in case the subprog linkage is global.
>>>
>>> Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
>> Looks good, thanks for this improvement. I had the same suggestion as
>> Yonghong in [0], and also left a question below.
>>
>> [0]: https://lore.kernel.org/all/2e008ab1-44b8-4d1b-a86d-1f347d7630e6@linux.dev/
>>
>> Acked-by: David Vernet <void@manifault.com>
>>
>>> ---
>>>   kernel/bpf/verifier.c                                  | 10 +++++++---
>>>   tools/testing/selftests/bpf/progs/verifier_spin_lock.c |  2 +-
>>>   2 files changed, 8 insertions(+), 4 deletions(-)
>>>
>>> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
>>> index 64fa188d00ad..f858c959753b 100644
>>> --- a/kernel/bpf/verifier.c
>>> +++ b/kernel/bpf/verifier.c
>>> @@ -9493,6 +9493,12 @@ 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, "function calls are not allowed while holding a lock\n");
>>> +                     return -EINVAL;
>>> +             }
>>> +
>>>                if (err) {
>>>                        verbose(env, "Caller passes invalid args into func#%d ('%s')\n",
>>>                                subprog, sub_name);
>>> @@ -17644,7 +17650,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 +17697,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) {
>> Can we do the same thing here for the RCU check below? It seems like the
>> exact same issue, as we're already allowed to call subprogs from within
>> an RCU read region, but the verifier will get confused and think we
>> haven't unlocked by the time we return to the caller.
>>
>> Assuming that's the case, we can take care of it in a separate patch
>> set.
> Makes sense, I'll send a separate patch for the RCU fix.
> Thanks for the review.

The following is what I recommended as well in another thread:

https://lore.kernel.org/bpf/20240131145454.86990-1-laoar.shao@gmail.com/T/#mff17cd64eeb1e17bd0e3e046fb52efeef9c86c25

>
diff mbox series

Patch

diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 64fa188d00ad..f858c959753b 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -9493,6 +9493,12 @@  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, "function calls are not allowed while holding a lock\n");
+			return -EINVAL;
+		}
+
 		if (err) {
 			verbose(env, "Caller passes invalid args into func#%d ('%s')\n",
 				subprog, sub_name);
@@ -17644,7 +17650,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 +17697,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;
 				}
diff --git a/tools/testing/selftests/bpf/progs/verifier_spin_lock.c b/tools/testing/selftests/bpf/progs/verifier_spin_lock.c
index 9c1aa69650f8..fb316c080c84 100644
--- a/tools/testing/selftests/bpf/progs/verifier_spin_lock.c
+++ b/tools/testing/selftests/bpf/progs/verifier_spin_lock.c
@@ -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)
 {