github.com/kubeshark/ebpf@v0.9.2/features/prog.go (about)

     1  package features
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"os"
     8  	"sync"
     9  
    10  	"github.com/kubeshark/ebpf"
    11  	"github.com/kubeshark/ebpf/asm"
    12  	"github.com/kubeshark/ebpf/internal"
    13  	"github.com/kubeshark/ebpf/internal/sys"
    14  	"github.com/kubeshark/ebpf/internal/unix"
    15  )
    16  
    17  func init() {
    18  	pc.types = make(map[ebpf.ProgramType]error)
    19  	pc.helpers = make(map[ebpf.ProgramType]map[asm.BuiltinFunc]error)
    20  	allocHelperCache()
    21  }
    22  
    23  func allocHelperCache() {
    24  	for pt := ebpf.UnspecifiedProgram + 1; pt <= pt.Max(); pt++ {
    25  		pc.helpers[pt] = make(map[asm.BuiltinFunc]error)
    26  	}
    27  }
    28  
    29  var (
    30  	pc progCache
    31  )
    32  
    33  type progCache struct {
    34  	typeMu sync.Mutex
    35  	types  map[ebpf.ProgramType]error
    36  
    37  	helperMu sync.Mutex
    38  	helpers  map[ebpf.ProgramType]map[asm.BuiltinFunc]error
    39  }
    40  
    41  func createProgLoadAttr(pt ebpf.ProgramType, helper asm.BuiltinFunc) (*sys.ProgLoadAttr, error) {
    42  	var expectedAttachType ebpf.AttachType
    43  	var progFlags uint32
    44  
    45  	insns := asm.Instructions{
    46  		asm.LoadImm(asm.R0, 0, asm.DWord),
    47  		asm.Return(),
    48  	}
    49  
    50  	if helper != asm.FnUnspec {
    51  		insns = append(asm.Instructions{helper.Call()}, insns...)
    52  	}
    53  
    54  	buf := bytes.NewBuffer(make([]byte, 0, insns.Size()))
    55  	if err := insns.Marshal(buf, internal.NativeEndian); err != nil {
    56  		return nil, err
    57  	}
    58  
    59  	bytecode := buf.Bytes()
    60  	instructions := sys.NewSlicePointer(bytecode)
    61  
    62  	// Some programs have expected attach types which are checked during the
    63  	// BPD_PROG_LOAD syscall.
    64  	switch pt {
    65  	case ebpf.CGroupSockAddr:
    66  		expectedAttachType = ebpf.AttachCGroupInet4Connect
    67  	case ebpf.CGroupSockopt:
    68  		expectedAttachType = ebpf.AttachCGroupGetsockopt
    69  	case ebpf.SkLookup:
    70  		expectedAttachType = ebpf.AttachSkLookup
    71  	case ebpf.Syscall:
    72  		progFlags = unix.BPF_F_SLEEPABLE
    73  	default:
    74  		expectedAttachType = ebpf.AttachNone
    75  	}
    76  
    77  	// Kernels before 5.0 (6c4fc209fcf9 "bpf: remove useless version check for prog load")
    78  	// require the version field to be set to the value of the KERNEL_VERSION
    79  	// macro for kprobe-type programs.
    80  	v, err := internal.KernelVersion()
    81  	if err != nil {
    82  		return nil, fmt.Errorf("detecting kernel version: %w", err)
    83  	}
    84  
    85  	return &sys.ProgLoadAttr{
    86  		ProgType:           sys.ProgType(pt),
    87  		Insns:              instructions,
    88  		InsnCnt:            uint32(len(bytecode) / asm.InstructionSize),
    89  		ProgFlags:          progFlags,
    90  		ExpectedAttachType: sys.AttachType(expectedAttachType),
    91  		License:            sys.NewStringPointer("GPL"),
    92  		KernVersion:        v.Kernel(),
    93  	}, nil
    94  }
    95  
    96  // HaveProgType probes the running kernel for the availability of the specified program type.
    97  //
    98  // Deprecated: use HaveProgramType() instead.
    99  var HaveProgType = HaveProgramType
   100  
   101  // HaveProgramType probes the running kernel for the availability of the specified program type.
   102  //
   103  // See the package documentation for the meaning of the error return value.
   104  func HaveProgramType(pt ebpf.ProgramType) error {
   105  	if err := validateProgramType(pt); err != nil {
   106  		return err
   107  	}
   108  
   109  	return haveProgramType(pt)
   110  
   111  }
   112  
   113  func validateProgramType(pt ebpf.ProgramType) error {
   114  	if pt > pt.Max() {
   115  		return os.ErrInvalid
   116  	}
   117  
   118  	if progLoadProbeNotImplemented(pt) {
   119  		// A probe for a these prog types has BTF requirements we currently cannot meet
   120  		// Once we figure out how to add a working probe in this package, we can remove
   121  		// this check
   122  		return fmt.Errorf("a probe for ProgType %s isn't implemented", pt.String())
   123  	}
   124  
   125  	return nil
   126  }
   127  
   128  func haveProgramType(pt ebpf.ProgramType) error {
   129  	pc.typeMu.Lock()
   130  	defer pc.typeMu.Unlock()
   131  	if err, ok := pc.types[pt]; ok {
   132  		return err
   133  	}
   134  
   135  	attr, err := createProgLoadAttr(pt, asm.FnUnspec)
   136  	if err != nil {
   137  		return fmt.Errorf("couldn't create the program load attribute: %w", err)
   138  	}
   139  
   140  	fd, err := sys.ProgLoad(attr)
   141  
   142  	switch {
   143  	// EINVAL occurs when attempting to create a program with an unknown type.
   144  	// E2BIG occurs when ProgLoadAttr contains non-zero bytes past the end
   145  	// of the struct known by the running kernel, meaning the kernel is too old
   146  	// to support the given prog type.
   147  	case errors.Is(err, unix.EINVAL), errors.Is(err, unix.E2BIG):
   148  		err = ebpf.ErrNotSupported
   149  
   150  	// EPERM is kept as-is and is not converted or wrapped.
   151  	case errors.Is(err, unix.EPERM):
   152  		break
   153  
   154  	// Wrap unexpected errors.
   155  	case err != nil:
   156  		err = fmt.Errorf("unexpected error during feature probe: %w", err)
   157  
   158  	default:
   159  		fd.Close()
   160  	}
   161  
   162  	pc.types[pt] = err
   163  
   164  	return err
   165  }
   166  
   167  // HaveProgramHelper probes the running kernel for the availability of the specified helper
   168  // function to a specified program type.
   169  // Return values have the following semantics:
   170  //
   171  //   err == nil: The feature is available.
   172  //   errors.Is(err, ebpf.ErrNotSupported): The feature is not available.
   173  //   err != nil: Any errors encountered during probe execution, wrapped.
   174  //
   175  // Note that the latter case may include false negatives, and that program creation may
   176  // succeed despite an error being returned.
   177  // Only `nil` and `ebpf.ErrNotSupported` are conclusive.
   178  //
   179  // Probe results are cached and persist throughout any process capability changes.
   180  func HaveProgramHelper(pt ebpf.ProgramType, helper asm.BuiltinFunc) error {
   181  	if err := validateProgramType(pt); err != nil {
   182  		return err
   183  	}
   184  
   185  	if err := validateProgramHelper(helper); err != nil {
   186  		return err
   187  	}
   188  
   189  	return haveProgramHelper(pt, helper)
   190  }
   191  
   192  func validateProgramHelper(helper asm.BuiltinFunc) error {
   193  	if helper > helper.Max() {
   194  		return os.ErrInvalid
   195  	}
   196  
   197  	return nil
   198  }
   199  
   200  func haveProgramHelper(pt ebpf.ProgramType, helper asm.BuiltinFunc) error {
   201  	pc.helperMu.Lock()
   202  	defer pc.helperMu.Unlock()
   203  	if err, ok := pc.helpers[pt][helper]; ok {
   204  		return err
   205  	}
   206  
   207  	attr, err := createProgLoadAttr(pt, helper)
   208  	if err != nil {
   209  		return fmt.Errorf("couldn't create the program load attribute: %w", err)
   210  	}
   211  
   212  	fd, err := sys.ProgLoad(attr)
   213  
   214  	switch {
   215  	// If there is no error we need to close the FD of the prog.
   216  	case err == nil:
   217  		fd.Close()
   218  
   219  	// EACCES occurs when attempting to create a program probe with a helper
   220  	// while the register args when calling this helper aren't set up properly.
   221  	// We interpret this as the helper being available, because the verifier
   222  	// returns EINVAL if the helper is not supported by the running kernel.
   223  	case errors.Is(err, unix.EACCES):
   224  		// TODO: possibly we need to check verifier output here to be sure
   225  		err = nil
   226  
   227  	// EINVAL occurs when attempting to create a program with an unknown helper.
   228  	// E2BIG occurs when BPFProgLoadAttr contains non-zero bytes past the end
   229  	// of the struct known by the running kernel, meaning the kernel is too old
   230  	// to support the given prog type.
   231  	case errors.Is(err, unix.EINVAL), errors.Is(err, unix.E2BIG):
   232  		// TODO: possibly we need to check verifier output here to be sure
   233  		err = ebpf.ErrNotSupported
   234  
   235  	// EPERM is kept as-is and is not converted or wrapped.
   236  	case errors.Is(err, unix.EPERM):
   237  		break
   238  
   239  	// Wrap unexpected errors.
   240  	case err != nil:
   241  		err = fmt.Errorf("unexpected error during feature probe: %w", err)
   242  	}
   243  
   244  	pc.helpers[pt][helper] = err
   245  
   246  	return err
   247  }
   248  
   249  func progLoadProbeNotImplemented(pt ebpf.ProgramType) bool {
   250  	switch pt {
   251  	case ebpf.Tracing, ebpf.StructOps, ebpf.Extension, ebpf.LSM:
   252  		return true
   253  	}
   254  	return false
   255  }