...

Source file src/github.com/cilium/ebpf/link/kprobe_test.go

Documentation: github.com/cilium/ebpf/link

     1  package link
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  	"testing"
     8  
     9  	qt "github.com/frankban/quicktest"
    10  
    11  	"github.com/cilium/ebpf"
    12  	"github.com/cilium/ebpf/asm"
    13  	"github.com/cilium/ebpf/internal/testutils"
    14  	"github.com/cilium/ebpf/internal/unix"
    15  )
    16  
    17  // Global symbol, present on all tested kernels.
    18  var ksym = "vprintk"
    19  
    20  // Collection of various symbols present in all tested kernels.
    21  // Compiler optimizations result in different names for these symbols.
    22  var symTests = []string{
    23  	"async_resume.cold",         // marked with 'cold' gcc attribute, unlikely to be executed
    24  	"echo_char.isra.0",          // function optimized by -fipa-sra
    25  	"get_buffer.constprop.0",    // optimized function with constant operands
    26  	"unregister_kprobes.part.0", // function body that was split and partially inlined
    27  }
    28  
    29  func TestKprobe(t *testing.T) {
    30  	prog := mustLoadProgram(t, ebpf.Kprobe, 0, "")
    31  
    32  	for _, tt := range symTests {
    33  		t.Run(tt, func(t *testing.T) {
    34  			k, err := Kprobe(tt, prog, nil)
    35  			if err != nil {
    36  				t.Fatal(err)
    37  			}
    38  			defer k.Close()
    39  		})
    40  	}
    41  
    42  	c := qt.New(t)
    43  
    44  	k, err := Kprobe("bogus", prog, nil)
    45  	c.Assert(err, qt.ErrorIs, os.ErrNotExist, qt.Commentf("got error: %s", err))
    46  	if k != nil {
    47  		k.Close()
    48  	}
    49  
    50  	k, err = Kprobe(ksym, prog, nil)
    51  	c.Assert(err, qt.IsNil)
    52  	defer k.Close()
    53  
    54  	testLink(t, k, prog)
    55  }
    56  
    57  func TestKretprobe(t *testing.T) {
    58  	prog := mustLoadProgram(t, ebpf.Kprobe, 0, "")
    59  
    60  	for _, tt := range symTests {
    61  		t.Run(tt, func(t *testing.T) {
    62  			k, err := Kretprobe(tt, prog, nil)
    63  			if err != nil {
    64  				t.Fatal(err)
    65  			}
    66  			defer k.Close()
    67  		})
    68  	}
    69  
    70  	c := qt.New(t)
    71  
    72  	k, err := Kretprobe("bogus", prog, nil)
    73  	c.Assert(err, qt.ErrorIs, os.ErrNotExist, qt.Commentf("got error: %s", err))
    74  	if k != nil {
    75  		k.Close()
    76  	}
    77  
    78  	k, err = Kretprobe(ksym, prog, nil)
    79  	c.Assert(err, qt.IsNil)
    80  	defer k.Close()
    81  
    82  	testLink(t, k, prog)
    83  }
    84  
    85  func TestKprobeErrors(t *testing.T) {
    86  	c := qt.New(t)
    87  
    88  	// Invalid Kprobe incantations. Kretprobe uses the same code paths
    89  	// with a different ret flag.
    90  	_, err := Kprobe("", nil, nil) // empty symbol
    91  	c.Assert(errors.Is(err, errInvalidInput), qt.IsTrue)
    92  
    93  	_, err = Kprobe("_", nil, nil) // empty prog
    94  	c.Assert(errors.Is(err, errInvalidInput), qt.IsTrue)
    95  
    96  	_, err = Kprobe(".", &ebpf.Program{}, nil) // illegal chars in symbol
    97  	c.Assert(errors.Is(err, errInvalidInput), qt.IsTrue)
    98  
    99  	_, err = Kprobe("foo", &ebpf.Program{}, nil) // wrong prog type
   100  	c.Assert(errors.Is(err, errInvalidInput), qt.IsTrue)
   101  }
   102  
   103  // Test k(ret)probe creation using perf_kprobe PMU.
   104  func TestKprobeCreatePMU(t *testing.T) {
   105  	// Requires at least 4.17 (e12f03d7031a "perf/core: Implement the 'perf_kprobe' PMU")
   106  	testutils.SkipOnOldKernel(t, "4.17", "perf_kprobe PMU")
   107  
   108  	c := qt.New(t)
   109  
   110  	// kprobe happy path. printk is always present.
   111  	pk, err := pmuKprobe(probeArgs{symbol: ksym})
   112  	c.Assert(err, qt.IsNil)
   113  	defer pk.Close()
   114  
   115  	c.Assert(pk.typ, qt.Equals, kprobeEvent)
   116  
   117  	// kretprobe happy path.
   118  	pr, err := pmuKprobe(probeArgs{symbol: ksym, ret: true})
   119  	c.Assert(err, qt.IsNil)
   120  	defer pr.Close()
   121  
   122  	c.Assert(pr.typ, qt.Equals, kretprobeEvent)
   123  
   124  	// Expect os.ErrNotExist when specifying a non-existent kernel symbol
   125  	// on kernels 4.17 and up.
   126  	_, err = pmuKprobe(probeArgs{symbol: "bogus"})
   127  	c.Assert(errors.Is(err, os.ErrNotExist), qt.IsTrue, qt.Commentf("got error: %s", err))
   128  
   129  	// A kernel bug was fixed in 97c753e62e6c where EINVAL was returned instead
   130  	// of ENOENT, but only for kretprobes.
   131  	_, err = pmuKprobe(probeArgs{symbol: "bogus", ret: true})
   132  	c.Assert(errors.Is(err, os.ErrNotExist), qt.IsTrue, qt.Commentf("got error: %s", err))
   133  }
   134  
   135  // Test fallback behaviour on kernels without perf_kprobe PMU available.
   136  func TestKprobePMUUnavailable(t *testing.T) {
   137  	c := qt.New(t)
   138  
   139  	pk, err := pmuKprobe(probeArgs{symbol: ksym})
   140  	if err == nil {
   141  		pk.Close()
   142  		t.Skipf("Kernel supports perf_kprobe PMU, not asserting error.")
   143  	}
   144  
   145  	// Only allow a PMU creation with a valid kernel symbol to fail with ErrNotSupported.
   146  	c.Assert(errors.Is(err, ErrNotSupported), qt.IsTrue, qt.Commentf("got error: %s", err))
   147  }
   148  
   149  func BenchmarkKprobeCreatePMU(b *testing.B) {
   150  	for n := 0; n < b.N; n++ {
   151  		pr, err := pmuKprobe(probeArgs{symbol: ksym})
   152  		if err != nil {
   153  			b.Error("error creating perf_kprobe PMU:", err)
   154  		}
   155  
   156  		if err := pr.Close(); err != nil {
   157  			b.Error("error closing perf_kprobe PMU:", err)
   158  		}
   159  	}
   160  }
   161  
   162  // Test tracefs k(ret)probe creation on all kernel versions.
   163  func TestKprobeTraceFS(t *testing.T) {
   164  	c := qt.New(t)
   165  
   166  	// Open and close tracefs k(ret)probes, checking all errors.
   167  	kp, err := tracefsKprobe(probeArgs{symbol: ksym})
   168  	c.Assert(err, qt.IsNil)
   169  	c.Assert(kp.Close(), qt.IsNil)
   170  	c.Assert(kp.typ, qt.Equals, kprobeEvent)
   171  
   172  	kp, err = tracefsKprobe(probeArgs{symbol: ksym, ret: true})
   173  	c.Assert(err, qt.IsNil)
   174  	c.Assert(kp.Close(), qt.IsNil)
   175  	c.Assert(kp.typ, qt.Equals, kretprobeEvent)
   176  
   177  	// Create two identical trace events, ensure their IDs differ.
   178  	k1, err := tracefsKprobe(probeArgs{symbol: ksym})
   179  	c.Assert(err, qt.IsNil)
   180  	defer k1.Close()
   181  	c.Assert(k1.tracefsID, qt.Not(qt.Equals), 0)
   182  
   183  	k2, err := tracefsKprobe(probeArgs{symbol: ksym})
   184  	c.Assert(err, qt.IsNil)
   185  	defer k2.Close()
   186  	c.Assert(k2.tracefsID, qt.Not(qt.Equals), 0)
   187  
   188  	// Compare the kprobes' tracefs IDs.
   189  	c.Assert(k1.tracefsID, qt.Not(qt.CmpEquals()), k2.tracefsID)
   190  
   191  	// Prepare probe args.
   192  	args := probeArgs{group: "testgroup", symbol: "symbol"}
   193  
   194  	// Write a k(ret)probe event for a non-existing symbol.
   195  	err = createTraceFSProbeEvent(kprobeType, args)
   196  	c.Assert(errors.Is(err, os.ErrNotExist), qt.IsTrue, qt.Commentf("got error: %s", err))
   197  
   198  	// A kernel bug was fixed in 97c753e62e6c where EINVAL was returned instead
   199  	// of ENOENT, but only for kretprobes.
   200  	args.ret = true
   201  	err = createTraceFSProbeEvent(kprobeType, args)
   202  	c.Assert(errors.Is(err, os.ErrNotExist), qt.IsTrue, qt.Commentf("got error: %s", err))
   203  }
   204  
   205  func BenchmarkKprobeCreateTraceFS(b *testing.B) {
   206  	for n := 0; n < b.N; n++ {
   207  		// Include <tracefs>/kprobe_events operations in the benchmark loop
   208  		// because we create one per perf event.
   209  		pr, err := tracefsKprobe(probeArgs{symbol: ksym})
   210  		if err != nil {
   211  			b.Error("error creating tracefs perf event:", err)
   212  		}
   213  
   214  		if err := pr.Close(); err != nil {
   215  			b.Error("error closing tracefs perf event:", err)
   216  		}
   217  	}
   218  }
   219  
   220  // Test k(ret)probe creation writing directly to <tracefs>/kprobe_events.
   221  // Only runs on 5.0 and over. Earlier versions ignored writes of duplicate
   222  // events, while 5.0 started returning -EEXIST when a kprobe event already
   223  // exists.
   224  func TestKprobeCreateTraceFS(t *testing.T) {
   225  	testutils.SkipOnOldKernel(t, "5.0", "<tracefs>/kprobe_events doesn't reject duplicate events")
   226  
   227  	c := qt.New(t)
   228  
   229  	pg, _ := randomGroup("ebpftest")
   230  	rg, _ := randomGroup("ebpftest")
   231  
   232  	// Tee up cleanups in case any of the Asserts abort the function.
   233  	defer func() {
   234  		_ = closeTraceFSProbeEvent(kprobeType, pg, ksym)
   235  		_ = closeTraceFSProbeEvent(kprobeType, rg, ksym)
   236  	}()
   237  
   238  	// Prepare probe args.
   239  	args := probeArgs{group: pg, symbol: ksym}
   240  
   241  	// Create a kprobe.
   242  	err := createTraceFSProbeEvent(kprobeType, args)
   243  	c.Assert(err, qt.IsNil)
   244  
   245  	// Attempt to create an identical kprobe using tracefs,
   246  	// expect it to fail with os.ErrExist.
   247  	err = createTraceFSProbeEvent(kprobeType, args)
   248  	c.Assert(errors.Is(err, os.ErrExist), qt.IsTrue,
   249  		qt.Commentf("expected consecutive kprobe creation to contain os.ErrExist, got: %v", err))
   250  
   251  	// Expect a successful close of the kprobe.
   252  	c.Assert(closeTraceFSProbeEvent(kprobeType, pg, ksym), qt.IsNil)
   253  
   254  	args.group = rg
   255  	args.ret = true
   256  
   257  	// Same test for a kretprobe.
   258  	err = createTraceFSProbeEvent(kprobeType, args)
   259  	c.Assert(err, qt.IsNil)
   260  
   261  	err = createTraceFSProbeEvent(kprobeType, args)
   262  	c.Assert(os.IsExist(err), qt.IsFalse,
   263  		qt.Commentf("expected consecutive kretprobe creation to contain os.ErrExist, got: %v", err))
   264  
   265  	// Expect a successful close of the kretprobe.
   266  	c.Assert(closeTraceFSProbeEvent(kprobeType, rg, ksym), qt.IsNil)
   267  }
   268  
   269  func TestKprobeTraceFSGroup(t *testing.T) {
   270  	c := qt.New(t)
   271  
   272  	// Expect <prefix>_<16 random hex chars>.
   273  	g, err := randomGroup("ebpftest")
   274  	c.Assert(err, qt.IsNil)
   275  	c.Assert(g, qt.Matches, `ebpftest_[a-f0-9]{16}`)
   276  
   277  	// Expect error when the generator's output exceeds 63 characters.
   278  	p := make([]byte, 47) // 63 - 17 (length of the random suffix and underscore) + 1
   279  	for i := range p {
   280  		p[i] = byte('a')
   281  	}
   282  	_, err = randomGroup(string(p))
   283  	c.Assert(err, qt.Not(qt.IsNil))
   284  
   285  	// Reject non-alphanumeric characters.
   286  	_, err = randomGroup("/")
   287  	c.Assert(err, qt.Not(qt.IsNil))
   288  }
   289  
   290  func TestDetermineRetprobeBit(t *testing.T) {
   291  	testutils.SkipOnOldKernel(t, "4.17", "perf_kprobe PMU")
   292  	c := qt.New(t)
   293  
   294  	rpk, err := kretprobeBit()
   295  	c.Assert(err, qt.IsNil)
   296  	c.Assert(rpk, qt.Equals, uint64(0))
   297  
   298  	rpu, err := uretprobeBit()
   299  	c.Assert(err, qt.IsNil)
   300  	c.Assert(rpu, qt.Equals, uint64(0))
   301  }
   302  
   303  func TestKprobeProgramCall(t *testing.T) {
   304  	m, p := newUpdaterMapProg(t, ebpf.Kprobe)
   305  
   306  	// Open Kprobe on `sys_getpid` and attach it
   307  	// to the ebpf program created above.
   308  	k, err := Kprobe("sys_getpid", p, nil)
   309  	if err != nil {
   310  		t.Fatal(err)
   311  	}
   312  
   313  	// Trigger ebpf program call.
   314  	unix.Getpid()
   315  
   316  	// Assert that the value at index 0 has been updated to 1.
   317  	assertMapValue(t, m, 0, 1)
   318  
   319  	// Detach the Kprobe.
   320  	if err := k.Close(); err != nil {
   321  		t.Fatal(err)
   322  	}
   323  
   324  	// Reset map value to 0 at index 0.
   325  	if err := m.Update(uint32(0), uint32(0), ebpf.UpdateExist); err != nil {
   326  		t.Fatal(err)
   327  	}
   328  
   329  	// Retrigger the ebpf program call.
   330  	unix.Getpid()
   331  
   332  	// Assert that this time the value has not been updated.
   333  	assertMapValue(t, m, 0, 0)
   334  }
   335  
   336  func newUpdaterMapProg(t *testing.T, typ ebpf.ProgramType) (*ebpf.Map, *ebpf.Program) {
   337  	// Create ebpf map. Will contain only one key with initial value 0.
   338  	m, err := ebpf.NewMap(&ebpf.MapSpec{
   339  		Type:       ebpf.Array,
   340  		KeySize:    4,
   341  		ValueSize:  4,
   342  		MaxEntries: 1,
   343  	})
   344  	if err != nil {
   345  		t.Fatal(err)
   346  	}
   347  
   348  	// Create ebpf program. When called, will set the value of key 0 in
   349  	// the map created above to 1.
   350  	p, err := ebpf.NewProgram(&ebpf.ProgramSpec{
   351  		Type: typ,
   352  		Instructions: asm.Instructions{
   353  			// u32 key = 0
   354  			asm.Mov.Imm(asm.R1, 0),
   355  			asm.StoreMem(asm.RFP, -4, asm.R1, asm.Word),
   356  
   357  			// u32 val = 1
   358  			asm.Mov.Imm(asm.R1, 1),
   359  			asm.StoreMem(asm.RFP, -8, asm.R1, asm.Word),
   360  
   361  			// bpf_map_update_elem(...)
   362  			asm.Mov.Reg(asm.R2, asm.RFP),
   363  			asm.Add.Imm(asm.R2, -4),
   364  			asm.Mov.Reg(asm.R3, asm.RFP),
   365  			asm.Add.Imm(asm.R3, -8),
   366  			asm.LoadMapPtr(asm.R1, m.FD()),
   367  			asm.Mov.Imm(asm.R4, 0),
   368  			asm.FnMapUpdateElem.Call(),
   369  
   370  			// exit 0
   371  			asm.Mov.Imm(asm.R0, 0),
   372  			asm.Return(),
   373  		},
   374  		License: "Dual MIT/GPL",
   375  	})
   376  	if err != nil {
   377  		t.Fatal(err)
   378  	}
   379  
   380  	// Close the program and map on test teardown.
   381  	t.Cleanup(func() {
   382  		m.Close()
   383  		p.Close()
   384  	})
   385  
   386  	return m, p
   387  }
   388  
   389  func assertMapValue(t *testing.T, m *ebpf.Map, k, v uint32) {
   390  	var val uint32
   391  	if err := m.Lookup(k, &val); err != nil {
   392  		t.Fatal(err)
   393  	}
   394  	if val != v {
   395  		t.Fatalf("unexpected value: want '%d', got '%d'", v, val)
   396  	}
   397  }
   398  
   399  func TestKprobeCookie(t *testing.T) {
   400  	testutils.SkipOnOldKernel(t, "5.15", "bpf_perf_link")
   401  
   402  	prog := mustLoadProgram(t, ebpf.Kprobe, 0, "")
   403  	k, err := Kprobe(ksym, prog, &KprobeOptions{Cookie: 1000})
   404  	if err != nil {
   405  		t.Fatal(err)
   406  	}
   407  	k.Close()
   408  }
   409  
   410  func TestKprobeToken(t *testing.T) {
   411  	tests := []struct {
   412  		args     probeArgs
   413  		expected string
   414  	}{
   415  		{probeArgs{symbol: "symbol"}, "symbol"},
   416  		{probeArgs{symbol: "symbol", offset: 1}, "symbol+0x1"},
   417  		{probeArgs{symbol: "symbol", offset: 65535}, "symbol+0xffff"},
   418  		{probeArgs{symbol: "symbol", offset: 65536}, "symbol+0x10000"},
   419  	}
   420  
   421  	for i, tt := range tests {
   422  		t.Run(fmt.Sprint(i), func(t *testing.T) {
   423  			po := kprobeToken(tt.args)
   424  			if tt.expected != po {
   425  				t.Errorf("Expected symbol+offset to be '%s', got '%s'", tt.expected, po)
   426  			}
   427  		})
   428  	}
   429  }
   430  

View as plain text