github.com/cilium/ebpf@v0.15.1-0.20240517100537-8079b37aa138/internal/tracefs/kprobe.go (about)

     1  package tracefs
     2  
     3  import (
     4  	"crypto/rand"
     5  	"errors"
     6  	"fmt"
     7  	"os"
     8  	"path/filepath"
     9  	"runtime"
    10  	"strings"
    11  	"sync"
    12  	"syscall"
    13  
    14  	"github.com/cilium/ebpf/internal"
    15  	"github.com/cilium/ebpf/internal/unix"
    16  )
    17  
    18  var (
    19  	ErrInvalidInput = errors.New("invalid input")
    20  
    21  	ErrInvalidMaxActive = errors.New("can only set maxactive on kretprobes")
    22  )
    23  
    24  //go:generate go run golang.org/x/tools/cmd/stringer@latest -type=ProbeType -linecomment
    25  
    26  type ProbeType uint8
    27  
    28  const (
    29  	Kprobe ProbeType = iota // kprobe
    30  	Uprobe                  // uprobe
    31  )
    32  
    33  func (pt ProbeType) eventsFile() (*os.File, error) {
    34  	path, err := sanitizeTracefsPath(fmt.Sprintf("%s_events", pt.String()))
    35  	if err != nil {
    36  		return nil, err
    37  	}
    38  
    39  	return os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0666)
    40  }
    41  
    42  type ProbeArgs struct {
    43  	Type                         ProbeType
    44  	Symbol, Group, Path          string
    45  	Offset, RefCtrOffset, Cookie uint64
    46  	Pid, RetprobeMaxActive       int
    47  	Ret                          bool
    48  }
    49  
    50  // RandomGroup generates a pseudorandom string for use as a tracefs group name.
    51  // Returns an error when the output string would exceed 63 characters (kernel
    52  // limitation), when rand.Read() fails or when prefix contains characters not
    53  // allowed by IsValidTraceID.
    54  func RandomGroup(prefix string) (string, error) {
    55  	if !validIdentifier(prefix) {
    56  		return "", fmt.Errorf("prefix '%s' must be alphanumeric or underscore: %w", prefix, ErrInvalidInput)
    57  	}
    58  
    59  	b := make([]byte, 8)
    60  	if _, err := rand.Read(b); err != nil {
    61  		return "", fmt.Errorf("reading random bytes: %w", err)
    62  	}
    63  
    64  	group := fmt.Sprintf("%s_%x", prefix, b)
    65  	if len(group) > 63 {
    66  		return "", fmt.Errorf("group name '%s' cannot be longer than 63 characters: %w", group, ErrInvalidInput)
    67  	}
    68  
    69  	return group, nil
    70  }
    71  
    72  // validIdentifier implements the equivalent of a regex match
    73  // against "^[a-zA-Z_][0-9a-zA-Z_]*$".
    74  //
    75  // Trace event groups, names and kernel symbols must adhere to this set
    76  // of characters. Non-empty, first character must not be a number, all
    77  // characters must be alphanumeric or underscore.
    78  func validIdentifier(s string) bool {
    79  	if len(s) < 1 {
    80  		return false
    81  	}
    82  	for i, c := range []byte(s) {
    83  		switch {
    84  		case c >= 'a' && c <= 'z':
    85  		case c >= 'A' && c <= 'Z':
    86  		case c == '_':
    87  		case i > 0 && c >= '0' && c <= '9':
    88  
    89  		default:
    90  			return false
    91  		}
    92  	}
    93  
    94  	return true
    95  }
    96  
    97  func sanitizeTracefsPath(path ...string) (string, error) {
    98  	base, err := getTracefsPath()
    99  	if err != nil {
   100  		return "", err
   101  	}
   102  	l := filepath.Join(path...)
   103  	p := filepath.Join(base, l)
   104  	if !strings.HasPrefix(p, base) {
   105  		return "", fmt.Errorf("path '%s' attempts to escape base path '%s': %w", l, base, ErrInvalidInput)
   106  	}
   107  	return p, nil
   108  }
   109  
   110  // getTracefsPath will return a correct path to the tracefs mount point.
   111  // Since kernel 4.1 tracefs should be mounted by default at /sys/kernel/tracing,
   112  // but may be also be available at /sys/kernel/debug/tracing if debugfs is mounted.
   113  // The available tracefs paths will depends on distribution choices.
   114  var getTracefsPath = sync.OnceValues(func() (string, error) {
   115  	for _, p := range []struct {
   116  		path   string
   117  		fsType int64
   118  	}{
   119  		{"/sys/kernel/tracing", unix.TRACEFS_MAGIC},
   120  		{"/sys/kernel/debug/tracing", unix.TRACEFS_MAGIC},
   121  		// RHEL/CentOS
   122  		{"/sys/kernel/debug/tracing", unix.DEBUGFS_MAGIC},
   123  	} {
   124  		if fsType, err := internal.FSType(p.path); err == nil && fsType == p.fsType {
   125  			return p.path, nil
   126  		}
   127  	}
   128  
   129  	return "", errors.New("neither debugfs nor tracefs are mounted")
   130  })
   131  
   132  // sanitizeIdentifier replaces every invalid character for the tracefs api with an underscore.
   133  //
   134  // It is equivalent to calling regexp.MustCompile("[^a-zA-Z0-9]+").ReplaceAllString("_").
   135  func sanitizeIdentifier(s string) string {
   136  	var skip bool
   137  	return strings.Map(func(c rune) rune {
   138  		switch {
   139  		case c >= 'a' && c <= 'z',
   140  			c >= 'A' && c <= 'Z',
   141  			c >= '0' && c <= '9':
   142  			skip = false
   143  			return c
   144  
   145  		case skip:
   146  			return -1
   147  
   148  		default:
   149  			skip = true
   150  			return '_'
   151  		}
   152  	}, s)
   153  }
   154  
   155  // EventID reads a trace event's ID from tracefs given its group and name.
   156  // The kernel requires group and name to be alphanumeric or underscore.
   157  func EventID(group, name string) (uint64, error) {
   158  	if !validIdentifier(group) {
   159  		return 0, fmt.Errorf("invalid tracefs group: %q", group)
   160  	}
   161  
   162  	if !validIdentifier(name) {
   163  		return 0, fmt.Errorf("invalid tracefs name: %q", name)
   164  	}
   165  
   166  	path, err := sanitizeTracefsPath("events", group, name, "id")
   167  	if err != nil {
   168  		return 0, err
   169  	}
   170  	tid, err := internal.ReadUint64FromFile("%d\n", path)
   171  	if errors.Is(err, os.ErrNotExist) {
   172  		return 0, err
   173  	}
   174  	if err != nil {
   175  		return 0, fmt.Errorf("reading trace event ID of %s/%s: %w", group, name, err)
   176  	}
   177  
   178  	return tid, nil
   179  }
   180  
   181  func probePrefix(ret bool, maxActive int) string {
   182  	if ret {
   183  		if maxActive > 0 {
   184  			return fmt.Sprintf("r%d", maxActive)
   185  		}
   186  		return "r"
   187  	}
   188  	return "p"
   189  }
   190  
   191  // Event represents an entry in a tracefs probe events file.
   192  type Event struct {
   193  	typ         ProbeType
   194  	group, name string
   195  	// event id allocated by the kernel. 0 if the event has already been removed.
   196  	id uint64
   197  }
   198  
   199  // NewEvent creates a new ephemeral trace event.
   200  //
   201  // Returns os.ErrNotExist if symbol is not a valid
   202  // kernel symbol, or if it is not traceable with kprobes. Returns os.ErrExist
   203  // if a probe with the same group and symbol already exists. Returns an error if
   204  // args.RetprobeMaxActive is used on non kprobe types. Returns ErrNotSupported if
   205  // the kernel is too old to support kretprobe maxactive.
   206  func NewEvent(args ProbeArgs) (*Event, error) {
   207  	// Before attempting to create a trace event through tracefs,
   208  	// check if an event with the same group and name already exists.
   209  	// Kernels 4.x and earlier don't return os.ErrExist on writing a duplicate
   210  	// entry, so we need to rely on reads for detecting uniqueness.
   211  	eventName := sanitizeIdentifier(args.Symbol)
   212  	_, err := EventID(args.Group, eventName)
   213  	if err == nil {
   214  		return nil, fmt.Errorf("trace event %s/%s: %w", args.Group, eventName, os.ErrExist)
   215  	}
   216  	if err != nil && !errors.Is(err, os.ErrNotExist) {
   217  		return nil, fmt.Errorf("checking trace event %s/%s: %w", args.Group, eventName, err)
   218  	}
   219  
   220  	// Open the kprobe_events file in tracefs.
   221  	f, err := args.Type.eventsFile()
   222  	if err != nil {
   223  		return nil, err
   224  	}
   225  	defer f.Close()
   226  
   227  	var pe, token string
   228  	switch args.Type {
   229  	case Kprobe:
   230  		// The kprobe_events syntax is as follows (see Documentation/trace/kprobetrace.txt):
   231  		// p[:[GRP/]EVENT] [MOD:]SYM[+offs]|MEMADDR [FETCHARGS] : Set a probe
   232  		// r[MAXACTIVE][:[GRP/]EVENT] [MOD:]SYM[+0] [FETCHARGS] : Set a return probe
   233  		// -:[GRP/]EVENT                                        : Clear a probe
   234  		//
   235  		// Some examples:
   236  		// r:ebpf_1234/r_my_kretprobe nf_conntrack_destroy
   237  		// p:ebpf_5678/p_my_kprobe __x64_sys_execve
   238  		//
   239  		// Leaving the kretprobe's MAXACTIVE set to 0 (or absent) will make the
   240  		// kernel default to NR_CPUS. This is desired in most eBPF cases since
   241  		// subsampling or rate limiting logic can be more accurately implemented in
   242  		// the eBPF program itself.
   243  		// See Documentation/kprobes.txt for more details.
   244  		if args.RetprobeMaxActive != 0 && !args.Ret {
   245  			return nil, ErrInvalidMaxActive
   246  		}
   247  		token = KprobeToken(args)
   248  		pe = fmt.Sprintf("%s:%s/%s %s", probePrefix(args.Ret, args.RetprobeMaxActive), args.Group, eventName, token)
   249  	case Uprobe:
   250  		// The uprobe_events syntax is as follows:
   251  		// p[:[GRP/]EVENT] PATH:OFFSET [FETCHARGS] : Set a probe
   252  		// r[:[GRP/]EVENT] PATH:OFFSET [FETCHARGS] : Set a return probe
   253  		// -:[GRP/]EVENT                           : Clear a probe
   254  		//
   255  		// Some examples:
   256  		// r:ebpf_1234/readline /bin/bash:0x12345
   257  		// p:ebpf_5678/main_mySymbol /bin/mybin:0x12345(0x123)
   258  		//
   259  		// See Documentation/trace/uprobetracer.txt for more details.
   260  		if args.RetprobeMaxActive != 0 {
   261  			return nil, ErrInvalidMaxActive
   262  		}
   263  		token = UprobeToken(args)
   264  		pe = fmt.Sprintf("%s:%s/%s %s", probePrefix(args.Ret, 0), args.Group, eventName, token)
   265  	}
   266  	_, err = f.WriteString(pe)
   267  
   268  	// Since commit 97c753e62e6c, ENOENT is correctly returned instead of EINVAL
   269  	// when trying to create a retprobe for a missing symbol.
   270  	if errors.Is(err, os.ErrNotExist) {
   271  		return nil, fmt.Errorf("token %s: not found: %w", token, err)
   272  	}
   273  	// Since commit ab105a4fb894, EILSEQ is returned when a kprobe sym+offset is resolved
   274  	// to an invalid insn boundary. The exact conditions that trigger this error are
   275  	// arch specific however.
   276  	if errors.Is(err, syscall.EILSEQ) {
   277  		return nil, fmt.Errorf("token %s: bad insn boundary: %w", token, os.ErrNotExist)
   278  	}
   279  	// ERANGE is returned when the `SYM[+offs]` token is too big and cannot
   280  	// be resolved.
   281  	if errors.Is(err, syscall.ERANGE) {
   282  		return nil, fmt.Errorf("token %s: offset too big: %w", token, os.ErrNotExist)
   283  	}
   284  
   285  	if err != nil {
   286  		return nil, fmt.Errorf("token %s: writing '%s': %w", token, pe, err)
   287  	}
   288  
   289  	// Get the newly-created trace event's id.
   290  	tid, err := EventID(args.Group, eventName)
   291  	if args.RetprobeMaxActive != 0 && errors.Is(err, os.ErrNotExist) {
   292  		// Kernels < 4.12 don't support maxactive and therefore auto generate
   293  		// group and event names from the symbol and offset. The symbol is used
   294  		// without any sanitization.
   295  		// See https://elixir.bootlin.com/linux/v4.10/source/kernel/trace/trace_kprobe.c#L712
   296  		event := fmt.Sprintf("kprobes/r_%s_%d", args.Symbol, args.Offset)
   297  		if err := removeEvent(args.Type, event); err != nil {
   298  			return nil, fmt.Errorf("failed to remove spurious maxactive event: %s", err)
   299  		}
   300  		return nil, fmt.Errorf("create trace event with non-default maxactive: %w", internal.ErrNotSupported)
   301  	}
   302  	if err != nil {
   303  		return nil, fmt.Errorf("get trace event id: %w", err)
   304  	}
   305  
   306  	evt := &Event{args.Type, args.Group, eventName, tid}
   307  	runtime.SetFinalizer(evt, (*Event).Close)
   308  	return evt, nil
   309  }
   310  
   311  // Close removes the event from tracefs.
   312  //
   313  // Returns os.ErrClosed if the event has already been closed before.
   314  func (evt *Event) Close() error {
   315  	if evt.id == 0 {
   316  		return os.ErrClosed
   317  	}
   318  
   319  	evt.id = 0
   320  	runtime.SetFinalizer(evt, nil)
   321  	pe := fmt.Sprintf("%s/%s", evt.group, evt.name)
   322  	return removeEvent(evt.typ, pe)
   323  }
   324  
   325  func removeEvent(typ ProbeType, pe string) error {
   326  	f, err := typ.eventsFile()
   327  	if err != nil {
   328  		return err
   329  	}
   330  	defer f.Close()
   331  
   332  	// See [k,u]probe_events syntax above. The probe type does not need to be specified
   333  	// for removals.
   334  	if _, err = f.WriteString("-:" + pe); err != nil {
   335  		return fmt.Errorf("remove event %q from %s: %w", pe, f.Name(), err)
   336  	}
   337  
   338  	return nil
   339  }
   340  
   341  // ID returns the tracefs ID associated with the event.
   342  func (evt *Event) ID() uint64 {
   343  	return evt.id
   344  }
   345  
   346  // Group returns the tracefs group used by the event.
   347  func (evt *Event) Group() string {
   348  	return evt.group
   349  }
   350  
   351  // KprobeToken creates the SYM[+offs] token for the tracefs api.
   352  func KprobeToken(args ProbeArgs) string {
   353  	po := args.Symbol
   354  
   355  	if args.Offset != 0 {
   356  		po += fmt.Sprintf("+%#x", args.Offset)
   357  	}
   358  
   359  	return po
   360  }