Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

selftests/bpf: add new verifier_gotox test

Add a set of tests to validate core gotox functionality
without need to rely on compilers.

Signed-off-by: Anton Protopopov <a.s.protopopov@gmail.com>
Link: https://lore.kernel.org/r/20251105090410.1250500-12-a.s.protopopov@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>

authored by

Anton Protopopov and committed by
Alexei Starovoitov
ccbdb48c dd3fd3c9

+391
+2
tools/testing/selftests/bpf/prog_tests/verifier.c
··· 35 35 #include "verifier_global_subprogs.skel.h" 36 36 #include "verifier_global_ptr_args.skel.h" 37 37 #include "verifier_gotol.skel.h" 38 + #include "verifier_gotox.skel.h" 38 39 #include "verifier_helper_access_var_len.skel.h" 39 40 #include "verifier_helper_packet_access.skel.h" 40 41 #include "verifier_helper_restricted.skel.h" ··· 174 173 void test_verifier_global_subprogs(void) { RUN(verifier_global_subprogs); } 175 174 void test_verifier_global_ptr_args(void) { RUN(verifier_global_ptr_args); } 176 175 void test_verifier_gotol(void) { RUN(verifier_gotol); } 176 + void test_verifier_gotox(void) { RUN(verifier_gotox); } 177 177 void test_verifier_helper_access_var_len(void) { RUN(verifier_helper_access_var_len); } 178 178 void test_verifier_helper_packet_access(void) { RUN(verifier_helper_packet_access); } 179 179 void test_verifier_helper_restricted(void) { RUN(verifier_helper_restricted); }
+389
tools/testing/selftests/bpf/progs/verifier_gotox.c
··· 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 + #ifdef __TARGET_ARCH_x86 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("writes into insn_array not allowed") 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 */ 388 + 389 + char _license[] SEC("license") = "GPL";