]> git.proxmox.com Git - mirror_ubuntu-kernels.git/commitdiff
nfp: bpf: account for additional stack usage when checking stack limit
authorQuentin Monnet <quentin.monnet@netronome.com>
Sun, 7 Oct 2018 11:56:53 +0000 (12:56 +0100)
committerDaniel Borkmann <daniel@iogearbox.net>
Mon, 8 Oct 2018 08:24:13 +0000 (10:24 +0200)
Offloaded programs using BPF-to-BPF calls use the stack to store the
return address when calling into a subprogram. Callees also need some
space to save eBPF registers R6 to R9. And contrarily to kernel
verifier, we align stack frames on 64 bytes (and not 32). Account for
all this when checking the stack size limit before JIT-ing the program.
This means we have to recompute maximum stack usage for the program, we
cannot get the value from the kernel.

In addition to adapting the checks on stack usage, move them to the
finalize() callback, now that we have it and because such checks are
part of the verification step rather than translation.

Signed-off-by: Quentin Monnet <quentin.monnet@netronome.com>
Reviewed-by: Jakub Kicinski <jakub.kicinski@netronome.com>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
drivers/net/ethernet/netronome/nfp/bpf/offload.c
drivers/net/ethernet/netronome/nfp/bpf/verifier.c

index 2ebd13b29c971f56101b4696c79499d3d3459dc2..49c7bead81136835cf39ec397b78945b32b42a3f 100644 (file)
@@ -252,17 +252,9 @@ err_free:
 static int nfp_bpf_translate(struct nfp_net *nn, struct bpf_prog *prog)
 {
        struct nfp_prog *nfp_prog = prog->aux->offload->dev_priv;
-       unsigned int stack_size;
        unsigned int max_instr;
        int err;
 
-       stack_size = nn_readb(nn, NFP_NET_CFG_BPF_STACK_SZ) * 64;
-       if (prog->aux->stack_depth > stack_size) {
-               nn_info(nn, "stack too large: program %dB > FW stack %dB\n",
-                       prog->aux->stack_depth, stack_size);
-               return -EOPNOTSUPP;
-       }
-
        max_instr = nn_readw(nn, NFP_NET_CFG_BPF_MAX_LEN);
        nfp_prog->__prog_alloc_len = max_instr * sizeof(u64);
 
index cc1b2c601f4e2c893d6d0cd4bef2f9c73830f25f..81a463726d559161c17c001cb9fa0b95eb9a31cd 100644 (file)
 #include <linux/bpf.h>
 #include <linux/bpf_verifier.h>
 #include <linux/kernel.h>
+#include <linux/netdevice.h>
 #include <linux/pkt_cls.h>
 
 #include "../nfp_app.h"
 #include "../nfp_main.h"
+#include "../nfp_net.h"
 #include "fw.h"
 #include "main.h"
 
@@ -662,10 +664,67 @@ nfp_assign_subprog_idx(struct bpf_verifier_env *env, struct nfp_prog *nfp_prog)
        return 0;
 }
 
+static unsigned int
+nfp_bpf_get_stack_usage(struct nfp_prog *nfp_prog, unsigned int cnt)
+{
+       struct nfp_insn_meta *meta = nfp_prog_first_meta(nfp_prog);
+       unsigned int max_depth = 0, depth = 0, frame = 0;
+       struct nfp_insn_meta *ret_insn[MAX_CALL_FRAMES];
+       unsigned short frame_depths[MAX_CALL_FRAMES];
+       unsigned short ret_prog[MAX_CALL_FRAMES];
+       unsigned short idx = meta->subprog_idx;
+
+       /* Inspired from check_max_stack_depth() from kernel verifier.
+        * Starting from main subprogram, walk all instructions and recursively
+        * walk all callees that given subprogram can call. Since recursion is
+        * prevented by the kernel verifier, this algorithm only needs a local
+        * stack of MAX_CALL_FRAMES to remember callsites.
+        */
+process_subprog:
+       frame_depths[frame] = nfp_prog->subprog[idx].stack_depth;
+       frame_depths[frame] = round_up(frame_depths[frame], STACK_FRAME_ALIGN);
+       depth += frame_depths[frame];
+       max_depth = max(max_depth, depth);
+
+continue_subprog:
+       for (; meta != nfp_prog_last_meta(nfp_prog) && meta->subprog_idx == idx;
+            meta = nfp_meta_next(meta)) {
+               if (!is_mbpf_pseudo_call(meta))
+                       continue;
+
+               /* We found a call to a subprogram. Remember instruction to
+                * return to and subprog id.
+                */
+               ret_insn[frame] = nfp_meta_next(meta);
+               ret_prog[frame] = idx;
+
+               /* Find the callee and start processing it. */
+               meta = nfp_bpf_goto_meta(nfp_prog, meta,
+                                        meta->n + 1 + meta->insn.imm, cnt);
+               idx = meta->subprog_idx;
+               frame++;
+               goto process_subprog;
+       }
+       /* End of for() loop means the last instruction of the subprog was
+        * reached. If we popped all stack frames, return; otherwise, go on
+        * processing remaining instructions from the caller.
+        */
+       if (frame == 0)
+               return max_depth;
+
+       depth -= frame_depths[frame];
+       frame--;
+       meta = ret_insn[frame];
+       idx = ret_prog[frame];
+       goto continue_subprog;
+}
+
 static int nfp_bpf_finalize(struct bpf_verifier_env *env)
 {
+       unsigned int stack_size, stack_needed;
        struct bpf_subprog_info *info;
        struct nfp_prog *nfp_prog;
+       struct nfp_net *nn;
        int i;
 
        nfp_prog = env->prog->aux->offload->dev_priv;
@@ -690,6 +749,15 @@ static int nfp_bpf_finalize(struct bpf_verifier_env *env)
                nfp_prog->subprog[i].stack_depth += BPF_REG_SIZE * 4;
        }
 
+       nn = netdev_priv(env->prog->aux->offload->netdev);
+       stack_size = nn_readb(nn, NFP_NET_CFG_BPF_STACK_SZ) * 64;
+       stack_needed = nfp_bpf_get_stack_usage(nfp_prog, env->prog->len);
+       if (stack_needed > stack_size) {
+               pr_vlog(env, "stack too large: program %dB > FW stack %dB\n",
+                       stack_needed, stack_size);
+               return -EOPNOTSUPP;
+       }
+
        return 0;
 }