github.com/kubeshark/ebpf@v0.9.2/link/kprobe_test.go (about) 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/kubeshark/ebpf" 12 "github.com/kubeshark/ebpf/asm" 13 "github.com/kubeshark/ebpf/internal/testutils" 14 "github.com/kubeshark/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 }