-
Notifications
You must be signed in to change notification settings - Fork 469
Support tail calls changes upon events changes (including fallbacks) #4938
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -130,6 +130,8 @@ type Tracee struct { | |||||||||||||||||||||||||||||||||||||||||||||
| // This does not mean they are required for tracee to function. | ||||||||||||||||||||||||||||||||||||||||||||||
| // TODO: remove this in favor of dependency manager nodes | ||||||||||||||||||||||||||||||||||||||||||||||
| requiredKsyms []string | ||||||||||||||||||||||||||||||||||||||||||||||
| // All possible tailcall map names from all Core events (computed once at startup) | ||||||||||||||||||||||||||||||||||||||||||||||
| allTailCallMapNames map[string]struct{} | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| func (t *Tracee) Stats() *metrics.Stats { | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -235,24 +237,28 @@ func New(cfg config.Config) (*Tracee, error) { | |||||||||||||||||||||||||||||||||||||||||||||
| // Create Tracee | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| t := &Tracee{ | ||||||||||||||||||||||||||||||||||||||||||||||
| config: cfg, | ||||||||||||||||||||||||||||||||||||||||||||||
| done: make(chan struct{}), | ||||||||||||||||||||||||||||||||||||||||||||||
| stats: metrics.NewStats(), | ||||||||||||||||||||||||||||||||||||||||||||||
| writtenFiles: make(map[string]string), | ||||||||||||||||||||||||||||||||||||||||||||||
| readFiles: make(map[string]string), | ||||||||||||||||||||||||||||||||||||||||||||||
| capturedFiles: make(map[string]int64), | ||||||||||||||||||||||||||||||||||||||||||||||
| streamsManager: streams.NewStreamsManager(), | ||||||||||||||||||||||||||||||||||||||||||||||
| policyManager: pm, | ||||||||||||||||||||||||||||||||||||||||||||||
| eventsDependencies: depsManager, | ||||||||||||||||||||||||||||||||||||||||||||||
| requiredKsyms: []string{}, | ||||||||||||||||||||||||||||||||||||||||||||||
| extraProbes: make(map[string]*probes.ProbeGroup), | ||||||||||||||||||||||||||||||||||||||||||||||
| dataTypeDecoder: bufferdecoder.NewTypeDecoder(), | ||||||||||||||||||||||||||||||||||||||||||||||
| config: cfg, | ||||||||||||||||||||||||||||||||||||||||||||||
| done: make(chan struct{}), | ||||||||||||||||||||||||||||||||||||||||||||||
| stats: metrics.NewStats(), | ||||||||||||||||||||||||||||||||||||||||||||||
| writtenFiles: make(map[string]string), | ||||||||||||||||||||||||||||||||||||||||||||||
| readFiles: make(map[string]string), | ||||||||||||||||||||||||||||||||||||||||||||||
| capturedFiles: make(map[string]int64), | ||||||||||||||||||||||||||||||||||||||||||||||
| streamsManager: streams.NewStreamsManager(), | ||||||||||||||||||||||||||||||||||||||||||||||
| policyManager: pm, | ||||||||||||||||||||||||||||||||||||||||||||||
| eventsDependencies: depsManager, | ||||||||||||||||||||||||||||||||||||||||||||||
| requiredKsyms: []string{}, | ||||||||||||||||||||||||||||||||||||||||||||||
| extraProbes: make(map[string]*probes.ProbeGroup), | ||||||||||||||||||||||||||||||||||||||||||||||
| dataTypeDecoder: bufferdecoder.NewTypeDecoder(), | ||||||||||||||||||||||||||||||||||||||||||||||
| allTailCallMapNames: make(map[string]struct{}), | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // clear initial policies to avoid wrong references | ||||||||||||||||||||||||||||||||||||||||||||||
| initialPolicies = nil | ||||||||||||||||||||||||||||||||||||||||||||||
| t.config.InitialPolicies = nil | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Initialize list of all possible tailcall map names from Core events | ||||||||||||||||||||||||||||||||||||||||||||||
| t.initAllTailCallMapNames() | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Add/Drop capabilities to/from the Base ring (always effective) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| capsToAdd, err := capabilities.ReqByString(t.config.Capabilities.AddCaps...) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -593,6 +599,114 @@ func (t *Tracee) initTailCall(tailCall events.TailCall) error { | |||||||||||||||||||||||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // initAllTailCallMapNames initializes the list of all possible tailcall map names | ||||||||||||||||||||||||||||||||||||||||||||||
| // by iterating through all Core event definitions and extracting their tailcall dependencies. | ||||||||||||||||||||||||||||||||||||||||||||||
| // This is called once during Tracee initialization to avoid repeated computation. | ||||||||||||||||||||||||||||||||||||||||||||||
| func (t *Tracee) initAllTailCallMapNames() { | ||||||||||||||||||||||||||||||||||||||||||||||
| for _, eventDefinition := range events.Core.GetDefinitions() { | ||||||||||||||||||||||||||||||||||||||||||||||
| deps := eventDefinition.GetDependencies() | ||||||||||||||||||||||||||||||||||||||||||||||
| primaryDeps := deps.GetPrimaryDependencies() | ||||||||||||||||||||||||||||||||||||||||||||||
| tailCalls := primaryDeps.GetTailCalls() | ||||||||||||||||||||||||||||||||||||||||||||||
| for _, tailCall := range tailCalls { | ||||||||||||||||||||||||||||||||||||||||||||||
| t.allTailCallMapNames[tailCall.GetMapName()] = struct{}{} | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Also check fallback dependencies for their tailcalls | ||||||||||||||||||||||||||||||||||||||||||||||
| fallbackDeps := deps.GetFallbackDependencies() | ||||||||||||||||||||||||||||||||||||||||||||||
| for _, fallback := range fallbackDeps { | ||||||||||||||||||||||||||||||||||||||||||||||
| tailCalls := fallback.GetTailCalls() | ||||||||||||||||||||||||||||||||||||||||||||||
| for _, tailCall := range tailCalls { | ||||||||||||||||||||||||||||||||||||||||||||||
| t.allTailCallMapNames[tailCall.GetMapName()] = struct{}{} | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // rebuildAllTailCalls rebuilds all tailcall mappings based on the current dependency state. | ||||||||||||||||||||||||||||||||||||||||||||||
| // This method clears existing tailcall maps and then repopulates them with current dependencies. | ||||||||||||||||||||||||||||||||||||||||||||||
| // It's called during initial setup and whenever the dependency state changes (fallbacks, event additions/removals). | ||||||||||||||||||||||||||||||||||||||||||||||
| func (t *Tracee) rebuildAllTailCalls() error { | ||||||||||||||||||||||||||||||||||||||||||||||
| err := t.clearAllTailCallMaps() | ||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||
| return errfmt.Errorf("failed to clear tailcall maps: %v", err) | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| err = t.buildAllTailCallMaps() | ||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||
| return errfmt.Errorf("failed to build tailcall maps: %v", err) | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // clearAllTailCallMaps clears all BPF maps that are used for tailcalls. | ||||||||||||||||||||||||||||||||||||||||||||||
| // It iterates through all BPF maps using the module iterator and clears any that are used for tailcalls. | ||||||||||||||||||||||||||||||||||||||||||||||
| func (t *Tracee) clearAllTailCallMaps() error { | ||||||||||||||||||||||||||||||||||||||||||||||
| // Iterate through all BPF maps and clear the ones used for tailcalls | ||||||||||||||||||||||||||||||||||||||||||||||
| iterator := t.bpfModule.Iterator() | ||||||||||||||||||||||||||||||||||||||||||||||
| for bpfMap := iterator.NextMap(); bpfMap != nil; bpfMap = iterator.NextMap() { | ||||||||||||||||||||||||||||||||||||||||||||||
| mapName := bpfMap.Name() | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Check if this map is used for tailcalls (using pre-computed list from all Core events) | ||||||||||||||||||||||||||||||||||||||||||||||
| if _, isTailCallMap := t.allTailCallMapNames[mapName]; isTailCallMap { | ||||||||||||||||||||||||||||||||||||||||||||||
| err := t.clearTailCallMap(bpfMap) | ||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||
| return errfmt.Errorf("failed to clear tailcall map %s: %v", mapName, err) | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // buildAllTailCallMaps builds all tailcall mappings based on current event dependencies. | ||||||||||||||||||||||||||||||||||||||||||||||
| func (t *Tracee) buildAllTailCallMaps() error { | ||||||||||||||||||||||||||||||||||||||||||||||
| for _, eventID := range t.eventsDependencies.GetEvents() { | ||||||||||||||||||||||||||||||||||||||||||||||
| depsNode, err := t.eventsDependencies.GetEvent(eventID) | ||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||
| return errfmt.Errorf("failed to get event dependencies for %v: %v", eventID, err) | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| deps := depsNode.GetDependencies() | ||||||||||||||||||||||||||||||||||||||||||||||
| tailCalls := deps.GetTailCalls() | ||||||||||||||||||||||||||||||||||||||||||||||
| for _, tailCall := range tailCalls { | ||||||||||||||||||||||||||||||||||||||||||||||
| err := t.initTailCall(tailCall) | ||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||
| return errfmt.Errorf("failed to initialize tail call: %v", err) | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // clearTailCallMap clears all entries in the specified tailcall map. | ||||||||||||||||||||||||||||||||||||||||||||||
| // It uses map key iteration to only clear existing entries, which is more efficient | ||||||||||||||||||||||||||||||||||||||||||||||
| // than clearing all possible indexes. | ||||||||||||||||||||||||||||||||||||||||||||||
| func (t *Tracee) clearTailCallMap(bpfMap *bpf.BPFMap) error { | ||||||||||||||||||||||||||||||||||||||||||||||
| iterator := bpfMap.Iterator() | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Collect all keys first to avoid iterator invalidation during deletion | ||||||||||||||||||||||||||||||||||||||||||||||
| var keysToDelete []uint32 | ||||||||||||||||||||||||||||||||||||||||||||||
| for iterator.Next() { | ||||||||||||||||||||||||||||||||||||||||||||||
| keyBytes := iterator.Key() | ||||||||||||||||||||||||||||||||||||||||||||||
| if len(keyBytes) >= 4 { // uint32 is 4 bytes | ||||||||||||||||||||||||||||||||||||||||||||||
| key := *(*uint32)(unsafe.Pointer(&keyBytes[0])) | ||||||||||||||||||||||||||||||||||||||||||||||
| keysToDelete = append(keysToDelete, key) | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Now delete all the collected keys | ||||||||||||||||||||||||||||||||||||||||||||||
| for _, key := range keysToDelete { | ||||||||||||||||||||||||||||||||||||||||||||||
| err := bpfMap.DeleteKey(unsafe.Pointer(&key)) | ||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||
| // Log but don't fail on individual delete errors | ||||||||||||||||||||||||||||||||||||||||||||||
| logger.Debugw("Failed to delete tailcall map entry", "map", bpfMap.Name(), "key", key, "error", err) | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+698
to
+706
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Now delete all the collected keys | |
| for _, key := range keysToDelete { | |
| err := bpfMap.DeleteKey(unsafe.Pointer(&key)) | |
| if err != nil { | |
| // Log but don't fail on individual delete errors | |
| logger.Debugw("Failed to delete tailcall map entry", "map", bpfMap.Name(), "key", key, "error", err) | |
| } | |
| } | |
| // Now delete all the collected keys, accumulate errors | |
| var deleteErrors []error | |
| for _, key := range keysToDelete { | |
| err := bpfMap.DeleteKey(unsafe.Pointer(&key)) | |
| if err != nil { | |
| // Log at warning level and accumulate error | |
| logger.Warnw("Failed to delete tailcall map entry", "map", bpfMap.Name(), "key", key, "error", err) | |
| deleteErrors = append(deleteErrors, fmt.Errorf("map %s key %d: %w", bpfMap.Name(), key, err)) | |
| } | |
| } | |
| if len(deleteErrors) > 0 { | |
| return errors.Join(deleteErrors...) | |
| } |
Copilot
AI
Sep 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The callback function could potentially deadlock since it's called within the dependency manager's mutex and rebuildAllTailCalls() may need to access dependency state. Consider using a goroutine or channel to defer the tail call rebuilding to avoid potential deadlocks.
| err := t.rebuildAllTailCalls() | |
| if err != nil { | |
| logger.Errorw("Failed to rebuild tailcalls after dependency change", "error", err) | |
| } | |
| go func() { | |
| err := t.rebuildAllTailCalls() | |
| if err != nil { | |
| logger.Errorw("Failed to rebuild tailcalls after dependency change", "error", err) | |
| } | |
| }() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The unsafe pointer conversion lacks bounds checking. If keyBytes has fewer than 4 bytes, this will cause a buffer overflow. Add a check to ensure len(keyBytes) == 4 before the conversion.