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

     1  // Copyright 2019-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 !withoutebpf
    16  
    17  package tracer
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"sort"
    23  
    24  	"github.com/cilium/ebpf"
    25  	"github.com/cilium/ebpf/link"
    26  
    27  	"github.com/inspektor-gadget/inspektor-gadget/pkg/btfgen"
    28  	containercollection "github.com/inspektor-gadget/inspektor-gadget/pkg/container-collection"
    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/utils/syscalls"
    32  )
    33  
    34  //go:generate go run github.com/cilium/ebpf/cmd/bpf2go -target ${TARGET} -cc clang -cflags ${CFLAGS} seccomp ./bpf/seccomp.bpf.c -- -I./bpf/
    35  
    36  const (
    37  	// Please update these values also in bpf/seccomp-common.h
    38  	syscallsCount              = 500
    39  	syscallsMapValueFooterSize = 1
    40  	syscallsMapValueSize       = syscallsCount + syscallsMapValueFooterSize
    41  )
    42  
    43  type Tracer struct {
    44  	objs seccompObjects
    45  
    46  	// progLink links the BPF program to the tracepoint.
    47  	// A reference is kept so it can be closed it explicitly, otherwise
    48  	// the garbage collector might unlink it via the finalizer at any
    49  	// moment.
    50  	progLink link.Link
    51  
    52  	// We keep references to mountns of containers we attach to, so we
    53  	// can collect information afterwards
    54  	containers map[*containercollection.Container][]string
    55  }
    56  
    57  func NewTracer() (*Tracer, error) {
    58  	t := &Tracer{}
    59  
    60  	if err := t.install(); err != nil {
    61  		t.Close()
    62  		return nil, err
    63  	}
    64  
    65  	return t, nil
    66  }
    67  
    68  func (t *Tracer) install() error {
    69  	spec, err := loadSeccomp()
    70  	if err != nil {
    71  		return fmt.Errorf("loading asset: %w", err)
    72  	}
    73  
    74  	opts := ebpf.CollectionOptions{
    75  		Programs: ebpf.ProgramOptions{
    76  			KernelTypes: btfgen.GetBTFSpec(),
    77  		},
    78  	}
    79  
    80  	if err := spec.LoadAndAssign(&t.objs, &opts); err != nil {
    81  		return fmt.Errorf("loading ebpf program: %w", err)
    82  	}
    83  
    84  	t.objs.SyscallsPerMntns.Update(uint64(0), [syscallsMapValueSize]byte{}, ebpf.UpdateAny)
    85  
    86  	t.progLink, err = link.AttachRawTracepoint(link.RawTracepointOptions{
    87  		Name:    "sys_enter",
    88  		Program: t.objs.IgSeccompE,
    89  	})
    90  	if err != nil {
    91  		return fmt.Errorf("attaching tracepoint: %w", err)
    92  	}
    93  
    94  	return nil
    95  }
    96  
    97  func syscallArrToNameList(v []byte) []string {
    98  	names := []string{}
    99  	for i, val := range v {
   100  		if val == 0 {
   101  			continue
   102  		}
   103  		name, ok := syscalls.GetSyscallNameByNumber(i)
   104  		if !ok {
   105  			name = fmt.Sprintf("syscall%d", i)
   106  		}
   107  		names = append(names, name)
   108  	}
   109  	sort.Strings(names)
   110  	return names
   111  }
   112  
   113  func (t *Tracer) Peek(mntns uint64) ([]string, error) {
   114  	b, err := t.objs.SyscallsPerMntns.LookupBytes(mntns)
   115  	if err != nil {
   116  		return nil, fmt.Errorf("looking up the seccomp map: %w", err)
   117  	}
   118  	// LookupBytes does not return an error when the entry is not found, so
   119  	// we need to test b==nil too
   120  	if b == nil {
   121  		// The container just hasn't done any syscall
   122  		return nil, fmt.Errorf("no syscall found")
   123  	}
   124  	if len(b) < syscallsCount {
   125  		return nil, fmt.Errorf("looking up the seccomp map: wrong length: %d", len(b))
   126  	}
   127  	return syscallArrToNameList(b[:syscallsCount]), nil
   128  }
   129  
   130  func (t *Tracer) Delete(mntns uint64) {
   131  	t.objs.SyscallsPerMntns.Delete(mntns)
   132  }
   133  
   134  // Close closes the tracer
   135  // TODO: Unexport this function when the refactoring is done
   136  func (t *Tracer) Close() {
   137  	t.progLink = gadgets.CloseLink(t.progLink)
   138  	t.objs.Close()
   139  }
   140  
   141  // ---
   142  
   143  func (g *GadgetDesc) NewInstance() (gadgets.Gadget, error) {
   144  	t := &Tracer{
   145  		containers: make(map[*containercollection.Container][]string),
   146  	}
   147  	return t, nil
   148  }
   149  
   150  func (t *Tracer) RunWithResult(gadgetCtx gadgets.GadgetContext) ([]byte, error) {
   151  	defer t.Close()
   152  	if err := t.install(); err != nil {
   153  		return nil, fmt.Errorf("installing tracer: %w", err)
   154  	}
   155  
   156  	// Notice this Tracer starts collecting data for all containers as soon as
   157  	// it is installed, and uses the attach/detach mechanism to know which
   158  	// containers to report data from.
   159  	gadgetcontext.WaitForTimeoutOrDone(gadgetCtx)
   160  
   161  	return t.collectResult()
   162  }
   163  
   164  func (t *Tracer) AttachContainer(container *containercollection.Container) error {
   165  	t.containers[container] = nil
   166  	return nil
   167  }
   168  
   169  func (t *Tracer) DetachContainer(container *containercollection.Container) error {
   170  	res, err := t.Peek(container.Mntns)
   171  	if err != nil {
   172  		t.containers[container] = []string{err.Error()}
   173  		return nil
   174  	}
   175  	t.containers[container] = res
   176  	return nil
   177  }
   178  
   179  func (t *Tracer) collectResult() ([]byte, error) {
   180  	out := make(map[string][]string)
   181  	for container, result := range t.containers {
   182  		out[container.K8s.ContainerName] = result
   183  	}
   184  	return json.MarshalIndent(out, "", "  ")
   185  }