-
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?
Support tail calls changes upon events changes (including fallbacks) #4938
Conversation
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.
Pull Request Overview
This PR implements support for dynamically updating eBPF tail calls when event dependencies change in the Tracee runtime security tool. The implementation adds state change notifications to the dependency manager and automatically rebuilds tail call mappings when events are added, removed, or when fallbacks are applied.
Key changes:
- Added state change watchers to the dependency manager that trigger when the dependency tree changes
- Implemented tail call map rebuilding functionality that clears and repopulates all tail call mappings
- Connected dependency state changes to tail call updates through a subscription mechanism
Reviewed Changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| pkg/events/dependencies/manager.go | Added state change subscription mechanism and triggers for dependency tree modifications |
| pkg/ebpf/tracee.go | Implemented tail call map rebuilding functionality and registered state change watcher |
| var keysToDelete []uint32 | ||
| for iterator.Next() { | ||
| keyBytes := iterator.Key() | ||
| if len(keyBytes) >= 4 { // uint32 is 4 bytes |
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 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.
| if len(keyBytes) >= 4 { // uint32 is 4 bytes | |
| if len(keyBytes) == 4 { // uint32 is 4 bytes |
| err := t.rebuildAllTailCalls() | ||
| if err != nil { | ||
| logger.Errorw("Failed to rebuild tailcalls after dependency change", "error", err) | ||
| } |
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) | |
| } | |
| }() |
| // 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) | ||
| } | ||
| } | ||
|
|
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.
Silently ignoring delete errors could mask serious issues like corrupted BPF maps or permission problems. Consider collecting and returning accumulated errors, or at least logging at a higher severity level for certain error types.
| // 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...) | |
| } |
|
I made it quickly with Cursor, so upon deciding that it is what we want to implement I will improve the names and logic. |
1. Explain what the PR does
A simple implementation idea for supporting the changes of tail calls upon changes in the events in use.
This allows both changes by policy and fallbacks/failures to work properly with the tailcalls.
Implementation
As the changes are hard to track, this PR implement a very simple idea - instead of tracking the diff, we can create for each change the tailcalls from scratches.
It works like this:
This might not be the most efficient solution, but it should be the easiest.
In the future we can think how to improve the efficiency with delaying the rebuilding of the maps until it is really needed so we can prevent multiple rebuildings in a row
2. Explain how to test it
3. Other comments