github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/gadgets/trace/capabilities/tracer/tracer_test.go (about) 1 // Copyright 2022-2023 The Inspektor Gadget authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 //go:build linux 16 // +build linux 17 18 package tracer_test 19 20 import ( 21 "fmt" 22 "log" 23 "net" 24 "os" 25 "os/exec" 26 "sort" 27 "testing" 28 "time" 29 30 "github.com/moby/moby/pkg/parsers/kernel" 31 "golang.org/x/sys/unix" 32 33 utilstest "github.com/inspektor-gadget/inspektor-gadget/internal/test" 34 "github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets/trace/capabilities/tracer" 35 "github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets/trace/capabilities/types" 36 eventtypes "github.com/inspektor-gadget/inspektor-gadget/pkg/types" 37 ) 38 39 func TestCapabilitiesTracerCreate(t *testing.T) { 40 t.Parallel() 41 42 utilstest.RequireRoot(t) 43 44 tracer := createTracer(t, &tracer.Config{}, func(*types.Event) {}) 45 if tracer == nil { 46 t.Fatal("Returned tracer was nil") 47 } 48 } 49 50 func TestTraceCapabilitiesTracerStopIdempotent(t *testing.T) { 51 t.Parallel() 52 53 utilstest.RequireRoot(t) 54 55 tracer := createTracer(t, &tracer.Config{}, func(*types.Event) {}) 56 57 // Check that a double stop doesn't cause issues 58 tracer.Stop() 59 tracer.Stop() 60 } 61 62 func TestCapabilitiesTracer(t *testing.T) { 63 t.Parallel() 64 65 utilstest.RequireRoot(t) 66 // Needs kernel >= 5.1.0 because it introduced the InsetID field. 67 utilstest.RequireKernelVersion(t, &kernel.VersionInfo{Kernel: 5, Major: 1, Minor: 0}) 68 69 const unprivilegedUID = int(1234) 70 const unprivilegedGID = int(5678) 71 72 false_ := false 73 74 type testDefinition struct { 75 getTracerConfig func(info *utilstest.RunnerInfo) *tracer.Config 76 runnerConfig *utilstest.RunnerConfig 77 generateEvent func() error 78 validateEvent func(t *testing.T, info *utilstest.RunnerInfo, _ interface{}, events []types.Event) 79 } 80 81 for name, test := range map[string]testDefinition{ 82 "captures_all_events_with_no_filters_configured": { 83 getTracerConfig: func(info *utilstest.RunnerInfo) *tracer.Config { 84 return &tracer.Config{} 85 }, 86 generateEvent: chown, 87 validateEvent: utilstest.ExpectAtLeastOneEvent(func(info *utilstest.RunnerInfo, _ interface{}) *types.Event { 88 return &types.Event{ 89 Event: eventtypes.Event{ 90 Type: eventtypes.NORMAL, 91 Timestamp: 1, 92 }, 93 WithMountNsID: eventtypes.WithMountNsID{MountNsID: info.MountNsID}, 94 Pid: uint32(info.Pid), 95 Uid: uint32(info.Uid), 96 Comm: info.Comm, 97 Syscall: "fchownat", 98 CapName: "CHOWN", 99 Cap: 0, 100 Audit: 1, 101 InsetID: &false_, 102 Verdict: "Allow", 103 CurrentUserNs: info.UserNsID, 104 TargetUserNs: info.UserNsID, 105 Caps: 0, 106 CapsNames: []string{}, 107 } 108 }), 109 }, 110 "captures_no_events_with_no_matching_filter": { 111 getTracerConfig: func(info *utilstest.RunnerInfo) *tracer.Config { 112 return &tracer.Config{ 113 MountnsMap: utilstest.CreateMntNsFilterMap(t, 0), 114 } 115 }, 116 generateEvent: chown, 117 validateEvent: utilstest.ExpectNoEvent[types.Event, interface{}], 118 }, 119 "captures_events_with_matching_filter_CAP_CHOWN": { 120 getTracerConfig: func(info *utilstest.RunnerInfo) *tracer.Config { 121 return &tracer.Config{ 122 MountnsMap: utilstest.CreateMntNsFilterMap(t, info.MountNsID), 123 } 124 }, 125 generateEvent: chown, 126 validateEvent: utilstest.ExpectAtLeastOneEvent(func(info *utilstest.RunnerInfo, _ interface{}) *types.Event { 127 return &types.Event{ 128 Event: eventtypes.Event{ 129 Type: eventtypes.NORMAL, 130 Timestamp: 1, 131 }, 132 WithMountNsID: eventtypes.WithMountNsID{MountNsID: info.MountNsID}, 133 Pid: uint32(info.Pid), 134 Uid: uint32(info.Uid), 135 Comm: info.Comm, 136 Syscall: "fchownat", 137 CapName: "CHOWN", 138 Cap: 0, 139 Audit: 1, 140 InsetID: &false_, 141 Verdict: "Allow", 142 CurrentUserNs: info.UserNsID, 143 TargetUserNs: info.UserNsID, 144 Caps: 0, 145 CapsNames: []string{}, 146 } 147 }), 148 }, 149 "captures_events_with_matching_filter_CAP_NET_BIND_SERVICE": { 150 getTracerConfig: func(info *utilstest.RunnerInfo) *tracer.Config { 151 return &tracer.Config{ 152 MountnsMap: utilstest.CreateMntNsFilterMap(t, info.MountNsID), 153 } 154 }, 155 generateEvent: bind, 156 validateEvent: utilstest.ExpectOneEvent(func(info *utilstest.RunnerInfo, _ interface{}) *types.Event { 157 return &types.Event{ 158 Event: eventtypes.Event{ 159 Type: eventtypes.NORMAL, 160 Timestamp: 1, 161 }, 162 WithMountNsID: eventtypes.WithMountNsID{MountNsID: info.MountNsID}, 163 Pid: uint32(info.Pid), 164 Uid: uint32(info.Uid), 165 Comm: info.Comm, 166 Syscall: "bind", 167 CapName: "NET_BIND_SERVICE", 168 Cap: 10, 169 Audit: 1, 170 InsetID: &false_, 171 Verdict: "Allow", 172 CurrentUserNs: info.UserNsID, 173 TargetUserNs: info.UserNsID, 174 Caps: 0, 175 CapsNames: []string{}, 176 } 177 }), 178 }, 179 "verdict_deny": { 180 getTracerConfig: func(info *utilstest.RunnerInfo) *tracer.Config { 181 return &tracer.Config{ 182 MountnsMap: utilstest.CreateMntNsFilterMap(t, info.MountNsID), 183 } 184 }, 185 runnerConfig: &utilstest.RunnerConfig{Uid: 1245}, 186 generateEvent: func() error { 187 if err := chown(); err == nil { 188 return fmt.Errorf("chown should have failed") 189 } 190 return nil 191 }, 192 validateEvent: utilstest.ExpectAtLeastOneEvent(func(info *utilstest.RunnerInfo, _ interface{}) *types.Event { 193 return &types.Event{ 194 Event: eventtypes.Event{ 195 Type: eventtypes.NORMAL, 196 Timestamp: 1, 197 }, 198 WithMountNsID: eventtypes.WithMountNsID{MountNsID: info.MountNsID}, 199 Pid: uint32(info.Pid), 200 Uid: uint32(info.Uid), 201 Comm: info.Comm, 202 Syscall: "fchownat", 203 CapName: "CHOWN", 204 Cap: 0, 205 Audit: 1, 206 InsetID: &false_, 207 Verdict: "Deny", 208 CurrentUserNs: info.UserNsID, 209 TargetUserNs: info.UserNsID, 210 Caps: 0, 211 CapsNames: []string{}, 212 } 213 }), 214 }, 215 "audit_only_false": { 216 getTracerConfig: func(info *utilstest.RunnerInfo) *tracer.Config { 217 return &tracer.Config{ 218 MountnsMap: utilstest.CreateMntNsFilterMap(t, info.MountNsID), 219 AuditOnly: false, 220 } 221 }, 222 generateEvent: generateNonAudit, 223 validateEvent: func(t *testing.T, info *utilstest.RunnerInfo, _ interface{}, events []types.Event) { 224 for _, event := range events { 225 if event.Audit == 0 { 226 return 227 } 228 } 229 230 t.Fatal("No audit event was captured") 231 }, 232 }, 233 "audit_only_true": { 234 getTracerConfig: func(info *utilstest.RunnerInfo) *tracer.Config { 235 return &tracer.Config{ 236 MountnsMap: utilstest.CreateMntNsFilterMap(t, info.MountNsID), 237 AuditOnly: true, 238 } 239 }, 240 generateEvent: generateNonAudit, 241 validateEvent: func(t *testing.T, info *utilstest.RunnerInfo, _ interface{}, events []types.Event) { 242 for _, event := range events { 243 if event.Audit == 0 { 244 t.Fatal("No audit event was captured") 245 } 246 } 247 }, 248 }, 249 "unique_false": { 250 getTracerConfig: func(info *utilstest.RunnerInfo) *tracer.Config { 251 return &tracer.Config{ 252 MountnsMap: utilstest.CreateMntNsFilterMap(t, info.MountNsID), 253 Unique: false, 254 } 255 }, 256 generateEvent: repeatChown, 257 validateEvent: func(t *testing.T, info *utilstest.RunnerInfo, _ interface{}, events []types.Event) { 258 nfound := 0 259 for _, event := range events { 260 if event.CapName == "CHOWN" { 261 nfound++ 262 } 263 } 264 265 if nfound <= 1 { 266 t.Fatalf("Capability not found") 267 } 268 }, 269 }, 270 "unique_true": { 271 getTracerConfig: func(info *utilstest.RunnerInfo) *tracer.Config { 272 return &tracer.Config{ 273 MountnsMap: utilstest.CreateMntNsFilterMap(t, info.MountNsID), 274 Unique: true, 275 } 276 }, 277 generateEvent: repeatChown, 278 validateEvent: func(t *testing.T, info *utilstest.RunnerInfo, _ interface{}, events []types.Event) { 279 nfound := 0 280 for _, event := range events { 281 if event.CapName == "CHOWN" { 282 nfound++ 283 } 284 } 285 286 if nfound == 0 { 287 t.Fatalf("Capability not found") 288 } 289 290 if nfound > 1 { 291 t.Fatalf("Capability not unique: found %d times", nfound) 292 } 293 }, 294 }, 295 "event_has_UID_and_GID_of_user_generating_event": { 296 getTracerConfig: func(info *utilstest.RunnerInfo) *tracer.Config { 297 return &tracer.Config{ 298 MountnsMap: utilstest.CreateMntNsFilterMap(t, info.MountNsID), 299 } 300 }, 301 runnerConfig: &utilstest.RunnerConfig{ 302 Uid: unprivilegedUID, 303 Gid: unprivilegedGID, 304 }, 305 generateEvent: func() error { 306 if err := chown(); err == nil { 307 return fmt.Errorf("chown should have failed") 308 } 309 return nil 310 }, 311 validateEvent: func(t *testing.T, info *utilstest.RunnerInfo, _ interface{}, events []types.Event) { 312 if len(events) != 1 { 313 t.Fatalf("Two events expected. %d received", len(events)) 314 } 315 316 utilstest.Equal(t, uint32(info.Uid), events[0].Uid, 317 "Event has bad UID") 318 utilstest.Equal(t, uint32(info.Gid), events[0].Gid, 319 "Event has bad GID") 320 }, 321 }, 322 } { 323 test := test 324 325 t.Run(name, func(t *testing.T) { 326 t.Parallel() 327 328 events := []types.Event{} 329 eventCallback := func(event *types.Event) { 330 // normalize 331 if event.Timestamp != 0 { 332 event.Timestamp = 1 333 } 334 event.Caps = 0 335 event.CapsNames = []string{} 336 337 events = append(events, *event) 338 } 339 340 runner := utilstest.NewRunnerWithTest(t, test.runnerConfig) 341 342 createTracer(t, test.getTracerConfig(runner.Info), eventCallback) 343 344 utilstest.RunWithRunner(t, runner, test.generateEvent) 345 346 // Give some time for the tracer to capture the events 347 time.Sleep(100 * time.Millisecond) 348 349 test.validateEvent(t, runner.Info, 0, events) 350 }) 351 } 352 } 353 354 func TestCapabilitiesTracerMultipleMntNsIDsFilter(t *testing.T) { 355 t.Parallel() 356 357 utilstest.RequireRoot(t) 358 359 events := []types.Event{} 360 eventCallback := func(event *types.Event) { 361 // normalize 362 event.Timestamp = 0 363 364 events = append(events, *event) 365 } 366 367 // struct with only fields we want to check on this test 368 type expectedEvent struct { 369 mntNsID uint64 370 } 371 372 const n int = 5 373 runners := make([]*utilstest.Runner, n) 374 expectedEvents := make([]expectedEvent, n) 375 mntNsIDs := make([]uint64, n) 376 377 for i := 0; i < n; i++ { 378 runners[i] = utilstest.NewRunnerWithTest(t, nil) 379 mntNsIDs[i] = runners[i].Info.MountNsID 380 expectedEvents[i].mntNsID = runners[i].Info.MountNsID 381 } 382 383 // Filter events from all runners but last one 384 config := &tracer.Config{ 385 MountnsMap: utilstest.CreateMntNsFilterMap(t, mntNsIDs[:n-1]...), 386 AuditOnly: true, 387 } 388 389 createTracer(t, config, eventCallback) 390 391 for i := 0; i < n; i++ { 392 utilstest.RunWithRunner(t, runners[i], bind) 393 } 394 395 // Give some time for the tracer to capture the events 396 time.Sleep(100 * time.Millisecond) 397 398 if len(events) != n-1 { 399 t.Fatalf("%d events were expected, %d found", n-1, len(events)) 400 } 401 402 // Pop last event since it shouldn't have been captured 403 expectedEvents = expectedEvents[:n-1] 404 405 // Order of events is not guaranteed, then we need to sort before comparing 406 sort.Slice(expectedEvents, func(i, j int) bool { 407 return expectedEvents[i].mntNsID < expectedEvents[j].mntNsID 408 }) 409 sort.Slice(events, func(i, j int) bool { 410 return events[i].WithMountNsID.MountNsID < events[j].WithMountNsID.MountNsID 411 }) 412 413 for i := 0; i < n-1; i++ { 414 utilstest.Equal(t, expectedEvents[i].mntNsID, events[i].WithMountNsID.MountNsID, 415 "Captured event has bad MountNsID") 416 417 utilstest.Equal(t, "NET_BIND_SERVICE", events[i].CapName, 418 "Captured event has bad CapName") 419 } 420 } 421 422 func createTracer( 423 t *testing.T, config *tracer.Config, callback func(*types.Event), 424 ) *tracer.Tracer { 425 t.Helper() 426 427 tracer, err := tracer.NewTracer(config, nil, callback) 428 if err != nil { 429 t.Fatalf("Error creating tracer: %s", err) 430 } 431 t.Cleanup(tracer.Stop) 432 433 return tracer 434 } 435 436 func generateNonAudit() error { 437 // This command will generate some non-audit capabilities checks 438 // https://github.com/torvalds/linux/blob/84368d882b9688bfac77ce48d33b1e20a4e4a787/kernel/kallsyms.c#L899 439 // If TestCapabilitiesTracer/audit_only_false fails we should 440 // check that this command is actually generating those checks 441 cmd := exec.Command("/bin/cat", "/proc/kallsyms") 442 if err := cmd.Run(); err != nil { 443 return fmt.Errorf("running command: %w", err) 444 } 445 446 return nil 447 } 448 449 func repeatChown() error { 450 for i := 0; i < 5; i++ { 451 if err := chown(); err != nil { 452 return err 453 } 454 } 455 456 return nil 457 } 458 459 // chown requires CAP_CHOWN 460 func chown() error { 461 file, err := os.CreateTemp("/tmp", "prefix") 462 if err != nil { 463 log.Fatal(err) 464 } 465 defer os.Remove(file.Name()) 466 467 return unix.Chown(file.Name(), 1000, 1000) 468 } 469 470 // bind requires CAP_NET_BIND_SERVICE as it binds to a port less than 1024 471 func bind() error { 472 ipStr := "127.0.0.1" 473 domain := unix.AF_INET 474 typ := unix.SOCK_STREAM 475 port := 555 476 477 fd, err := unix.Socket(domain, typ, 0) 478 if err != nil { 479 return err 480 } 481 defer unix.Close(fd) 482 483 var sa unix.Sockaddr 484 485 ip := net.ParseIP(ipStr) 486 487 if ip.To4() != nil { 488 sa4 := &unix.SockaddrInet4{Port: port} 489 copy(sa4.Addr[:], ip.To4()) 490 sa = sa4 491 } else if ip.To16() != nil { 492 sa6 := &unix.SockaddrInet6{Port: port} 493 copy(sa6.Addr[:], ip.To16()) 494 sa = sa6 495 } else { 496 return fmt.Errorf("invalid IP address") 497 } 498 499 if err := unix.Bind(fd, sa); err != nil { 500 return fmt.Errorf("Bind: %w", err) 501 } 502 503 return nil 504 }