xref: /linux/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c (revision 24f171c7e145f43b9f187578e89b0982ce87e54c)
1 // SPDX-License-Identifier: GPL-2.0
2 
3 #include <bpf/bpf.h>
4 #include <test_progs.h>
5 
6 #ifdef __x86_64__
7 static int map_create(__u32 map_type, __u32 max_entries)
8 {
9 	const char *map_name = "insn_array";
10 	__u32 key_size = 4;
11 	__u32 value_size = sizeof(struct bpf_insn_array_value);
12 
13 	return bpf_map_create(map_type, map_name, key_size, value_size, max_entries, NULL);
14 }
15 
16 static int prog_load(struct bpf_insn *insns, __u32 insn_cnt, int *fd_array, __u32 fd_array_cnt)
17 {
18 	LIBBPF_OPTS(bpf_prog_load_opts, opts);
19 
20 	opts.fd_array = fd_array;
21 	opts.fd_array_cnt = fd_array_cnt;
22 
23 	return bpf_prog_load(BPF_PROG_TYPE_XDP, NULL, "GPL", insns, insn_cnt, &opts);
24 }
25 
26 static void __check_success(struct bpf_insn *insns, __u32 insn_cnt, __u32 *map_in, __u32 *map_out)
27 {
28 	struct bpf_insn_array_value val = {};
29 	int prog_fd = -1, map_fd, i;
30 
31 	map_fd = map_create(BPF_MAP_TYPE_INSN_ARRAY, insn_cnt);
32 	if (!ASSERT_GE(map_fd, 0, "map_create"))
33 		return;
34 
35 	for (i = 0; i < insn_cnt; i++) {
36 		val.orig_off = map_in[i];
37 		if (!ASSERT_EQ(bpf_map_update_elem(map_fd, &i, &val, 0), 0, "bpf_map_update_elem"))
38 			goto cleanup;
39 	}
40 
41 	if (!ASSERT_EQ(bpf_map_freeze(map_fd), 0, "bpf_map_freeze"))
42 		goto cleanup;
43 
44 	prog_fd = prog_load(insns, insn_cnt, &map_fd, 1);
45 	if (!ASSERT_GE(prog_fd, 0, "bpf(BPF_PROG_LOAD)"))
46 		goto cleanup;
47 
48 	for (i = 0; i < insn_cnt; i++) {
49 		char buf[64];
50 
51 		if (!ASSERT_EQ(bpf_map_lookup_elem(map_fd, &i, &val), 0, "bpf_map_lookup_elem"))
52 			goto cleanup;
53 
54 		snprintf(buf, sizeof(buf), "val.xlated_off should be equal map_out[%d]", i);
55 		ASSERT_EQ(val.xlated_off, map_out[i], buf);
56 	}
57 
58 cleanup:
59 	close(prog_fd);
60 	close(map_fd);
61 }
62 
63 /*
64  * Load a program, which will not be anyhow mangled by the verifier.  Add an
65  * insn_array map pointing to every instruction. Check that it hasn't changed
66  * after the program load.
67  */
68 static void check_one_to_one_mapping(void)
69 {
70 	struct bpf_insn insns[] = {
71 		BPF_MOV64_IMM(BPF_REG_0, 4),
72 		BPF_MOV64_IMM(BPF_REG_0, 3),
73 		BPF_MOV64_IMM(BPF_REG_0, 2),
74 		BPF_MOV64_IMM(BPF_REG_0, 1),
75 		BPF_MOV64_IMM(BPF_REG_0, 0),
76 		BPF_EXIT_INSN(),
77 	};
78 	__u32 map_in[] = {0, 1, 2, 3, 4, 5};
79 	__u32 map_out[] = {0, 1, 2, 3, 4, 5};
80 
81 	__check_success(insns, ARRAY_SIZE(insns), map_in, map_out);
82 }
83 
84 /*
85  * Load a program with two patches (get jiffies, for simplicity). Add an
86  * insn_array map pointing to every instruction. Check how it was changed
87  * after the program load.
88  */
89 static void check_simple(void)
90 {
91 	struct bpf_insn insns[] = {
92 		BPF_MOV64_IMM(BPF_REG_0, 2),
93 		BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_jiffies64),
94 		BPF_MOV64_IMM(BPF_REG_0, 1),
95 		BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_jiffies64),
96 		BPF_MOV64_IMM(BPF_REG_0, 0),
97 		BPF_EXIT_INSN(),
98 	};
99 	__u32 map_in[] = {0, 1, 2, 3, 4, 5};
100 	__u32 map_out[] = {0, 1, 4, 5, 8, 9};
101 
102 	__check_success(insns, ARRAY_SIZE(insns), map_in, map_out);
103 }
104 
105 /*
106  * Verifier can delete code in two cases: nops & dead code. From insn
107  * array's point of view, the two cases are the same, so test using
108  * the simplest method: by loading some nops
109  */
110 static void check_deletions(void)
111 {
112 	struct bpf_insn insns[] = {
113 		BPF_MOV64_IMM(BPF_REG_0, 2),
114 		BPF_JMP_IMM(BPF_JA, 0, 0, 0), /* nop */
115 		BPF_MOV64_IMM(BPF_REG_0, 1),
116 		BPF_JMP_IMM(BPF_JA, 0, 0, 0), /* nop */
117 		BPF_MOV64_IMM(BPF_REG_0, 0),
118 		BPF_EXIT_INSN(),
119 	};
120 	__u32 map_in[] = {0, 1, 2, 3, 4, 5};
121 	__u32 map_out[] = {0, -1, 1, -1, 2, 3};
122 
123 	__check_success(insns, ARRAY_SIZE(insns), map_in, map_out);
124 }
125 
126 /*
127  * Same test as check_deletions, but also add code which adds instructions
128  */
129 static void check_deletions_with_functions(void)
130 {
131 	struct bpf_insn insns[] = {
132 		BPF_JMP_IMM(BPF_JA, 0, 0, 0), /* nop */
133 		BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_jiffies64),
134 		BPF_JMP_IMM(BPF_JA, 0, 0, 0), /* nop */
135 		BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 1, 0, 2),
136 		BPF_MOV64_IMM(BPF_REG_0, 1),
137 		BPF_EXIT_INSN(),
138 		BPF_JMP_IMM(BPF_JA, 0, 0, 0), /* nop */
139 		BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_jiffies64),
140 		BPF_JMP_IMM(BPF_JA, 0, 0, 0), /* nop */
141 		BPF_MOV64_IMM(BPF_REG_0, 2),
142 		BPF_EXIT_INSN(),
143 	};
144 	__u32 map_in[] =  { 0, 1,  2, 3, 4, 5, /* func */  6, 7,  8, 9, 10};
145 	__u32 map_out[] = {-1, 0, -1, 3, 4, 5, /* func */ -1, 6, -1, 9, 10};
146 
147 	__check_success(insns, ARRAY_SIZE(insns), map_in, map_out);
148 }
149 
150 /*
151  * Try to load a program with a map which points to outside of the program
152  */
153 static void check_out_of_bounds_index(void)
154 {
155 	struct bpf_insn insns[] = {
156 		BPF_MOV64_IMM(BPF_REG_0, 4),
157 		BPF_MOV64_IMM(BPF_REG_0, 3),
158 		BPF_MOV64_IMM(BPF_REG_0, 2),
159 		BPF_MOV64_IMM(BPF_REG_0, 1),
160 		BPF_MOV64_IMM(BPF_REG_0, 0),
161 		BPF_EXIT_INSN(),
162 	};
163 	int prog_fd, map_fd;
164 	struct bpf_insn_array_value val = {};
165 	int key;
166 
167 	map_fd = map_create(BPF_MAP_TYPE_INSN_ARRAY, 1);
168 	if (!ASSERT_GE(map_fd, 0, "map_create"))
169 		return;
170 
171 	key = 0;
172 	val.orig_off = ARRAY_SIZE(insns); /* too big */
173 	if (!ASSERT_EQ(bpf_map_update_elem(map_fd, &key, &val, 0), 0, "bpf_map_update_elem"))
174 		goto cleanup;
175 
176 	if (!ASSERT_EQ(bpf_map_freeze(map_fd), 0, "bpf_map_freeze"))
177 		goto cleanup;
178 
179 	prog_fd = prog_load(insns, ARRAY_SIZE(insns), &map_fd, 1);
180 	if (!ASSERT_EQ(prog_fd, -EINVAL, "program should have been rejected (prog_fd != -EINVAL)")) {
181 		close(prog_fd);
182 		goto cleanup;
183 	}
184 
185 cleanup:
186 	close(map_fd);
187 }
188 
189 /*
190  * Try to load a program with a map which points to the middle of 16-bit insn
191  */
192 static void check_mid_insn_index(void)
193 {
194 	struct bpf_insn insns[] = {
195 		BPF_LD_IMM64(BPF_REG_0, 0), /* 2 x 8 */
196 		BPF_EXIT_INSN(),
197 	};
198 	int prog_fd, map_fd;
199 	struct bpf_insn_array_value val = {};
200 	int key;
201 
202 	map_fd = map_create(BPF_MAP_TYPE_INSN_ARRAY, 1);
203 	if (!ASSERT_GE(map_fd, 0, "map_create"))
204 		return;
205 
206 	key = 0;
207 	val.orig_off = 1; /* middle of 16-byte instruction */
208 	if (!ASSERT_EQ(bpf_map_update_elem(map_fd, &key, &val, 0), 0, "bpf_map_update_elem"))
209 		goto cleanup;
210 
211 	if (!ASSERT_EQ(bpf_map_freeze(map_fd), 0, "bpf_map_freeze"))
212 		goto cleanup;
213 
214 	prog_fd = prog_load(insns, ARRAY_SIZE(insns), &map_fd, 1);
215 	if (!ASSERT_EQ(prog_fd, -EINVAL, "program should have been rejected (prog_fd != -EINVAL)")) {
216 		close(prog_fd);
217 		goto cleanup;
218 	}
219 
220 cleanup:
221 	close(map_fd);
222 }
223 
224 static void check_incorrect_index(void)
225 {
226 	check_out_of_bounds_index();
227 	check_mid_insn_index();
228 }
229 
230 static int set_bpf_jit_harden(char *level)
231 {
232 	char old_level;
233 	int err = -1;
234 	int fd = -1;
235 
236 	fd = open("/proc/sys/net/core/bpf_jit_harden", O_RDWR | O_NONBLOCK);
237 	if (fd < 0) {
238 		ASSERT_FAIL("open .../bpf_jit_harden returned %d (errno=%d)", fd, errno);
239 		return -1;
240 	}
241 
242 	err = read(fd, &old_level, 1);
243 	if (err != 1) {
244 		ASSERT_FAIL("read from .../bpf_jit_harden returned %d (errno=%d)", err, errno);
245 		err = -1;
246 		goto end;
247 	}
248 
249 	lseek(fd, 0, SEEK_SET);
250 
251 	err = write(fd, level, 1);
252 	if (err != 1) {
253 		ASSERT_FAIL("write to .../bpf_jit_harden returned %d (errno=%d)", err, errno);
254 		err = -1;
255 		goto end;
256 	}
257 
258 	err = 0;
259 	*level = old_level;
260 end:
261 	if (fd >= 0)
262 		close(fd);
263 	return err;
264 }
265 
266 static void check_blindness(void)
267 {
268 	struct bpf_insn insns[] = {
269 		BPF_MOV64_IMM(BPF_REG_0, 4),
270 		BPF_MOV64_IMM(BPF_REG_0, 3),
271 		BPF_MOV64_IMM(BPF_REG_0, 2),
272 		BPF_MOV64_IMM(BPF_REG_0, 1),
273 		BPF_EXIT_INSN(),
274 	};
275 	int prog_fd = -1, map_fd;
276 	struct bpf_insn_array_value val = {};
277 	char bpf_jit_harden = '@'; /* non-exizsting value */
278 	int i;
279 
280 	map_fd = map_create(BPF_MAP_TYPE_INSN_ARRAY, ARRAY_SIZE(insns));
281 	if (!ASSERT_GE(map_fd, 0, "map_create"))
282 		return;
283 
284 	for (i = 0; i < ARRAY_SIZE(insns); i++) {
285 		val.orig_off = i;
286 		if (!ASSERT_EQ(bpf_map_update_elem(map_fd, &i, &val, 0), 0, "bpf_map_update_elem"))
287 			goto cleanup;
288 	}
289 
290 	if (!ASSERT_EQ(bpf_map_freeze(map_fd), 0, "bpf_map_freeze"))
291 		goto cleanup;
292 
293 	bpf_jit_harden = '2';
294 	if (set_bpf_jit_harden(&bpf_jit_harden)) {
295 		bpf_jit_harden = '@'; /* open, read or write failed => no write was done */
296 		goto cleanup;
297 	}
298 
299 	prog_fd = prog_load(insns, ARRAY_SIZE(insns), &map_fd, 1);
300 	if (!ASSERT_GE(prog_fd, 0, "bpf(BPF_PROG_LOAD)"))
301 		goto cleanup;
302 
303 	for (i = 0; i < ARRAY_SIZE(insns); i++) {
304 		char fmt[32];
305 
306 		if (!ASSERT_EQ(bpf_map_lookup_elem(map_fd, &i, &val), 0, "bpf_map_lookup_elem"))
307 			goto cleanup;
308 
309 		snprintf(fmt, sizeof(fmt), "val should be equal 3*%d", i);
310 		ASSERT_EQ(val.xlated_off, i * 3, fmt);
311 	}
312 
313 cleanup:
314 	/* restore the old one */
315 	if (bpf_jit_harden != '@')
316 		set_bpf_jit_harden(&bpf_jit_harden);
317 
318 	close(prog_fd);
319 	close(map_fd);
320 }
321 
322 /* Once map was initialized, it should be frozen */
323 static void check_load_unfrozen_map(void)
324 {
325 	struct bpf_insn insns[] = {
326 		BPF_MOV64_IMM(BPF_REG_0, 0),
327 		BPF_EXIT_INSN(),
328 	};
329 	int prog_fd = -1, map_fd;
330 	struct bpf_insn_array_value val = {};
331 	int i;
332 
333 	map_fd = map_create(BPF_MAP_TYPE_INSN_ARRAY, ARRAY_SIZE(insns));
334 	if (!ASSERT_GE(map_fd, 0, "map_create"))
335 		return;
336 
337 	for (i = 0; i < ARRAY_SIZE(insns); i++) {
338 		val.orig_off = i;
339 		if (!ASSERT_EQ(bpf_map_update_elem(map_fd, &i, &val, 0), 0, "bpf_map_update_elem"))
340 			goto cleanup;
341 	}
342 
343 	prog_fd = prog_load(insns, ARRAY_SIZE(insns), &map_fd, 1);
344 	if (!ASSERT_EQ(prog_fd, -EINVAL, "program should have been rejected (prog_fd != -EINVAL)"))
345 		goto cleanup;
346 
347 	/* correctness: now freeze the map, the program should load fine */
348 
349 	if (!ASSERT_EQ(bpf_map_freeze(map_fd), 0, "bpf_map_freeze"))
350 		goto cleanup;
351 
352 	prog_fd = prog_load(insns, ARRAY_SIZE(insns), &map_fd, 1);
353 	if (!ASSERT_GE(prog_fd, 0, "bpf(BPF_PROG_LOAD)"))
354 		goto cleanup;
355 
356 	for (i = 0; i < ARRAY_SIZE(insns); i++) {
357 		if (!ASSERT_EQ(bpf_map_lookup_elem(map_fd, &i, &val), 0, "bpf_map_lookup_elem"))
358 			goto cleanup;
359 
360 		ASSERT_EQ(val.xlated_off, i, "val should be equal i");
361 	}
362 
363 cleanup:
364 	close(prog_fd);
365 	close(map_fd);
366 }
367 
368 /* Map can be used only by one BPF program */
369 static void check_no_map_reuse(void)
370 {
371 	struct bpf_insn insns[] = {
372 		BPF_MOV64_IMM(BPF_REG_0, 0),
373 		BPF_EXIT_INSN(),
374 	};
375 	int prog_fd = -1, map_fd, extra_fd = -1;
376 	struct bpf_insn_array_value val = {};
377 	int i;
378 
379 	map_fd = map_create(BPF_MAP_TYPE_INSN_ARRAY, ARRAY_SIZE(insns));
380 	if (!ASSERT_GE(map_fd, 0, "map_create"))
381 		return;
382 
383 	for (i = 0; i < ARRAY_SIZE(insns); i++) {
384 		val.orig_off = i;
385 		if (!ASSERT_EQ(bpf_map_update_elem(map_fd, &i, &val, 0), 0, "bpf_map_update_elem"))
386 			goto cleanup;
387 	}
388 
389 	if (!ASSERT_EQ(bpf_map_freeze(map_fd), 0, "bpf_map_freeze"))
390 		goto cleanup;
391 
392 	prog_fd = prog_load(insns, ARRAY_SIZE(insns), &map_fd, 1);
393 	if (!ASSERT_GE(prog_fd, 0, "bpf(BPF_PROG_LOAD)"))
394 		goto cleanup;
395 
396 	for (i = 0; i < ARRAY_SIZE(insns); i++) {
397 		if (!ASSERT_EQ(bpf_map_lookup_elem(map_fd, &i, &val), 0, "bpf_map_lookup_elem"))
398 			goto cleanup;
399 
400 		ASSERT_EQ(val.xlated_off, i, "val should be equal i");
401 	}
402 
403 	extra_fd = prog_load(insns, ARRAY_SIZE(insns), &map_fd, 1);
404 	if (!ASSERT_EQ(extra_fd, -EBUSY, "program should have been rejected (extra_fd != -EBUSY)"))
405 		goto cleanup;
406 
407 	/* correctness: check that prog is still loadable without fd_array */
408 	extra_fd = prog_load(insns, ARRAY_SIZE(insns), NULL, 0);
409 	if (!ASSERT_GE(extra_fd, 0, "bpf(BPF_PROG_LOAD): expected no error"))
410 		goto cleanup;
411 
412 cleanup:
413 	close(extra_fd);
414 	close(prog_fd);
415 	close(map_fd);
416 }
417 
418 static void check_bpf_no_lookup(void)
419 {
420 	struct bpf_insn insns[] = {
421 		BPF_LD_MAP_FD(BPF_REG_1, 0),
422 		BPF_ST_MEM(BPF_DW, BPF_REG_10, -8, 0),
423 		BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),
424 		BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -8),
425 		BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_map_lookup_elem),
426 		BPF_EXIT_INSN(),
427 	};
428 	int prog_fd = -1, map_fd;
429 
430 	map_fd = map_create(BPF_MAP_TYPE_INSN_ARRAY, 1);
431 	if (!ASSERT_GE(map_fd, 0, "map_create"))
432 		return;
433 
434 	insns[0].imm = map_fd;
435 
436 	if (!ASSERT_EQ(bpf_map_freeze(map_fd), 0, "bpf_map_freeze"))
437 		goto cleanup;
438 
439 	prog_fd = prog_load(insns, ARRAY_SIZE(insns), NULL, 0);
440 	if (!ASSERT_EQ(prog_fd, -EINVAL, "program should have been rejected (prog_fd != -EINVAL)"))
441 		goto cleanup;
442 
443 	/* correctness: check that prog is still loadable with normal map */
444 	close(map_fd);
445 	map_fd = map_create(BPF_MAP_TYPE_ARRAY, 1);
446 	insns[0].imm = map_fd;
447 	prog_fd = prog_load(insns, ARRAY_SIZE(insns), NULL, 0);
448 	if (!ASSERT_GE(prog_fd, 0, "bpf(BPF_PROG_LOAD)"))
449 		goto cleanup;
450 
451 cleanup:
452 	close(prog_fd);
453 	close(map_fd);
454 }
455 
456 static void check_bpf_side(void)
457 {
458 	check_bpf_no_lookup();
459 }
460 
461 static void __test_bpf_insn_array(void)
462 {
463 	/* Test if offsets are adjusted properly */
464 
465 	if (test__start_subtest("one2one"))
466 		check_one_to_one_mapping();
467 
468 	if (test__start_subtest("simple"))
469 		check_simple();
470 
471 	if (test__start_subtest("deletions"))
472 		check_deletions();
473 
474 	if (test__start_subtest("deletions-with-functions"))
475 		check_deletions_with_functions();
476 
477 	if (test__start_subtest("blindness"))
478 		check_blindness();
479 
480 	/* Check all kinds of operations and related restrictions */
481 
482 	if (test__start_subtest("incorrect-index"))
483 		check_incorrect_index();
484 
485 	if (test__start_subtest("load-unfrozen-map"))
486 		check_load_unfrozen_map();
487 
488 	if (test__start_subtest("no-map-reuse"))
489 		check_no_map_reuse();
490 
491 	if (test__start_subtest("bpf-side-ops"))
492 		check_bpf_side();
493 }
494 #else
495 static void __test_bpf_insn_array(void)
496 {
497 	test__skip();
498 }
499 #endif
500 
501 void test_bpf_insn_array(void)
502 {
503 	__test_bpf_insn_array();
504 }
505