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

Configure Feed

Select the types of activity you want to include in your feed.

Merge branch 'properly-load-insn-array-values-with-offsets'

Anton Protopopov says:

====================
properly load insn array values with offsets

As was reported by the BPF CI bot in [1] the direct address
of an instruction array returned by map_direct_value_addr()
is incorrect if the offset is non-zero. Fix this bug and
add selftests.

Also (commit 2), return EACCES instead of EINVAL when offsets
aren't correct.

[1] https://lore.kernel.org/bpf/0447c47ac58306546a5dbdbad2601f3e77fa8eb24f3a4254dda3a39f6133e68f@mail.kernel.org/
====================

Link: https://patch.msgid.link/20260111153047.8388-1-a.s.protopopov@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>

+210 -2
+2 -2
kernel/bpf/bpf_insn_array.c
··· 123 123 124 124 if ((off % sizeof(long)) != 0 || 125 125 (off / sizeof(long)) >= map->max_entries) 126 - return -EINVAL; 126 + return -EACCES; 127 127 128 128 /* from BPF's point of view, this map is a jump table */ 129 - *imm = (unsigned long)insn_array->ips + off; 129 + *imm = (unsigned long)insn_array->ips; 130 130 131 131 return 0; 132 132 }
+208
tools/testing/selftests/bpf/prog_tests/bpf_gotox.c
··· 240 240 bpf_link__destroy(link); 241 241 } 242 242 243 + /* 244 + * The following subtests do not use skeleton rather than to check 245 + * if the test should be skipped. 246 + */ 247 + 248 + static int create_jt_map(__u32 max_entries) 249 + { 250 + const char *map_name = "jt"; 251 + __u32 key_size = 4; 252 + __u32 value_size = sizeof(struct bpf_insn_array_value); 253 + 254 + return bpf_map_create(BPF_MAP_TYPE_INSN_ARRAY, map_name, 255 + key_size, value_size, max_entries, NULL); 256 + } 257 + 258 + static int prog_load(struct bpf_insn *insns, __u32 insn_cnt) 259 + { 260 + return bpf_prog_load(BPF_PROG_TYPE_RAW_TRACEPOINT, NULL, "GPL", insns, insn_cnt, NULL); 261 + } 262 + 263 + static int __check_ldimm64_off_prog_load(__u32 max_entries, __u32 off) 264 + { 265 + struct bpf_insn insns[] = { 266 + BPF_LD_IMM64_RAW(BPF_REG_1, BPF_PSEUDO_MAP_VALUE, 0), 267 + BPF_MOV64_IMM(BPF_REG_0, 0), 268 + BPF_EXIT_INSN(), 269 + }; 270 + int map_fd, ret; 271 + 272 + map_fd = create_jt_map(max_entries); 273 + if (!ASSERT_GE(map_fd, 0, "create_jt_map")) 274 + return -1; 275 + if (!ASSERT_EQ(bpf_map_freeze(map_fd), 0, "bpf_map_freeze")) { 276 + close(map_fd); 277 + return -1; 278 + } 279 + 280 + insns[0].imm = map_fd; 281 + insns[1].imm = off; 282 + 283 + ret = prog_load(insns, ARRAY_SIZE(insns)); 284 + close(map_fd); 285 + return ret; 286 + } 287 + 288 + /* 289 + * Check that loads from an instruction array map are only allowed with offsets 290 + * which are multiples of 8 and do not point to outside of the map. 291 + */ 292 + static void check_ldimm64_off_load(struct bpf_gotox *skel __always_unused) 293 + { 294 + const __u32 max_entries = 10; 295 + int prog_fd; 296 + __u32 off; 297 + 298 + for (off = 0; off < max_entries; off++) { 299 + prog_fd = __check_ldimm64_off_prog_load(max_entries, off * 8); 300 + if (!ASSERT_GE(prog_fd, 0, "__check_ldimm64_off_prog_load")) 301 + return; 302 + close(prog_fd); 303 + } 304 + 305 + prog_fd = __check_ldimm64_off_prog_load(max_entries, 7 /* not a multiple of 8 */); 306 + if (!ASSERT_EQ(prog_fd, -EACCES, "__check_ldimm64_off_prog_load: should be -EACCES")) { 307 + close(prog_fd); 308 + return; 309 + } 310 + 311 + prog_fd = __check_ldimm64_off_prog_load(max_entries, max_entries * 8 /* too large */); 312 + if (!ASSERT_EQ(prog_fd, -EACCES, "__check_ldimm64_off_prog_load: should be -EACCES")) { 313 + close(prog_fd); 314 + return; 315 + } 316 + } 317 + 318 + static int __check_ldimm64_gotox_prog_load(struct bpf_insn *insns, 319 + __u32 insn_cnt, 320 + __u32 off1, __u32 off2) 321 + { 322 + const __u32 values[] = {5, 7, 9, 11, 13, 15}; 323 + const __u32 max_entries = ARRAY_SIZE(values); 324 + struct bpf_insn_array_value val = {}; 325 + int map_fd, ret, i; 326 + 327 + map_fd = create_jt_map(max_entries); 328 + if (!ASSERT_GE(map_fd, 0, "create_jt_map")) 329 + return -1; 330 + 331 + for (i = 0; i < max_entries; i++) { 332 + val.orig_off = values[i]; 333 + if (!ASSERT_EQ(bpf_map_update_elem(map_fd, &i, &val, 0), 0, 334 + "bpf_map_update_elem")) { 335 + close(map_fd); 336 + return -1; 337 + } 338 + } 339 + 340 + if (!ASSERT_EQ(bpf_map_freeze(map_fd), 0, "bpf_map_freeze")) { 341 + close(map_fd); 342 + return -1; 343 + } 344 + 345 + /* r1 = &map + offset1 */ 346 + insns[0].imm = map_fd; 347 + insns[1].imm = off1; 348 + 349 + /* r1 += off2 */ 350 + insns[2].imm = off2; 351 + 352 + ret = prog_load(insns, insn_cnt); 353 + close(map_fd); 354 + return ret; 355 + } 356 + 357 + static void reject_offsets(struct bpf_insn *insns, __u32 insn_cnt, __u32 off1, __u32 off2) 358 + { 359 + int prog_fd; 360 + 361 + prog_fd = __check_ldimm64_gotox_prog_load(insns, insn_cnt, off1, off2); 362 + if (!ASSERT_EQ(prog_fd, -EACCES, "__check_ldimm64_gotox_prog_load")) 363 + close(prog_fd); 364 + } 365 + 366 + /* 367 + * Verify a bit more complex programs which include indirect jumps 368 + * and with jump tables loaded with a non-zero offset 369 + */ 370 + static void check_ldimm64_off_gotox(struct bpf_gotox *skel __always_unused) 371 + { 372 + struct bpf_insn insns[] = { 373 + /* 374 + * The following instructions perform an indirect jump to 375 + * labels below. Thus valid offsets in the map are {0,...,5}. 376 + * The program rewrites the offsets in the instructions below: 377 + * r1 = &map + offset1 378 + * r1 += offset2 379 + * r1 = *r1 380 + * gotox r1 381 + */ 382 + BPF_LD_IMM64_RAW(BPF_REG_1, BPF_PSEUDO_MAP_VALUE, 0), 383 + BPF_ALU64_IMM(BPF_ADD, BPF_REG_1, 0), 384 + BPF_LDX_MEM(BPF_DW, BPF_REG_1, BPF_REG_1, 0), 385 + BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_1, 0, 0, 0), 386 + 387 + /* case 0: */ 388 + BPF_MOV64_IMM(BPF_REG_0, 0), 389 + BPF_EXIT_INSN(), 390 + /* case 1: */ 391 + BPF_MOV64_IMM(BPF_REG_0, 1), 392 + BPF_EXIT_INSN(), 393 + /* case 2: */ 394 + BPF_MOV64_IMM(BPF_REG_0, 2), 395 + BPF_EXIT_INSN(), 396 + /* case 3: */ 397 + BPF_MOV64_IMM(BPF_REG_0, 3), 398 + BPF_EXIT_INSN(), 399 + /* case 4: */ 400 + BPF_MOV64_IMM(BPF_REG_0, 4), 401 + BPF_EXIT_INSN(), 402 + /* default: */ 403 + BPF_MOV64_IMM(BPF_REG_0, 5), 404 + BPF_EXIT_INSN(), 405 + }; 406 + int prog_fd, err; 407 + __u32 off1, off2; 408 + 409 + /* allow all combinations off1 + off2 < 6 */ 410 + for (off1 = 0; off1 < 6; off1++) { 411 + for (off2 = 0; off1 + off2 < 6; off2++) { 412 + LIBBPF_OPTS(bpf_test_run_opts, topts); 413 + 414 + prog_fd = __check_ldimm64_gotox_prog_load(insns, ARRAY_SIZE(insns), 415 + off1 * 8, off2 * 8); 416 + if (!ASSERT_GE(prog_fd, 0, "__check_ldimm64_gotox_prog_load")) 417 + return; 418 + 419 + err = bpf_prog_test_run_opts(prog_fd, &topts); 420 + if (!ASSERT_OK(err, "test_run_opts err")) { 421 + close(prog_fd); 422 + return; 423 + } 424 + 425 + if (!ASSERT_EQ(topts.retval, off1 + off2, "test_run_opts retval")) { 426 + close(prog_fd); 427 + return; 428 + } 429 + 430 + close(prog_fd); 431 + } 432 + } 433 + 434 + /* reject off1 + off2 >= 6 */ 435 + reject_offsets(insns, ARRAY_SIZE(insns), 8 * 3, 8 * 3); 436 + reject_offsets(insns, ARRAY_SIZE(insns), 8 * 7, 8 * 0); 437 + reject_offsets(insns, ARRAY_SIZE(insns), 8 * 0, 8 * 7); 438 + 439 + /* reject (off1 + off2) % 8 != 0 */ 440 + reject_offsets(insns, ARRAY_SIZE(insns), 3, 3); 441 + reject_offsets(insns, ARRAY_SIZE(insns), 7, 0); 442 + reject_offsets(insns, ARRAY_SIZE(insns), 0, 7); 443 + } 444 + 243 445 void test_bpf_gotox(void) 244 446 { 245 447 struct bpf_gotox *skel; ··· 489 287 490 288 if (test__start_subtest("one-map-two-jumps")) 491 289 __subtest(skel, check_one_map_two_jumps); 290 + 291 + if (test__start_subtest("check-ldimm64-off")) 292 + __subtest(skel, check_ldimm64_off_load); 293 + 294 + if (test__start_subtest("check-ldimm64-off-gotox")) 295 + __subtest(skel, check_ldimm64_off_gotox); 492 296 493 297 bpf_gotox__destroy(skel); 494 298 }