xref: /linux/tools/testing/selftests/bpf/progs/verifier_gotox.c (revision fbc988f236a60cf50a89da78fc6b7213daa9999c)
1 // SPDX-License-Identifier: GPL-2.0
2 /* Copyright (c) 2025 Isovalent */
3 
4 #include <linux/bpf.h>
5 #include <bpf/bpf_helpers.h>
6 #include "bpf_misc.h"
7 #include "../../../include/linux/filter.h"
8 
9 #if defined(__TARGET_ARCH_x86) || defined(__TARGET_ARCH_arm64) || defined(__TARGET_ARCH_powerpc)
10 
11 #define DEFINE_SIMPLE_JUMP_TABLE_PROG(NAME, SRC_REG, OFF, IMM, OUTCOME)	\
12 									\
13 	SEC("socket")							\
14 	OUTCOME								\
15 	__naked void jump_table_ ## NAME(void)				\
16 	{								\
17 		asm volatile ("						\
18 		.pushsection .jumptables,\"\",@progbits;		\
19 	jt0_%=:								\
20 		.quad ret0_%= - socket;					\
21 		.quad ret1_%= - socket;					\
22 		.size jt0_%=, 16;					\
23 		.global jt0_%=;						\
24 		.popsection;						\
25 									\
26 		r0 = jt0_%= ll;						\
27 		r0 += 8;						\
28 		r0 = *(u64 *)(r0 + 0);					\
29 		.8byte %[gotox_r0];					\
30 		ret0_%=:						\
31 		r0 = 0;							\
32 		exit;							\
33 		ret1_%=:						\
34 		r0 = 1;							\
35 		exit;							\
36 	"	:							\
37 		: __imm_insn(gotox_r0, BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_0, (SRC_REG), (OFF) , (IMM))) \
38 		: __clobber_all);					\
39 	}
40 
41 /*
42  * The first program which doesn't use reserved fields
43  * loads and works properly. The rest fail to load.
44  */
45 DEFINE_SIMPLE_JUMP_TABLE_PROG(ok,                          BPF_REG_0, 0, 0, __success __retval(1))
46 DEFINE_SIMPLE_JUMP_TABLE_PROG(reserved_field_src_reg,      BPF_REG_1, 0, 0, __failure __msg("BPF_JA|BPF_X uses reserved fields"))
47 DEFINE_SIMPLE_JUMP_TABLE_PROG(reserved_field_non_zero_off, BPF_REG_0, 1, 0, __failure __msg("BPF_JA|BPF_X uses reserved fields"))
48 DEFINE_SIMPLE_JUMP_TABLE_PROG(reserved_field_non_zero_imm, BPF_REG_0, 0, 1, __failure __msg("BPF_JA|BPF_X uses reserved fields"))
49 
50 #define DEFINE_TERMINAL_GOTOX_PROG(NAME, BASE)			\
51 	__naked void NAME(void)					\
52 	{							\
53 	asm volatile ("						\
54 	.pushsection .jumptables,\"\",@progbits;		\
55 jt0_%=:							\
56 	.quad ret0_%= - " BASE ";				\
57 	.size jt0_%=, 8;					\
58 	.global jt0_%=;						\
59 	.popsection;						\
60 								\
61 	r0 = jt0_%= ll;						\
62 	r0 = *(u64 *)(r0 + 0);					\
63 	goto end_%=;						\
64 ret0_%=:							\
65 	r0 = 0;							\
66 	exit;							\
67 end_%=:							\
68 	.8byte %[gotox_r0];					\
69 "	:							\
70 	: __imm_insn(gotox_r0, BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X,	\
71 					    BPF_REG_0, 0, 0, 0))	\
72 	: __clobber_all);					\
73 	}
74 
75 SEC("socket")
76 __success __retval(0)
77 DEFINE_TERMINAL_GOTOX_PROG(jump_table_terminal_gotox, "socket")
78 
79 static __noinline __used
80 DEFINE_TERMINAL_GOTOX_PROG(terminal_gotox_subprog1, ".text")
81 
82 static __noinline __used int terminal_gotox_subprog2(void)
83 {
84 	return 0;
85 }
86 
87 SEC("socket")
88 __success __retval(0)
89 __naked void jump_table_terminal_gotox_subprog(void)
90 {
91 	asm volatile ("						\
92 	call terminal_gotox_subprog1;				\
93 	call terminal_gotox_subprog2;				\
94 	exit;							\
95 "	::: __clobber_all);
96 }
97 
98 /*
99  * Gotox is forbidden when there is no jump table loaded
100  * which points to the sub-function where the gotox is used
101  */
102 SEC("socket")
103 __failure __msg("no jump tables found for subprog starting at 0")
104 __naked void jump_table_no_jump_table(void)
105 {
106 	asm volatile ("						\
107 	.8byte %[gotox_r0];					\
108 	r0 = 1;							\
109 	exit;							\
110 "	:							\
111 	: __imm_insn(gotox_r0, BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_0, 0, 0 , 0))
112 	: __clobber_all);
113 }
114 
115 /*
116  * Incorrect type of the target register, only PTR_TO_INSN allowed
117  */
118 SEC("socket")
119 __failure __msg("R1 has type scalar, expected PTR_TO_INSN")
120 __naked void jump_table_incorrect_dst_reg_type(void)
121 {
122 	asm volatile ("						\
123 	.pushsection .jumptables,\"\",@progbits;		\
124 jt0_%=:								\
125 	.quad ret0_%= - socket;					\
126 	.quad ret1_%= - socket;					\
127 	.size jt0_%=, 16;					\
128 	.global jt0_%=;						\
129 	.popsection;						\
130 								\
131 	r0 = jt0_%= ll;						\
132 	r0 += 8;						\
133 	r0 = *(u64 *)(r0 + 0);					\
134 	r1 = 42;						\
135 	.8byte %[gotox_r1];					\
136 	ret0_%=:						\
137 	r0 = 0;							\
138 	exit;							\
139 	ret1_%=:						\
140 	r0 = 1;							\
141 	exit;							\
142 "	:							\
143 	: __imm_insn(gotox_r1, BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_1, 0, 0 , 0))
144 	: __clobber_all);
145 }
146 
147 #define DEFINE_INVALID_SIZE_PROG(READ_SIZE, OUTCOME)			\
148 									\
149 	SEC("socket")							\
150 	OUTCOME								\
151 	__naked void jump_table_invalid_read_size_ ## READ_SIZE(void)	\
152 	{								\
153 		asm volatile ("						\
154 		.pushsection .jumptables,\"\",@progbits;		\
155 	jt0_%=:								\
156 		.quad ret0_%= - socket;					\
157 		.quad ret1_%= - socket;					\
158 		.size jt0_%=, 16;					\
159 		.global jt0_%=;						\
160 		.popsection;						\
161 									\
162 		r0 = jt0_%= ll;						\
163 		r0 += 8;						\
164 		r0 = *(" #READ_SIZE " *)(r0 + 0);			\
165 		.8byte %[gotox_r0];					\
166 		ret0_%=:						\
167 		r0 = 0;							\
168 		exit;							\
169 		ret1_%=:						\
170 		r0 = 1;							\
171 		exit;							\
172 	"	:							\
173 		: __imm_insn(gotox_r0, BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_0, 0, 0 , 0)) \
174 		: __clobber_all);					\
175 	}
176 
177 DEFINE_INVALID_SIZE_PROG(u32, __failure __msg("Invalid read of 4 bytes from insn_array"))
178 DEFINE_INVALID_SIZE_PROG(u16, __failure __msg("Invalid read of 2 bytes from insn_array"))
179 DEFINE_INVALID_SIZE_PROG(u8,  __failure __msg("Invalid read of 1 bytes from insn_array"))
180 
181 SEC("socket")
182 __failure __msg("misaligned value access off 1+0 size 8")
183 __naked void jump_table_misaligned_access(void)
184 {
185 	asm volatile ("						\
186 	.pushsection .jumptables,\"\",@progbits;		\
187 jt0_%=:								\
188 	.quad ret0_%= - socket;					\
189 	.quad ret1_%= - socket;					\
190 	.size jt0_%=, 16;					\
191 	.global jt0_%=;						\
192 	.popsection;						\
193 								\
194 	r0 = jt0_%= ll;						\
195 	r0 += 1;						\
196 	r0 = *(u64 *)(r0 + 0);					\
197 	.8byte %[gotox_r0];					\
198 	ret0_%=:						\
199 	r0 = 0;							\
200 	exit;							\
201 	ret1_%=:						\
202 	r0 = 1;							\
203 	exit;							\
204 "	:							\
205 	: __imm_insn(gotox_r0, BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_0, 0, 0 , 0))
206 	: __clobber_all);
207 }
208 
209 SEC("socket")
210 __failure __msg("invalid access to map value, value_size=16 off=24 size=8")
211 __naked void jump_table_invalid_mem_acceess_pos(void)
212 {
213 	asm volatile ("						\
214 	.pushsection .jumptables,\"\",@progbits;		\
215 jt0_%=:								\
216 	.quad ret0_%= - socket;					\
217 	.quad ret1_%= - socket;					\
218 	.size jt0_%=, 16;					\
219 	.global jt0_%=;						\
220 	.popsection;						\
221 								\
222 	r0 = jt0_%= ll;						\
223 	r0 += 24;						\
224 	r0 = *(u64 *)(r0 + 0);					\
225 	.8byte %[gotox_r0];					\
226 	ret0_%=:						\
227 	r0 = 0;							\
228 	exit;							\
229 	ret1_%=:						\
230 	r0 = 1;							\
231 	exit;							\
232 "	:							\
233 	: __imm_insn(gotox_r0, BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_0, 0, 0 , 0))
234 	: __clobber_all);
235 }
236 
237 SEC("socket")
238 __failure __msg("R0 min value is negative")
239 __naked void jump_table_invalid_mem_acceess_neg(void)
240 {
241 	asm volatile ("						\
242 	.pushsection .jumptables,\"\",@progbits;		\
243 jt0_%=:								\
244 	.quad ret0_%= - socket;					\
245 	.quad ret1_%= - socket;					\
246 	.size jt0_%=, 16;					\
247 	.global jt0_%=;						\
248 	.popsection;						\
249 								\
250 	r0 = jt0_%= ll;						\
251 	r0 -= 24;						\
252 	r0 = *(u64 *)(r0 + 0);					\
253 	.8byte %[gotox_r0];					\
254 	ret0_%=:						\
255 	r0 = 0;							\
256 	exit;							\
257 	ret1_%=:						\
258 	r0 = 1;							\
259 	exit;							\
260 "	:							\
261 	: __imm_insn(gotox_r0, BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_0, 0, 0 , 0))
262 	: __clobber_all);
263 }
264 
265 SEC("socket")
266 __success __retval(1)
267 __naked void jump_table_add_sub_ok(void)
268 {
269 	asm volatile ("						\
270 	.pushsection .jumptables,\"\",@progbits;		\
271 jt0_%=:								\
272 	.quad ret0_%= - socket;					\
273 	.quad ret1_%= - socket;					\
274 	.size jt0_%=, 16;					\
275 	.global jt0_%=;						\
276 	.popsection;						\
277 								\
278 	r0 = jt0_%= ll;						\
279 	r0 -= 24;						\
280 	r0 += 32;						\
281 	r0 = *(u64 *)(r0 + 0);					\
282 	.8byte %[gotox_r0];					\
283 	ret0_%=:						\
284 	r0 = 0;							\
285 	exit;							\
286 	ret1_%=:						\
287 	r0 = 1;							\
288 	exit;							\
289 "	:							\
290 	: __imm_insn(gotox_r0, BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_0, 0, 0 , 0))
291 	: __clobber_all);
292 }
293 
294 SEC("socket")
295 __failure __msg("write into map forbidden, value_size=16 off=8 size=8")
296 __naked void jump_table_no_writes(void)
297 {
298 	asm volatile ("						\
299 	.pushsection .jumptables,\"\",@progbits;		\
300 jt0_%=:								\
301 	.quad ret0_%= - socket;					\
302 	.quad ret1_%= - socket;					\
303 	.size jt0_%=, 16;					\
304 	.global jt0_%=;						\
305 	.popsection;						\
306 								\
307 	r0 = jt0_%= ll;						\
308 	r0 += 8;						\
309 	r1 = 0xbeef;						\
310 	*(u64 *)(r0 + 0) = r1;					\
311 	.8byte %[gotox_r0];					\
312 	ret0_%=:						\
313 	r0 = 0;							\
314 	exit;							\
315 	ret1_%=:						\
316 	r0 = 1;							\
317 	exit;							\
318 "	:							\
319 	: __imm_insn(gotox_r0, BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_0, 0, 0 , 0))
320 	: __clobber_all);
321 }
322 
323 #define DEFINE_JUMP_TABLE_USE_REG(REG)					\
324 	SEC("socket")							\
325 	__success __retval(1)						\
326 	__naked void jump_table_use_reg_r ## REG(void)			\
327 	{								\
328 		asm volatile ("						\
329 		.pushsection .jumptables,\"\",@progbits;		\
330 	jt0_%=:								\
331 		.quad ret0_%= - socket;					\
332 		.quad ret1_%= - socket;					\
333 		.size jt0_%=, 16;					\
334 		.global jt0_%=;						\
335 		.popsection;						\
336 									\
337 		r0 = jt0_%= ll;						\
338 		r0 += 8;						\
339 		r" #REG " = *(u64 *)(r0 + 0);				\
340 		.8byte %[gotox_rX];					\
341 		ret0_%=:						\
342 		r0 = 0;							\
343 		exit;							\
344 		ret1_%=:						\
345 		r0 = 1;							\
346 		exit;							\
347 	"	:							\
348 		: __imm_insn(gotox_rX, BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_ ## REG, 0, 0 , 0)) \
349 		: __clobber_all);					\
350 	}
351 
352 DEFINE_JUMP_TABLE_USE_REG(0)
353 DEFINE_JUMP_TABLE_USE_REG(1)
354 DEFINE_JUMP_TABLE_USE_REG(2)
355 DEFINE_JUMP_TABLE_USE_REG(3)
356 DEFINE_JUMP_TABLE_USE_REG(4)
357 DEFINE_JUMP_TABLE_USE_REG(5)
358 DEFINE_JUMP_TABLE_USE_REG(6)
359 DEFINE_JUMP_TABLE_USE_REG(7)
360 DEFINE_JUMP_TABLE_USE_REG(8)
361 DEFINE_JUMP_TABLE_USE_REG(9)
362 
363 __used static int test_subprog(void)
364 {
365 	return 0;
366 }
367 
368 SEC("socket")
369 __failure __msg("jump table for insn 4 points outside of the subprog [0,10]")
370 __naked void jump_table_outside_subprog(void)
371 {
372 	asm volatile ("						\
373 	.pushsection .jumptables,\"\",@progbits;		\
374 jt0_%=:								\
375 	.quad ret0_%= - socket;					\
376 	.quad ret1_%= - socket;					\
377 	.quad ret_out_%= - socket;				\
378 	.size jt0_%=, 24;					\
379 	.global jt0_%=;						\
380 	.popsection;						\
381 								\
382 	r0 = jt0_%= ll;						\
383 	r0 += 8;						\
384 	r0 = *(u64 *)(r0 + 0);					\
385 	.8byte %[gotox_r0];					\
386 	ret0_%=:						\
387 	r0 = 0;							\
388 	exit;							\
389 	ret1_%=:						\
390 	r0 = 1;							\
391 	call test_subprog;					\
392 	exit;							\
393 	ret_out_%=:						\
394 "	:							\
395 	: __imm_insn(gotox_r0, BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_0, 0, 0 , 0))
396 	: __clobber_all);
397 }
398 
399 SEC("socket")
400 __success __retval(1)
401 __naked void jump_table_contains_non_unique_values(void)
402 {
403 	asm volatile ("						\
404 	.pushsection .jumptables,\"\",@progbits;		\
405 jt0_%=:								\
406 	.quad ret0_%= - socket;					\
407 	.quad ret1_%= - socket;					\
408 	.quad ret0_%= - socket;					\
409 	.quad ret1_%= - socket;					\
410 	.quad ret0_%= - socket;					\
411 	.quad ret1_%= - socket;					\
412 	.quad ret0_%= - socket;					\
413 	.quad ret1_%= - socket;					\
414 	.quad ret0_%= - socket;					\
415 	.quad ret1_%= - socket;					\
416 	.size jt0_%=, 80;					\
417 	.global jt0_%=;						\
418 	.popsection;						\
419 								\
420 	r0 = jt0_%= ll;						\
421 	r0 += 8;						\
422 	r0 = *(u64 *)(r0 + 0);					\
423 	.8byte %[gotox_r0];					\
424 	ret0_%=:						\
425 	r0 = 0;							\
426 	exit;							\
427 	ret1_%=:						\
428 	r0 = 1;							\
429 	exit;							\
430 "	:							\
431 	: __imm_insn(gotox_r0, BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_0, 0, 0 , 0))
432 	: __clobber_all);
433 }
434 
435 /* check valid spill/fill, ptr to insn */
436 SEC("socket")
437 __success
438 __naked void spill_fill_ptr_to_insn(void)
439 {
440 	asm volatile (
441 	".pushsection .jumptables,\"\",@progbits;"
442 	"jt0_%=:"
443 	".quad ret0_%= - socket;"
444 	".size jt0_%=, 8;"
445 	".global jt0_%=;"
446 	".popsection;"
447 	"r0 = jt0_%= ll;"
448 	"r0 = *(u64 *)(r0 + 0);"
449 	"*(u64 *)(r10 - 8) = r0;"
450 	"r0 = *(u64 *)(r10 - 8);"
451 	".8byte %[gotox_r0];"
452 	"ret0_%=:"
453 	"r0 = 0;"
454 	"exit;"
455 	:
456 	: __imm_insn(gotox_r0, BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_0, 0, 0, 0))
457 	: __clobber_all);
458 }
459 
460 #endif /* __TARGET_ARCH_x86 || __TARGET_ARCH_arm64 || __TARGET_ARCH_powerpc*/
461 
462 char _license[] SEC("license") = "GPL";
463