Skip to content

Conversation

@kobrineli
Copy link
Contributor

@kobrineli kobrineli commented Oct 27, 2025

Description

This change adds matchParentBinaries selector, which might be useful for proper and granular filtering of parent binaries, which is needed in some specific cases.

For instance, there is a python script, which invokes some system calls, which we want to intercept and report. If such script is executed by some system process, we want to filter it out. Otherwise, we report it. For this filtering we need a selector for parent binary, because we cannot filter out events only by current binary, which in case of python script execution is always python.

For more real example, consider we want to hook all calls of chmod system call to prevent creating new binaries on the system manually. apt-key binary, when it installs some packages (such cases we don't want to report), doesn't call chmod directly, but uses /usr/bin/chmod binary, which calls chmod system call inside. matchParentBinaries selector would help to create accurate exclusion for this case.

Example of policy with matchParentBinaries selector:

Consider we want to get events about all files, which were made executable, with chmod syscall, but don't want to get events about apt-key making files executable. Unfortunately, apt-key doesn't make files executable with syscall directly, but uses /usr/bin/chmod binary, which calls chmod function, so to filter such events we need to have selectors for both parent and current binary, so the resulting policy will look like this:

apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
  name: chmod-x-bit
spec:
  kprobes:
    - call: "sys_chmod"
      syscall: true
      args:
        - index: 0
          type: "string"
          label: "pathname"
        - index: 1
          type: "int"
          label: "mode"
      selectors:
        - matchArgs:
            - index: 1
              operator: "Mask"
              values:
                - "73" # X bit for owner, group or all (001001001)
          matchBinaries:
            - operator: "NotIn"
              values:
                "/usr/bin/chmod"
        - matchArgs:
            - index: 1
              operator: "Mask"
              values:
                - "73" # X bit for owner, group or all (001001001)
          matchBinaries:
            - operator: "In"
              values:
              - "/usr/bin/chmod"
          matchParentBinaries:
            - operator: "NotIn"
              values:
              - "/usr/bin/apt-key"

If current binary is /usr/bin/chmod, we don't care about parent binary, but if current binary is /usr/bin/chmod, we don't want the parent binary to be /usr/bin/apt-key.

Changelog

@kobrineli kobrineli requested a review from a team as a code owner October 27, 2025 13:10
@kobrineli kobrineli requested a review from kevsecurity October 27, 2025 13:10
@netlify
Copy link

netlify bot commented Oct 27, 2025

Deploy Preview for tetragon ready!

Name Link
🔨 Latest commit 5d3179d
🔍 Latest deploy log https://app.netlify.com/projects/tetragon/deploys/690dfc4de66b1d00084eb3d2
😎 Deploy Preview https://deploy-preview-4254--tetragon.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@kobrineli kobrineli force-pushed the add-match-parents-filter branch 2 times, most recently from 4f73f6d to e31cabe Compare October 27, 2025 13:27
@kobrineli
Copy link
Contributor Author

Probably I should add docs for this selector

@kobrineli kobrineli force-pushed the add-match-parents-filter branch from e31cabe to 70ac377 Compare October 27, 2025 13:41
Copy link
Contributor

@kkourt kkourt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

One thing that would help reviewing this PR would be to split it into multiple commits.
See https://tetragon.io/docs/contribution-guide/submitting-a-pull-request/.

Before the implementation, though, it would be interesting to specify the fetaure so that's it is clear what the semantics are. This can be done in the PR itself (e.g,. in a commit message), in an issue, or in a CFP (see https://github.com/cilium/design-cfps). Whatever works best for you!

For example, it would be very useful to have an example policy that uses the newly introduced operator and discuss how it works

@kkourt
Copy link
Contributor

kkourt commented Oct 27, 2025

Another thing to note is that the functionality is similar to https://tetragon.io/docs/concepts/tracing-policy/selectors/#follow-children. So I wonder if something like:

- matchBinaries:
  - operator: "In"
    values:
    - "/usr/sbin/sshd"
    followChildren: true
  - operator: "NotIn"
    values:
    - "/usr/sbin/sshd"

Would achieve a similar result. The first thing to check would be if matchBinaries are ANDed as, for example, MatchAargs are. The second thing, is that this will not just match the parent, but every ancestor which might or might not be what we want.

@kobrineli
Copy link
Contributor Author

@kkourt
Tetragon currently supports only one match binary operator by selector:
https://github.com/cilium/tetragon/blob/main/pkg/selectors/kernel.go#L1426

So listing multiple operators in match binary selector will fail.

@kkourt
Copy link
Contributor

kkourt commented Oct 27, 2025

One more thing to note is that this looks related to

@kkourt Tetragon currently supports only one match binary operator by selector: https://github.com/cilium/tetragon/blob/main/pkg/selectors/kernel.go#L1426

So listing multiple operators in match binary selector will fail.

Thanks!

I don't remember why this limitation exists, but my guess is that this is something that can be fixed. So, then, the question in my mind is whether the use-case you are describing is better served by matching on the immediate parent or all on ancestors (for which some support already exists).

PS: Might be worth updating the docs to reflect above.

@kevsecurity
Copy link
Contributor

@kkourt Tetragon currently supports only one match binary operator by selector: https://github.com/cilium/tetragon/blob/main/pkg/selectors/kernel.go#L1426

So listing multiple operators in match binary selector will fail.

I wonder if supporting 2 binary selectors would be easier than supporting N, but would potentially achieve the result without supporting parent selectors altogether?

We could maybe have a limit to how many "generations" of children we follow. In this case, a limit of 1 generation would prevent reporting grandchildren. Haven't looked to see if this is easy or not however.

@kobrineli
Copy link
Contributor Author

I wonder if supporting 2 binary selectors would be easier than supporting N, but would potentially achieve the result without supporting parent selectors altogether?

Currently exactly 1 matchBinaries selector is mapped by selector idx. If it is not complicated to support mapping the array of matchBinaries selectors by selector idx, probably with additional field with index in tg_mb_paths map, then it would be easier, than supporting matchParents selector. But I'm afraid, that such changes will lead to hard refactoring in bpf code, while supporting separate selector, which can also be only one for current selectors set, seems pretty easy.

Moreover, maybe explicit parents filtering, working in the same way as current process binary filtering, is easier to understand and to read.

@kobrineli
Copy link
Contributor Author

kobrineli commented Oct 27, 2025

So, then, the question in my mind is whether the use-case you are describing is better served by matching on the immediate parent or all on ancestors (for which some support already exists).

With current existing support we cannot exclude the binary itself because of the selector number limitation.
Moreover, we may want to apply filter for concrete child of parent process, rather than to all process children (as with followChildren option), combining matchBinaries and matchParents selectors.

@kobrineli kobrineli force-pushed the add-match-parents-filter branch from 70ac377 to d503dbd Compare October 27, 2025 15:28
@kobrineli
Copy link
Contributor Author

So I wonder if something like:

- matchBinaries:
  - operator: "In"
    values:
    - "/usr/sbin/sshd"
    followChildren: true
  - operator: "NotIn"
    values:
    - "/usr/sbin/sshd"

Would achieve a similar result.

If we want to match concrete child process, seems like this wouldn't achieve the same result.

@kobrineli kobrineli force-pushed the add-match-parents-filter branch from d503dbd to 12bf008 Compare October 28, 2025 08:03
@kobrineli
Copy link
Contributor Author

@kkourt
Hi!
I've separated PR into several commits.

@kkourt kkourt added the release-note/major This PR introduces major new functionality label Oct 28, 2025
@kkourt
Copy link
Contributor

kkourt commented Oct 28, 2025

@kkourt Hi! I've separated PR into several commits.

Thanks! Can you please add some context in the commit messages (see: https://tetragon.io/docs/contribution-guide/submitting-a-pull-request/)?

Before merging the PR, we would need tests and documentation updates. If you are looking for early feedback, can you add an example (maybe in the commit message) on how to use the new selector?

@kkourt
Copy link
Contributor

kkourt commented Oct 28, 2025

I'm also seeing some CI failures:

--- FAIL: TestKprobeSigkillExecveMap1 (306.00s)
    kprobe_sigkill_test.go:65: child pid is 32313 and spec file is /tmp/sigkill-3506928072.yaml
    logcapture.go:24: time=2025-10-28T08:17:34.681Z level=INFO msg="BTF discovery: default kernel btf file found" btf-file=/sys/kernel/btf/vmlinux
    logcapture.go:24: time=2025-10-28T08:17:34.681Z level=INFO msg="Creating new EventCache" retries=15 delay=2s
    logcapture.go:24: time=2025-10-28T08:17:34.681Z level=INFO msg="Starting process manager" enableK8s=false enableProcessCred=true enableProcessNs=true
    logcapture.go:24: time=2025-10-28T08:17:34.681Z level=INFO msg="Starting JSON exporter"
    logcapture.go:24: time=2025-10-28T08:17:34.681Z level=INFO msg="Cgroup rate disabled (0/0s)"
    logcapture.go:24: time=2025-10-28T08:17:34.682Z level=INFO msg="BTF file: using metadata file" metadata=/sys/kernel/btf/vmlinux
    logcapture.go:24: time=2025-10-28T08:17:34.682Z level=INFO msg="Loading sensor" name=__base__
    logcapture.go:24: time=2025-10-28T08:17:34.682Z level=INFO msg="Loading kernel version 6.11.11"
    logcapture.go:24: time=2025-10-28T08:17:34.945Z level=INFO msg="Loaded sensor successfully" sensor=__base__
    logcapture.go:24: time=2025-10-28T08:17:34.984Z level=INFO msg="Read ProcFS /proc appended 222/284 entries"
    logcapture.go:24: time=2025-10-28T08:17:34.984Z level=WARN msg="failed to put value in execve_map" value="&{Process:{Pid:10 Pad:0 Ktime:540000000} Parent:{Pid:2 Pad:0 Ktime:540000000} Flags:0 Nspid:0 Namespaces:{UtsInum:4026531838 IpcInum:4026531839 MntInum:4026531841 PidInum:4026531836 PidChildInum:4026531836 NetInum:4026531840 TimeInum:4026531834 TimeChildInum:4026531834 CgroupInum:4026531835 UserInum:4026531837} Capabilities:{Permitted:2199023255551 Effective:2199023255551 Inheritable:0} Binary:{PathLength:27 Reversed:0 Path:[107 119 111 114 107 101 114 47 48 58 48 72 45 101 118 101 110 116 115 95 104 105 103 104 112 114 105 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] End:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] EndR:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Args:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] MBSet:0 MBGen:0}}" error="update: key too big for map: argument list too long"

@kobrineli
Copy link
Contributor Author

I'm also seeing some CI failures:

--- FAIL: TestKprobeSigkillExecveMap1 (306.00s)
    kprobe_sigkill_test.go:65: child pid is 32313 and spec file is /tmp/sigkill-3506928072.yaml
    logcapture.go:24: time=2025-10-28T08:17:34.681Z level=INFO msg="BTF discovery: default kernel btf file found" btf-file=/sys/kernel/btf/vmlinux
    logcapture.go:24: time=2025-10-28T08:17:34.681Z level=INFO msg="Creating new EventCache" retries=15 delay=2s
    logcapture.go:24: time=2025-10-28T08:17:34.681Z level=INFO msg="Starting process manager" enableK8s=false enableProcessCred=true enableProcessNs=true
    logcapture.go:24: time=2025-10-28T08:17:34.681Z level=INFO msg="Starting JSON exporter"
    logcapture.go:24: time=2025-10-28T08:17:34.681Z level=INFO msg="Cgroup rate disabled (0/0s)"
    logcapture.go:24: time=2025-10-28T08:17:34.682Z level=INFO msg="BTF file: using metadata file" metadata=/sys/kernel/btf/vmlinux
    logcapture.go:24: time=2025-10-28T08:17:34.682Z level=INFO msg="Loading sensor" name=__base__
    logcapture.go:24: time=2025-10-28T08:17:34.682Z level=INFO msg="Loading kernel version 6.11.11"
    logcapture.go:24: time=2025-10-28T08:17:34.945Z level=INFO msg="Loaded sensor successfully" sensor=__base__
    logcapture.go:24: time=2025-10-28T08:17:34.984Z level=INFO msg="Read ProcFS /proc appended 222/284 entries"
    logcapture.go:24: time=2025-10-28T08:17:34.984Z level=WARN msg="failed to put value in execve_map" value="&{Process:{Pid:10 Pad:0 Ktime:540000000} Parent:{Pid:2 Pad:0 Ktime:540000000} Flags:0 Nspid:0 Namespaces:{UtsInum:4026531838 IpcInum:4026531839 MntInum:4026531841 PidInum:4026531836 PidChildInum:4026531836 NetInum:4026531840 TimeInum:4026531834 TimeChildInum:4026531834 CgroupInum:4026531835 UserInum:4026531837} Capabilities:{Permitted:2199023255551 Effective:2199023255551 Inheritable:0} Binary:{PathLength:27 Reversed:0 Path:[107 119 111 114 107 101 114 47 48 58 48 72 45 101 118 101 110 116 115 95 104 105 103 104 112 114 105 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] End:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] EndR:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Args:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] MBSet:0 MBGen:0}}" error="update: key too big for map: argument list too long"

Is it an issue of this PR?
There is error update: key too big for map: argument list too long while putting a value into execve_map, which was not changed in this PR.

@kobrineli kobrineli force-pushed the add-match-parents-filter branch 2 times, most recently from eeab3e7 to 5d54ba8 Compare October 28, 2025 15:30
Copy link
Member

@mtardy mtardy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, overall looks good to me, just a few nits

Probably I should add docs for this selector

Please update the documentation with this new selector (see https://tetragon.io/docs/concepts/tracing-policy/selectors/).

@mtardy
Copy link
Member

mtardy commented Oct 28, 2025

I see checkpatch is complaining and other CI checks, you can run make checkpatch locally and see more docs on how to create a nice PR here https://tetragon.io/docs/contribution-guide/submitting-a-pull-request/ if that can help :)

Copy link
Contributor

@kevsecurity kevsecurity left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM (barring the red test results).

Copy link
Contributor

@olsajiri olsajiri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it be possible to handle this as special case of current BinarySelector, having some parent bool like:

diff --git a/pkg/k8s/apis/cilium.io/v1alpha1/types.go b/pkg/k8s/apis/cilium.io/v1alpha1/types.go
index 6c3210b0acf5..b5133b1e5fe2 100644
--- a/pkg/k8s/apis/cilium.io/v1alpha1/types.go
+++ b/pkg/k8s/apis/cilium.io/v1alpha1/types.go
@@ -124,6 +124,10 @@ type BinarySelector struct {
        // +kubebuilder:validation:Optional
        // +kubebuilder:default=false
        FollowChildren bool `json:"followChildren"`
+       // match parent binary
+       // +kubebuilder:validation:Optional
+       // +kubebuilder:default=false
+       Parent bool `json:"parent"`
 }

then all the code dups with binary selectors would go away
we could perhaps think a bit about some maps max_entries tuning

@kobrineli kobrineli force-pushed the add-match-parents-filter branch from 5d54ba8 to 600672b Compare October 29, 2025 09:44
@kobrineli
Copy link
Contributor Author

@olsajiri Hi!
Current implementation of matchBinaries selector allows to have only one matchBinaries per selector, so filtering both current binary and parent binary will not be possible.

matchBinaries selector refactoring might be complicated and not so easy as adding additional new selector for direct parent filtering.

Moreover, matchBinaries selector supports followChildren option, which works with mbsetid field of process in tetragon execve map, so combining 2 different matchBinaries selector, which both can change this field, will be a problem, and this place will need to be changed as well.

@kobrineli
Copy link
Contributor Author

@olsajiri
Hi! Are there any issues left that block merge of this PR?

@kobrineli kobrineli force-pushed the add-match-parents-filter branch 3 times, most recently from 41d8652 to afdfd36 Compare November 7, 2025 14:01
This adds genericMatchBinariesSelector enumeration,
renames match binaries maps and changes match binaries
parsing to allow parsing other matchBinaries-like selectors
in future with the same code.

Signed-off-by: Kobrin Ilay <[email protected]>
This commit introduces new matchParentBinaries selector,
which is used for filtering parent process binaries. New
selector works similarly to matchBinaries selector, including
followChildren option, which allows to match not only
direct parent binaries, but transitive parent binaries
as well.

matchParentBinaries selector is needed in cases when we
want to have granular filters on parent binaries for some
events. For example, we may want to intercept calls for
specific system call from specific binary only in case
it was executed with interactive shell. For that we can
add following selector, which will match only process with
bash, sh or zsh parent:

```
- matchParentBinaries:
  - operator: "In"
    values:
    - "/usr/bin/bash"
    - "/usr/bin/sh"
    - "/usr/bin/zsh"
```

Signed-off-by: Kobrin Ilay <[email protected]>
This commit adds crds generated for matchParentBinaries selector

Signed-off-by: Kobrin Ilay <[email protected]>
@kobrineli kobrineli force-pushed the add-match-parents-filter branch from afdfd36 to 5d3179d Compare November 7, 2025 14:03
@kobrineli kobrineli requested a review from olsajiri November 7, 2025 14:29
Copy link
Member

@mtardy mtardy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't check the code of this, but for the docs change LGTM! Thanks for taking the feedback :)

Copy link
Contributor

@olsajiri olsajiri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good, thanks

Copy link
Contributor

@kkourt kkourt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

observertesthelper.LoopEvents(ctx, t, &doneWG, &readyWG, obs)
readyWG.Wait()

if err := exec.Command("/usr/bin/bash", "-c", "echo '/usr/bin/tail /etc/passwd' | /usr/bin/bash").Run(); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need both /usr/bin/bash -c and cmd | bash... Shouldn't one be enough?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for some reason when I test locally and set filters on both parent and current processes binaries, I don't catch the event when execute /usr/bin/bash -c '/usr/bin/tail /etc/passwd', but catch it when execute /usr/bin/bash", "-c", "echo '/usr/bin/tail /etc/passwd' | /usr/bin/bash

but when I match only current process binary and execute /usr/bin/bash -c '/usr/bin/tail /etc/passwd', I catch the event and see, that parent process binary is /usr/bin/bash..

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we need both /usr/bin/bash -c and | /usr/bin/bash for correct test: the first one executes shell, in which we can use pipe to redirect echo output to shell again.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if I understand. If I do bash -c "tail -1 /etc/hostname", I'm seeing the following event:

{
  "process_exec": {
    "process": {
      "binary": "/usr/bin/tail",
      "arguments": "-1 /etc/hostname",
      "flags": "execve",
    },
    "parent": {
      "binary": "/usr/bin/bash",
      "arguments": "-c \"tail -1 /etc/hostname\"",
    }
  },
}

So I would expect the tail process to be matched by the matchParentBinary selector.

Copy link
Contributor Author

@kobrineli kobrineli Nov 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, there is such event. But it is not matched by selector.
But if you check exec ids of process and parent, they will be the same (like on screenshot I attached above): executing bash -c ... under strace shown that both execve("/usr/bin/bash") and execve("/usr/bin/tail") are called in the same process, and because of the fact that both execve are called within the same process, when we get parent process, we get parent of bash, not tail (so we get grandparent actually).

Unfortunately, I don't see the way to fix it, because when execve call is invoked in the same process, we overwrite execve map value and loose info about bash process. We could pass parent path, which is known, to match_binaries, but we won't have info like mbset, etc, so I suggest to leave it as it is.

Copy link
Contributor Author

@kobrineli kobrineli Nov 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked, event looks like this:

{
  "process_tracepoint": {
    "process": {
      "exec_id": "a2luZC1jb250cm9sLXBsYW5lOjcyMTQ2MzcwMTIzMTc4ODoyMTQ0MzM0",
      "pid": 2144334,
      "uid": 1001,
      "cwd": "/home/kobrineli",
      "binary": "/usr/bin/tail",
      "arguments": "/etc/passwd",
      "flags": "execve",
      "start_time": "2025-11-18T02:00:56.515413635Z",
      "auid": 1001,
      "parent_exec_id": "a2luZC1jb250cm9sLXBsYW5lOjcyMTQ2MzY4NzA2Njc2NzoyMTQ0MzM0",
      "refcnt": 1,
      "cap": {},
      "tid": 2144334
    },
    "parent": {
      "exec_id": "a2luZC1jb250cm9sLXBsYW5lOjcyMTQ2MzY4NzA2Njc2NzoyMTQ0MzM0",
      "pid": 2144334,
      "uid": 1001,
      "cwd": "/home/kobrineli",
      "binary": "/usr/bin/bash",
      "arguments": "-c \"/usr/bin/tail /etc/passwd\"",
      "flags": "execve clone",
      "start_time": "2025-11-18T02:00:56.501249715Z",
      "parent_exec_id": "a2luZC1jb250cm9sLXBsYW5lOjcyMTQ1OTE4NzgwNjY3MToyMTQ0MjU4",
      "cap": {},
      "tid": 2144334
    },
    "subsys": "syscalls",
    "event": "sys_exit_execve",
    "action": "KPROBE_ACTION_POST"
  }
}

process and parent pids, tids are same.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm... I don't see how we could merge this if we cannot catch the above (simple) use-case. One solution would be to keep the parent binary in the execve map, but that's (a lot of) additional memory overhead.

I think that such cases where execve is called in parent process instead of creating a new one with fork is more an exclusion than a normal situation.

Normally processes are created as combination of fork + execve, and we will be able to catch such cases.

I agree, that saving parent process info in execve map will result in big memory overhead.

Maybe it makes sense to add a note about such cases in docs and merge in current state?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that such cases where execve is called in parent process instead of creating a new one with fork is more an exclusion than a normal situation.

Even if just an exception (which I'm not sure if it's the case), we still need to do proper filtering for it. And given how matchBinaries work and what users would expect from a matchParentBinaries, I don't see how documentation could fix this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is that perhaps just an issue with the test? when I run the test by hand it seems to work properly

Copy link
Contributor Author

@kobrineli kobrineli Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is that perhaps just an issue with the test? when I run the test by hand it seems to work properly

The test works correctly.
The problem is that matchParentBinaries selector cannot catch cases in which process and its parent has same pids, because execve is called twice without a fork, like in bash -c <cmd>, so the test with bash -c without | bash wouldn't work.

@kobrineli kobrineli force-pushed the add-match-parents-filter branch from 5d3179d to c85dc58 Compare November 11, 2025 12:39
This commits adds logic for parsing matchParentBinaries selector.
New selector is stored in same maps as matchBinaries selector,
but has key offset equal to MaxSelectors.

Signed-off-by: Kobrin Ilay <[email protected]>
This commit adds bpf code for parent binaries filtering for new matchParentBinaries selector.
Match binaries bpf maps sizes are increased to MAX_SELECTORS * 2 to store both selectors options.
Parent filtering is processed with the same code, but key for matchParentBinaries has offset
equal to MAX_SELECTORS.

Signed-off-by: Kobrin Ilay <[email protected]>
This commit adds test for new matchParentBinaries selector.
In the test we verify that all operations (In, NotIn, Prefix, NotPrefix,
Postfix, NotPostfix) work correctly.

Signed-off-by: Kobrin Ilay <[email protected]>
@kobrineli kobrineli force-pushed the add-match-parents-filter branch from c85dc58 to b810fa7 Compare November 11, 2025 12:42
@kobrineli
Copy link
Contributor Author

I don't know why golangci-lint failed on windows only with issues unrelated to the PR.
Ran make check locally, everything is fine:

make check
docker run --rm -v `pwd`:/app:Z -w /app --env GOTOOLCHAIN=auto docker.io/golangci/golangci-lint:v2.6.0@sha256:cc8c1277eefdb5f88ba1381ee30a8bdf709e3615db9c843c9fcc04d9ac1d27a8 golangci-lint run
0 issues.

@kobrineli
Copy link
Contributor Author

Copy link
Contributor

@kkourt kkourt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Marking this as "Request changes" to avoid accidentally being merged.
See #4254 (comment)

@kobrineli
Copy link
Contributor Author

kobrineli commented Nov 14, 2025

@olsajiri @kkourt
Hi!
I have a solution, which fixes bug described in #4254 (comment).

The bug is that when we match for parent binaries, we search for parent by current process real parent pid. But if execve system call was invoked in process without previous fork system call, i.e. new binary is executed in the same process, matchParentBinaries selector would lose info about real parent of current process (same pid, different binary), and filter by actual grand parent.

There are 2 possible solutions:

  1. save parent binary in execve_map_value, but it will have big memory overhead (value size will increase almost twice)
  2. create binary_heap_map and binaries_map, which we will use to dynamically store info about parent binary only in cases when cleanup process pid is equal to current process pid.
    The map would look like this:
struct {
    __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
    __uint(max_entries, 1);
    __type(key, __u32);
    __type(value, struct binary);
} binary_heap_map SEC(".maps");

struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __uint(max_entries, 1);
    __type(key, __u32);
    __type(value, struct binary);
} binaries_map SEC(".maps");

And we will work with them in bpf_execve_event like this:

        if (curr->key.pid == event->cleanup_key.pid) {
            __u32 zero = 0;
            struct binary *bin = map_lookup_elem(&binary_heap_map, &zero);
            if (bin) {
                memcpy(bin, &curr->bin, sizeof(curr->bin));
                map_update_elem(&binaries_map, &curr->key.pid, bin, BPF_ANY);
            }
        }

And then, when we match parent binaries, we check for the entry in binaries map and if it exists, use it instead of parent binary. I checked, that worked.

Separate map could give us not so big memory overhead as parent binary field in execve map value, because we could set its size to be N times less than execve map (because case when parent pid and current process pid are same is not common)

What do you think about it?

@olsajiri
Copy link
Contributor

There are 2 possible solutions:

1. save parent binary in `execve_map_value`, but it will have big memory overhead (value size will increase almost twice)

2. create `binary_heap_map` and `binaries_map`, which we will use to dynamically store info about parent binary only in cases when cleanup process pid is equal to current process pid.
   The map would look like this:

What do you think about it?

we were discussing this with @kkourt and IIUC we lean to have have documented that both binaries selectors match only existing processes.. so we disregard cases were process does multiple execs

that said, if you would have the change for option 2) it'd be great to check that and see if that might be way out

Adds tests for cases when bash runs binary in same process. Also adds check not only for true positive, but also for true negative.

Signed-off-by: Kobrin Ilay <[email protected]>
Signed-off-by: Kobrin Ilay <[email protected]>
Signed-off-by: Kobrin Ilay <[email protected]>
@kobrineli
Copy link
Contributor Author

kobrineli commented Nov 18, 2025

@olsajiri @kkourt
Hi!
I've pushed changes which fix the problem with matching parent in case of multiple execs in same process.

What changed:

  • now we create parent_binaries_map and binary_heap_map maps, which are used for storing parent binaries info in case of multiple execs
  • parent binaries map size is set to be 32 times less than execve map size, so with default 32768 entries in execve map there will be only 1024 entries in parent binaries size. The number was taken almost randomly, I assume that there will be not many processes created without previous fork, but we can make ratio bigger or smaller
  • if parent binary entry was found when matching parent binaries, we check found parent binary instead of parent process binary

Also I've added more tests, which include running bash with -c option only, so child process is created with parent process pid, later I'm going to add tests with followChildren as well.

I've checked changes locally, now it works fine with all cases, including using followChildren (for such cases with multiple execs saving bitset is already handled (here)[https://github.com/cilium/tetragon/blob/b82408601fe54ece9ae1aa0e94b6d9fe52be3e5f/bpf/lib/process.h#L335] ).

All tests passed, I'll fix checkpatch in the end if everything else is fine.

If you don't like the idea, I'm okay with reverting these changes and documenting that current selectors work only with real processes and don't with for processes created with multiple execs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-note/major This PR introduces major new functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants