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  }