xref: /openbmc/linux/drivers/net/ethernet/netronome/nfp/bpf/verifier.c (revision 9dae47aba0a055f761176d9297371d5bb24289ec)
1 /*
2  * Copyright (C) 2016-2017 Netronome Systems, Inc.
3  *
4  * This software is dual licensed under the GNU General License Version 2,
5  * June 1991 as shown in the file COPYING in the top-level directory of this
6  * source tree or the BSD 2-Clause License provided below.  You have the
7  * option to license this software under the complete terms of either license.
8  *
9  * The BSD 2-Clause License:
10  *
11  *     Redistribution and use in source and binary forms, with or
12  *     without modification, are permitted provided that the following
13  *     conditions are met:
14  *
15  *      1. Redistributions of source code must retain the above
16  *         copyright notice, this list of conditions and the following
17  *         disclaimer.
18  *
19  *      2. Redistributions in binary form must reproduce the above
20  *         copyright notice, this list of conditions and the following
21  *         disclaimer in the documentation and/or other materials
22  *         provided with the distribution.
23  *
24  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
28  * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
29  * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
30  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31  * SOFTWARE.
32  */
33 
34 #define pr_fmt(fmt)	"NFP net bpf: " fmt
35 
36 #include <linux/bpf.h>
37 #include <linux/bpf_verifier.h>
38 #include <linux/kernel.h>
39 #include <linux/pkt_cls.h>
40 
41 #include "fw.h"
42 #include "main.h"
43 
44 struct nfp_insn_meta *
45 nfp_bpf_goto_meta(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta,
46 		  unsigned int insn_idx, unsigned int n_insns)
47 {
48 	unsigned int forward, backward, i;
49 
50 	backward = meta->n - insn_idx;
51 	forward = insn_idx - meta->n;
52 
53 	if (min(forward, backward) > n_insns - insn_idx - 1) {
54 		backward = n_insns - insn_idx - 1;
55 		meta = nfp_prog_last_meta(nfp_prog);
56 	}
57 	if (min(forward, backward) > insn_idx && backward > insn_idx) {
58 		forward = insn_idx;
59 		meta = nfp_prog_first_meta(nfp_prog);
60 	}
61 
62 	if (forward < backward)
63 		for (i = 0; i < forward; i++)
64 			meta = nfp_meta_next(meta);
65 	else
66 		for (i = 0; i < backward; i++)
67 			meta = nfp_meta_prev(meta);
68 
69 	return meta;
70 }
71 
72 static void
73 nfp_record_adjust_head(struct nfp_app_bpf *bpf, struct nfp_prog *nfp_prog,
74 		       struct nfp_insn_meta *meta,
75 		       const struct bpf_reg_state *reg2)
76 {
77 	unsigned int location =	UINT_MAX;
78 	int imm;
79 
80 	/* Datapath usually can give us guarantees on how much adjust head
81 	 * can be done without the need for any checks.  Optimize the simple
82 	 * case where there is only one adjust head by a constant.
83 	 */
84 	if (reg2->type != SCALAR_VALUE || !tnum_is_const(reg2->var_off))
85 		goto exit_set_location;
86 	imm = reg2->var_off.value;
87 	/* Translator will skip all checks, we need to guarantee min pkt len */
88 	if (imm > ETH_ZLEN - ETH_HLEN)
89 		goto exit_set_location;
90 	if (imm > (int)bpf->adjust_head.guaranteed_add ||
91 	    imm < -bpf->adjust_head.guaranteed_sub)
92 		goto exit_set_location;
93 
94 	if (nfp_prog->adjust_head_location) {
95 		/* Only one call per program allowed */
96 		if (nfp_prog->adjust_head_location != meta->n)
97 			goto exit_set_location;
98 
99 		if (meta->arg2.var_off.value != imm)
100 			goto exit_set_location;
101 	}
102 
103 	location = meta->n;
104 exit_set_location:
105 	nfp_prog->adjust_head_location = location;
106 }
107 
108 static int
109 nfp_bpf_check_call(struct nfp_prog *nfp_prog, struct bpf_verifier_env *env,
110 		   struct nfp_insn_meta *meta)
111 {
112 	const struct bpf_reg_state *reg2 = cur_regs(env) + BPF_REG_2;
113 	struct nfp_app_bpf *bpf = nfp_prog->bpf;
114 	u32 func_id = meta->insn.imm;
115 
116 	switch (func_id) {
117 	case BPF_FUNC_xdp_adjust_head:
118 		if (!bpf->adjust_head.off_max) {
119 			pr_warn("adjust_head not supported by FW\n");
120 			return -EOPNOTSUPP;
121 		}
122 		if (!(bpf->adjust_head.flags & NFP_BPF_ADJUST_HEAD_NO_META)) {
123 			pr_warn("adjust_head: FW requires shifting metadata, not supported by the driver\n");
124 			return -EOPNOTSUPP;
125 		}
126 
127 		nfp_record_adjust_head(bpf, nfp_prog, meta, reg2);
128 		break;
129 	default:
130 		pr_warn("unsupported function id: %d\n", func_id);
131 		return -EOPNOTSUPP;
132 	}
133 
134 	meta->arg2 = *reg2;
135 
136 	return 0;
137 }
138 
139 static int
140 nfp_bpf_check_exit(struct nfp_prog *nfp_prog,
141 		   struct bpf_verifier_env *env)
142 {
143 	const struct bpf_reg_state *reg0 = cur_regs(env) + BPF_REG_0;
144 	u64 imm;
145 
146 	if (nfp_prog->type == BPF_PROG_TYPE_XDP)
147 		return 0;
148 
149 	if (!(reg0->type == SCALAR_VALUE && tnum_is_const(reg0->var_off))) {
150 		char tn_buf[48];
151 
152 		tnum_strn(tn_buf, sizeof(tn_buf), reg0->var_off);
153 		pr_info("unsupported exit state: %d, var_off: %s\n",
154 			reg0->type, tn_buf);
155 		return -EINVAL;
156 	}
157 
158 	imm = reg0->var_off.value;
159 	if (nfp_prog->type == BPF_PROG_TYPE_SCHED_CLS &&
160 	    imm <= TC_ACT_REDIRECT &&
161 	    imm != TC_ACT_SHOT && imm != TC_ACT_STOLEN &&
162 	    imm != TC_ACT_QUEUED) {
163 		pr_info("unsupported exit state: %d, imm: %llx\n",
164 			reg0->type, imm);
165 		return -EINVAL;
166 	}
167 
168 	return 0;
169 }
170 
171 static int
172 nfp_bpf_check_stack_access(struct nfp_prog *nfp_prog,
173 			   struct nfp_insn_meta *meta,
174 			   const struct bpf_reg_state *reg)
175 {
176 	s32 old_off, new_off;
177 
178 	if (!tnum_is_const(reg->var_off)) {
179 		pr_info("variable ptr stack access\n");
180 		return -EINVAL;
181 	}
182 
183 	if (meta->ptr.type == NOT_INIT)
184 		return 0;
185 
186 	old_off = meta->ptr.off + meta->ptr.var_off.value;
187 	new_off = reg->off + reg->var_off.value;
188 
189 	meta->ptr_not_const |= old_off != new_off;
190 
191 	if (!meta->ptr_not_const)
192 		return 0;
193 
194 	if (old_off % 4 == new_off % 4)
195 		return 0;
196 
197 	pr_info("stack access changed location was:%d is:%d\n",
198 		old_off, new_off);
199 	return -EINVAL;
200 }
201 
202 static int
203 nfp_bpf_check_ptr(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta,
204 		  struct bpf_verifier_env *env, u8 reg_no)
205 {
206 	const struct bpf_reg_state *reg = cur_regs(env) + reg_no;
207 	int err;
208 
209 	if (reg->type != PTR_TO_CTX &&
210 	    reg->type != PTR_TO_STACK &&
211 	    reg->type != PTR_TO_PACKET) {
212 		pr_info("unsupported ptr type: %d\n", reg->type);
213 		return -EINVAL;
214 	}
215 
216 	if (reg->type == PTR_TO_STACK) {
217 		err = nfp_bpf_check_stack_access(nfp_prog, meta, reg);
218 		if (err)
219 			return err;
220 	}
221 
222 	if (meta->ptr.type != NOT_INIT && meta->ptr.type != reg->type) {
223 		pr_info("ptr type changed for instruction %d -> %d\n",
224 			meta->ptr.type, reg->type);
225 		return -EINVAL;
226 	}
227 
228 	meta->ptr = *reg;
229 
230 	return 0;
231 }
232 
233 static int
234 nfp_verify_insn(struct bpf_verifier_env *env, int insn_idx, int prev_insn_idx)
235 {
236 	struct nfp_prog *nfp_prog = env->prog->aux->offload->dev_priv;
237 	struct nfp_insn_meta *meta = nfp_prog->verifier_meta;
238 
239 	meta = nfp_bpf_goto_meta(nfp_prog, meta, insn_idx, env->prog->len);
240 	nfp_prog->verifier_meta = meta;
241 
242 	if (meta->insn.src_reg >= MAX_BPF_REG ||
243 	    meta->insn.dst_reg >= MAX_BPF_REG) {
244 		pr_err("program uses extended registers - jit hardening?\n");
245 		return -EINVAL;
246 	}
247 
248 	if (meta->insn.code == (BPF_JMP | BPF_CALL))
249 		return nfp_bpf_check_call(nfp_prog, env, meta);
250 	if (meta->insn.code == (BPF_JMP | BPF_EXIT))
251 		return nfp_bpf_check_exit(nfp_prog, env);
252 
253 	if (is_mbpf_load(meta))
254 		return nfp_bpf_check_ptr(nfp_prog, meta, env,
255 					 meta->insn.src_reg);
256 	if (is_mbpf_store(meta))
257 		return nfp_bpf_check_ptr(nfp_prog, meta, env,
258 					 meta->insn.dst_reg);
259 
260 	return 0;
261 }
262 
263 const struct bpf_prog_offload_ops nfp_bpf_analyzer_ops = {
264 	.insn_hook = nfp_verify_insn,
265 };
266