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 }