github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/gadgets/trace/exec/tracer/tracer.go (about)

     1  // Copyright 2019-2024 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 !withoutebpf
    16  
    17  package tracer
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"os"
    23  	"unsafe"
    24  
    25  	"github.com/cilium/ebpf"
    26  	"github.com/cilium/ebpf/link"
    27  	"github.com/cilium/ebpf/perf"
    28  
    29  	gadgetcontext "github.com/inspektor-gadget/inspektor-gadget/pkg/gadget-context"
    30  	"github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets"
    31  	"github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets/trace/exec/types"
    32  	eventtypes "github.com/inspektor-gadget/inspektor-gadget/pkg/types"
    33  )
    34  
    35  //go:generate go run github.com/cilium/ebpf/cmd/bpf2go -target bpfel -cc clang -cflags ${CFLAGS} -type event execsnoop ./bpf/execsnoop.bpf.c -- -I./bpf/
    36  //go:generate go run github.com/cilium/ebpf/cmd/bpf2go -target bpfel -cc clang -cflags ${CFLAGS} -type event execsnoopWithLongPaths ./bpf/execsnoop.bpf.c -- -DWITH_LONG_PATHS -I./bpf/
    37  
    38  // needs to be kept in sync with execsnoopEvent from execsnoop_bpfel.go without the Args field
    39  type execsnoopEventAbbrev struct {
    40  	MntnsId    uint64
    41  	Timestamp  uint64
    42  	Pid        uint32
    43  	Ppid       uint32
    44  	Uid        uint32
    45  	Gid        uint32
    46  	Loginuid   uint32
    47  	Sessionid  uint32
    48  	Retval     int32
    49  	ArgsCount  int32
    50  	UpperLayer bool
    51  	_          [3]byte
    52  	ArgsSize   uint32
    53  	Comm       [16]uint8
    54  	Pcomm      [16]uint8
    55  }
    56  
    57  // needs to be kept in sync with execsnoopWithLongPathsEvent from execsnoopwithlongpaths_bpfel.go without the Args field
    58  type execsnoopWithLongPathsEventAbbrev struct {
    59  	MntnsId    uint64
    60  	Timestamp  uint64
    61  	Pid        uint32
    62  	Ppid       uint32
    63  	Uid        uint32
    64  	Gid        uint32
    65  	Loginuid   uint32
    66  	Sessionid  uint32
    67  	Retval     int32
    68  	ArgsCount  int32
    69  	UpperLayer bool
    70  	_          [3]byte
    71  	ArgsSize   uint32
    72  	Comm       [16]uint8
    73  	Pcomm      [16]uint8
    74  	Cwd        [4096]uint8
    75  	ExePath    [4096]uint8
    76  }
    77  
    78  type Config struct {
    79  	MountnsMap   *ebpf.Map
    80  	GetPaths     bool
    81  	IgnoreErrors bool
    82  }
    83  
    84  type Tracer struct {
    85  	config        *Config
    86  	enricher      gadgets.DataEnricherByMntNs
    87  	eventCallback func(*types.Event)
    88  
    89  	objs      execsnoopObjects
    90  	enterLink link.Link
    91  	exitLink  link.Link
    92  	reader    *perf.Reader
    93  }
    94  
    95  func NewTracer(config *Config, enricher gadgets.DataEnricherByMntNs,
    96  	eventCallback func(*types.Event),
    97  ) (*Tracer, error) {
    98  	t := &Tracer{
    99  		config:        config,
   100  		enricher:      enricher,
   101  		eventCallback: eventCallback,
   102  	}
   103  
   104  	if err := t.install(); err != nil {
   105  		t.close()
   106  		return nil, err
   107  	}
   108  
   109  	go t.run()
   110  
   111  	return t, nil
   112  }
   113  
   114  // Stop stops the tracer
   115  // TODO: Remove after refactoring
   116  func (t *Tracer) Stop() {
   117  	t.close()
   118  }
   119  
   120  func (t *Tracer) close() {
   121  	t.enterLink = gadgets.CloseLink(t.enterLink)
   122  	t.exitLink = gadgets.CloseLink(t.exitLink)
   123  
   124  	if t.reader != nil {
   125  		t.reader.Close()
   126  	}
   127  
   128  	t.objs.Close()
   129  }
   130  
   131  func (t *Tracer) install() error {
   132  	var spec *ebpf.CollectionSpec
   133  	var err error
   134  
   135  	if t.config.GetPaths {
   136  		spec, err = loadExecsnoopWithLongPaths()
   137  	} else {
   138  		spec, err = loadExecsnoop()
   139  	}
   140  	if err != nil {
   141  		return fmt.Errorf("loading ebpf program: %w", err)
   142  	}
   143  
   144  	consts := map[string]interface{}{
   145  		"ignore_failed": t.config.IgnoreErrors,
   146  	}
   147  
   148  	if err := gadgets.LoadeBPFSpec(t.config.MountnsMap, spec, consts, &t.objs); err != nil {
   149  		return fmt.Errorf("loading ebpf spec: %w", err)
   150  	}
   151  
   152  	t.enterLink, err = link.Tracepoint("syscalls", "sys_enter_execve", t.objs.IgExecveE, nil)
   153  	if err != nil {
   154  		return fmt.Errorf("attaching enter tracepoint: %w", err)
   155  	}
   156  
   157  	t.exitLink, err = link.Tracepoint("syscalls", "sys_exit_execve", t.objs.IgExecveX, nil)
   158  	if err != nil {
   159  		return fmt.Errorf("attaching exit tracepoint: %w", err)
   160  	}
   161  
   162  	reader, err := perf.NewReader(t.objs.execsnoopMaps.Events, gadgets.PerfBufferPages*os.Getpagesize())
   163  	if err != nil {
   164  		return fmt.Errorf("creating perf ring buffer: %w", err)
   165  	}
   166  	t.reader = reader
   167  
   168  	return nil
   169  }
   170  
   171  func (t *Tracer) run() {
   172  	for {
   173  		record, err := t.reader.Read()
   174  		if err != nil {
   175  			if errors.Is(err, perf.ErrClosed) {
   176  				// nothing to do, we're done
   177  				return
   178  			}
   179  
   180  			msg := fmt.Sprintf("Error reading perf ring buffer: %s", err)
   181  			t.eventCallback(types.Base(eventtypes.Err(msg)))
   182  			return
   183  		}
   184  
   185  		if record.LostSamples > 0 {
   186  			msg := fmt.Sprintf("lost %d samples", record.LostSamples)
   187  			t.eventCallback(types.Base(eventtypes.Warn(msg)))
   188  			continue
   189  		}
   190  
   191  		// this works regardless the kind of event because cwd is defined at the end of the
   192  		// structure. (Just before args that are handled in a different way below)
   193  		bpfEvent := (*execsnoopEventAbbrev)(unsafe.Pointer(&record.RawSample[0]))
   194  
   195  		event := types.Event{
   196  			Event: eventtypes.Event{
   197  				Type:      eventtypes.NORMAL,
   198  				Timestamp: gadgets.WallTimeFromBootTime(bpfEvent.Timestamp),
   199  			},
   200  			Pid:           bpfEvent.Pid,
   201  			Ppid:          bpfEvent.Ppid,
   202  			Uid:           bpfEvent.Uid,
   203  			Gid:           bpfEvent.Gid,
   204  			LoginUid:      bpfEvent.Loginuid,
   205  			SessionId:     bpfEvent.Sessionid,
   206  			UpperLayer:    bpfEvent.UpperLayer,
   207  			WithMountNsID: eventtypes.WithMountNsID{MountNsID: bpfEvent.MntnsId},
   208  			Retval:        int(bpfEvent.Retval),
   209  			Comm:          gadgets.FromCString(bpfEvent.Comm[:]),
   210  			Pcomm:         gadgets.FromCString(bpfEvent.Pcomm[:]),
   211  		}
   212  
   213  		argsCount := 0
   214  		buf := []byte{}
   215  		args := record.RawSample[unsafe.Offsetof(execsnoopEvent{}.Args):]
   216  
   217  		if t.config.GetPaths {
   218  			bpfEventWithLongPaths := (*execsnoopWithLongPathsEventAbbrev)(unsafe.Pointer(&record.RawSample[0]))
   219  			event.Cwd = gadgets.FromCString(bpfEventWithLongPaths.Cwd[:])
   220  			event.ExePath = gadgets.FromCString(bpfEventWithLongPaths.ExePath[:])
   221  			args = record.RawSample[unsafe.Offsetof(execsnoopWithLongPathsEvent{}.Args):]
   222  		}
   223  
   224  		for i := 0; i < int(bpfEvent.ArgsSize) && argsCount < int(bpfEvent.ArgsCount); i++ {
   225  			c := args[i]
   226  			if c == 0 {
   227  				event.Args = append(event.Args, string(buf))
   228  				argsCount = 0
   229  				buf = []byte{}
   230  			} else {
   231  				buf = append(buf, c)
   232  			}
   233  		}
   234  
   235  		if t.enricher != nil {
   236  			t.enricher.EnrichByMntNs(&event.CommonData, event.MountNsID)
   237  		}
   238  
   239  		t.eventCallback(&event)
   240  	}
   241  }
   242  
   243  // --- Registry changes
   244  
   245  func (t *Tracer) Run(gadgetCtx gadgets.GadgetContext) error {
   246  	t.config.GetPaths = gadgetCtx.GadgetParams().Get(ParamPaths).AsBool()
   247  	t.config.IgnoreErrors = gadgetCtx.GadgetParams().Get(ParamIgnoreErrors).AsBool()
   248  
   249  	defer t.close()
   250  	if err := t.install(); err != nil {
   251  		return fmt.Errorf("installing tracer: %w", err)
   252  	}
   253  
   254  	go t.run()
   255  	gadgetcontext.WaitForTimeoutOrDone(gadgetCtx)
   256  
   257  	return nil
   258  }
   259  
   260  func (t *Tracer) SetMountNsMap(mountnsMap *ebpf.Map) {
   261  	t.config.MountnsMap = mountnsMap
   262  }
   263  
   264  func (t *Tracer) SetEventHandler(handler any) {
   265  	nh, ok := handler.(func(ev *types.Event))
   266  	if !ok {
   267  		panic("event handler invalid")
   268  	}
   269  	t.eventCallback = nh
   270  }
   271  
   272  func (g *GadgetDesc) NewInstance() (gadgets.Gadget, error) {
   273  	tracer := &Tracer{
   274  		config: &Config{},
   275  	}
   276  	return tracer, nil
   277  }