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  }