github.com/kubeshark/ebpf@v0.9.2/link/uprobe_test.go (about) 1 package link 2 3 import ( 4 "errors" 5 "fmt" 6 "go/build" 7 "os" 8 "os/exec" 9 "path" 10 "testing" 11 12 qt "github.com/frankban/quicktest" 13 14 "github.com/kubeshark/ebpf" 15 "github.com/kubeshark/ebpf/internal/testutils" 16 "github.com/kubeshark/ebpf/internal/unix" 17 ) 18 19 var ( 20 bashEx, _ = OpenExecutable("/bin/bash") 21 bashSym = "main" 22 ) 23 24 func TestExecutable(t *testing.T) { 25 _, err := OpenExecutable("") 26 if err == nil { 27 t.Fatal("create executable: expected error on empty path") 28 } 29 30 if bashEx.path != "/bin/bash" { 31 t.Fatalf("create executable: unexpected path '%s'", bashEx.path) 32 } 33 34 _, err = bashEx.offset(bashSym) 35 if err != nil { 36 t.Fatalf("find offset: %v", err) 37 } 38 39 _, err = bashEx.offset("bogus") 40 if err == nil { 41 t.Fatal("find symbol: expected error") 42 } 43 } 44 45 func TestOffsetWithOpts(t *testing.T) { 46 c := qt.New(t) 47 48 _ = mustLoadProgram(t, ebpf.Kprobe, 0, "") 49 50 symbolOffset, err := bashEx.offset(bashSym) 51 if err != nil { 52 t.Fatal(err) 53 } 54 55 offset, err := bashEx.offsetWithOpts(bashSym, nil) 56 if err != nil { 57 t.Fatal(err) 58 } 59 c.Assert(offset, qt.Equals, uint64(symbolOffset)) 60 61 offset, err = bashEx.offsetWithOpts(bashSym, &UprobeOptions{}) 62 if err != nil { 63 t.Fatal(err) 64 } 65 c.Assert(offset, qt.Equals, uint64(symbolOffset)) 66 67 offset, err = bashEx.offsetWithOpts(bashSym, &UprobeOptions{Offset: 0x1}) 68 if err != nil { 69 t.Fatal(err) 70 } 71 c.Assert(offset, qt.Equals, uint64(0x1)) 72 73 offset, err = bashEx.offsetWithOpts(bashSym, &UprobeOptions{RelativeOffset: 0x2}) 74 if err != nil { 75 t.Fatal(err) 76 } 77 c.Assert(offset, qt.Equals, uint64(symbolOffset+0x2)) 78 79 offset, err = bashEx.offsetWithOpts(bashSym, &UprobeOptions{Offset: 0x1, RelativeOffset: 0x2}) 80 if err != nil { 81 t.Fatal(err) 82 } 83 c.Assert(offset, qt.Equals, uint64(0x1)) 84 } 85 86 func TestUprobe(t *testing.T) { 87 c := qt.New(t) 88 89 prog := mustLoadProgram(t, ebpf.Kprobe, 0, "") 90 91 up, err := bashEx.Uprobe(bashSym, prog, nil) 92 c.Assert(err, qt.IsNil) 93 defer up.Close() 94 95 testLink(t, up, prog) 96 } 97 98 func TestUprobeExtNotFound(t *testing.T) { 99 prog := mustLoadProgram(t, ebpf.Kprobe, 0, "") 100 101 // This symbol will not be present in Executable (elf.SHN_UNDEF). 102 _, err := bashEx.Uprobe("open", prog, nil) 103 if err == nil { 104 t.Fatal("expected error") 105 } 106 } 107 108 func TestUprobeExtWithOpts(t *testing.T) { 109 prog := mustLoadProgram(t, ebpf.Kprobe, 0, "") 110 111 // This Uprobe is broken and will not work because the offset is not 112 // correct. This is expected since the offset is provided by the user. 113 up, err := bashEx.Uprobe("open", prog, &UprobeOptions{Offset: 0x1}) 114 if err != nil { 115 t.Fatal(err) 116 } 117 defer up.Close() 118 } 119 120 func TestUprobeWithPID(t *testing.T) { 121 prog := mustLoadProgram(t, ebpf.Kprobe, 0, "") 122 123 up, err := bashEx.Uprobe(bashSym, prog, &UprobeOptions{PID: os.Getpid()}) 124 if err != nil { 125 t.Fatal(err) 126 } 127 defer up.Close() 128 } 129 130 func TestUprobeWithNonExistentPID(t *testing.T) { 131 prog := mustLoadProgram(t, ebpf.Kprobe, 0, "") 132 133 // trying to open a perf event on a non-existent PID will return ESRCH. 134 _, err := bashEx.Uprobe(bashSym, prog, &UprobeOptions{PID: -2}) 135 if !errors.Is(err, unix.ESRCH) { 136 t.Fatalf("expected ESRCH, got %v", err) 137 } 138 } 139 140 func TestUretprobe(t *testing.T) { 141 c := qt.New(t) 142 143 prog := mustLoadProgram(t, ebpf.Kprobe, 0, "") 144 145 up, err := bashEx.Uretprobe(bashSym, prog, nil) 146 c.Assert(err, qt.IsNil) 147 defer up.Close() 148 149 testLink(t, up, prog) 150 } 151 152 // Test u(ret)probe creation using perf_uprobe PMU. 153 func TestUprobeCreatePMU(t *testing.T) { 154 // Requires at least 4.17 (e12f03d7031a "perf/core: Implement the 'perf_kprobe' PMU") 155 testutils.SkipOnOldKernel(t, "4.17", "perf_kprobe PMU") 156 157 c := qt.New(t) 158 159 // Fetch the offset from the /bin/bash Executable already defined. 160 off, err := bashEx.offset(bashSym) 161 c.Assert(err, qt.IsNil) 162 163 // Prepare probe args. 164 args := probeArgs{ 165 symbol: bashSym, 166 path: bashEx.path, 167 offset: off, 168 pid: perfAllThreads, 169 } 170 171 // uprobe PMU 172 pu, err := pmuUprobe(args) 173 c.Assert(err, qt.IsNil) 174 defer pu.Close() 175 176 c.Assert(pu.typ, qt.Equals, uprobeEvent) 177 178 // uretprobe PMU 179 args.ret = true 180 pr, err := pmuUprobe(args) 181 c.Assert(err, qt.IsNil) 182 defer pr.Close() 183 184 c.Assert(pr.typ, qt.Equals, uretprobeEvent) 185 } 186 187 // Test fallback behaviour on kernels without perf_uprobe PMU available. 188 func TestUprobePMUUnavailable(t *testing.T) { 189 c := qt.New(t) 190 191 // Fetch the offset from the /bin/bash Executable already defined. 192 off, err := bashEx.offset(bashSym) 193 c.Assert(err, qt.IsNil) 194 195 // Prepare probe args. 196 args := probeArgs{ 197 symbol: bashSym, 198 path: bashEx.path, 199 offset: off, 200 pid: perfAllThreads, 201 } 202 203 pk, err := pmuUprobe(args) 204 if err == nil { 205 pk.Close() 206 t.Skipf("Kernel supports perf_uprobe PMU, not asserting error.") 207 } 208 209 // Expect ErrNotSupported. 210 c.Assert(errors.Is(err, ErrNotSupported), qt.IsTrue, qt.Commentf("got error: %s", err)) 211 } 212 213 // Test tracefs u(ret)probe creation on all kernel versions. 214 func TestUprobeTraceFS(t *testing.T) { 215 c := qt.New(t) 216 217 // Fetch the offset from the /bin/bash Executable already defined. 218 off, err := bashEx.offset(bashSym) 219 c.Assert(err, qt.IsNil) 220 221 // Prepare probe args. 222 args := probeArgs{ 223 symbol: sanitizeSymbol(bashSym), 224 path: bashEx.path, 225 offset: off, 226 pid: perfAllThreads, 227 } 228 229 // Open and close tracefs u(ret)probes, checking all errors. 230 up, err := tracefsUprobe(args) 231 c.Assert(err, qt.IsNil) 232 c.Assert(up.Close(), qt.IsNil) 233 c.Assert(up.typ, qt.Equals, uprobeEvent) 234 235 args.ret = true 236 up, err = tracefsUprobe(args) 237 c.Assert(err, qt.IsNil) 238 c.Assert(up.Close(), qt.IsNil) 239 c.Assert(up.typ, qt.Equals, uretprobeEvent) 240 241 // Create two identical trace events, ensure their IDs differ. 242 args.ret = false 243 u1, err := tracefsUprobe(args) 244 c.Assert(err, qt.IsNil) 245 defer u1.Close() 246 c.Assert(u1.tracefsID, qt.Not(qt.Equals), 0) 247 248 u2, err := tracefsUprobe(args) 249 c.Assert(err, qt.IsNil) 250 defer u2.Close() 251 c.Assert(u2.tracefsID, qt.Not(qt.Equals), 0) 252 253 // Compare the uprobes' tracefs IDs. 254 c.Assert(u1.tracefsID, qt.Not(qt.CmpEquals()), u2.tracefsID) 255 } 256 257 // Test u(ret)probe creation writing directly to <tracefs>/uprobe_events. 258 // Only runs on 5.0 and over. Earlier versions ignored writes of duplicate 259 // events, while 5.0 started returning -EEXIST when a uprobe event already 260 // exists. 261 func TestUprobeCreateTraceFS(t *testing.T) { 262 testutils.SkipOnOldKernel(t, "5.0", "<tracefs>/uprobe_events doesn't reject duplicate events") 263 264 c := qt.New(t) 265 266 // Fetch the offset from the /bin/bash Executable already defined. 267 off, err := bashEx.offset(bashSym) 268 c.Assert(err, qt.IsNil) 269 270 // Sanitize the symbol in order to be used in tracefs API. 271 ssym := sanitizeSymbol(bashSym) 272 273 pg, _ := randomGroup("ebpftest") 274 rg, _ := randomGroup("ebpftest") 275 276 // Tee up cleanups in case any of the Asserts abort the function. 277 defer func() { 278 _ = closeTraceFSProbeEvent(uprobeType, pg, ssym) 279 _ = closeTraceFSProbeEvent(uprobeType, rg, ssym) 280 }() 281 282 // Prepare probe args. 283 args := probeArgs{ 284 group: pg, 285 symbol: ssym, 286 path: bashEx.path, 287 offset: off, 288 } 289 290 // Create a uprobe. 291 err = createTraceFSProbeEvent(uprobeType, args) 292 c.Assert(err, qt.IsNil) 293 294 // Attempt to create an identical uprobe using tracefs, 295 // expect it to fail with os.ErrExist. 296 err = createTraceFSProbeEvent(uprobeType, args) 297 c.Assert(errors.Is(err, os.ErrExist), qt.IsTrue, 298 qt.Commentf("expected consecutive uprobe creation to contain os.ErrExist, got: %v", err)) 299 300 // Expect a successful close of the kprobe. 301 c.Assert(closeTraceFSProbeEvent(uprobeType, pg, ssym), qt.IsNil) 302 303 args.group = rg 304 args.ret = true 305 306 // Same test for a kretprobe. 307 err = createTraceFSProbeEvent(uprobeType, args) 308 c.Assert(err, qt.IsNil) 309 310 err = createTraceFSProbeEvent(uprobeType, args) 311 c.Assert(os.IsExist(err), qt.IsFalse, 312 qt.Commentf("expected consecutive uretprobe creation to contain os.ErrExist, got: %v", err)) 313 314 // Expect a successful close of the uretprobe. 315 c.Assert(closeTraceFSProbeEvent(uprobeType, rg, ssym), qt.IsNil) 316 } 317 318 func TestUprobeSanitizedSymbol(t *testing.T) { 319 tests := []struct { 320 symbol string 321 expected string 322 }{ 323 {"readline", "readline"}, 324 {"main.Func123", "main_Func123"}, 325 {"a.....a", "a_a"}, 326 {"./;'{}[]a", "_a"}, 327 {"***xx**xx###", "_xx_xx_"}, 328 {`@P#r$i%v^3*+t)i&k++--`, "_P_r_i_v_3_t_i_k_"}, 329 } 330 331 for i, tt := range tests { 332 t.Run(fmt.Sprint(i), func(t *testing.T) { 333 sanitized := sanitizeSymbol(tt.symbol) 334 if tt.expected != sanitized { 335 t.Errorf("Expected sanitized symbol to be '%s', got '%s'", tt.expected, sanitized) 336 } 337 }) 338 } 339 } 340 341 func TestUprobeToken(t *testing.T) { 342 tests := []struct { 343 args probeArgs 344 expected string 345 }{ 346 {probeArgs{path: "/bin/bash"}, "/bin/bash:0x0"}, 347 {probeArgs{path: "/bin/bash", offset: 1}, "/bin/bash:0x1"}, 348 {probeArgs{path: "/bin/bash", offset: 65535}, "/bin/bash:0xffff"}, 349 {probeArgs{path: "/bin/bash", offset: 65536}, "/bin/bash:0x10000"}, 350 {probeArgs{path: "/bin/bash", offset: 1, refCtrOffset: 1}, "/bin/bash:0x1(0x1)"}, 351 {probeArgs{path: "/bin/bash", offset: 1, refCtrOffset: 65535}, "/bin/bash:0x1(0xffff)"}, 352 } 353 354 for i, tt := range tests { 355 t.Run(fmt.Sprint(i), func(t *testing.T) { 356 po := uprobeToken(tt.args) 357 if tt.expected != po { 358 t.Errorf("Expected path:offset to be '%s', got '%s'", tt.expected, po) 359 } 360 }) 361 } 362 } 363 364 func TestUprobeProgramCall(t *testing.T) { 365 tests := []struct { 366 name string 367 elf string 368 args []string 369 sym string 370 }{ 371 { 372 "bash", 373 "/bin/bash", 374 []string{"--help"}, 375 "main", 376 }, 377 { 378 "go-binary", 379 path.Join(build.Default.GOROOT, "bin/go"), 380 []string{"version"}, 381 "main.main", 382 }, 383 } 384 385 for _, tt := range tests { 386 t.Run(tt.name, func(t *testing.T) { 387 if tt.name == "go-binary" { 388 // https://github.com/kubeshark/ebpf/issues/406 389 testutils.SkipOnOldKernel(t, "4.14", "uprobes on Go binaries silently fail on kernel < 4.14") 390 } 391 392 m, p := newUpdaterMapProg(t, ebpf.Kprobe) 393 394 // Load the executable. 395 ex, err := OpenExecutable(tt.elf) 396 if err != nil { 397 t.Fatal(err) 398 } 399 400 // Open Uprobe on the executable for the given symbol 401 // and attach it to the ebpf program created above. 402 u, err := ex.Uprobe(tt.sym, p, nil) 403 if errors.Is(err, ErrNoSymbol) { 404 // Assume bash::main and go::main.main always exists 405 // and skip the test if the symbol can't be found as 406 // certain OS (eg. Debian) strip binaries. 407 t.Skipf("executable %s appear to be stripped, skipping", tt.elf) 408 } 409 if err != nil { 410 t.Fatal(err) 411 } 412 413 // Trigger ebpf program call. 414 trigger := func(t *testing.T) { 415 if err := exec.Command(tt.elf, tt.args...).Run(); err != nil { 416 t.Fatal(err) 417 } 418 } 419 trigger(t) 420 421 // Assert that the value at index 0 has been updated to 1. 422 assertMapValue(t, m, 0, 1) 423 424 // Detach the Uprobe. 425 if err := u.Close(); err != nil { 426 t.Fatal(err) 427 } 428 429 // Reset map value to 0 at index 0. 430 if err := m.Update(uint32(0), uint32(0), ebpf.UpdateExist); err != nil { 431 t.Fatal(err) 432 } 433 434 // Retrigger the ebpf program call. 435 trigger(t) 436 437 // Assert that this time the value has not been updated. 438 assertMapValue(t, m, 0, 0) 439 }) 440 } 441 } 442 443 func TestUprobeProgramWrongPID(t *testing.T) { 444 m, p := newUpdaterMapProg(t, ebpf.Kprobe) 445 446 // Load the '/bin/bash' executable. 447 ex, err := OpenExecutable("/bin/bash") 448 if err != nil { 449 t.Fatal(err) 450 } 451 452 // Open Uprobe on '/bin/bash' for the symbol 'main' 453 // and attach it to the ebpf program created above. 454 // Create the perf-event with the current process' PID 455 // to make sure the event is not fired when we will try 456 // to trigger the program execution via exec. 457 u, err := ex.Uprobe("main", p, &UprobeOptions{PID: os.Getpid()}) 458 if err != nil { 459 t.Fatal(err) 460 } 461 defer u.Close() 462 463 // Trigger ebpf program call. 464 if err := exec.Command("/bin/bash", "--help").Run(); err != nil { 465 t.Fatal(err) 466 } 467 468 // Assert that the value at index 0 is still 0. 469 assertMapValue(t, m, 0, 0) 470 } 471 472 func TestHaveRefCtrOffsetPMU(t *testing.T) { 473 testutils.CheckFeatureTest(t, haveRefCtrOffsetPMU) 474 }