xref: /linux/tools/testing/selftests/bpf/progs/verifier_gotox.c (revision b61104e7a6349bd2c2b3e2fb3260d87f15eda8f4)
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)
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 /*
51  * Gotox is forbidden when there is no jump table loaded
52  * which points to the sub-function where the gotox is used
53  */
54 SEC("socket")
55 __failure __msg("no jump tables found for subprog starting at 0")
56 __naked void jump_table_no_jump_table(void)
57 {
58 	asm volatile ("						\
59 	.8byte %[gotox_r0];					\
60 	r0 = 1;							\
61 	exit;							\
62 "	:							\
63 	: __imm_insn(gotox_r0, BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_0, 0, 0 , 0))
64 	: __clobber_all);
65 }
66 
67 /*
68  * Incorrect type of the target register, only PTR_TO_INSN allowed
69  */
70 SEC("socket")
71 __failure __msg("R1 has type scalar, expected PTR_TO_INSN")
72 __naked void jump_table_incorrect_dst_reg_type(void)
73 {
74 	asm volatile ("						\
75 	.pushsection .jumptables,\"\",@progbits;		\
76 jt0_%=:								\
77 	.quad ret0_%= - socket;					\
78 	.quad ret1_%= - socket;					\
79 	.size jt0_%=, 16;					\
80 	.global jt0_%=;						\
81 	.popsection;						\
82 								\
83 	r0 = jt0_%= ll;						\
84 	r0 += 8;						\
85 	r0 = *(u64 *)(r0 + 0);					\
86 	r1 = 42;						\
87 	.8byte %[gotox_r1];					\
88 	ret0_%=:						\
89 	r0 = 0;							\
90 	exit;							\
91 	ret1_%=:						\
92 	r0 = 1;							\
93 	exit;							\
94 "	:							\
95 	: __imm_insn(gotox_r1, BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_1, 0, 0 , 0))
96 	: __clobber_all);
97 }
98 
99 #define DEFINE_INVALID_SIZE_PROG(READ_SIZE, OUTCOME)			\
100 									\
101 	SEC("socket")							\
102 	OUTCOME								\
103 	__naked void jump_table_invalid_read_size_ ## READ_SIZE(void)	\
104 	{								\
105 		asm volatile ("						\
106 		.pushsection .jumptables,\"\",@progbits;		\
107 	jt0_%=:								\
108 		.quad ret0_%= - socket;					\
109 		.quad ret1_%= - socket;					\
110 		.size jt0_%=, 16;					\
111 		.global jt0_%=;						\
112 		.popsection;						\
113 									\
114 		r0 = jt0_%= ll;						\
115 		r0 += 8;						\
116 		r0 = *(" #READ_SIZE " *)(r0 + 0);			\
117 		.8byte %[gotox_r0];					\
118 		ret0_%=:						\
119 		r0 = 0;							\
120 		exit;							\
121 		ret1_%=:						\
122 		r0 = 1;							\
123 		exit;							\
124 	"	:							\
125 		: __imm_insn(gotox_r0, BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_0, 0, 0 , 0)) \
126 		: __clobber_all);					\
127 	}
128 
129 DEFINE_INVALID_SIZE_PROG(u32, __failure __msg("Invalid read of 4 bytes from insn_array"))
130 DEFINE_INVALID_SIZE_PROG(u16, __failure __msg("Invalid read of 2 bytes from insn_array"))
131 DEFINE_INVALID_SIZE_PROG(u8,  __failure __msg("Invalid read of 1 bytes from insn_array"))
132 
133 SEC("socket")
134 __failure __msg("misaligned value access off 0+1+0 size 8")
135 __naked void jump_table_misaligned_access(void)
136 {
137 	asm volatile ("						\
138 	.pushsection .jumptables,\"\",@progbits;		\
139 jt0_%=:								\
140 	.quad ret0_%= - socket;					\
141 	.quad ret1_%= - socket;					\
142 	.size jt0_%=, 16;					\
143 	.global jt0_%=;						\
144 	.popsection;						\
145 								\
146 	r0 = jt0_%= ll;						\
147 	r0 += 1;						\
148 	r0 = *(u64 *)(r0 + 0);					\
149 	.8byte %[gotox_r0];					\
150 	ret0_%=:						\
151 	r0 = 0;							\
152 	exit;							\
153 	ret1_%=:						\
154 	r0 = 1;							\
155 	exit;							\
156 "	:							\
157 	: __imm_insn(gotox_r0, BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_0, 0, 0 , 0))
158 	: __clobber_all);
159 }
160 
161 SEC("socket")
162 __failure __msg("invalid access to map value, value_size=16 off=24 size=8")
163 __naked void jump_table_invalid_mem_acceess_pos(void)
164 {
165 	asm volatile ("						\
166 	.pushsection .jumptables,\"\",@progbits;		\
167 jt0_%=:								\
168 	.quad ret0_%= - socket;					\
169 	.quad ret1_%= - socket;					\
170 	.size jt0_%=, 16;					\
171 	.global jt0_%=;						\
172 	.popsection;						\
173 								\
174 	r0 = jt0_%= ll;						\
175 	r0 += 24;						\
176 	r0 = *(u64 *)(r0 + 0);					\
177 	.8byte %[gotox_r0];					\
178 	ret0_%=:						\
179 	r0 = 0;							\
180 	exit;							\
181 	ret1_%=:						\
182 	r0 = 1;							\
183 	exit;							\
184 "	:							\
185 	: __imm_insn(gotox_r0, BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_0, 0, 0 , 0))
186 	: __clobber_all);
187 }
188 
189 SEC("socket")
190 __failure __msg("invalid access to map value, value_size=16 off=-24 size=8")
191 __naked void jump_table_invalid_mem_acceess_neg(void)
192 {
193 	asm volatile ("						\
194 	.pushsection .jumptables,\"\",@progbits;		\
195 jt0_%=:								\
196 	.quad ret0_%= - socket;					\
197 	.quad ret1_%= - socket;					\
198 	.size jt0_%=, 16;					\
199 	.global jt0_%=;						\
200 	.popsection;						\
201 								\
202 	r0 = jt0_%= ll;						\
203 	r0 -= 24;						\
204 	r0 = *(u64 *)(r0 + 0);					\
205 	.8byte %[gotox_r0];					\
206 	ret0_%=:						\
207 	r0 = 0;							\
208 	exit;							\
209 	ret1_%=:						\
210 	r0 = 1;							\
211 	exit;							\
212 "	:							\
213 	: __imm_insn(gotox_r0, BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_0, 0, 0 , 0))
214 	: __clobber_all);
215 }
216 
217 SEC("socket")
218 __success __retval(1)
219 __naked void jump_table_add_sub_ok(void)
220 {
221 	asm volatile ("						\
222 	.pushsection .jumptables,\"\",@progbits;		\
223 jt0_%=:								\
224 	.quad ret0_%= - socket;					\
225 	.quad ret1_%= - socket;					\
226 	.size jt0_%=, 16;					\
227 	.global jt0_%=;						\
228 	.popsection;						\
229 								\
230 	r0 = jt0_%= ll;						\
231 	r0 -= 24;						\
232 	r0 += 32;						\
233 	r0 = *(u64 *)(r0 + 0);					\
234 	.8byte %[gotox_r0];					\
235 	ret0_%=:						\
236 	r0 = 0;							\
237 	exit;							\
238 	ret1_%=:						\
239 	r0 = 1;							\
240 	exit;							\
241 "	:							\
242 	: __imm_insn(gotox_r0, BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_0, 0, 0 , 0))
243 	: __clobber_all);
244 }
245 
246 SEC("socket")
247 __failure __msg("write into map forbidden, value_size=16 off=8 size=8")
248 __naked void jump_table_no_writes(void)
249 {
250 	asm volatile ("						\
251 	.pushsection .jumptables,\"\",@progbits;		\
252 jt0_%=:								\
253 	.quad ret0_%= - socket;					\
254 	.quad ret1_%= - socket;					\
255 	.size jt0_%=, 16;					\
256 	.global jt0_%=;						\
257 	.popsection;						\
258 								\
259 	r0 = jt0_%= ll;						\
260 	r0 += 8;						\
261 	r1 = 0xbeef;						\
262 	*(u64 *)(r0 + 0) = r1;					\
263 	.8byte %[gotox_r0];					\
264 	ret0_%=:						\
265 	r0 = 0;							\
266 	exit;							\
267 	ret1_%=:						\
268 	r0 = 1;							\
269 	exit;							\
270 "	:							\
271 	: __imm_insn(gotox_r0, BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_0, 0, 0 , 0))
272 	: __clobber_all);
273 }
274 
275 #define DEFINE_JUMP_TABLE_USE_REG(REG)					\
276 	SEC("socket")							\
277 	__success __retval(1)						\
278 	__naked void jump_table_use_reg_r ## REG(void)			\
279 	{								\
280 		asm volatile ("						\
281 		.pushsection .jumptables,\"\",@progbits;		\
282 	jt0_%=:								\
283 		.quad ret0_%= - socket;					\
284 		.quad ret1_%= - socket;					\
285 		.size jt0_%=, 16;					\
286 		.global jt0_%=;						\
287 		.popsection;						\
288 									\
289 		r0 = jt0_%= ll;						\
290 		r0 += 8;						\
291 		r" #REG " = *(u64 *)(r0 + 0);				\
292 		.8byte %[gotox_rX];					\
293 		ret0_%=:						\
294 		r0 = 0;							\
295 		exit;							\
296 		ret1_%=:						\
297 		r0 = 1;							\
298 		exit;							\
299 	"	:							\
300 		: __imm_insn(gotox_rX, BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_ ## REG, 0, 0 , 0)) \
301 		: __clobber_all);					\
302 	}
303 
304 DEFINE_JUMP_TABLE_USE_REG(0)
305 DEFINE_JUMP_TABLE_USE_REG(1)
306 DEFINE_JUMP_TABLE_USE_REG(2)
307 DEFINE_JUMP_TABLE_USE_REG(3)
308 DEFINE_JUMP_TABLE_USE_REG(4)
309 DEFINE_JUMP_TABLE_USE_REG(5)
310 DEFINE_JUMP_TABLE_USE_REG(6)
311 DEFINE_JUMP_TABLE_USE_REG(7)
312 DEFINE_JUMP_TABLE_USE_REG(8)
313 DEFINE_JUMP_TABLE_USE_REG(9)
314 
315 __used static int test_subprog(void)
316 {
317 	return 0;
318 }
319 
320 SEC("socket")
321 __failure __msg("jump table for insn 4 points outside of the subprog [0,10]")
322 __naked void jump_table_outside_subprog(void)
323 {
324 	asm volatile ("						\
325 	.pushsection .jumptables,\"\",@progbits;		\
326 jt0_%=:								\
327 	.quad ret0_%= - socket;					\
328 	.quad ret1_%= - socket;					\
329 	.quad ret_out_%= - socket;				\
330 	.size jt0_%=, 24;					\
331 	.global jt0_%=;						\
332 	.popsection;						\
333 								\
334 	r0 = jt0_%= ll;						\
335 	r0 += 8;						\
336 	r0 = *(u64 *)(r0 + 0);					\
337 	.8byte %[gotox_r0];					\
338 	ret0_%=:						\
339 	r0 = 0;							\
340 	exit;							\
341 	ret1_%=:						\
342 	r0 = 1;							\
343 	call test_subprog;					\
344 	exit;							\
345 	ret_out_%=:						\
346 "	:							\
347 	: __imm_insn(gotox_r0, BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_0, 0, 0 , 0))
348 	: __clobber_all);
349 }
350 
351 SEC("socket")
352 __success __retval(1)
353 __naked void jump_table_contains_non_unique_values(void)
354 {
355 	asm volatile ("						\
356 	.pushsection .jumptables,\"\",@progbits;		\
357 jt0_%=:								\
358 	.quad ret0_%= - socket;					\
359 	.quad ret1_%= - socket;					\
360 	.quad ret0_%= - socket;					\
361 	.quad ret1_%= - socket;					\
362 	.quad ret0_%= - socket;					\
363 	.quad ret1_%= - socket;					\
364 	.quad ret0_%= - socket;					\
365 	.quad ret1_%= - socket;					\
366 	.quad ret0_%= - socket;					\
367 	.quad ret1_%= - socket;					\
368 	.size jt0_%=, 80;					\
369 	.global jt0_%=;						\
370 	.popsection;						\
371 								\
372 	r0 = jt0_%= ll;						\
373 	r0 += 8;						\
374 	r0 = *(u64 *)(r0 + 0);					\
375 	.8byte %[gotox_r0];					\
376 	ret0_%=:						\
377 	r0 = 0;							\
378 	exit;							\
379 	ret1_%=:						\
380 	r0 = 1;							\
381 	exit;							\
382 "	:							\
383 	: __imm_insn(gotox_r0, BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_0, 0, 0 , 0))
384 	: __clobber_all);
385 }
386 
387 #endif /* __TARGET_ARCH_x86 || __TARGET_ARCH_arm64 */
388 
389 char _license[] SEC("license") = "GPL";
390