github.com/cilium/ebpf@v0.15.1-0.20240517100537-8079b37aa138/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  // Test all scenarios where the VerifierError.Truncated flag is expected to be
   467  // true, marked with an x. LL means ProgramOption.LogLevel.
   468  //
   469  // |      | Valid | Invalid |
   470  // |------|-------|---------|
   471  // | LL=0 |       |    x    |
   472  // | LL>0 |   x   |    x    |
   473  func TestProgramVerifierLogTruncated(t *testing.T) {
   474  	// Make the buffer intentionally small to coerce ENOSPC.
   475  	// 128 bytes is the smallest the kernel will accept.
   476  	logSize := 128
   477  
   478  	check := func(t *testing.T, err error) {
   479  		t.Helper()
   480  
   481  		if err == nil {
   482  			t.Fatal("Expected an error")
   483  		}
   484  		var ve *internal.VerifierError
   485  		if !errors.As(err, &ve) {
   486  			t.Fatal("Error is not a VerifierError")
   487  		}
   488  		if !ve.Truncated {
   489  			t.Errorf("VerifierError is not truncated: %+v", ve)
   490  		}
   491  	}
   492  
   493  	// Generate a base program of sufficient size whose verifier log does not fit
   494  	// a 128-byte buffer. This should always result in ENOSPC, setting the
   495  	// VerifierError.Truncated flag.
   496  	var base asm.Instructions
   497  	for i := 0; i < 32; i++ {
   498  		base = append(base, asm.Mov.Reg(asm.R0, asm.R1))
   499  	}
   500  
   501  	// Touch R10 (read-only frame pointer) to reliably force a verifier error.
   502  	invalid := slices.Clone(base)
   503  	invalid = append(invalid, asm.Mov.Reg(asm.R10, asm.R0))
   504  	invalid = append(invalid, asm.Return())
   505  
   506  	valid := slices.Clone(base)
   507  	valid = append(valid, asm.Return())
   508  
   509  	// Start out with testing against the invalid program.
   510  	spec := &ProgramSpec{
   511  		Type:         SocketFilter,
   512  		License:      "MIT",
   513  		Instructions: invalid,
   514  	}
   515  
   516  	// Set an undersized log buffer without explicitly requesting a verifier log
   517  	// for an invalid program.
   518  	_, err := NewProgramWithOptions(spec, ProgramOptions{LogSize: logSize})
   519  	check(t, err)
   520  
   521  	// Explicitly request a verifier log for an invalid program.
   522  	_, err = NewProgramWithOptions(spec, ProgramOptions{
   523  		LogSize:  logSize,
   524  		LogLevel: LogLevelInstruction,
   525  	})
   526  	check(t, err)
   527  
   528  	// Run tests against a valid program from here on out.
   529  	spec.Instructions = valid
   530  
   531  	// Don't request a verifier log, only set LogSize. Expect the valid program to
   532  	// be created without errors.
   533  	prog, err := NewProgramWithOptions(spec, ProgramOptions{
   534  		LogSize: logSize,
   535  	})
   536  	if err != nil {
   537  		t.Fatal(err)
   538  	}
   539  	prog.Close()
   540  
   541  	// Explicitly request verifier log for a valid program. If a log is requested
   542  	// and the buffer is too small, ENOSPC occurs even for valid programs.
   543  	_, err = NewProgramWithOptions(spec, ProgramOptions{
   544  		LogSize:  logSize,
   545  		LogLevel: LogLevelInstruction,
   546  	})
   547  	check(t, err)
   548  }
   549  
   550  func TestProgramWithUnsatisfiedMap(t *testing.T) {
   551  	coll, err := LoadCollectionSpec("testdata/loader-el.elf")
   552  	if err != nil {
   553  		t.Fatal(err)
   554  	}
   555  
   556  	// The program will have at least one map reference.
   557  	progSpec := coll.Programs["xdp_prog"]
   558  	progSpec.ByteOrder = nil
   559  
   560  	_, err = NewProgram(progSpec)
   561  	testutils.SkipIfNotSupported(t, err)
   562  	if !errors.Is(err, asm.ErrUnsatisfiedMapReference) {
   563  		t.Fatal("Expected an error wrapping asm.ErrUnsatisfiedMapReference, got", err)
   564  	}
   565  	t.Log(err)
   566  }
   567  
   568  func TestProgramName(t *testing.T) {
   569  	if err := haveObjName(); err != nil {
   570  		t.Skip(err)
   571  	}
   572  
   573  	prog := mustSocketFilter(t)
   574  
   575  	var info sys.ProgInfo
   576  	if err := sys.ObjInfo(prog.fd, &info); err != nil {
   577  		t.Fatal(err)
   578  	}
   579  
   580  	if name := unix.ByteSliceToString(info.Name[:]); name != "test" {
   581  		t.Errorf("Name is not test, got '%s'", name)
   582  	}
   583  }
   584  
   585  func TestSanitizeName(t *testing.T) {
   586  	for input, want := range map[string]string{
   587  		"test":     "test",
   588  		"t-est":    "test",
   589  		"t_est":    "t_est",
   590  		"hörnchen": "hrnchen",
   591  	} {
   592  		if have := SanitizeName(input, -1); have != want {
   593  			t.Errorf("Wanted '%s' got '%s'", want, have)
   594  		}
   595  	}
   596  }
   597  
   598  func TestProgramCloneNil(t *testing.T) {
   599  	p, err := (*Program)(nil).Clone()
   600  	if err != nil {
   601  		t.Fatal(err)
   602  	}
   603  
   604  	if p != nil {
   605  		t.Fatal("Cloning a nil Program doesn't return nil")
   606  	}
   607  }
   608  
   609  func TestProgramMarshaling(t *testing.T) {
   610  	const idx = uint32(0)
   611  
   612  	arr := createProgramArray(t)
   613  	defer arr.Close()
   614  
   615  	prog := mustSocketFilter(t)
   616  
   617  	if err := arr.Put(idx, prog); err != nil {
   618  		t.Fatal("Can't put program:", err)
   619  	}
   620  
   621  	if err := arr.Lookup(idx, Program{}); err == nil {
   622  		t.Fatal("Lookup accepts non-pointer Program")
   623  	}
   624  
   625  	var prog2 *Program
   626  	defer prog2.Close()
   627  
   628  	if err := arr.Lookup(idx, prog2); err == nil {
   629  		t.Fatal("Get accepts *Program")
   630  	}
   631  
   632  	testutils.SkipOnOldKernel(t, "4.12", "lookup for ProgramArray")
   633  
   634  	if err := arr.Lookup(idx, &prog2); err != nil {
   635  		t.Fatal("Can't unmarshal program:", err)
   636  	}
   637  	defer prog2.Close()
   638  
   639  	if prog2 == nil {
   640  		t.Fatal("Unmarshalling set program to nil")
   641  	}
   642  }
   643  
   644  func TestProgramFromFD(t *testing.T) {
   645  	prog := mustSocketFilter(t)
   646  
   647  	// If you're thinking about copying this, don't. Use
   648  	// Clone() instead.
   649  	prog2, err := NewProgramFromFD(dupFD(t, prog.FD()))
   650  	testutils.SkipIfNotSupported(t, err)
   651  	if err != nil {
   652  		t.Fatal(err)
   653  	}
   654  	defer prog2.Close()
   655  
   656  	// Name and type are supposed to be copied from program info.
   657  	if haveObjName() == nil && prog2.name != "test" {
   658  		t.Errorf("Expected program to have name test, got '%s'", prog2.name)
   659  	}
   660  
   661  	if prog2.typ != SocketFilter {
   662  		t.Errorf("Expected program to have type SocketFilter, got '%s'", prog2.typ)
   663  	}
   664  }
   665  
   666  func TestHaveProgTestRun(t *testing.T) {
   667  	testutils.CheckFeatureTest(t, haveProgRun)
   668  }
   669  
   670  func TestProgramGetNextID(t *testing.T) {
   671  	testutils.SkipOnOldKernel(t, "4.13", "bpf_prog_get_next_id")
   672  
   673  	// Ensure there is at least one program loaded
   674  	_ = mustSocketFilter(t)
   675  
   676  	// As there can be multiple eBPF programs, we loop over all of them and
   677  	// make sure, the IDs increase and the last call will return ErrNotExist
   678  	last := ProgramID(0)
   679  	for {
   680  		next, err := ProgramGetNextID(last)
   681  		if errors.Is(err, os.ErrNotExist) {
   682  			if last == 0 {
   683  				t.Fatal("Got ErrNotExist on the first iteration")
   684  			}
   685  			break
   686  		}
   687  		if err != nil {
   688  			t.Fatal("Unexpected error:", err)
   689  		}
   690  		if next <= last {
   691  			t.Fatalf("Expected next ID (%d) to be higher than the last ID (%d)", next, last)
   692  		}
   693  		last = next
   694  	}
   695  }
   696  
   697  func TestNewProgramFromID(t *testing.T) {
   698  	prog := mustSocketFilter(t)
   699  
   700  	info, err := prog.Info()
   701  	testutils.SkipIfNotSupported(t, err)
   702  	if err != nil {
   703  		t.Fatal("Could not get program info:", err)
   704  	}
   705  
   706  	id, ok := info.ID()
   707  	if !ok {
   708  		t.Skip("Program ID not supported")
   709  	}
   710  
   711  	prog2, err := NewProgramFromID(id)
   712  	if err != nil {
   713  		t.Fatalf("Can't get FD for program ID %d: %v", id, err)
   714  	}
   715  	prog2.Close()
   716  
   717  	// As there can be multiple programs, we use max(uint32) as ProgramID to trigger an expected error.
   718  	_, err = NewProgramFromID(ProgramID(math.MaxUint32))
   719  	if !errors.Is(err, os.ErrNotExist) {
   720  		t.Fatal("Expected ErrNotExist, got:", err)
   721  	}
   722  }
   723  
   724  func TestProgramRejectIncorrectByteOrder(t *testing.T) {
   725  	spec := socketFilterSpec.Copy()
   726  
   727  	spec.ByteOrder = binary.BigEndian
   728  	if spec.ByteOrder == internal.NativeEndian {
   729  		spec.ByteOrder = binary.LittleEndian
   730  	}
   731  
   732  	_, err := NewProgram(spec)
   733  	if err == nil {
   734  		t.Error("Incorrect ByteOrder should be rejected at load time")
   735  	}
   736  }
   737  
   738  func TestProgramSpecTag(t *testing.T) {
   739  	arr := createArray(t)
   740  
   741  	spec := &ProgramSpec{
   742  		Type: SocketFilter,
   743  		Instructions: asm.Instructions{
   744  			asm.LoadImm(asm.R0, -1, asm.DWord),
   745  			asm.LoadMapPtr(asm.R1, arr.FD()),
   746  			asm.Mov.Imm32(asm.R0, 0),
   747  			asm.Return(),
   748  		},
   749  		License: "MIT",
   750  	}
   751  
   752  	prog, err := NewProgram(spec)
   753  	if err != nil {
   754  		t.Fatal(err)
   755  	}
   756  	defer prog.Close()
   757  
   758  	info, err := prog.Info()
   759  	testutils.SkipIfNotSupported(t, err)
   760  	if err != nil {
   761  		t.Fatal(err)
   762  	}
   763  
   764  	tag, err := spec.Tag()
   765  	if err != nil {
   766  		t.Fatal("Can't calculate tag:", err)
   767  	}
   768  
   769  	if tag != info.Tag {
   770  		t.Errorf("Calculated tag %s doesn't match kernel tag %s", tag, info.Tag)
   771  	}
   772  }
   773  
   774  func TestProgramAttachToKernel(t *testing.T) {
   775  	// See https://github.com/torvalds/linux/commit/290248a5b7d829871b3ea3c62578613a580a1744
   776  	testutils.SkipOnOldKernel(t, "5.5", "attach_btf_id")
   777  
   778  	haveTestmod := haveTestmod(t)
   779  
   780  	tests := []struct {
   781  		attachTo    string
   782  		programType ProgramType
   783  		attachType  AttachType
   784  		flags       uint32
   785  	}{
   786  		{
   787  			attachTo:    "task_getpgid",
   788  			programType: LSM,
   789  			attachType:  AttachLSMMac,
   790  		},
   791  		{
   792  			attachTo:    "inet_dgram_connect",
   793  			programType: Tracing,
   794  			attachType:  AttachTraceFEntry,
   795  		},
   796  		{
   797  			attachTo:    "inet_dgram_connect",
   798  			programType: Tracing,
   799  			attachType:  AttachTraceFExit,
   800  		},
   801  		{
   802  			attachTo:    "bpf_modify_return_test",
   803  			programType: Tracing,
   804  			attachType:  AttachModifyReturn,
   805  		},
   806  		{
   807  			attachTo:    "kfree_skb",
   808  			programType: Tracing,
   809  			attachType:  AttachTraceRawTp,
   810  		},
   811  		{
   812  			attachTo:    "bpf_testmod_test_read",
   813  			programType: Tracing,
   814  			attachType:  AttachTraceFEntry,
   815  		},
   816  		{
   817  			attachTo:    "bpf_testmod_test_read",
   818  			programType: Tracing,
   819  			attachType:  AttachTraceFExit,
   820  		},
   821  		{
   822  			attachTo:    "bpf_testmod_test_read",
   823  			programType: Tracing,
   824  			attachType:  AttachModifyReturn,
   825  		},
   826  		{
   827  			attachTo:    "bpf_testmod_test_read",
   828  			programType: Tracing,
   829  			attachType:  AttachTraceRawTp,
   830  		},
   831  	}
   832  	for _, test := range tests {
   833  		name := fmt.Sprintf("%s:%s", test.attachType, test.attachTo)
   834  		t.Run(name, func(t *testing.T) {
   835  			if strings.HasPrefix(test.attachTo, "bpf_testmod_") && !haveTestmod {
   836  				t.Skip("bpf_testmod not loaded")
   837  			}
   838  
   839  			prog, err := NewProgram(&ProgramSpec{
   840  				AttachTo:   test.attachTo,
   841  				AttachType: test.attachType,
   842  				Instructions: asm.Instructions{
   843  					asm.LoadImm(asm.R0, 0, asm.DWord),
   844  					asm.Return(),
   845  				},
   846  				License: "GPL",
   847  				Type:    test.programType,
   848  				Flags:   test.flags,
   849  			})
   850  			if err != nil {
   851  				t.Fatal("Can't load program:", err)
   852  			}
   853  			prog.Close()
   854  		})
   855  	}
   856  }
   857  
   858  func TestProgramKernelTypes(t *testing.T) {
   859  	if _, err := os.Stat("/sys/kernel/btf/vmlinux"); os.IsNotExist(err) {
   860  		t.Skip("/sys/kernel/btf/vmlinux not present")
   861  	}
   862  
   863  	btfSpec, err := btf.LoadSpec("/sys/kernel/btf/vmlinux")
   864  	if err != nil {
   865  		t.Fatal(err)
   866  	}
   867  
   868  	prog, err := NewProgramWithOptions(&ProgramSpec{
   869  		Type:       Tracing,
   870  		AttachType: AttachTraceIter,
   871  		AttachTo:   "bpf_map",
   872  		Instructions: asm.Instructions{
   873  			asm.Mov.Imm(asm.R0, 0),
   874  			asm.Return(),
   875  		},
   876  		License: "MIT",
   877  	}, ProgramOptions{
   878  		KernelTypes: btfSpec,
   879  	})
   880  	testutils.SkipIfNotSupported(t, err)
   881  	if err != nil {
   882  		t.Fatal("NewProgram with Target:", err)
   883  	}
   884  	prog.Close()
   885  }
   886  
   887  func TestProgramBindMap(t *testing.T) {
   888  	testutils.SkipOnOldKernel(t, "5.10", "BPF_PROG_BIND_MAP")
   889  
   890  	arr, err := NewMap(&MapSpec{
   891  		Type:       Array,
   892  		KeySize:    4,
   893  		ValueSize:  4,
   894  		MaxEntries: 1,
   895  	})
   896  	if err != nil {
   897  		t.Errorf("Failed to load map: %v", err)
   898  	}
   899  	defer arr.Close()
   900  
   901  	prog := mustSocketFilter(t)
   902  
   903  	// The attached map does not contain BTF information. So
   904  	// the metadata part of the program will be empty. This
   905  	// test just makes sure that we can bind a map to a program.
   906  	if err := prog.BindMap(arr); err != nil {
   907  		t.Errorf("Failed to bind map to program: %v", err)
   908  	}
   909  }
   910  
   911  func TestProgramInstructions(t *testing.T) {
   912  	name := "test_prog"
   913  	spec := &ProgramSpec{
   914  		Type: SocketFilter,
   915  		Name: name,
   916  		Instructions: asm.Instructions{
   917  			asm.LoadImm(asm.R0, -1, asm.DWord).WithSymbol(name),
   918  			asm.Return(),
   919  		},
   920  		License: "MIT",
   921  	}
   922  
   923  	prog, err := NewProgram(spec)
   924  	if err != nil {
   925  		t.Fatal(err)
   926  	}
   927  	defer prog.Close()
   928  
   929  	pi, err := prog.Info()
   930  	testutils.SkipIfNotSupported(t, err)
   931  	if err != nil {
   932  		t.Fatal(err)
   933  	}
   934  
   935  	insns, err := pi.Instructions()
   936  	if err != nil {
   937  		t.Fatal(err)
   938  	}
   939  
   940  	tag, err := spec.Tag()
   941  	if err != nil {
   942  		t.Fatal(err)
   943  	}
   944  
   945  	tagXlated, err := insns.Tag(internal.NativeEndian)
   946  	if err != nil {
   947  		t.Fatal(err)
   948  	}
   949  
   950  	if tag != tagXlated {
   951  		t.Fatalf("tag %s differs from xlated instructions tag %s", tag, tagXlated)
   952  	}
   953  }
   954  
   955  func TestProgramLoadErrors(t *testing.T) {
   956  	testutils.SkipOnOldKernel(t, "4.10", "stable verifier log output")
   957  
   958  	spec, err := LoadCollectionSpec(testutils.NativeFile(t, "testdata/errors-%s.elf"))
   959  	qt.Assert(t, qt.IsNil(err))
   960  
   961  	var b btf.Builder
   962  	raw, err := b.Marshal(nil, nil)
   963  	qt.Assert(t, qt.IsNil(err))
   964  	empty, err := btf.LoadSpecFromReader(bytes.NewReader(raw))
   965  	qt.Assert(t, qt.IsNil(err))
   966  
   967  	for _, test := range []struct {
   968  		name string
   969  		want error
   970  	}{
   971  		{"poisoned_single", errBadRelocation},
   972  		{"poisoned_double", errBadRelocation},
   973  		{"poisoned_kfunc", errUnknownKfunc},
   974  	} {
   975  		progSpec := spec.Programs[test.name]
   976  		qt.Assert(t, qt.IsNotNil(progSpec))
   977  
   978  		t.Run(test.name, func(t *testing.T) {
   979  			t.Log(progSpec.Instructions)
   980  			_, err := NewProgramWithOptions(progSpec, ProgramOptions{
   981  				KernelTypes: empty,
   982  			})
   983  			testutils.SkipIfNotSupported(t, err)
   984  
   985  			qt.Assert(t, qt.ErrorIs(err, test.want))
   986  
   987  			var ve *VerifierError
   988  			qt.Assert(t, qt.ErrorAs(err, &ve))
   989  			t.Logf("%-5v", ve)
   990  		})
   991  	}
   992  }
   993  
   994  func BenchmarkNewProgram(b *testing.B) {
   995  	testutils.SkipOnOldKernel(b, "5.18", "kfunc support")
   996  	spec, err := LoadCollectionSpec(testutils.NativeFile(b, "testdata/kfunc-%s.elf"))
   997  	qt.Assert(b, qt.IsNil(err))
   998  
   999  	b.ReportAllocs()
  1000  	b.ResetTimer()
  1001  
  1002  	for i := 0; i < b.N; i++ {
  1003  		_, err := NewProgram(spec.Programs["benchmark"])
  1004  		if !errors.Is(err, unix.EACCES) {
  1005  			b.Fatal("Unexpected error:", err)
  1006  		}
  1007  	}
  1008  }
  1009  
  1010  func createProgramArray(t *testing.T) *Map {
  1011  	t.Helper()
  1012  
  1013  	arr, err := NewMap(&MapSpec{
  1014  		Type:       ProgramArray,
  1015  		KeySize:    4,
  1016  		ValueSize:  4,
  1017  		MaxEntries: 1,
  1018  	})
  1019  	if err != nil {
  1020  		t.Fatal(err)
  1021  	}
  1022  	return arr
  1023  }
  1024  
  1025  var socketFilterSpec = &ProgramSpec{
  1026  	Name: "test",
  1027  	Type: SocketFilter,
  1028  	Instructions: asm.Instructions{
  1029  		asm.LoadImm(asm.R0, 2, asm.DWord),
  1030  		asm.Return(),
  1031  	},
  1032  	License: "MIT",
  1033  }
  1034  
  1035  func mustSocketFilter(tb testing.TB) *Program {
  1036  	tb.Helper()
  1037  
  1038  	prog, err := NewProgram(socketFilterSpec)
  1039  	if err != nil {
  1040  		tb.Fatal(err)
  1041  	}
  1042  	tb.Cleanup(func() { prog.Close() })
  1043  
  1044  	return prog
  1045  }
  1046  
  1047  // Print the full verifier log when loading a program fails.
  1048  func ExampleVerifierError_retrieveFullLog() {
  1049  	_, err := NewProgram(&ProgramSpec{
  1050  		Type: SocketFilter,
  1051  		Instructions: asm.Instructions{
  1052  			asm.LoadImm(asm.R0, 0, asm.DWord),
  1053  			// Missing Return
  1054  		},
  1055  		License: "MIT",
  1056  	})
  1057  
  1058  	var ve *VerifierError
  1059  	if errors.As(err, &ve) {
  1060  		// Using %+v will print the whole verifier error, not just the last
  1061  		// few lines.
  1062  		fmt.Printf("Verifier error: %+v\n", ve)
  1063  	}
  1064  }
  1065  
  1066  // VerifierLog understands a variety of formatting flags.
  1067  func ExampleVerifierError() {
  1068  	err := internal.ErrorWithLog(
  1069  		"catastrophe",
  1070  		syscall.ENOSPC,
  1071  		[]byte("first\nsecond\nthird"),
  1072  		false,
  1073  	)
  1074  
  1075  	fmt.Printf("With %%s: %s\n", err)
  1076  	err.Truncated = true
  1077  	fmt.Printf("With %%v and a truncated log: %v\n", err)
  1078  	fmt.Printf("All log lines: %+v\n", err)
  1079  	fmt.Printf("First line: %+1v\n", err)
  1080  	fmt.Printf("Last two lines: %-2v\n", err)
  1081  
  1082  	// Output: With %s: catastrophe: no space left on device: third (2 line(s) omitted)
  1083  	// With %v and a truncated log: catastrophe: no space left on device: second: third (truncated, 1 line(s) omitted)
  1084  	// All log lines: catastrophe: no space left on device:
  1085  	// 	first
  1086  	// 	second
  1087  	// 	third
  1088  	// 	(truncated)
  1089  	// First line: catastrophe: no space left on device:
  1090  	// 	first
  1091  	// 	(2 line(s) omitted)
  1092  	// 	(truncated)
  1093  	// Last two lines: catastrophe: no space left on device:
  1094  	// 	(1 line(s) omitted)
  1095  	// 	second
  1096  	// 	third
  1097  	// 	(truncated)
  1098  }
  1099  
  1100  // Use NewProgramWithOptions if you'd like to get the verifier output
  1101  // for a program, or if you want to change the buffer size used when
  1102  // generating error messages.
  1103  func ExampleProgram_retrieveVerifierLog() {
  1104  	spec := &ProgramSpec{
  1105  		Type: SocketFilter,
  1106  		Instructions: asm.Instructions{
  1107  			asm.LoadImm(asm.R0, 0, asm.DWord),
  1108  			asm.Return(),
  1109  		},
  1110  		License: "MIT",
  1111  	}
  1112  
  1113  	prog, err := NewProgramWithOptions(spec, ProgramOptions{
  1114  		LogLevel: LogLevelInstruction,
  1115  		LogSize:  1024,
  1116  	})
  1117  	if err != nil {
  1118  		panic(err)
  1119  	}
  1120  	defer prog.Close()
  1121  
  1122  	fmt.Println("The verifier output is:")
  1123  	fmt.Println(prog.VerifierLog)
  1124  }
  1125  
  1126  // It's possible to read a program directly from a ProgramArray.
  1127  func ExampleProgram_unmarshalFromMap() {
  1128  	progArray, err := LoadPinnedMap("/path/to/map", nil)
  1129  	if err != nil {
  1130  		panic(err)
  1131  	}
  1132  	defer progArray.Close()
  1133  
  1134  	// Load a single program
  1135  	var prog *Program
  1136  	if err := progArray.Lookup(uint32(0), &prog); err != nil {
  1137  		panic(err)
  1138  	}
  1139  	defer prog.Close()
  1140  
  1141  	fmt.Println("first prog:", prog)
  1142  
  1143  	// Iterate all programs
  1144  	var (
  1145  		key     uint32
  1146  		entries = progArray.Iterate()
  1147  	)
  1148  
  1149  	for entries.Next(&key, &prog) {
  1150  		fmt.Println(key, "is", prog)
  1151  	}
  1152  
  1153  	if err := entries.Err(); err != nil {
  1154  		panic(err)
  1155  	}
  1156  }
  1157  
  1158  func ExampleProgramSpec_Tag() {
  1159  	spec := &ProgramSpec{
  1160  		Type: SocketFilter,
  1161  		Instructions: asm.Instructions{
  1162  			asm.LoadImm(asm.R0, 0, asm.DWord),
  1163  			asm.Return(),
  1164  		},
  1165  		License: "MIT",
  1166  	}
  1167  
  1168  	prog, _ := NewProgram(spec)
  1169  	info, _ := prog.Info()
  1170  	tag, _ := spec.Tag()
  1171  
  1172  	if info.Tag != tag {
  1173  		fmt.Printf("The tags don't match: %s != %s\n", info.Tag, tag)
  1174  	} else {
  1175  		fmt.Println("The programs are identical, tag is", tag)
  1176  	}
  1177  }
  1178  
  1179  func dupFD(tb testing.TB, fd int) int {
  1180  	tb.Helper()
  1181  
  1182  	dup, err := unix.FcntlInt(uintptr(fd), unix.F_DUPFD_CLOEXEC, 1)
  1183  	if err != nil {
  1184  		tb.Fatal("Can't dup fd:", err)
  1185  	}
  1186  
  1187  	return dup
  1188  }