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