* [PATCH bpf v2 0/3] Two more fixes for check_max_stack_depth
@ 2023-07-17 16:15 Kumar Kartikeya Dwivedi
2023-07-17 16:15 ` [PATCH bpf v2 1/3] bpf: Fix subprog idx logic in check_max_stack_depth Kumar Kartikeya Dwivedi
` (2 more replies)
0 siblings, 3 replies; 5+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2023-07-17 16:15 UTC (permalink / raw)
To: bpf; +Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Martin KaFai Lau
I noticed two more bugs while reviewing the code, description and
examples available in the patches.
One leads to incorrect subprog index to be stored in the frame stack
maintained by the function (leading to incorrect tail_call_reachable
marks, among other things).
The other problem is missing exploration pass of other async callbacks
when they are not called from the main prog. Call chains rooted at them
can thus bypass the stack limits (32 call frames * max permitted stack
depth per function).
Changelog:
----------
v1 -> v2
v1: https://lore.kernel.org/bpf/20230713003118.1327943-1-memxor@gmail.com
* Fix commit message for patch 2 (Alexei)
Kumar Kartikeya Dwivedi (3):
bpf: Fix subprog idx logic in check_max_stack_depth
bpf: Repeat check_max_stack_depth for async callbacks
selftests/bpf: Add more tests for check_max_stack_depth bug
kernel/bpf/verifier.c | 32 +++++++++++++++----
.../selftests/bpf/progs/async_stack_depth.c | 25 +++++++++++++--
2 files changed, 48 insertions(+), 9 deletions(-)
base-commit: 9840036786d90cea11a90d1f30b6dc003b34ee67
--
2.40.1
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH bpf v2 1/3] bpf: Fix subprog idx logic in check_max_stack_depth
2023-07-17 16:15 [PATCH bpf v2 0/3] Two more fixes for check_max_stack_depth Kumar Kartikeya Dwivedi
@ 2023-07-17 16:15 ` Kumar Kartikeya Dwivedi
2023-07-17 16:15 ` [PATCH bpf v2 2/3] bpf: Repeat check_max_stack_depth for async callbacks Kumar Kartikeya Dwivedi
2023-07-17 16:15 ` [PATCH bpf v2 3/3] selftests/bpf: Add more tests for check_max_stack_depth bug Kumar Kartikeya Dwivedi
2 siblings, 0 replies; 5+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2023-07-17 16:15 UTC (permalink / raw)
To: bpf; +Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Martin KaFai Lau
The assignment to idx in check_max_stack_depth happens once we see a
bpf_pseudo_call or bpf_pseudo_func. This is not an issue as the rest of
the code performs a few checks and then pushes the frame to the frame
stack, except the case of async callbacks. If the async callback case
causes the loop iteration to be skipped, the idx assignment will be
incorrect on the next iteration of the loop. The value stored in the
frame stack (as the subprogno of the current subprog) will be incorrect.
This leads to incorrect checks and incorrect tail_call_reachable
marking. Save the target subprog in a new variable and only assign to
idx once we are done with the is_async_cb check which may skip pushing
of frame to the frame stack and subsequent stack depth checks and tail
call markings.
Fixes: 7ddc80a476c2 ("bpf: Teach stack depth check about async callbacks.")
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
kernel/bpf/verifier.c | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 930b5555cfd3..e682056dd144 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -5621,7 +5621,7 @@ static int check_max_stack_depth(struct bpf_verifier_env *env)
continue_func:
subprog_end = subprog[idx + 1].start;
for (; i < subprog_end; i++) {
- int next_insn;
+ int next_insn, sidx;
if (!bpf_pseudo_call(insn + i) && !bpf_pseudo_func(insn + i))
continue;
@@ -5631,14 +5631,14 @@ static int check_max_stack_depth(struct bpf_verifier_env *env)
/* find the callee */
next_insn = i + insn[i].imm + 1;
- idx = find_subprog(env, next_insn);
- if (idx < 0) {
+ sidx = find_subprog(env, next_insn);
+ if (sidx < 0) {
WARN_ONCE(1, "verifier bug. No program starts at insn %d\n",
next_insn);
return -EFAULT;
}
- if (subprog[idx].is_async_cb) {
- if (subprog[idx].has_tail_call) {
+ if (subprog[sidx].is_async_cb) {
+ if (subprog[sidx].has_tail_call) {
verbose(env, "verifier bug. subprog has tail_call and async cb\n");
return -EFAULT;
}
@@ -5647,6 +5647,7 @@ static int check_max_stack_depth(struct bpf_verifier_env *env)
continue;
}
i = next_insn;
+ idx = sidx;
if (subprog[idx].has_tail_call)
tail_call_reachable = true;
--
2.40.1
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH bpf v2 2/3] bpf: Repeat check_max_stack_depth for async callbacks
2023-07-17 16:15 [PATCH bpf v2 0/3] Two more fixes for check_max_stack_depth Kumar Kartikeya Dwivedi
2023-07-17 16:15 ` [PATCH bpf v2 1/3] bpf: Fix subprog idx logic in check_max_stack_depth Kumar Kartikeya Dwivedi
@ 2023-07-17 16:15 ` Kumar Kartikeya Dwivedi
2023-07-18 22:29 ` Alexei Starovoitov
2023-07-17 16:15 ` [PATCH bpf v2 3/3] selftests/bpf: Add more tests for check_max_stack_depth bug Kumar Kartikeya Dwivedi
2 siblings, 1 reply; 5+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2023-07-17 16:15 UTC (permalink / raw)
To: bpf; +Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Martin KaFai Lau
While the check_max_stack_depth function explores call chains emanating
from the main prog, which is typically enough to cover all possible call
chains, it doesn't explore those rooted at async callbacks unless the
async callback will have been directly called, since unlike non-async
callbacks it skips their instruction exploration as they don't
contribute to stack depth.
It could be the case that the async callback leads to a callchain which
exceeds the stack depth, but this is never reachable while only
exploring the entry point from main subprog. Hence, repeat the check for
the main subprog *and* all async callbacks marked by the symbolic
execution pass of the verifier, as execution of the program may begin at
any of them.
Consider functions with following stack depths:
main: 256
async: 256
foo: 256
main:
rX = async
bpf_timer_set_callback(...)
async:
foo()
Here, async is not descended as it does not contribute to stack depth of
main (since it is referenced using bpf_pseudo_func and not
bpf_pseudo_call). However, when async is invoked asynchronously, it will
end up breaching the MAX_BPF_STACK limit by calling foo.
Hence, in addition to main, we also need to explore call chains
beginning at all async callback subprogs in a program.
Fixes: 7ddc80a476c2 ("bpf: Teach stack depth check about async callbacks.")
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
kernel/bpf/verifier.c | 21 +++++++++++++++++++--
1 file changed, 19 insertions(+), 2 deletions(-)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index e682056dd144..02a021c524ab 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -5573,16 +5573,17 @@ static int update_stack_depth(struct bpf_verifier_env *env,
* Since recursion is prevented by check_cfg() this algorithm
* only needs a local stack of MAX_CALL_FRAMES to remember callsites
*/
-static int check_max_stack_depth(struct bpf_verifier_env *env)
+static int check_max_stack_depth_subprog(struct bpf_verifier_env *env, int idx)
{
- int depth = 0, frame = 0, idx = 0, i = 0, subprog_end;
struct bpf_subprog_info *subprog = env->subprog_info;
struct bpf_insn *insn = env->prog->insnsi;
+ int depth = 0, frame = 0, i, subprog_end;
bool tail_call_reachable = false;
int ret_insn[MAX_CALL_FRAMES];
int ret_prog[MAX_CALL_FRAMES];
int j;
+ i = subprog[idx].start;
process_func:
/* protect against potential stack overflow that might happen when
* bpf2bpf calls get combined with tailcalls. Limit the caller's stack
@@ -5683,6 +5684,22 @@ static int check_max_stack_depth(struct bpf_verifier_env *env)
goto continue_func;
}
+static int check_max_stack_depth(struct bpf_verifier_env *env)
+{
+ struct bpf_subprog_info *si = env->subprog_info;
+ int ret;
+
+ for (int i = 0; i < env->subprog_cnt; i++) {
+ if (!i || si[i].is_async_cb) {
+ ret = check_max_stack_depth_subprog(env, i);
+ if (ret < 0)
+ return ret;
+ }
+ continue;
+ }
+ return 0;
+}
+
#ifndef CONFIG_BPF_JIT_ALWAYS_ON
static int get_callee_stack_depth(struct bpf_verifier_env *env,
const struct bpf_insn *insn, int idx)
--
2.40.1
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH bpf v2 3/3] selftests/bpf: Add more tests for check_max_stack_depth bug
2023-07-17 16:15 [PATCH bpf v2 0/3] Two more fixes for check_max_stack_depth Kumar Kartikeya Dwivedi
2023-07-17 16:15 ` [PATCH bpf v2 1/3] bpf: Fix subprog idx logic in check_max_stack_depth Kumar Kartikeya Dwivedi
2023-07-17 16:15 ` [PATCH bpf v2 2/3] bpf: Repeat check_max_stack_depth for async callbacks Kumar Kartikeya Dwivedi
@ 2023-07-17 16:15 ` Kumar Kartikeya Dwivedi
2 siblings, 0 replies; 5+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2023-07-17 16:15 UTC (permalink / raw)
To: bpf; +Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Martin KaFai Lau
Another test which now exercies the path of the verifier where it will
explore call chains rooted at the async callback. Without the prior
fixes, this program loads successfully, which is incorrect.
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
.../selftests/bpf/progs/async_stack_depth.c | 25 +++++++++++++++++--
1 file changed, 23 insertions(+), 2 deletions(-)
diff --git a/tools/testing/selftests/bpf/progs/async_stack_depth.c b/tools/testing/selftests/bpf/progs/async_stack_depth.c
index 477ba950bb43..3517c0e01206 100644
--- a/tools/testing/selftests/bpf/progs/async_stack_depth.c
+++ b/tools/testing/selftests/bpf/progs/async_stack_depth.c
@@ -22,9 +22,16 @@ static int timer_cb(void *map, int *key, struct bpf_timer *timer)
return buf[69];
}
+__attribute__((noinline))
+static int bad_timer_cb(void *map, int *key, struct bpf_timer *timer)
+{
+ volatile char buf[300] = {};
+ return buf[255] + timer_cb(NULL, NULL, NULL);
+}
+
SEC("tc")
-__failure __msg("combined stack size of 2 calls")
-int prog(struct __sk_buff *ctx)
+__failure __msg("combined stack size of 2 calls is 576. Too large")
+int pseudo_call_check(struct __sk_buff *ctx)
{
struct hmap_elem *elem;
volatile char buf[256] = {};
@@ -37,4 +44,18 @@ int prog(struct __sk_buff *ctx)
return bpf_timer_set_callback(&elem->timer, timer_cb) + buf[0];
}
+SEC("tc")
+__failure __msg("combined stack size of 2 calls is 608. Too large")
+int async_call_root_check(struct __sk_buff *ctx)
+{
+ struct hmap_elem *elem;
+ volatile char buf[256] = {};
+
+ elem = bpf_map_lookup_elem(&hmap, &(int){0});
+ if (!elem)
+ return 0;
+
+ return bpf_timer_set_callback(&elem->timer, bad_timer_cb) + buf[0];
+}
+
char _license[] SEC("license") = "GPL";
--
2.40.1
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH bpf v2 2/3] bpf: Repeat check_max_stack_depth for async callbacks
2023-07-17 16:15 ` [PATCH bpf v2 2/3] bpf: Repeat check_max_stack_depth for async callbacks Kumar Kartikeya Dwivedi
@ 2023-07-18 22:29 ` Alexei Starovoitov
0 siblings, 0 replies; 5+ messages in thread
From: Alexei Starovoitov @ 2023-07-18 22:29 UTC (permalink / raw)
To: Kumar Kartikeya Dwivedi
Cc: bpf, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Martin KaFai Lau
On Mon, Jul 17, 2023 at 9:15 AM Kumar Kartikeya Dwivedi
<memxor@gmail.com> wrote:
>
> While the check_max_stack_depth function explores call chains emanating
> from the main prog, which is typically enough to cover all possible call
> chains, it doesn't explore those rooted at async callbacks unless the
> async callback will have been directly called, since unlike non-async
> callbacks it skips their instruction exploration as they don't
> contribute to stack depth.
>
> It could be the case that the async callback leads to a callchain which
> exceeds the stack depth, but this is never reachable while only
> exploring the entry point from main subprog. Hence, repeat the check for
> the main subprog *and* all async callbacks marked by the symbolic
> execution pass of the verifier, as execution of the program may begin at
> any of them.
>
> Consider functions with following stack depths:
> main: 256
> async: 256
> foo: 256
>
> main:
> rX = async
> bpf_timer_set_callback(...)
>
> async:
> foo()
>
> Here, async is not descended as it does not contribute to stack depth of
> main (since it is referenced using bpf_pseudo_func and not
> bpf_pseudo_call). However, when async is invoked asynchronously, it will
> end up breaching the MAX_BPF_STACK limit by calling foo.
>
> Hence, in addition to main, we also need to explore call chains
> beginning at all async callback subprogs in a program.
>
> Fixes: 7ddc80a476c2 ("bpf: Teach stack depth check about async callbacks.")
> Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Applied. Thanks
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2023-07-18 22:29 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2023-07-17 16:15 [PATCH bpf v2 0/3] Two more fixes for check_max_stack_depth Kumar Kartikeya Dwivedi
2023-07-17 16:15 ` [PATCH bpf v2 1/3] bpf: Fix subprog idx logic in check_max_stack_depth Kumar Kartikeya Dwivedi
2023-07-17 16:15 ` [PATCH bpf v2 2/3] bpf: Repeat check_max_stack_depth for async callbacks Kumar Kartikeya Dwivedi
2023-07-18 22:29 ` Alexei Starovoitov
2023-07-17 16:15 ` [PATCH bpf v2 3/3] selftests/bpf: Add more tests for check_max_stack_depth bug Kumar Kartikeya Dwivedi
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).