github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/gadgets/trace/exec/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 "os" 23 "os/exec" 24 "path" 25 "sort" 26 "strings" 27 "testing" 28 "time" 29 30 "github.com/stretchr/testify/require" 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/exec/tracer" 35 "github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets/trace/exec/types" 36 eventtypes "github.com/inspektor-gadget/inspektor-gadget/pkg/types" 37 ) 38 39 func TestExecTracerCreate(t *testing.T) { 40 t.Parallel() 41 42 utilstest.RequireRoot(t) 43 44 tracer := createTracer(t, &tracer.Config{}, func(*types.Event) {}) 45 require.NotNil(t, tracer, "Returned tracer was nil") 46 } 47 48 func TestExecTracerStopIdempotent(t *testing.T) { 49 t.Parallel() 50 51 utilstest.RequireRoot(t) 52 53 tracer := createTracer(t, &tracer.Config{}, func(*types.Event) {}) 54 55 // Check that a double stop doesn't cause issues 56 tracer.Stop() 57 tracer.Stop() 58 } 59 60 func TestExecTracer(t *testing.T) { 61 t.Parallel() 62 63 utilstest.RequireRoot(t) 64 65 const unprivilegedUID = int(1435) 66 const unprivilegedGID = int(6789) 67 68 manyArgs := []string{} 69 // 19 is DEFAULT_MAXARGS - 1 (-1 because args[0] is on the first position). 70 for i := 0; i < 19; i++ { 71 manyArgs = append(manyArgs, "/dev/null") 72 } 73 74 cwd, err := os.Getwd() 75 require.Nil(t, err, "Failed to get current working directory: %s", err) 76 77 executable, err := os.Executable() 78 require.Nil(t, err, "Failed to get executable path: %s", err) 79 pcomm := path.Base(executable) 80 81 type testDefinition struct { 82 shouldSkip func(t *testing.T) 83 getTracerConfig func(info *utilstest.RunnerInfo) *tracer.Config 84 runnerConfig *utilstest.RunnerConfig 85 generateEvent func() (int, error) 86 validateEvent func(t *testing.T, info *utilstest.RunnerInfo, catPid int, events []types.Event) 87 } 88 89 loginuid := utilstest.ReadFileAsUint32(t, "/proc/self/loginuid") 90 sessionid := utilstest.ReadFileAsUint32(t, "/proc/self/sessionid") 91 92 for name, test := range map[string]testDefinition{ 93 "captures_all_events_with_no_filters_configured": { 94 getTracerConfig: func(info *utilstest.RunnerInfo) *tracer.Config { 95 return &tracer.Config{} 96 }, 97 generateEvent: generateEvent, 98 validateEvent: utilstest.ExpectAtLeastOneEvent(func(info *utilstest.RunnerInfo, catPid int) *types.Event { 99 return &types.Event{ 100 Event: eventtypes.Event{ 101 Type: eventtypes.NORMAL, 102 }, 103 Pid: uint32(catPid), 104 Ppid: uint32(info.Pid), 105 Uid: uint32(info.Uid), 106 LoginUid: loginuid, 107 SessionId: sessionid, 108 WithMountNsID: eventtypes.WithMountNsID{MountNsID: info.MountNsID}, 109 Retval: 0, 110 Comm: "cat", 111 Pcomm: pcomm, 112 Args: []string{"/bin/cat", "/dev/null"}, 113 } 114 }), 115 }, 116 "captures_no_events_with_no_matching_filter": { 117 getTracerConfig: func(info *utilstest.RunnerInfo) *tracer.Config { 118 return &tracer.Config{ 119 MountnsMap: utilstest.CreateMntNsFilterMap(t, 0), 120 } 121 }, 122 generateEvent: generateEvent, 123 validateEvent: utilstest.ExpectNoEvent[types.Event, int], 124 }, 125 "captures_events_with_matching_filter": { 126 getTracerConfig: func(info *utilstest.RunnerInfo) *tracer.Config { 127 return &tracer.Config{ 128 MountnsMap: utilstest.CreateMntNsFilterMap(t, info.MountNsID), 129 } 130 }, 131 generateEvent: generateEvent, 132 validateEvent: utilstest.ExpectOneEvent(func(info *utilstest.RunnerInfo, catPid int) *types.Event { 133 return &types.Event{ 134 Event: eventtypes.Event{ 135 Type: eventtypes.NORMAL, 136 }, 137 Pid: uint32(catPid), 138 Ppid: uint32(info.Pid), 139 Uid: uint32(info.Uid), 140 LoginUid: loginuid, 141 SessionId: sessionid, 142 WithMountNsID: eventtypes.WithMountNsID{MountNsID: info.MountNsID}, 143 Retval: 0, 144 Comm: "cat", 145 Pcomm: pcomm, 146 Args: []string{"/bin/cat", "/dev/null"}, 147 } 148 }), 149 }, 150 "event_has_UID_and_GID_of_user_generating_event": { 151 getTracerConfig: func(info *utilstest.RunnerInfo) *tracer.Config { 152 return &tracer.Config{ 153 MountnsMap: utilstest.CreateMntNsFilterMap(t, info.MountNsID), 154 } 155 }, 156 runnerConfig: &utilstest.RunnerConfig{ 157 Uid: unprivilegedUID, 158 Gid: unprivilegedGID, 159 }, 160 generateEvent: generateEvent, 161 validateEvent: func(t *testing.T, info *utilstest.RunnerInfo, _ int, events []types.Event) { 162 require.Len(t, events, 1, "One event expected") 163 require.Equal(t, uint32(info.Uid), events[0].Uid, "Event has bad UID") 164 require.Equal(t, uint32(info.Gid), events[0].Gid, "Event has bad GID") 165 }, 166 }, 167 "truncates_captured_args_in_trace_to_maximum_possible_length": { 168 getTracerConfig: func(info *utilstest.RunnerInfo) *tracer.Config { 169 return &tracer.Config{ 170 MountnsMap: utilstest.CreateMntNsFilterMap(t, info.MountNsID), 171 } 172 }, 173 generateEvent: func() (int, error) { 174 args := append(manyArgs, "/dev/null") 175 cmd := exec.Command("/bin/cat", args...) 176 if err := cmd.Run(); err != nil { 177 return 0, fmt.Errorf("running command: %w", err) 178 } 179 180 return cmd.Process.Pid, nil 181 }, 182 validateEvent: func(t *testing.T, info *utilstest.RunnerInfo, _ int, events []types.Event) { 183 require.Len(t, events, 1, "One event expected") 184 require.Equal(t, append([]string{"/bin/cat"}, manyArgs...), events[0].Args, "Event has bad args") 185 }, 186 }, 187 "event_has_correct_paths": { 188 getTracerConfig: func(info *utilstest.RunnerInfo) *tracer.Config { 189 return &tracer.Config{ 190 MountnsMap: utilstest.CreateMntNsFilterMap(t, info.MountNsID), 191 GetPaths: true, 192 } 193 }, 194 generateEvent: func() (int, error) { 195 args := append(manyArgs, "/dev/null") 196 cmd := exec.Command("/bin/cat", args...) 197 if err := cmd.Run(); err != nil { 198 return 0, fmt.Errorf("running command: %w", err) 199 } 200 201 return cmd.Process.Pid, nil 202 }, 203 validateEvent: func(t *testing.T, info *utilstest.RunnerInfo, _ int, events []types.Event) { 204 require.Len(t, events, 1, "One event expected") 205 require.Equal(t, events[0].Cwd, cwd, "Event has bad cwd") 206 // Depending on the Linux distribution, /bin can be a symlink to /usr/bin 207 exepath := strings.TrimPrefix(events[0].ExePath, "/usr") 208 require.Equal(t, exepath, "/bin/cat", "Event has bad exe path") 209 }, 210 }, 211 "event_failed": { 212 getTracerConfig: func(info *utilstest.RunnerInfo) *tracer.Config { 213 return &tracer.Config{ 214 MountnsMap: utilstest.CreateMntNsFilterMap(t, info.MountNsID), 215 } 216 }, 217 generateEvent: generateFailedEvent, 218 validateEvent: func(t *testing.T, info *utilstest.RunnerInfo, _ int, events []types.Event) { 219 require.Len(t, events, 1, "One event expected") 220 require.Equal(t, []string{"/bin/foobar"}, events[0].Args, "Event has bad args") 221 require.NotEqual(t, int(0), events[0].Retval, "Event returns 0, while it should return an error code") 222 }, 223 }, 224 "event_failed_ignored": { 225 getTracerConfig: func(info *utilstest.RunnerInfo) *tracer.Config { 226 return &tracer.Config{ 227 MountnsMap: utilstest.CreateMntNsFilterMap(t, info.MountNsID), 228 IgnoreErrors: true, 229 } 230 }, 231 generateEvent: generateFailedEvent, 232 validateEvent: func(t *testing.T, info *utilstest.RunnerInfo, _ int, events []types.Event) { 233 require.Len(t, events, 0, "Zero events expected") 234 }, 235 }, 236 "event_from_non_main_thread_success": { 237 shouldSkip: func(t *testing.T) { 238 if _, err := exec.LookPath("python3"); err != nil { 239 t.Skip("Python3 not found") 240 } 241 }, 242 getTracerConfig: func(info *utilstest.RunnerInfo) *tracer.Config { 243 return &tracer.Config{ 244 MountnsMap: utilstest.CreateMntNsFilterMap(t, info.MountNsID), 245 } 246 }, 247 runnerConfig: &utilstest.RunnerConfig{ 248 Uid: unprivilegedUID, 249 Gid: unprivilegedGID, 250 }, 251 generateEvent: generateEventFromThread(true), 252 validateEvent: func(t *testing.T, info *utilstest.RunnerInfo, _ int, events []types.Event) { 253 // python + cat 254 require.Len(t, events, 2, "Two events expected") 255 require.Equal(t, "python3", events[0].Comm, "Event has bad comm") 256 require.Equal(t, 0, events[0].Retval, "Event has bad retval") 257 require.Equal(t, "cat", events[1].Comm, "Event has bad comm") 258 require.Equal(t, 0, events[1].Retval, "Event has bad retval") 259 }, 260 }, 261 "event_from_non_main_thread_fail": { 262 shouldSkip: func(t *testing.T) { 263 if _, err := exec.LookPath("python3"); err != nil { 264 t.Skip("Python3 not found") 265 } 266 }, 267 getTracerConfig: func(info *utilstest.RunnerInfo) *tracer.Config { 268 return &tracer.Config{ 269 MountnsMap: utilstest.CreateMntNsFilterMap(t, info.MountNsID), 270 } 271 }, 272 runnerConfig: &utilstest.RunnerConfig{ 273 Uid: unprivilegedUID, 274 Gid: unprivilegedGID, 275 }, 276 generateEvent: generateEventFromThread(false), 277 validateEvent: func(t *testing.T, info *utilstest.RunnerInfo, _ int, events []types.Event) { 278 // python + cat 279 require.Len(t, events, 2, "Two events expected") 280 require.Equal(t, "python3", events[0].Comm, "Event has bad comm") 281 require.Equal(t, 0, events[0].Retval, "Event has bad retval") 282 require.Equal(t, "python3", events[1].Comm, "Event has bad comm") 283 require.Equal(t, -int(unix.ENOENT), events[1].Retval, "Event has bad retval") 284 }, 285 }, 286 } { 287 test := test 288 289 t.Run(name, func(t *testing.T) { 290 t.Parallel() 291 292 if test.shouldSkip != nil { 293 test.shouldSkip(t) 294 } 295 296 events := []types.Event{} 297 eventCallback := func(event *types.Event) { 298 // normalize 299 event.Timestamp = 0 300 301 events = append(events, *event) 302 } 303 304 runner := utilstest.NewRunnerWithTest(t, test.runnerConfig) 305 306 createTracer(t, test.getTracerConfig(runner.Info), eventCallback) 307 308 var catPid int 309 310 utilstest.RunWithRunner(t, runner, func() error { 311 var err error 312 catPid, err = test.generateEvent() 313 return err 314 }) 315 316 // Give some time for the tracer to capture the events 317 time.Sleep(100 * time.Millisecond) 318 319 test.validateEvent(t, runner.Info, catPid, events) 320 }) 321 } 322 } 323 324 func TestExecTracerMultipleMntNsIDsFilter(t *testing.T) { 325 t.Parallel() 326 327 utilstest.RequireRoot(t) 328 329 events := []types.Event{} 330 eventCallback := func(event *types.Event) { 331 // normalize 332 event.Timestamp = 0 333 334 events = append(events, *event) 335 } 336 337 // struct with only fields we want to check on this test 338 type expectedEvent struct { 339 mntNsID uint64 340 catPid int 341 } 342 343 const n int = 5 344 runners := make([]*utilstest.Runner, n) 345 expectedEvents := make([]expectedEvent, n) 346 mntNsIDs := make([]uint64, n) 347 348 for i := 0; i < n; i++ { 349 runners[i] = utilstest.NewRunnerWithTest(t, nil) 350 mntNsIDs[i] = runners[i].Info.MountNsID 351 expectedEvents[i].mntNsID = runners[i].Info.MountNsID 352 } 353 354 // Filter events from all runners but last one 355 config := &tracer.Config{ 356 MountnsMap: utilstest.CreateMntNsFilterMap(t, mntNsIDs[:n-1]...), 357 } 358 359 createTracer(t, config, eventCallback) 360 361 for i := 0; i < n; i++ { 362 utilstest.RunWithRunner(t, runners[i], func() error { 363 var err error 364 expectedEvents[i].catPid, err = generateEvent() 365 return err 366 }) 367 } 368 369 // Give some time for the tracer to capture the events 370 time.Sleep(100 * time.Millisecond) 371 372 require.Len(t, events, n-1) 373 374 // Pop last event since it shouldn't have been captured 375 expectedEvents = expectedEvents[:n-1] 376 377 // Order or events is not guaranteed, then we need to sort before comparing 378 sort.Slice(expectedEvents, func(i, j int) bool { 379 return expectedEvents[i].mntNsID < expectedEvents[j].mntNsID 380 }) 381 sort.Slice(events, func(i, j int) bool { 382 return events[i].WithMountNsID.MountNsID < events[j].WithMountNsID.MountNsID 383 }) 384 385 for i := 0; i < n-1; i++ { 386 require.Equal(t, expectedEvents[i].mntNsID, events[i].WithMountNsID.MountNsID, 387 "Captured event has bad MountNsID") 388 389 require.Equal(t, uint32(expectedEvents[i].catPid), events[i].Pid, 390 "Captured event has bad PID") 391 } 392 } 393 394 func createTracer( 395 t *testing.T, config *tracer.Config, callback func(*types.Event), 396 ) *tracer.Tracer { 397 t.Helper() 398 399 tracer, err := tracer.NewTracer(config, nil, callback) 400 require.Nil(t, err, "Error creating tracer: %s", err) 401 t.Cleanup(tracer.Stop) 402 403 return tracer 404 } 405 406 // Function to generate an event used most of the times. 407 // Returns pid of executed process. 408 func generateEvent() (int, error) { 409 cmd := exec.Command("/bin/cat", "/dev/null") 410 if err := cmd.Run(); err != nil { 411 return 0, fmt.Errorf("running command: %w", err) 412 } 413 414 return cmd.Process.Pid, nil 415 } 416 417 // Function to generate a failed event. 418 // Return 0 as no process is created. 419 func generateFailedEvent() (int, error) { 420 // Ignore error since we want to capture a failed event 421 exec.Command("/bin/foobar").Run() 422 return 0, nil 423 } 424 425 // Function to generate an exec() event from a thread. 426 func generateEventFromThread(success bool) func() (int, error) { 427 return func() (int, error) { 428 bin := "/bin/cat" 429 if !success { 430 bin = "/bin/NONE" 431 } 432 script := fmt.Sprintf(` 433 import threading 434 import os 435 436 def exec(): 437 os.execve("%s", ["cat", "/dev/null"], {}) 438 439 def main(): 440 thread = threading.Thread(target=exec) 441 thread.start() 442 thread.join() 443 444 if __name__ == "__main__": 445 main() 446 `, bin) 447 cmd := exec.Command("python3", "-c", script) 448 if err := cmd.Run(); err != nil { 449 return 0, fmt.Errorf("running command: %w", err) 450 } 451 return cmd.Process.Pid, nil 452 } 453 }