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
terminal_gotox_subprog2(void)82 static __noinline __used int terminal_gotox_subprog2(void)
83 {
84 return 0;
85 }
86
87 SEC("socket")
88 __success __retval(0)
jump_table_terminal_gotox_subprog(void)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")
jump_table_no_jump_table(void)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")
jump_table_incorrect_dst_reg_type(void)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")
jump_table_misaligned_access(void)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")
jump_table_invalid_mem_acceess_pos(void)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")
jump_table_invalid_mem_acceess_neg(void)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)
jump_table_add_sub_ok(void)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")
jump_table_no_writes(void)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
test_subprog(void)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]")
jump_table_outside_subprog(void)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)
jump_table_contains_non_unique_values(void)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
spill_fill_ptr_to_insn(void)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