github.com/cilium/ebpf@v0.16.0/prog_test.go (about) 1 package ebpf 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "errors" 7 "fmt" 8 "math" 9 "os" 10 "path/filepath" 11 "runtime" 12 "slices" 13 "strings" 14 "syscall" 15 "testing" 16 "time" 17 18 "github.com/go-quicktest/qt" 19 20 "github.com/cilium/ebpf/asm" 21 "github.com/cilium/ebpf/btf" 22 "github.com/cilium/ebpf/internal" 23 "github.com/cilium/ebpf/internal/sys" 24 "github.com/cilium/ebpf/internal/testutils" 25 "github.com/cilium/ebpf/internal/unix" 26 ) 27 28 func TestProgramRun(t *testing.T) { 29 testutils.SkipOnOldKernel(t, "4.8", "XDP program") 30 31 pat := []byte{0xDE, 0xAD, 0xBE, 0xEF} 32 buf := internal.EmptyBPFContext 33 34 // r1 : ctx_start 35 // r1+4: ctx_end 36 ins := asm.Instructions{ 37 // r2 = *(r1+4) 38 asm.LoadMem(asm.R2, asm.R1, 4, asm.Word), 39 // r1 = *(r1+0) 40 asm.LoadMem(asm.R1, asm.R1, 0, asm.Word), 41 // r3 = r1 42 asm.Mov.Reg(asm.R3, asm.R1), 43 // r3 += len(buf) 44 asm.Add.Imm(asm.R3, int32(len(buf))), 45 // if r3 > r2 goto +len(pat) 46 asm.JGT.Reg(asm.R3, asm.R2, "out"), 47 } 48 for i, b := range pat { 49 ins = append(ins, asm.StoreImm(asm.R1, int16(i), int64(b), asm.Byte)) 50 } 51 ins = append(ins, 52 // return 42 53 asm.LoadImm(asm.R0, 42, asm.DWord).WithSymbol("out"), 54 asm.Return(), 55 ) 56 57 t.Log(ins) 58 59 prog, err := NewProgram(&ProgramSpec{ 60 Name: "test", 61 Type: XDP, 62 Instructions: ins, 63 License: "MIT", 64 }) 65 if err != nil { 66 t.Fatal(err) 67 } 68 defer prog.Close() 69 70 p2, err := prog.Clone() 71 if err != nil { 72 t.Fatal("Can't clone program") 73 } 74 defer p2.Close() 75 76 prog.Close() 77 prog = p2 78 79 ret, out, err := prog.Test(buf) 80 testutils.SkipIfNotSupported(t, err) 81 if err != nil { 82 t.Fatal(err) 83 } 84 85 if ret != 42 { 86 t.Error("Expected return value to be 42, got", ret) 87 } 88 89 if !bytes.Equal(out[:len(pat)], pat) { 90 t.Errorf("Expected %v, got %v", pat, out) 91 } 92 } 93 94 func TestProgramRunWithOptions(t *testing.T) { 95 testutils.SkipOnOldKernel(t, "5.15", "XDP ctx_in/ctx_out") 96 97 ins := asm.Instructions{ 98 // Return XDP_ABORTED 99 asm.LoadImm(asm.R0, 0, asm.DWord), 100 asm.Return(), 101 } 102 103 prog, err := NewProgram(&ProgramSpec{ 104 Name: "test", 105 Type: XDP, 106 Instructions: ins, 107 License: "MIT", 108 }) 109 if err != nil { 110 t.Fatal(err) 111 } 112 defer prog.Close() 113 114 buf := internal.EmptyBPFContext 115 xdp := sys.XdpMd{ 116 Data: 0, 117 DataEnd: uint32(len(buf)), 118 } 119 xdpOut := sys.XdpMd{} 120 opts := RunOptions{ 121 Data: buf, 122 Context: xdp, 123 ContextOut: &xdpOut, 124 } 125 ret, err := prog.Run(&opts) 126 testutils.SkipIfNotSupported(t, err) 127 if err != nil { 128 t.Fatal(err) 129 } 130 131 if ret != 0 { 132 t.Error("Expected return value to be 0, got", ret) 133 } 134 135 if xdp != xdpOut { 136 t.Errorf("Expect xdp (%+v) == xdpOut (%+v)", xdp, xdpOut) 137 } 138 } 139 140 func TestProgramRunRawTracepoint(t *testing.T) { 141 testutils.SkipOnOldKernel(t, "5.10", "RawTracepoint test run") 142 143 ins := asm.Instructions{ 144 // Return 0 145 asm.LoadImm(asm.R0, 0, asm.DWord), 146 asm.Return(), 147 } 148 149 prog, err := NewProgram(&ProgramSpec{ 150 Name: "test", 151 Type: RawTracepoint, 152 Instructions: ins, 153 License: "MIT", 154 }) 155 if err != nil { 156 t.Fatal(err) 157 } 158 defer prog.Close() 159 160 ret, err := prog.Run(&RunOptions{}) 161 testutils.SkipIfNotSupported(t, err) 162 if err != nil { 163 t.Fatal(err) 164 } 165 166 if ret != 0 { 167 t.Error("Expected return value to be 0, got", ret) 168 } 169 } 170 171 func TestProgramRunEmptyData(t *testing.T) { 172 testutils.SkipOnOldKernel(t, "5.13", "sk_lookup BPF_PROG_RUN") 173 174 ins := asm.Instructions{ 175 // Return SK_DROP 176 asm.LoadImm(asm.R0, 0, asm.DWord), 177 asm.Return(), 178 } 179 180 prog, err := NewProgram(&ProgramSpec{ 181 Name: "test", 182 Type: SkLookup, 183 AttachType: AttachSkLookup, 184 Instructions: ins, 185 License: "MIT", 186 }) 187 if err != nil { 188 t.Fatal(err) 189 } 190 defer prog.Close() 191 192 opts := RunOptions{ 193 Context: sys.SkLookup{ 194 Family: syscall.AF_INET, 195 }, 196 } 197 ret, err := prog.Run(&opts) 198 testutils.SkipIfNotSupported(t, err) 199 if err != nil { 200 t.Fatal(err) 201 } 202 203 if ret != 0 { 204 t.Error("Expected return value to be 0, got", ret) 205 } 206 } 207 208 func TestProgramBenchmark(t *testing.T) { 209 prog := mustSocketFilter(t) 210 211 ret, duration, err := prog.Benchmark(internal.EmptyBPFContext, 1, nil) 212 testutils.SkipIfNotSupported(t, err) 213 if err != nil { 214 t.Fatal("Error from Benchmark:", err) 215 } 216 217 if ret != 2 { 218 t.Error("Expected return value 2, got", ret) 219 } 220 221 if duration == 0 { 222 t.Error("Expected non-zero duration") 223 } 224 } 225 226 func TestProgramTestRunInterrupt(t *testing.T) { 227 testutils.SkipOnOldKernel(t, "5.0", "EINTR from BPF_PROG_TEST_RUN") 228 229 prog := mustSocketFilter(t) 230 231 var ( 232 tgid = unix.Getpid() 233 tidChan = make(chan int, 1) 234 exit = make(chan struct{}) 235 errs = make(chan error, 1) 236 timeout = time.After(5 * time.Second) 237 ) 238 239 defer close(exit) 240 241 go func() { 242 runtime.LockOSThread() 243 defer func() { 244 // Wait for the test to allow us to unlock the OS thread, to 245 // ensure that we don't send SIGUSR1 to the wrong thread. 246 <-exit 247 runtime.UnlockOSThread() 248 }() 249 250 tidChan <- unix.Gettid() 251 252 // Block this thread in the BPF syscall, so that we can 253 // trigger EINTR by sending a signal. 254 opts := RunOptions{ 255 Data: internal.EmptyBPFContext, 256 Repeat: math.MaxInt32, 257 Reset: func() { 258 // We don't know how long finishing the 259 // test run would take, so flag that we've seen 260 // an interruption and abort the goroutine. 261 close(errs) 262 runtime.Goexit() 263 }, 264 } 265 _, _, err := prog.run(&opts) 266 267 errs <- err 268 }() 269 270 tid := <-tidChan 271 for { 272 err := unix.Tgkill(tgid, tid, syscall.SIGUSR1) 273 if err != nil { 274 t.Fatal("Can't send signal to goroutine thread:", err) 275 } 276 277 select { 278 case err, ok := <-errs: 279 if !ok { 280 return 281 } 282 283 testutils.SkipIfNotSupported(t, err) 284 if err == nil { 285 t.Fatal("testRun wasn't interrupted") 286 } 287 288 t.Fatal("testRun returned an error:", err) 289 290 case <-timeout: 291 t.Fatal("Timed out trying to interrupt the goroutine") 292 293 default: 294 } 295 } 296 } 297 298 func TestProgramClose(t *testing.T) { 299 prog := mustSocketFilter(t) 300 301 if err := prog.Close(); err != nil { 302 t.Fatal("Can't close program:", err) 303 } 304 } 305 306 func TestProgramPin(t *testing.T) { 307 prog := mustSocketFilter(t) 308 309 tmp := testutils.TempBPFFS(t) 310 311 path := filepath.Join(tmp, "program") 312 if err := prog.Pin(path); err != nil { 313 t.Fatal(err) 314 } 315 316 pinned := prog.IsPinned() 317 qt.Assert(t, qt.IsTrue(pinned)) 318 319 prog.Close() 320 321 prog, err := LoadPinnedProgram(path, nil) 322 testutils.SkipIfNotSupported(t, err) 323 if err != nil { 324 t.Fatal(err) 325 } 326 defer prog.Close() 327 328 if prog.Type() != SocketFilter { 329 t.Error("Expected pinned program to have type SocketFilter, but got", prog.Type()) 330 } 331 332 if haveObjName() == nil { 333 if prog.name != "test" { 334 t.Errorf("Expected program to have object name 'test', got '%s'", prog.name) 335 } 336 } else { 337 if prog.name != "program" { 338 t.Errorf("Expected program to have file name 'program', got '%s'", prog.name) 339 } 340 } 341 342 if !prog.IsPinned() { 343 t.Error("Expected IsPinned to be true") 344 } 345 } 346 347 func TestProgramUnpin(t *testing.T) { 348 prog := mustSocketFilter(t) 349 350 tmp := testutils.TempBPFFS(t) 351 352 path := filepath.Join(tmp, "program") 353 if err := prog.Pin(path); err != nil { 354 t.Fatal(err) 355 } 356 357 pinned := prog.IsPinned() 358 qt.Assert(t, qt.IsTrue(pinned)) 359 360 if err := prog.Unpin(); err != nil { 361 t.Fatal("Failed to unpin program:", err) 362 } 363 if _, err := os.Stat(path); err == nil { 364 t.Fatal("Pinned program path still exists after unpinning:", err) 365 } 366 } 367 368 func TestProgramLoadPinnedWithFlags(t *testing.T) { 369 // Introduced in commit 6e71b04a8224. 370 testutils.SkipOnOldKernel(t, "4.14", "file_flags in BPF_OBJ_GET") 371 372 prog := mustSocketFilter(t) 373 374 tmp := testutils.TempBPFFS(t) 375 376 path := filepath.Join(tmp, "program") 377 if err := prog.Pin(path); err != nil { 378 t.Fatal(err) 379 } 380 381 prog.Close() 382 383 _, err := LoadPinnedProgram(path, &LoadPinOptions{ 384 Flags: math.MaxUint32, 385 }) 386 testutils.SkipIfNotSupported(t, err) 387 if !errors.Is(err, unix.EINVAL) { 388 t.Fatal("Invalid flags don't trigger an error:", err) 389 } 390 } 391 392 func TestProgramVerifierOutputOnError(t *testing.T) { 393 _, err := NewProgram(&ProgramSpec{ 394 Type: SocketFilter, 395 Instructions: asm.Instructions{ 396 asm.Return(), 397 }, 398 License: "MIT", 399 }) 400 if err == nil { 401 t.Fatal("Expected program to be invalid") 402 } 403 404 ve, ok := err.(*VerifierError) 405 if !ok { 406 t.Fatal("NewProgram does return an unwrapped VerifierError") 407 } 408 409 if !strings.Contains(ve.Error(), "R0 !read_ok") { 410 t.Logf("%+v", ve) 411 t.Error("Missing verifier log in error summary") 412 } 413 } 414 415 func TestProgramKernelVersion(t *testing.T) { 416 testutils.SkipOnOldKernel(t, "4.20", "KernelVersion") 417 prog, err := NewProgram(&ProgramSpec{ 418 Type: Kprobe, 419 Instructions: asm.Instructions{ 420 asm.LoadImm(asm.R0, 0, asm.DWord), 421 asm.Return(), 422 }, 423 KernelVersion: 42, 424 License: "MIT", 425 }) 426 if err != nil { 427 t.Fatal("Could not load Kprobe program") 428 } 429 defer prog.Close() 430 } 431 432 func TestProgramVerifierOutput(t *testing.T) { 433 prog, err := NewProgramWithOptions(socketFilterSpec, ProgramOptions{ 434 LogLevel: LogLevelInstruction, 435 }) 436 if err != nil { 437 t.Fatal(err) 438 } 439 defer prog.Close() 440 441 if prog.VerifierLog == "" { 442 t.Error("Expected VerifierLog to be present") 443 } 444 445 // Issue 64 446 _, err = NewProgramWithOptions(&ProgramSpec{ 447 Type: SocketFilter, 448 Instructions: asm.Instructions{ 449 asm.Mov.Reg(asm.R0, asm.R1), 450 }, 451 License: "MIT", 452 }, ProgramOptions{ 453 LogLevel: LogLevelInstruction, 454 }) 455 456 if err == nil { 457 t.Fatal("Expected an error from invalid program") 458 } 459 460 var ve *internal.VerifierError 461 if !errors.As(err, &ve) { 462 t.Error("Error is not a VerifierError") 463 } 464 } 465 466 func TestProgramVerifierLog(t *testing.T) { 467 check := func(t *testing.T, err error) { 468 t.Helper() 469 470 var ve *internal.VerifierError 471 qt.Assert(t, qt.ErrorAs(err, &ve)) 472 } 473 474 // Generate a base program of sufficient size whose verifier log does not fit 475 // a 128-byte buffer. This should always result in ENOSPC, setting the 476 // VerifierError.Truncated flag. 477 var base asm.Instructions 478 for i := 0; i < 32; i++ { 479 base = append(base, asm.Mov.Reg(asm.R0, asm.R1)) 480 } 481 482 // Touch R10 (read-only frame pointer) to reliably force a verifier error. 483 invalid := slices.Clone(base) 484 invalid = append(invalid, asm.Mov.Reg(asm.R10, asm.R0)) 485 invalid = append(invalid, asm.Return()) 486 487 valid := slices.Clone(base) 488 valid = append(valid, asm.Return()) 489 490 // Start out with testing against the invalid program. 491 spec := &ProgramSpec{ 492 Type: SocketFilter, 493 License: "MIT", 494 Instructions: invalid, 495 } 496 497 // Set an undersized log buffer without explicitly requesting a verifier log 498 // for an invalid program. 499 _, err := NewProgramWithOptions(spec, ProgramOptions{}) 500 check(t, err) 501 502 // Explicitly request a verifier log for an invalid program. 503 _, err = NewProgramWithOptions(spec, ProgramOptions{ 504 LogLevel: LogLevelInstruction, 505 }) 506 check(t, err) 507 508 // Run tests against a valid program from here on out. 509 spec.Instructions = valid 510 511 // Don't request a verifier log, only set LogSize. Expect the valid program to 512 // be created without errors. 513 prog, err := NewProgramWithOptions(spec, ProgramOptions{}) 514 qt.Assert(t, qt.IsNil(err)) 515 prog.Close() 516 517 // Explicitly request verifier log for a valid program. If a log is requested 518 // and the buffer is too small, ENOSPC occurs even for valid programs. 519 prog, err = NewProgramWithOptions(spec, ProgramOptions{ 520 LogLevel: LogLevelInstruction, 521 }) 522 qt.Assert(t, qt.IsNil(err)) 523 prog.Close() 524 } 525 526 func TestProgramWithUnsatisfiedMap(t *testing.T) { 527 coll, err := LoadCollectionSpec("testdata/loader-el.elf") 528 if err != nil { 529 t.Fatal(err) 530 } 531 532 // The program will have at least one map reference. 533 progSpec := coll.Programs["xdp_prog"] 534 progSpec.ByteOrder = nil 535 536 _, err = NewProgram(progSpec) 537 testutils.SkipIfNotSupported(t, err) 538 if !errors.Is(err, asm.ErrUnsatisfiedMapReference) { 539 t.Fatal("Expected an error wrapping asm.ErrUnsatisfiedMapReference, got", err) 540 } 541 t.Log(err) 542 } 543 544 func TestProgramName(t *testing.T) { 545 if err := haveObjName(); err != nil { 546 t.Skip(err) 547 } 548 549 prog := mustSocketFilter(t) 550 551 var info sys.ProgInfo 552 if err := sys.ObjInfo(prog.fd, &info); err != nil { 553 t.Fatal(err) 554 } 555 556 if name := unix.ByteSliceToString(info.Name[:]); name != "test" { 557 t.Errorf("Name is not test, got '%s'", name) 558 } 559 } 560 561 func TestSanitizeName(t *testing.T) { 562 for input, want := range map[string]string{ 563 "test": "test", 564 "t-est": "test", 565 "t_est": "t_est", 566 "hörnchen": "hrnchen", 567 } { 568 if have := SanitizeName(input, -1); have != want { 569 t.Errorf("Wanted '%s' got '%s'", want, have) 570 } 571 } 572 } 573 574 func TestProgramCloneNil(t *testing.T) { 575 p, err := (*Program)(nil).Clone() 576 if err != nil { 577 t.Fatal(err) 578 } 579 580 if p != nil { 581 t.Fatal("Cloning a nil Program doesn't return nil") 582 } 583 } 584 585 func TestProgramMarshaling(t *testing.T) { 586 const idx = uint32(0) 587 588 arr := createProgramArray(t) 589 defer arr.Close() 590 591 prog := mustSocketFilter(t) 592 593 if err := arr.Put(idx, prog); err != nil { 594 t.Fatal("Can't put program:", err) 595 } 596 597 if err := arr.Lookup(idx, Program{}); err == nil { 598 t.Fatal("Lookup accepts non-pointer Program") 599 } 600 601 var prog2 *Program 602 defer prog2.Close() 603 604 if err := arr.Lookup(idx, prog2); err == nil { 605 t.Fatal("Get accepts *Program") 606 } 607 608 testutils.SkipOnOldKernel(t, "4.12", "lookup for ProgramArray") 609 610 if err := arr.Lookup(idx, &prog2); err != nil { 611 t.Fatal("Can't unmarshal program:", err) 612 } 613 defer prog2.Close() 614 615 if prog2 == nil { 616 t.Fatal("Unmarshalling set program to nil") 617 } 618 } 619 620 func TestProgramFromFD(t *testing.T) { 621 prog := mustSocketFilter(t) 622 623 // If you're thinking about copying this, don't. Use 624 // Clone() instead. 625 prog2, err := NewProgramFromFD(dupFD(t, prog.FD())) 626 testutils.SkipIfNotSupported(t, err) 627 if err != nil { 628 t.Fatal(err) 629 } 630 defer prog2.Close() 631 632 // Name and type are supposed to be copied from program info. 633 if haveObjName() == nil && prog2.name != "test" { 634 t.Errorf("Expected program to have name test, got '%s'", prog2.name) 635 } 636 637 if prog2.typ != SocketFilter { 638 t.Errorf("Expected program to have type SocketFilter, got '%s'", prog2.typ) 639 } 640 } 641 642 func TestHaveProgTestRun(t *testing.T) { 643 testutils.CheckFeatureTest(t, haveProgRun) 644 } 645 646 func TestProgramGetNextID(t *testing.T) { 647 testutils.SkipOnOldKernel(t, "4.13", "bpf_prog_get_next_id") 648 649 // Ensure there is at least one program loaded 650 _ = mustSocketFilter(t) 651 652 // As there can be multiple eBPF programs, we loop over all of them and 653 // make sure, the IDs increase and the last call will return ErrNotExist 654 last := ProgramID(0) 655 for { 656 next, err := ProgramGetNextID(last) 657 if errors.Is(err, os.ErrNotExist) { 658 if last == 0 { 659 t.Fatal("Got ErrNotExist on the first iteration") 660 } 661 break 662 } 663 if err != nil { 664 t.Fatal("Unexpected error:", err) 665 } 666 if next <= last { 667 t.Fatalf("Expected next ID (%d) to be higher than the last ID (%d)", next, last) 668 } 669 last = next 670 } 671 } 672 673 func TestNewProgramFromID(t *testing.T) { 674 prog := mustSocketFilter(t) 675 676 info, err := prog.Info() 677 testutils.SkipIfNotSupported(t, err) 678 if err != nil { 679 t.Fatal("Could not get program info:", err) 680 } 681 682 id, ok := info.ID() 683 if !ok { 684 t.Skip("Program ID not supported") 685 } 686 687 prog2, err := NewProgramFromID(id) 688 if err != nil { 689 t.Fatalf("Can't get FD for program ID %d: %v", id, err) 690 } 691 prog2.Close() 692 693 // As there can be multiple programs, we use max(uint32) as ProgramID to trigger an expected error. 694 _, err = NewProgramFromID(ProgramID(math.MaxUint32)) 695 if !errors.Is(err, os.ErrNotExist) { 696 t.Fatal("Expected ErrNotExist, got:", err) 697 } 698 } 699 700 func TestProgramRejectIncorrectByteOrder(t *testing.T) { 701 spec := socketFilterSpec.Copy() 702 703 spec.ByteOrder = binary.BigEndian 704 if spec.ByteOrder == internal.NativeEndian { 705 spec.ByteOrder = binary.LittleEndian 706 } 707 708 _, err := NewProgram(spec) 709 if err == nil { 710 t.Error("Incorrect ByteOrder should be rejected at load time") 711 } 712 } 713 714 func TestProgramSpecCopy(t *testing.T) { 715 a := &ProgramSpec{ 716 "test", 717 1, 718 1, 719 "attach", 720 nil, // Can't copy Program 721 "section", 722 asm.Instructions{ 723 asm.Return(), 724 }, 725 1, 726 "license", 727 1, 728 binary.LittleEndian, 729 } 730 731 qt.Check(t, qt.IsNil((*ProgramSpec)(nil).Copy())) 732 qt.Assert(t, testutils.IsDeepCopy(a.Copy(), a)) 733 } 734 735 func TestProgramSpecTag(t *testing.T) { 736 arr := createArray(t) 737 738 spec := &ProgramSpec{ 739 Type: SocketFilter, 740 Instructions: asm.Instructions{ 741 asm.LoadImm(asm.R0, -1, asm.DWord), 742 asm.LoadMapPtr(asm.R1, arr.FD()), 743 asm.Mov.Imm32(asm.R0, 0), 744 asm.Return(), 745 }, 746 License: "MIT", 747 } 748 749 prog, err := NewProgram(spec) 750 if err != nil { 751 t.Fatal(err) 752 } 753 defer prog.Close() 754 755 info, err := prog.Info() 756 testutils.SkipIfNotSupported(t, err) 757 if err != nil { 758 t.Fatal(err) 759 } 760 761 tag, err := spec.Tag() 762 if err != nil { 763 t.Fatal("Can't calculate tag:", err) 764 } 765 766 if tag != info.Tag { 767 t.Errorf("Calculated tag %s doesn't match kernel tag %s", tag, info.Tag) 768 } 769 } 770 771 func TestProgramAttachToKernel(t *testing.T) { 772 // See https://github.com/torvalds/linux/commit/290248a5b7d829871b3ea3c62578613a580a1744 773 testutils.SkipOnOldKernel(t, "5.5", "attach_btf_id") 774 775 haveTestmod := haveTestmod(t) 776 777 tests := []struct { 778 attachTo string 779 programType ProgramType 780 attachType AttachType 781 flags uint32 782 }{ 783 { 784 attachTo: "task_getpgid", 785 programType: LSM, 786 attachType: AttachLSMMac, 787 }, 788 { 789 attachTo: "inet_dgram_connect", 790 programType: Tracing, 791 attachType: AttachTraceFEntry, 792 }, 793 { 794 attachTo: "inet_dgram_connect", 795 programType: Tracing, 796 attachType: AttachTraceFExit, 797 }, 798 { 799 attachTo: "bpf_modify_return_test", 800 programType: Tracing, 801 attachType: AttachModifyReturn, 802 }, 803 { 804 attachTo: "kfree_skb", 805 programType: Tracing, 806 attachType: AttachTraceRawTp, 807 }, 808 { 809 attachTo: "bpf_testmod_test_read", 810 programType: Tracing, 811 attachType: AttachTraceFEntry, 812 }, 813 { 814 attachTo: "bpf_testmod_test_read", 815 programType: Tracing, 816 attachType: AttachTraceFExit, 817 }, 818 { 819 attachTo: "bpf_testmod_test_read", 820 programType: Tracing, 821 attachType: AttachModifyReturn, 822 }, 823 { 824 attachTo: "bpf_testmod_test_read", 825 programType: Tracing, 826 attachType: AttachTraceRawTp, 827 }, 828 } 829 for _, test := range tests { 830 name := fmt.Sprintf("%s:%s", test.attachType, test.attachTo) 831 t.Run(name, func(t *testing.T) { 832 if strings.HasPrefix(test.attachTo, "bpf_testmod_") && !haveTestmod { 833 t.Skip("bpf_testmod not loaded") 834 } 835 836 prog, err := NewProgram(&ProgramSpec{ 837 AttachTo: test.attachTo, 838 AttachType: test.attachType, 839 Instructions: asm.Instructions{ 840 asm.LoadImm(asm.R0, 0, asm.DWord), 841 asm.Return(), 842 }, 843 License: "GPL", 844 Type: test.programType, 845 Flags: test.flags, 846 }) 847 if err != nil { 848 t.Fatal("Can't load program:", err) 849 } 850 prog.Close() 851 }) 852 } 853 } 854 855 func TestProgramKernelTypes(t *testing.T) { 856 if _, err := os.Stat("/sys/kernel/btf/vmlinux"); os.IsNotExist(err) { 857 t.Skip("/sys/kernel/btf/vmlinux not present") 858 } 859 860 btfSpec, err := btf.LoadSpec("/sys/kernel/btf/vmlinux") 861 if err != nil { 862 t.Fatal(err) 863 } 864 865 prog, err := NewProgramWithOptions(&ProgramSpec{ 866 Type: Tracing, 867 AttachType: AttachTraceIter, 868 AttachTo: "bpf_map", 869 Instructions: asm.Instructions{ 870 asm.Mov.Imm(asm.R0, 0), 871 asm.Return(), 872 }, 873 License: "MIT", 874 }, ProgramOptions{ 875 KernelTypes: btfSpec, 876 }) 877 testutils.SkipIfNotSupported(t, err) 878 if err != nil { 879 t.Fatal("NewProgram with Target:", err) 880 } 881 prog.Close() 882 } 883 884 func TestProgramBindMap(t *testing.T) { 885 testutils.SkipOnOldKernel(t, "5.10", "BPF_PROG_BIND_MAP") 886 887 arr, err := NewMap(&MapSpec{ 888 Type: Array, 889 KeySize: 4, 890 ValueSize: 4, 891 MaxEntries: 1, 892 }) 893 if err != nil { 894 t.Errorf("Failed to load map: %v", err) 895 } 896 defer arr.Close() 897 898 prog := mustSocketFilter(t) 899 900 // The attached map does not contain BTF information. So 901 // the metadata part of the program will be empty. This 902 // test just makes sure that we can bind a map to a program. 903 if err := prog.BindMap(arr); err != nil { 904 t.Errorf("Failed to bind map to program: %v", err) 905 } 906 } 907 908 func TestProgramInstructions(t *testing.T) { 909 name := "test_prog" 910 spec := &ProgramSpec{ 911 Type: SocketFilter, 912 Name: name, 913 Instructions: asm.Instructions{ 914 asm.LoadImm(asm.R0, -1, asm.DWord).WithSymbol(name), 915 asm.Return(), 916 }, 917 License: "MIT", 918 } 919 920 prog, err := NewProgram(spec) 921 if err != nil { 922 t.Fatal(err) 923 } 924 defer prog.Close() 925 926 pi, err := prog.Info() 927 testutils.SkipIfNotSupported(t, err) 928 if err != nil { 929 t.Fatal(err) 930 } 931 932 insns, err := pi.Instructions() 933 if err != nil { 934 t.Fatal(err) 935 } 936 937 tag, err := spec.Tag() 938 if err != nil { 939 t.Fatal(err) 940 } 941 942 tagXlated, err := insns.Tag(internal.NativeEndian) 943 if err != nil { 944 t.Fatal(err) 945 } 946 947 if tag != tagXlated { 948 t.Fatalf("tag %s differs from xlated instructions tag %s", tag, tagXlated) 949 } 950 } 951 952 func TestProgramLoadErrors(t *testing.T) { 953 testutils.SkipOnOldKernel(t, "4.10", "stable verifier log output") 954 955 spec, err := LoadCollectionSpec(testutils.NativeFile(t, "testdata/errors-%s.elf")) 956 qt.Assert(t, qt.IsNil(err)) 957 958 var b btf.Builder 959 raw, err := b.Marshal(nil, nil) 960 qt.Assert(t, qt.IsNil(err)) 961 empty, err := btf.LoadSpecFromReader(bytes.NewReader(raw)) 962 qt.Assert(t, qt.IsNil(err)) 963 964 for _, test := range []struct { 965 name string 966 want error 967 }{ 968 {"poisoned_single", errBadRelocation}, 969 {"poisoned_double", errBadRelocation}, 970 {"poisoned_kfunc", errUnknownKfunc}, 971 } { 972 progSpec := spec.Programs[test.name] 973 qt.Assert(t, qt.IsNotNil(progSpec)) 974 975 t.Run(test.name, func(t *testing.T) { 976 t.Log(progSpec.Instructions) 977 _, err := NewProgramWithOptions(progSpec, ProgramOptions{ 978 KernelTypes: empty, 979 }) 980 testutils.SkipIfNotSupported(t, err) 981 982 var ve *VerifierError 983 qt.Assert(t, qt.ErrorAs(err, &ve)) 984 t.Logf("%-5v", ve) 985 986 qt.Assert(t, qt.ErrorIs(err, test.want)) 987 }) 988 } 989 } 990 991 func BenchmarkNewProgram(b *testing.B) { 992 testutils.SkipOnOldKernel(b, "5.18", "kfunc support") 993 spec, err := LoadCollectionSpec(testutils.NativeFile(b, "testdata/kfunc-%s.elf")) 994 qt.Assert(b, qt.IsNil(err)) 995 996 b.ReportAllocs() 997 b.ResetTimer() 998 999 for i := 0; i < b.N; i++ { 1000 _, err := NewProgram(spec.Programs["benchmark"]) 1001 if !errors.Is(err, unix.EACCES) { 1002 b.Fatal("Unexpected error:", err) 1003 } 1004 } 1005 } 1006 1007 func createProgramArray(t *testing.T) *Map { 1008 t.Helper() 1009 1010 arr, err := NewMap(&MapSpec{ 1011 Type: ProgramArray, 1012 KeySize: 4, 1013 ValueSize: 4, 1014 MaxEntries: 1, 1015 }) 1016 if err != nil { 1017 t.Fatal(err) 1018 } 1019 return arr 1020 } 1021 1022 var socketFilterSpec = &ProgramSpec{ 1023 Name: "test", 1024 Type: SocketFilter, 1025 Instructions: asm.Instructions{ 1026 asm.LoadImm(asm.R0, 2, asm.DWord), 1027 asm.Return(), 1028 }, 1029 License: "MIT", 1030 } 1031 1032 func mustSocketFilter(tb testing.TB) *Program { 1033 tb.Helper() 1034 1035 prog, err := NewProgram(socketFilterSpec) 1036 if err != nil { 1037 tb.Fatal(err) 1038 } 1039 tb.Cleanup(func() { prog.Close() }) 1040 1041 return prog 1042 } 1043 1044 // Print the full verifier log when loading a program fails. 1045 func ExampleVerifierError_retrieveFullLog() { 1046 _, err := NewProgram(&ProgramSpec{ 1047 Type: SocketFilter, 1048 Instructions: asm.Instructions{ 1049 asm.LoadImm(asm.R0, 0, asm.DWord), 1050 // Missing Return 1051 }, 1052 License: "MIT", 1053 }) 1054 1055 var ve *VerifierError 1056 if errors.As(err, &ve) { 1057 // Using %+v will print the whole verifier error, not just the last 1058 // few lines. 1059 fmt.Printf("Verifier error: %+v\n", ve) 1060 } 1061 } 1062 1063 // VerifierLog understands a variety of formatting flags. 1064 func ExampleVerifierError() { 1065 err := internal.ErrorWithLog( 1066 "catastrophe", 1067 syscall.ENOSPC, 1068 []byte("first\nsecond\nthird"), 1069 ) 1070 1071 fmt.Printf("With %%s: %s\n", err) 1072 fmt.Printf("All log lines: %+v\n", err) 1073 fmt.Printf("First line: %+1v\n", err) 1074 fmt.Printf("Last two lines: %-2v\n", err) 1075 1076 // Output: With %s: catastrophe: no space left on device: third (2 line(s) omitted) 1077 // All log lines: catastrophe: no space left on device: 1078 // first 1079 // second 1080 // third 1081 // First line: catastrophe: no space left on device: 1082 // first 1083 // (2 line(s) omitted) 1084 // Last two lines: catastrophe: no space left on device: 1085 // (1 line(s) omitted) 1086 // second 1087 // third 1088 } 1089 1090 // Use NewProgramWithOptions if you'd like to get the verifier output 1091 // for a program, or if you want to change the buffer size used when 1092 // generating error messages. 1093 func ExampleProgram_retrieveVerifierLog() { 1094 spec := &ProgramSpec{ 1095 Type: SocketFilter, 1096 Instructions: asm.Instructions{ 1097 asm.LoadImm(asm.R0, 0, asm.DWord), 1098 asm.Return(), 1099 }, 1100 License: "MIT", 1101 } 1102 1103 prog, err := NewProgramWithOptions(spec, ProgramOptions{ 1104 LogLevel: LogLevelInstruction, 1105 }) 1106 if err != nil { 1107 panic(err) 1108 } 1109 defer prog.Close() 1110 1111 fmt.Println("The verifier output is:") 1112 fmt.Println(prog.VerifierLog) 1113 } 1114 1115 // It's possible to read a program directly from a ProgramArray. 1116 func ExampleProgram_unmarshalFromMap() { 1117 progArray, err := LoadPinnedMap("/path/to/map", nil) 1118 if err != nil { 1119 panic(err) 1120 } 1121 defer progArray.Close() 1122 1123 // Load a single program 1124 var prog *Program 1125 if err := progArray.Lookup(uint32(0), &prog); err != nil { 1126 panic(err) 1127 } 1128 defer prog.Close() 1129 1130 fmt.Println("first prog:", prog) 1131 1132 // Iterate all programs 1133 var ( 1134 key uint32 1135 entries = progArray.Iterate() 1136 ) 1137 1138 for entries.Next(&key, &prog) { 1139 fmt.Println(key, "is", prog) 1140 } 1141 1142 if err := entries.Err(); err != nil { 1143 panic(err) 1144 } 1145 } 1146 1147 func ExampleProgramSpec_Tag() { 1148 spec := &ProgramSpec{ 1149 Type: SocketFilter, 1150 Instructions: asm.Instructions{ 1151 asm.LoadImm(asm.R0, 0, asm.DWord), 1152 asm.Return(), 1153 }, 1154 License: "MIT", 1155 } 1156 1157 prog, _ := NewProgram(spec) 1158 info, _ := prog.Info() 1159 tag, _ := spec.Tag() 1160 1161 if info.Tag != tag { 1162 fmt.Printf("The tags don't match: %s != %s\n", info.Tag, tag) 1163 } else { 1164 fmt.Println("The programs are identical, tag is", tag) 1165 } 1166 } 1167 1168 func dupFD(tb testing.TB, fd int) int { 1169 tb.Helper() 1170 1171 dup, err := unix.FcntlInt(uintptr(fd), unix.F_DUPFD_CLOEXEC, 1) 1172 if err != nil { 1173 tb.Fatal("Can't dup fd:", err) 1174 } 1175 1176 return dup 1177 }