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  }