diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml new file mode 100644 index 000000000000..afab409cd700 --- /dev/null +++ b/.github/dependabot.yaml @@ -0,0 +1,22 @@ +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + - package-ecosystem: "gomod" + directories: + - "/" + - "/api" + - "/types" + - "/signatures/helpers" + schedule: + interval: "daily" + - package-ecosystem: "docker" + directories: + - "/builder" + schedule: + interval: "daily" diff --git a/.github/labeler.yml b/.github/labeler.yml index 7a1d86401b0d..5e408bfd2fc9 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,82 +1,126 @@ +# Define labels for areas and kinds with match objects + # -# area +# Area Labels # -# area/grpc area/grpc: - - pkg/server/grpc/** - - pkg/server/grpc/**/* -# area/api + - changed-files: + - any-glob-to-any-file: + - pkg/server/grpc/** + - pkg/server/grpc/**/* + area/api: - - api/** - - api/**/* -# area/arm64: + - changed-files: + - any-glob-to-any-file: + - api/** + - api/**/* + area/build: - - builder/** - - "**/Makefile" - - "**/Dockerfile" - - go.sum - - go.mod - - staticcheck.conf - - .github/** - - .clang-format - - .dockerignore + - changed-files: + - any-glob-to-any-file: + - builder/** + - '**/Makefile' + - '**/Dockerfile' + - go.sum + - go.mod + - staticcheck.conf + - .github/** + - .clang-format + - .dockerignore + area/capture: - - "*capture*" -# area/co-re: + - changed-files: + - any-glob-to-any-file: + - '*capture*' + area/ebpf: - - pkg/ebpf/* - - pkg/ebpf/**/* + - changed-files: + - any-glob-to-any-file: + - pkg/ebpf/* + - pkg/ebpf/**/* + area/events: - - pkg/events/* - - pkg/events/**/* + - changed-files: + - any-glob-to-any-file: + - pkg/events/* + - pkg/events/**/* + area/filtering: - - pkg/filters/* - - pkg/filters/**/* + - changed-files: + - any-glob-to-any-file: + - pkg/filters/* + - pkg/filters/**/* + area/flags: - - pkg/cmd/flags/* - - pkg/cmd/flags/**/* + - changed-files: + - any-glob-to-any-file: + - pkg/cmd/flags/* + - pkg/cmd/flags/**/* + area/kubernetes: - - deploy/* - - deploy/**/* + - changed-files: + - any-glob-to-any-file: + - deploy/* + - deploy/**/* + area/logging: - - pkg/logger/* - - pkg/logger/**/* - - pkg/errfmt/* - - pkg/errfmt/**/* -# area/network: + - changed-files: + - any-glob-to-any-file: + - pkg/logger/* + - pkg/logger/**/* + - pkg/errfmt/* + - pkg/errfmt/**/* + area/performance: - - pkg/metrics/* - - pkg/metrics/**/* + - changed-files: + - any-glob-to-any-file: + - pkg/metrics/* + - pkg/metrics/**/* + area/release: - - packaging/* - - packaging/**/* -# area/rules: + - changed-files: + - any-glob-to-any-file: + - packaging/* + - packaging/**/* + area/signatures: - - pkg/signatures/* - - pkg/signatures/**/* - - signatures/* - - signatures/**/* + - changed-files: + - any-glob-to-any-file: + - pkg/signatures/* + - pkg/signatures/**/* + - signatures/* + - signatures/**/* + area/testing: - - tests/* - - tests/**/* - - "**/*_test.go" -# area/uprobe: + - changed-files: + - any-glob-to-any-file: + - tests/* + - tests/**/* + - '**/*_test.go' + area/UX: - - pkg/cmd/* - - pkg/cmd/**/* - - cmd/* - - cmd/**/* + - changed-files: + - any-glob-to-any-file: + - pkg/cmd/* + - pkg/cmd/**/* + - cmd/* + - cmd/**/* + # -# kind +# Kind Labels # + kind/documentation: - - docs/* - - docs/**/* - - "**/*.md" - - mkdocs.yml - - NOTICE - - LICENSE - - cmd/tracee-gptdocs/* + - changed-files: + - any-glob-to-any-file: + - docs/* + - docs/**/* + - '**/*.md' + - mkdocs.yml + - NOTICE + - LICENSE + - cmd/tracee-gptdocs/* + # kind/bug: # kind/chore: # kind/feature: diff --git a/.github/workflows/auto-author-assign.yml b/.github/workflows/auto-author-assign.yml index a2243bdca186..3ad6431dafaa 100644 --- a/.github/workflows/auto-author-assign.yml +++ b/.github/workflows/auto-author-assign.yml @@ -15,4 +15,4 @@ jobs: name: Assign Author runs-on: ubuntu-latest steps: - - uses: toshimaru/auto-author-assign@ebd30f10fb56e46eb0759a14951f36991426fed0 # v2.1.0 + - uses: toshimaru/auto-author-assign@16f0022cf3d7970c106d8d1105f75a1165edb516 # v2.1.1 diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml new file mode 100644 index 000000000000..2aac3ba8380d --- /dev/null +++ b/.github/workflows/codeql.yaml @@ -0,0 +1,53 @@ +name: "CodeQL Advanced" + +on: + push: + branches: + - "main" + - "v*.*.*" + pull_request: + branches: + - "main" + - "v*.*.*" + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + runs-on: 'ubuntu-latest' + permissions: + security-events: write + packages: read + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: c-cpp + build-mode: autobuild + - language: go + build-mode: autobuild + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y libelf-dev clang make gcc pkg-config + + - name: Update submodules + run: git submodule update --init --recursive + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 7eb0a60f1ccb..746bf375f811 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -12,4 +12,7 @@ jobs: pull-requests: write runs-on: ubuntu-latest steps: - - uses: actions/labeler@ac9175f8a1f3625fd0d4fb234536d26811351594 # v4.3.0 + - uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5.0.0 + with: + sync-labels: false + configuration-path: .github/labeler.yml diff --git a/.github/workflows/mkdocs-dev.yaml b/.github/workflows/mkdocs-dev.yaml index 5ea98cf17649..3688a338c03d 100644 --- a/.github/workflows/mkdocs-dev.yaml +++ b/.github/workflows/mkdocs-dev.yaml @@ -20,7 +20,7 @@ jobs: with: fetch-depth: 0 persist-credentials: true - - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 + - uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 with: python-version: 3.12.3 - name: Install dependencies diff --git a/.github/workflows/mkdocs-latest.yaml b/.github/workflows/mkdocs-latest.yaml index 5d3c33b2e6db..2034c0cce14e 100644 --- a/.github/workflows/mkdocs-latest.yaml +++ b/.github/workflows/mkdocs-latest.yaml @@ -20,11 +20,11 @@ jobs: fetch-depth: 0 persist-credentials: true - name: Login to docker.io registry - uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: username: ${{ secrets.DOCKERHUB_USER }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 + - uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 with: python-version: 3.12.3 - name: Install dependencies diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 4f7dd9815d09..b30f0b07e026 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -6,7 +6,8 @@ on: workflow_dispatch: {} pull_request: branches: - - main + - "main" + - "v.*" paths: - "!docs/**" - "!deploy/**" @@ -78,6 +79,7 @@ env: SECURITY_PATH_NOTIFY SET_FS_PWD SUSPICIOUS_SYSCALL_SOURCE + STACK_PIVOT jobs: # # DOC VERIFICATION @@ -90,7 +92,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Check if flags flags man/markdown docs were changed id: changed-files - uses: tj-actions/changed-files@2d756ea4c53f7f6b397767d8723b3a10a9f35bf2 # v44.0.0 + uses: tj-actions/changed-files@d6e91a2266cdb9d62096cebf1e8546899c6aa18f # v45.0.6 with: files_yaml: | md_files: @@ -295,6 +297,7 @@ jobs: output="[" first=1 for job in "${!job_names[@]}"; do + timestamp=$(date +%s) ami="${job_names[$job]%% *}" arch="${job_names[$job]##* }" if (( first )); then @@ -302,7 +305,7 @@ jobs: else output+="," fi - output+="{\"job_name\": \"$job\", \"arch\": \"$arch\", \"ami\": \"$ami\", \"sufix\": \"$num\"}" + output+="{\"job_name\": \"$job\", \"arch\": \"$arch\", \"ami\": \"$ami\", \"sufix\": \"$timestamp\"}" done output+="]" echo "matrix$num=$output" >> $GITHUB_OUTPUT diff --git a/.github/workflows/publish-helm.yaml b/.github/workflows/publish-helm.yaml index 7c89fe9a56f7..8c7983dd1cf9 100644 --- a/.github/workflows/publish-helm.yaml +++ b/.github/workflows/publish-helm.yaml @@ -27,11 +27,11 @@ jobs: ref: ${{ github.event.inputs.ref }} fetch-depth: 0 - name: Install Helm - uses: azure/setup-helm@b7246b12e77f7134dc2d460a3d5bad15bbe29390 # v4.1.0 + uses: azure/setup-helm@fe7b79cd5ee1e45176fcad797de68ecaf3ca4814 # v4.2.0 with: version: v3.14.3 - name: Set up python - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 + uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 with: python-version: 3.12.3 - name: Set up aqua charts @@ -39,9 +39,9 @@ jobs: helm repo add aqua https://aquasecurity.github.io/helm-charts - name: Setup Chart Linting id: lint - uses: helm/chart-testing-action@e6669bcd63d7cb57cb4380c33043eebe5d111992 # v2.6.1 + uses: helm/chart-testing-action@0d28d3144d3a25ea2cc349d6e59901c4ff469b3b # v2.7.0 - name: Setup Kubernetes cluster (KIND) - uses: helm/kind-action@99576bfa6ddf9a8e612d83b513da5a75875caced # v1.9.0 + uses: helm/kind-action@a1b0e391336a6ee6713a0583f8c6240d70863de3 # v1.12.0 with: version: ${{ env.KIND_VERSION }} node_image: ${{ env.KIND_IMAGE }} diff --git a/.github/workflows/release-snapshot.yaml b/.github/workflows/release-snapshot.yaml index 35edaae381ac..4286bcd4343d 100644 --- a/.github/workflows/release-snapshot.yaml +++ b/.github/workflows/release-snapshot.yaml @@ -32,7 +32,7 @@ jobs: # with: # cosign-release: 'v2.2.4' - name: Login to docker.io registry - uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: username: ${{ secrets.DOCKERHUB_USER }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -75,7 +75,7 @@ jobs: # with: # cosign-release: 'v2.2.4' - name: Login to docker.io registry - uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: username: ${{ secrets.DOCKERHUB_USER }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -117,7 +117,7 @@ jobs: # with: # cosign-release: 'v2.2.4' - name: Login to docker.io registry - uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: username: ${{ secrets.DOCKERHUB_USER }} password: ${{ secrets.DOCKERHUB_TOKEN }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 04b8359f38fd..504438d658b5 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -29,11 +29,11 @@ jobs: submodules: true fetch-depth: 0 - name: Install Cosign - uses: sigstore/cosign-installer@e1523de7571e31dbe865fd2e80c5c7c23ae71eb4 # v3.4.0 + uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0 with: cosign-release: 'v2.2.4' - name: Login to docker.io registry - uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: username: ${{ secrets.DOCKERHUB_USER }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -74,11 +74,11 @@ jobs: submodules: true fetch-depth: 0 - name: Install Cosign - uses: sigstore/cosign-installer@e1523de7571e31dbe865fd2e80c5c7c23ae71eb4 # v3.4.0 + uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0 with: cosign-release: 'v2.2.4' - name: Login to docker.io registry - uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: username: ${{ secrets.DOCKERHUB_USER }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -115,11 +115,11 @@ jobs: submodules: true fetch-depth: 0 - name: Install Cosign - uses: sigstore/cosign-installer@e1523de7571e31dbe865fd2e80c5c7c23ae71eb4 # v3.4.0 + uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0 with: cosign-release: 'v2.2.4' - name: Login to docker.io registry - uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: username: ${{ secrets.DOCKERHUB_USER }} password: ${{ secrets.DOCKERHUB_TOKEN }} diff --git a/.github/workflows/test-helm-chart.yaml b/.github/workflows/test-helm-chart.yaml index 234e103b1576..3070cd438a4b 100644 --- a/.github/workflows/test-helm-chart.yaml +++ b/.github/workflows/test-helm-chart.yaml @@ -15,22 +15,22 @@ jobs: fetch-depth: 0 - name: Set up Helm - uses: azure/setup-helm@b7246b12e77f7134dc2d460a3d5bad15bbe29390 # v4.1.0 + uses: azure/setup-helm@fe7b79cd5ee1e45176fcad797de68ecaf3ca4814 # v4.2.0 with: version: v3.14.3 - - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 + - uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 with: python-version: 3.12.3 - name: Set up chart-testing - uses: helm/chart-testing-action@e6669bcd63d7cb57cb4380c33043eebe5d111992 # v2.6.1 + uses: helm/chart-testing-action@0d28d3144d3a25ea2cc349d6e59901c4ff469b3b # v2.7.0 - name: Run chart-testing (lint) run: ct lint --config deploy/helm/ct.yaml --lint-conf deploy/helm/lintconf.yaml --chart-yaml-schema deploy/helm/chart_schema.yaml - name: Create KIND Cluster - uses: helm/kind-action@99576bfa6ddf9a8e612d83b513da5a75875caced # v1.9.0 + uses: helm/kind-action@a1b0e391336a6ee6713a0583f8c6240d70863de3 # v1.12.0 - name: Run chart-testing (install) run: ct install --config deploy/helm/ct.yaml diff --git a/3rdparty/libbpf b/3rdparty/libbpf index 20ea95b4505c..09b9e83102eb 160000 --- a/3rdparty/libbpf +++ b/3rdparty/libbpf @@ -1 +1 @@ -Subproject commit 20ea95b4505c477af3b6ff6ce9d19cee868ddc5d +Subproject commit 09b9e83102eb8ab9e540d36b4559c55f3bcdb95d diff --git a/Makefile b/Makefile index 8ca4813a7321..ecab6b4f7942 100644 --- a/Makefile +++ b/Makefile @@ -320,6 +320,7 @@ help: @echo " $$ STATIC=1 make ... # build static binaries" @echo " $$ BTFHUB=1 STATIC=1 make ... # build static binaries, embed BTF" @echo " $$ DEBUG=1 make ... # build binaries with debug symbols" + @echo " $$ METRICS=1 make ... # build enabling BPF metrics" @echo "" # diff --git a/api/go.mod b/api/go.mod index 9e70b9f17b70..cf355705ecfd 100644 --- a/api/go.mod +++ b/api/go.mod @@ -3,13 +3,13 @@ module github.com/aquasecurity/tracee/api go 1.22.0 require ( - google.golang.org/grpc v1.64.1 - google.golang.org/protobuf v1.34.1 + google.golang.org/grpc v1.69.4 + google.golang.org/protobuf v1.36.3 ) require ( - golang.org/x/net v0.26.0 // indirect - golang.org/x/sys v0.21.0 // indirect - golang.org/x/text v0.16.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect + golang.org/x/net v0.33.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect ) diff --git a/api/go.sum b/api/go.sum index 2cb5f55d5c6a..198fe3fb21cd 100644 --- a/api/go.sum +++ b/api/go.sum @@ -1,14 +1,32 @@ +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 h1:AgADTJarZTBqgjiUzRgfaBchgYB3/WFTC80GPwsMcRI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= -google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= -google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= -google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= -google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= +go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= +go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= +go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= +go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= +go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= +go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= +go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= +go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= +go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= +google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= diff --git a/builder/Dockerfile.k8s b/builder/Dockerfile.k8s index e1312e7d52b5..4dee640d9b2c 100644 --- a/builder/Dockerfile.k8s +++ b/builder/Dockerfile.k8s @@ -1,5 +1,5 @@ -FROM golang:1.22.3 +FROM golang:1.23.5 -RUN go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.13.0 +RUN go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.17.0 WORKDIR /tracee diff --git a/builder/Dockerfile.mkdocs b/builder/Dockerfile.mkdocs index 3a79487d3f6c..a678d26841c1 100644 --- a/builder/Dockerfile.mkdocs +++ b/builder/Dockerfile.mkdocs @@ -1,4 +1,4 @@ -FROM squidfunk/mkdocs-material:8.3.0 +FROM squidfunk/mkdocs-material:9.5.50 RUN pip install --upgrade pip && \ pip install mike && \ diff --git a/builder/Dockerfile.protoc b/builder/Dockerfile.protoc index 9070103d1a99..5efab73d7970 100644 --- a/builder/Dockerfile.protoc +++ b/builder/Dockerfile.protoc @@ -1,4 +1,4 @@ -FROM golang:1.22.7 +FROM golang:1.23.5 ARG PROTOC_VERSION="29.0" # v5.29.0 ARG PROTOC_ZIP="protoc-${PROTOC_VERSION}-linux-x86_64.zip" diff --git a/cmd/tracee/cmd/root.go b/cmd/tracee/cmd/root.go index 71c384524c02..e6e1a475d317 100644 --- a/cmd/tracee/cmd/root.go +++ b/cmd/tracee/cmd/root.go @@ -228,8 +228,8 @@ func initCmd() error { rootCmd.Flags().StringArrayP( "proctree", "t", - []string{"none"}, - "[process|thread]\t\t\tControl process tree options", + []string{"source=none"}, + "[source=[events|signals|both]...]\tControl process tree options", ) err = viper.BindPFlag("proctree", rootCmd.Flags().Lookup("proctree")) if err != nil { diff --git a/deploy/helm/tracee/crds/tracee.aquasec.com_policies.yaml b/deploy/helm/tracee/crds/tracee.aquasec.com_policies.yaml index e82212d3abb7..210bf8a5e332 100644 --- a/deploy/helm/tracee/crds/tracee.aquasec.com_policies.yaml +++ b/deploy/helm/tracee/crds/tracee.aquasec.com_policies.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.17.0 name: policies.tracee.aquasec.com spec: group: tracee.aquasec.com @@ -19,14 +19,19 @@ spec: openAPIV3Schema: properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object diff --git a/docs/contributing/building/building.md b/docs/contributing/building/building.md index cc542fce7da2..8807e58a89fc 100644 --- a/docs/contributing/building/building.md +++ b/docs/contributing/building/building.md @@ -12,43 +12,33 @@ 2. Building **dependencies** - 1. **clang** && **llvm** (12, 13 or 14) - 1. **golang** (1.22.3 toolchain) - 1. **libelf** and **libelf-dev** + 1. `clang` && `llvm` (14) + 2. `golang` (1.22.3 toolchain) + 3. `libelf` and `libelf-dev` (or elfutils-libelf and elfutils-libelf-devel) - 1. **zlib1g** and **zlib1g-dev** + 4. `zlib1g` and `zlib1g-dev` (or zlib and zlib-devel) - 1. **libzstd-dev** for static build (libelf linkage) - 1. **clang-format-12** (specific version) for `fix-fmt` + 5. `libzstd-dev` for static build (libelf linkage) + 6. `clang-format-12` (specific version) for `fix-fmt` > You might take a look at the following files to understand how to have a > building environment: > > 1. [.github/actions/build-dependencies/action.yaml](https://github.com/aquasecurity/tracee/blob/main/.github/actions/build-dependencies/action.yaml) - > 1. [packaging/Dockerfile.ubuntu-packaging](https://github.com/aquasecurity/tracee/blob/main/packaging/Dockerfile.ubuntu-packaging) - > 1. [packaging/Dockerfile.fedora-packaging](https://github.com/aquasecurity/tracee/blob/main/packaging/Dockerfile.fedora-packaging) + > 1. [builder/Dockerfile.ubuntu-tracee-make](https://github.com/aquasecurity/tracee/blob/main/builder/Dockerfile.ubuntu-tracee-make) + > 1. [builder/Dockerfile.alpine-tracee-make](https://github.com/aquasecurity/tracee/blob/main/builder/Dockerfile.alpine-tracee-make) > > Those are very good examples for you to replicate a working environment. 3. **Clone** [tracee repository](https://github.com/aquasecurity/tracee/) - ```console - git clone git@github.com:aquasecurity/tracee - ``` - - ```text - Cloning into 'tracee'... - remote: Enumerating objects: 13251, done. - remote: Counting objects: 100% (555/555), done. - remote: Compressing objects: 100% (240/240), done. - remote: Total 13251 (delta 343), reused 369 (delta 280), pack-reused 12696 - Receiving objects: 100% (13251/13251), 11.75 MiB | 8.62 MiB/s, done. - Resolving deltas: 100% (8105/8105), done. + ```bash + git clone [https://github.com/aquasecurity/tracee/](https://github.com/aquasecurity/tracee/) ``` 4. All makefiles have a **help** target to give you needed instructions - ```console + ```bash make help ``` @@ -68,6 +58,7 @@ $ make e2e-net-signatures # build ./dist/e2e-net-signatures $ make e2e-inst-signatures # build ./dist/e2e-inst-signatures $ make tracee # build ./dist/tracee + $ make tracee-operator # build ./dist/tracee-operator # clean @@ -78,6 +69,7 @@ $ make clean-tracee-bench # wipe ./dist/tracee-bench $ make clean-signatures # wipe ./dist/signatures $ make clean-tracee # wipe ./dist/tracee + $ make clean-tracee-operator # wipe ./dist/tracee-operator # test @@ -90,65 +82,28 @@ $ STATIC=1 make ... # build static binaries $ BTFHUB=1 STATIC=1 make ... # build static binaries, embed BTF $ DEBUG=1 make ... # build binaries with debug symbols + $ METRICS=1 make ... # build enabling BPF metrics + ``` 5. Build **all** targets at once - ```console + ```bash make all ``` - ```text - Submodule 'libbpf' (https://github.com/libbpf/libbpf.git) registered for path '3rdparty/libbpf' - Cloning into '/home/rafaeldtinoco/tracee/3rdparty/libbpf'... - mkdir -p dist/signatures - GOOS=linux CC=clang GOARCH=amd64 CGO_CFLAGS= CGO_LDFLAGS= go build \ - --buildmode=plugin \ - -o dist/signatures/builtin.so \ - signatures/golang/export.go signatures/golang/kubernetes_api_connection.go signatures/golang/stdio_over_socket.go - ``` - 6. Build a **static binary** by setting `STATIC=1` - ```console + ```bash STATIC=1 make all ``` - ```text - CC="clang" \ - CFLAGS=""-fPIC"" \ - LD_FLAGS="" \ - make \ - -C ./3rdparty/libbpf/src \ - BUILD_STATIC_ONLY=1 \ - DESTDIR=/home/rafaeldtinoco/tracee/dist/libbpf \ - OBJDIR=/home/rafaeldtinoco/tracee/dist/libbpf/obj \ - INCLUDEDIR= LIBDIR= UAPIDIR= prefix= libdir= \ - install install_uapi_headers - ... - ``` - 7. Build a **static binary** with [BTFHUB Support](https://github.com/aquasecurity/btfhub) - ```console + ```bash BTFHUB=1 STATIC=1 make all ``` - ```text - Cloning into '/home/rafaeldtinoco/tracee/3rdparty/btfhub'... - remote: Enumerating objects: 205, done. - remote: Counting objects: 100% (16/16), done. - remote: Compressing objects: 100% (12/12), done. - remote: Total 205 (delta 4), reused 10 (delta 3), pack-reused 189 - Receiving objects: 100% (205/205), 10.59 MiB | 7.56 MiB/s, done. - Resolving deltas: 100% (73/73), done. - Cloning into '/home/rafaeldtinoco/tracee/3rdparty/btfhub-archive'... - remote: Enumerating objects: 1993, done. - remote: Counting objects: 100% (28/28), done. - remote: Compressing objects: 100% (23/23), done. - Receiving objects: 15% (301/1993), 154.97 MiB | 15.72 MiB/s - ``` - !!! Note BTFHUB support will embed several very small files (BTF files) into your final binary. Those files will allow **tracee** binary to be executed @@ -163,19 +118,16 @@ >plugin.Open("/tracee/dist/signatures/builtin.so"): Dynamic loading not supported >``` -8. Build a **debugable binary** with DWARF generation by setting `DEBUG=1` +8. Build a **debuggable binary** with DWARF debug symbols by setting `DEBUG=1` - ```console + ```bash DEBUG=1 make ``` - - ```text - GOOS=linux CC=clang GOARCH=amd64 CGO_CFLAGS="-I/home/gg/code/tracee/dist/libbpf" CGO_LDFLAGS="-lelf -lz /home/gg/code/tracee/dist/libbpf/libbpf.a" go build \ - -tags core,ebpf \ - -ldflags=" \ - -extldflags \"\" \ - -X main.version=\"v0.8.0-107-g121efeb\" \ - " \ - -v -o dist/tracee \ - ./cmd/tracee + +9. Build enabling BPF metrics by setting `METRICS=1` + + BPF metrics are only available if the BPF object is built with `METRICS` debug flag defined. + + ```bash + METRICS=1 make ``` diff --git a/docs/contributing/building/environment.md b/docs/contributing/building/environment.md index 0926103b6389..ab13b6630834 100644 --- a/docs/contributing/building/environment.md +++ b/docs/contributing/building/environment.md @@ -14,72 +14,43 @@ If you want to build tracee on your local machine [read this](./building.md). +## Quick steps -## Quick steps (**impatient readers**) +1. Build tracee environment: -!!! Example - - * Build and execute **tracee**: - - ```console - make -f builder/Makefile.tracee-make alpine-prepare - make -f builder/Makefile.tracee-make alpine-shell - ``` - - and inside the container: - - ```console - make clean - make tracee - sudo ./dist/tracee \ - -o option:parse-arguments \ - --scope comm=bash \ - --scope follow - ``` - - Now, in your host's bash shell, execute a command. You will see all events - (except scheduler ones) being printed, in "table format", to stdout. - - * Build and execute **tracee**: - - ```console - make -f builder/Makefile.tracee-make alpine-prepare - make -f builder/Makefile.tracee-make alpine-shell - ``` + ```bash + make -f builder/Makefile.tracee-make alpine-prepare + make -f builder/Makefile.tracee-make alpine-shell + ``` - and inside the container: +2. Build and execute tracee: - ```console - make clean - make all - sudo ./dist/tracee \ - -o format:json \ - -o option:parse-arguments \ - --scope comm=bash \ - --scope follow - ``` - - Now, in your host's bash shell, execute: `sudo strace /bin/ls` and observe - tracee warning you about a possible risk (with its Anti-Debugging signature). + ```bash + make clean + make tracee + sudo ./dist/tracee \ + -o option:parse-arguments \ + --scope comm=bash \ + --scope follow + ``` -Now, for **more patient readers** ... +Now, in your host's shell, execute a command. You will see all events +(except scheduler ones) being printed, in "table format", to stdout. ## How to build and use the environment In order to have a controlled building environment for tracee, tracee provides -a `Makefile.tracee-make` file that allows you to create and use a docker -container environment to build & test **tracee**. +a `Makefile.tracee-make` file that allows you to create and use a docker container environment to build & test **tracee**. -Two different environments are maintained for building tracee: +There are Two different environments that are maintained for building tracee: -* Alpine -* Ubuntu +* **Alpine** +* **Ubuntu** -The reason for that is that **Alpine Linux** is based in the -[musl](https://en.wikipedia.org/wiki/Musl) C standard library, while the -**Ubuntu Linux** uses [glibc](https://en.wikipedia.org/wiki/Glibc). By -supporting both building environments we can always be sure that the project -builds (and executes) correctly in both environments. +The reason for that is that `Alpine Linux` is based in the [musl](https://en.wikipedia.org/wiki/Musl) C standard library, +while the `Ubuntu Linux` uses [glibc](https://en.wikipedia.org/wiki/Glibc). + +By supporting both building environments we can always be sure that the project builds (and executes) correctly in both environments. !!! Attention Locally created containers, called `alpine-tracee-make` or @@ -91,13 +62,13 @@ builds (and executes) correctly in both environments. * To create an **alpine-tracee-make** container: - ```console + ```bash make -f builder/Makefile.tracee-make alpine-prepare ``` * To create an **ubuntu-tracee-make** container: - ```console + ```bash make -f builder/Makefile.tracee-make ubuntu-prepare ``` @@ -105,67 +76,80 @@ builds (and executes) correctly in both environments. * To execute an **alpine-tracee-make** shell: - ```console + ```bash make -f builder/Makefile.tracee-make alpine-shell ``` * To execute an **ubuntu-tracee-make** shell: - ```console + ```bash make -f builder/Makefile.tracee-make ubuntu-shell ``` ### Using build environment as a **make** replacement -Instead of executing a builder shell, you may use `alpine-tracee-make`, or -`ubuntu-tracee-make`, as a replacement for the `make` command: +Instead of executing a builder shell, you may use `alpine-make`, or +`ubuntu-make`, as a replacement for the `make` command: -```console -make -f builder/Makefile.tracee-make ubuntu-prepare -make -f builder/Makefile.tracee-make ubuntu-make ARG="help" -make -f builder/Makefile.tracee-make ubuntu-make ARG="clean" -make -f builder/Makefile.tracee-make ubuntu-make ARG="bpf" -make -f builder/Makefile.tracee-make ubuntu-make ARG="tracee" -make -f builder/Makefile.tracee-make ubuntu-make ARG="all" -``` +1. Create builder environment as described: + [Creating a builder environment](#creating-a-builder-environment) +2. Compile tracee using `ubuntu-make` -And, after the compilation, run the commands directly in your host: + * Build tracee binary: -```console -sudo ./dist/tracee \ - -o option:parse-arguments \ - --scope comm=bash \ - --scope follow -``` + ```bash + make -f builder/Makefile.tracee-make ubuntu-make ARG="tracee" + ``` -> **Note**: the generated binary must be compatible to your host (depending on -> glibc version, for example). + * Show available `ubuntu-make` commands: -If you don't want to depend on host's libraries versions, or if you are using -the `alpine-tracee-make` container as a replacement for `make`, and your host -is not an **Alpine Linux**, then you may set `STATIC=1` variable so you can run -compiled binaries in your host: + ```bash + make -f builder/Makefile.tracee-make ubuntu-make ARG="help" + ``` -```console -make -f builder/Makefile.tracee-make alpine-prepare -make -f builder/Makefile.tracee-make alpine-make ARG="help" -STATIC=1 make -f builder/Makefile.tracee-make alpine-make ARG="all" -``` + * Remove tracee binary -and execute the static binary from your host: + ```bash + make -f builder/Makefile.tracee-make ubuntu-make ARG="clean" + ``` -```console -ldd dist/tracee -``` + * Build binaries for all -```text -not a dynamic executable -``` + ```bash + make -f builder/Makefile.tracee-make ubuntu-make ARG="all" + ``` -!!! Attention - compiling **tracee-rules** with STATIC=1 won't allow you to use golang based - signatures: - > ```text - > 2021/12/13 13:27:21 error opening plugin /tracee/dist/signatures/builtin.so: - > plugin.Open("/tracee/dist/signatures/builtin.so"): Dynamic loading not supported - > ``` +3. Run tracee binary + + ```bash + sudo ./dist/tracee + ``` + +> **Note**: the generated binary must be compatible to your host (depending on glibc version). + +If you don't want to depend on host's libraries versions, or you are using the `alpine-make` container as a replacement for `make`, then it's necessary to set `STATIC` variable to `1` so you can run compiled binaries in your host machine: + +1. Compile tracee + + ```bash + make -f builder/Makefile.tracee-make alpine-prepare + STATIC=1 make -f builder/Makefile.tracee-make alpine-make ARG="all" + ``` + +2. Verify the executable is static + + * Note: ldd prints the shared libraries required by an executable file + + ```bash + ldd dist/tracee + ``` + + ```text + not a dynamic executable + ``` + +3. Execute the static binary from your host + + ```bash + sudo ./dist/tracee + ``` diff --git a/docs/contributing/documentation.md b/docs/contributing/documentation.md deleted file mode 100644 index c178dccefadd..000000000000 --- a/docs/contributing/documentation.md +++ /dev/null @@ -1,40 +0,0 @@ -# Contributing to the documentation - -We welcome contributions to the Tracee documentation. - -Our documentation aims to follow the [Diátaxis documentation framework](https://diataxis.fr/) which includes: - -1. A getting started section -- Installation Guidelines and simple scenarios -2. Tutorials -- End-to-end tutorials that have real-world use cases -3. Reference Material -- How to use Tracee -4. Contribution Guidelines - -To contribute to the documentation please - -1. Clone the Tracee GitHub repository -2. Make changes in the `./docs` directory in the root folder -3. Remember to edit the index in `mkdocs.yml` in the root folder if you add or move existing files or directories -4. Test your changes to the documentation -5. Submit a PR - -## Test Documentation Changes - -You can test your changes to the documentation by building and running a docker container. - -Prerequisites: Docker installed and running locally. - -Build the container image: -```bash -make -f ./builder/Makefile.mkdocs mkdocs-build -``` - -Serve the container image: -```bash -make -f ./builder/Makefile.mkdocs mkdocs-serve -``` - -Open `localhost:8000/tracee` - -Now you should see the documentation. - - diff --git a/docs/contributing/guidelines.md b/docs/contributing/guidelines.md index 864bba54ca5d..f66958b87aea 100644 --- a/docs/contributing/guidelines.md +++ b/docs/contributing/guidelines.md @@ -1,80 +1,111 @@ -## Style Guide for Contributors +# Style Guide for Contributors For those willing to contribute to Tracee, this repository has code formatting -guidelines being enforced. It is recommended that, before committing your -changes, you run the following command: +guidelines being enforced. -1. Check for formatting issues +## Contributing to Documentation -!!! check-fmt Example +Our documentation aims to follow the [Diátaxis documentation framework](https://diataxis.fr/). - ```console - make check-fmt - ``` +To contribute to the documentation: - ```text - Checking C and eBPF files and headers formatting... - Checking golang files formatting... - ``` - - > This will make sure PRs won't fail due to same checks being enforced. +1. Clone the Tracee GitHub repository. +2. Make changes in the `/docs` directory in the root folder. +3. Remember to edit the index in `mkdocs.yml` if you add or move existing files or directories. +4. Test your changes to the documentation. -2. Fix Go and C source files formatting +### Test Documentation Changes -!!! fix-fmt Example +You can test your changes to the documentation by building and running a docker container. +Prerequisites: Docker installed and running locally. +Build the container image: - ```console - make fix-fmt - ``` +```bash +make -f ./builder/Makefile.mkdocs mkdocs-build +``` - ```text - Fixing C and eBPF files and headers formatting... - Formatting ./pkg/ebpf/c/missing_definitions.h - Formatting ./pkg/ebpf/c/struct_flavors.h - Formatting ./pkg/ebpf/c/tracee.bpf.c - Formatting ./pkg/ebpf/c/vmlinux.h - - Fixing golang files formatting... - patching file pkg/ebpf/tracee.go - ``` +Serve the container image: + +```bash +make -f ./builder/Makefile.mkdocs mkdocs-serve +``` + +Open `localhost:8000/tracee` + +Now you should see the documentation. +Check that everything is like you intent. + +And finally Submit a PR about the changes. - ```console - git status -s +## Contributing to Tracee Code + +We welcome contributions to Tracee's codebase! Before submitting your changes, please familiarize yourself with these guidelines. +To contribute to the code: + +### Before You Commit + +Tracee relies on several generated files and has strict formatting requirements. Ensure you run the following commands before committing: + +**`NOTE:`** In order to not depend on host's libraries versions, we recommend that you always run make and other project dependencies on a virtual environment so the formatting will be align with Tracee guidelines + +1. Man Pages Generation: If you've modified core code or documentation that impacts the man pages, run: + + ```bash + make -f builder/Makefile.man ``` - ```text - M Makefile - M builder/Makefile.checkers - M pkg/ebpf/c/missing_definitions.h - M pkg/ebpf/c/struct_flavors.h - M pkg/ebpf/c/tracee.bpf.c - M pkg/ebpf/c/vmlinux.h + This regenerates the man pages to reflect your changes. + +2. Protocol Buffer Compilation: If your changes involve modifications to protocol buffer (`.proto`) files,run: + + ```bash + make -f builder/Makefile.man ``` -3. Static Check Go and C source files + This regenerates the corresponding Go code. +3. Pre-commit checks: Every time you're about to create a pull request, execute: -!!! check-code Example + **`NOTE:`** If your host machine dependencies doesn't align with Tracee dependencies, This command have to run on a supported [environment](./building/environment.md) - ```console - make check-code + ```bash + make check-pr ``` - ```text - Checking Golang vet... - make[2]: warning: jobserver unavailable: using -j1. Add '+' to parent make rule. - GOOS=linux CC=clang GOARCH=amd64 CGO_CFLAGS="-I/home/rafaeldtinoco/work/ebpf/tracee-review/dist/libbpf" CGO_LDFLAGS="-lelf -lz /home/rafaeldtinoco/work/ebpf/tracee-review/dist/libbpf/libbpf.a" \ - go vet \ - -tags core,ebpf \ - ./... - - Checking Golang with StaticChecker... - make[2]: warning: jobserver unavailable: using -j1. Add '+' to parent make rule. - GOOS=linux CC=clang GOARCH=amd64 CGO_CFLAGS="-I/home/rafaeldtinoco/work/ebpf/tracee-review/dist/libbpf" CGO_LDFLAGS="-lelf -lz /home/rafaeldtinoco/work/ebpf/tracee-review/dist/libbpf/libbpf.a" \ - staticcheck -f stylish \ - -tags core,ebpf \ - ./... - ✖ 0 problems (0 errors, 0 warnings, 0 ignored) - - Checking Golang with errcheck... + This command performs essential checks: + - `check-fmt`: Verifies code formatting adheres to project standards. + - `check-lint`: Runs linting tools (e.g., `golangci-lint`) to catch potential issues. + - `check-code`: Performs static code analysis for both Go and C code. + - `format-pr`: Displays the commits in your PR in a standardized format. + + **Note:** `check-fmt`,`check-lint`,`check-code`,`format-pr` are individual make command combined under `check-pr`. You can run the following command without any vm using Makefile + + - For `check-fmt`: + + ```bash + make -f builder/Makefile.checkers fmt-check + ``` + + - For `check-code`: + + ```bash + make -f builder/Makefile.checkers code-check + ``` + +4. Fixing Code Formatting: If `check-fmt` reports issues, use: + + ```bash + make -f builder/Makefile.checkers fmt-fix ``` + + This automatically formats your Go and C code to meet project standards. Review the changes with `git status -s` before committing. + +### Performance Considerations + +Performance is a critical aspect of Tracee. + +To ensure your contributions maintain optimal performance, follow the guidelines in [Performance Considerations](./performance.md) page. + +### Kubernetes Considerations + +If your contribution impacts Tracee's behavior within a Kubernetes cluster, follow the guidelines in [Kubernetes Considerations](./kubernetes.md). diff --git a/docs/contributing/kubernetes.md b/docs/contributing/kubernetes.md new file mode 100644 index 000000000000..dc4965cf9dd7 --- /dev/null +++ b/docs/contributing/kubernetes.md @@ -0,0 +1,66 @@ +# Kubernetes Considerations for Tracee Contributors + +1. **Set up a Local Kubernetes Cluster** + + We recommend using a local Kubernetes cluster for development and testing. Popular options include: + + * **kind:** Kubernetes IN Docker + * **minikube:** Runs a single-node cluster in a VM + * **MicroK8s:** Lightweight, snap-based Kubernetes + + Tracee's Makefile provides convenient targets for setting up MicroK8s: + + ```bash + make -f builder/Makefile.k8s help + ``` + + ```text + To build the operator docker container: + + $ make -f builder/Makefile.k8s build + + To generate the kubernetes manifests: + + $ make -f builder/Makefile.k8s manifests + + To generate operator code: + + $ make -f builder/Makefile.k8s generate + + Or simply: + + $ make -f builder/Makefile.k8s + ``` + +2. **Deploy Tracee** + + Deploy Tracee to your chosen local cluster. The deployment method will vary depending on your needs: + + * **DaemonSet:** For system-wide tracing, deploy Tracee as a DaemonSet. + * **Tracee Operator:** Use the Tracee operator for simplified management. + * **Other Methods:** Explore alternative methods like sidecar containers based on your use case. + +3. **Test Your Changes** + + Test your modifications with realistic scenarios within the Kubernetes environment. Consider these examples: + + * **Application Monitoring:** Deploy sample applications and observe Tracee's event capture for expected behavior. + * **Network Policies:** Configure network policies to restrict pod communication and verify Tracee captures relevant network events. + * **Resource Constraints:** Apply resource limits to pods and ensure Tracee functions correctly under constrained conditions. + * **Security Policies:** Implement Pod Security Policies and/or Security Contexts to validate Tracee's compliance and event capture in secure environments. + +4. **Monitor Tracee's Performance** + + Utilize Kubernetes' observability tools to monitor Tracee: + + * **Resource Usage:** Track pod resource consumption (CPU, memory) using `kubectl top` or monitoring dashboards. + * **Logs:** Analyze Tracee logs for errors, warnings, or unexpected behavior using `kubectl logs`. + * **Kubernetes Metrics:** If available, leverage Kubernetes metrics to gain insights into Tracee's performance. + +5. **Clean Up** + + After testing, remove deployed Tracee components and test resources to maintain a clean cluster environment. + +## Kubernetes-Specific Features + +If your contribution involves Kubernetes-specific features within Tracee (e.g., capturing Kubernetes events or metadata), ensure you test these functionalities thoroughly within the cluster environment. Pay close attention to event accuracy and any integration with Kubernetes APIs. diff --git a/docs/contributing/overview.md b/docs/contributing/overview.md index 04884f78cf46..570c5f7c7963 100644 --- a/docs/contributing/overview.md +++ b/docs/contributing/overview.md @@ -1,4 +1,4 @@ -## Contributing +# Contributing Thank you for taking interest in contributing to Tracee! This document covers our working practices and conventions. @@ -6,48 +6,65 @@ Thank you for taking interest in contributing to Tracee! This document covers ou We encourage open discussion and collaboration using both GitHub Issues and Discussions. -- [Discussions](https://github.com/aquasecurity/tracee/discussions) are free-style conversational tool, we use them for conversations. +- [Discussions](https://github.com/aquasecurity/tracee/discussions) are free-style conversational tool. - [Issues](https://github.com/aquasecurity/tracee/issues) are project management tool, we use them to keep track on who's working on what and plan ahead. If you have a suggestion, question, or a general comment - please use Discussions. If there's a clear work item (including bugs) - you can open an Issue. -### Discussions: +## Discussions -- We have the following discussion topics: - 1. [Announcements](https://github.com/aquasecurity/tracee/discussions/categories/announcements): One way communication from the team to the community. Consider this like our mini blog +- We have the following discussion topics: + 1. [Announcements](https://github.com/aquasecurity/tracee/discussions/categories/announcements): One way communication from the team to the community. Consider this like our mini blog. 1. [Questions and Help](https://github.com/aquasecurity/tracee/discussions/categories/questions-and-help): For help and support. Consider this similar to StackOverflow. 1. [Development](https://github.com/aquasecurity/tracee/discussions/categories/development): For discussing potential features, and collaborating on their design. -### Issues: - -1. Every issue needs to be actionable and assignable. Consider the scope of the issue if assigned to one person, and break down if necessary. -1. Be clear and definitive when composing issues. For bug reports, include detailed error messages and environment description. For features, include a clear scope and acceptance criteria. -1. Since we have different projects under the same monorepo, use labels to denote areas that the issue relates to: - 1. `tracee` - 1. `tracee-ebpf` - 1. `tracee-rules` - 1. `signatures` - 1. If non of the labels is relevant don't add any (usually for top-level issues) -1. We use the following labels to describe the type of issue: - 1. `bug` - 1. `good-first-issue` -1. Self-assign or request assignment for issues you intend to work on. Don't work on an issue assigned to someone else without checking with them first and reassigning. +## Issues + +1. **Actionable and Assignable:** Every issue must be clear, actionable, and assignable to a specific person. Break down large issues into smaller, more manageable tasks. +2. **Clear and Definitive Descriptions:** Be precise in your issue descriptions: + - **Bug Reports:** Include the following: + - Detailed steps to reproduce the bug. + - The complete error message. + - Your operating system, Tracee version, and any other relevant environment details. + - **Feature Requests:** Define: + - A clear scope for the feature. + - Specific acceptance criteria that will be used to determine if the feature is complete. +3. **Issue Labels:** We use these labels to categorize and track issues: + - `area/...` (e.g., `ebpf`, `events`): Specifies the area of Tracee affected by the issue. + - `kind/...` (e.g., `bug`, `chore`, `enhancement`, `documentation`): Indicates the type of issue. + - `milestone/...`: Specifies the target release for the issue. + - `priority/...`: Indicates the urgency of the issue. + - `good-first-issue`: Marks issues suitable for first-time contributors. + - `backport`: Applies to PRs targeting release branches for integrating changes from `main`. The original `main` PR gets labeled `backported/vX.X.X` after merge. + - `cherry-pick`: Similar to `backport`, but for specific commits. The original `main` PR gets labeled `cherry-picked/vX.X.X` after merge. + - `candidate/...`: (e.g., `candidate/v0.1.2`) Identifies PRs in the `main` branch as candidates for backporting or cherry-picking to a release branch (e.g., `v0.1.2`). The specific method will be determined during the porting process. + - `backported/...` (e.g., `backported/v0.1.2`): Marks PRs in the `main` branch as the basis for backporting changes to a release branch (e.g., `v0.1.2`) after the porting process. + - `cherry-picked/...` (e.g., `cherry-picked/v0.1.2`): Marks PRs in the `main` branch as the basis for cherry-picking commits to a release branch (e.g., `v0.1.2`) after the porting process. +4. **Issue Assignment:** Self-assign issues or request assignment. Don't work on an issue assigned to someone else without their consent. + +**Backporting and Cherry-Picking Workflow:** + +To backport or cherry-pick a change: + +1. Create a new PR targeting the appropriate release branch. +2. Label the new PR with `backport` or `cherry-pick`, depending on the method used. +3. Once the new PR is merged, remove the `candidate/vX.X.X` label from the original PR in `main`. +4. Finally, add the `backported/vX.X.X` or `cherry-picked/vX.X.X` label to the original PR in `main`, as appropriate. ## Pull Requests 1. Every Pull Request should have an associated Issue unless it is a trivial fix. -1. When adding a flag option or other UX related change, make sure the design is explicitly described in the associated issue, and a maintainer approved it. -1. Commit subject should succinctly describe the change: +2. When adding a flag option or other UX related change, make sure the design is explicitly described in the associated issue, and a maintainer approved it. +3. Commit subject should succinctly describe the change: 1. Max 50 chars. - 1. Written in imperative mood: begin with a verb like "fix", "add", "improve", or "refactor"; Think "once applied, this commit will...". - 1. If ambiguous, mention the area that this commit affects (see area labels above). -1. Optional commit body (separated by empty line from subject) may explain why the change was made and not how. Wrap at 72 chars. -1. Code related information should be in commit message, review related information should be in PR description. -1. For changes that span different areas please try to make each change self contained and independent. - + 2. Written in imperative mood: begin with a verb like "fix", "add", "improve", or "refactor"; Think "once applied, this commit will...". + 3. If ambiguous, mention the area that this commit affects (see area labels above). +4. Optional commit body (separated by empty line from subject) may explain why the change was made and not how. Wrap at 72 chars. +5. Code related information should be in commit message, review related information should be in PR description. +6. For changes that span different areas please try to make each change self contained and independent. ## Code -1. Follow Golang's code review standards: https://github.com/golang/go/wiki/CodeReviewComments. -1. Follow `gofmt` + `govet` + `goimports` formatting. -1. Tests should be included alongside code changes wherever applicable, except for parts that are harder to test and are not currently tested (e.g. eBPF). When modifying already tested code, your changes must be represented in the existing tests. +1. Follow Golang's code review standards: [https://github.com/golang/go/wiki/CodeReviewComments](https://github.com/golang/go/wiki/CodeReviewComments) +2. Follow `gofmt` + `govet` + `goimports` formatting. +3. Tests should be included alongside code changes wherever applicable, except for parts that are harder to test and are not currently tested (e.g. eBPF). When modifying already tested code, your changes must be represented in the existing tests. diff --git a/docs/contributing/performance.md b/docs/contributing/performance.md new file mode 100644 index 000000000000..f7d782c3307b --- /dev/null +++ b/docs/contributing/performance.md @@ -0,0 +1,33 @@ +# Performance Considerations + +1. **Profiling Tracee for Performance test** - Tracee integrates with Pyroscope and Pprof for continuous profiling. When running Tracee locally for development or testing, use the `--pyroscope --pprof` command-line option. + + ```bash + sudo ./dist/tracee --pyroscope --pprof + ``` + + This enables profiling data to be sent to a local server. The Tracee repository includes a convenient way to deploy a performance dashboard for analyzing this data. Run the following for more details: + + ```bash + make -f builder/Makefile.performance help + ``` + +2. **Performance Dashboard:** The provided performance dashboard allows visualization of host metrics, CPU flame graphs, and other performance-related data. Follow these steps to deploy locally and see instructions on using the dashboard: + + ```bash + make -f builder/Makefile.performance dashboard-start + ``` + + ```bash + make -f builder/Makefile.performance dashboard-stop + ``` + +3. **Benchmarking:** Before submitting significant code changes, consider running benchmarks to assess their impact on Tracee's performance. (Details on specific benchmarking tools or scripts used within the Tracee project should be added here. If there are existing benchmarks, provide instructions on how to run them. If not, suggest a methodology). + +4. **Common Performance Pitfalls:** + + - **Excessive eBPF Events:** Be mindful of the number and frequency of eBPF events being generated. Overly frequent events can lead to performance overhead. (Provide Tracee-specific examples or best practices to avoid this, such as filtering events effectively or using appropriate sampling rates.) + - **Inefficient eBPF Programs:** Optimize your eBPF programs for minimal overhead. (Provide Tracee-specific guidance. Are there common patterns to avoid within Tracee's eBPF context?) + - **Resource Consumption:** Consider the CPU and memory usage of Tracee itself. Avoid unnecessary allocations or computations. + +By adhering to these practices, you can contribute to Tracee's performance and help ensure its efficiency. Remember that performance is an ongoing effort, so continuous profiling, benchmarking, and optimization are essential. diff --git a/docs/contributing/setup-development-machine-with-vagrant.md b/docs/contributing/setup-development-machine-with-vagrant.md index 1e1df0f95117..fcf8da29daaf 100644 --- a/docs/contributing/setup-development-machine-with-vagrant.md +++ b/docs/contributing/setup-development-machine-with-vagrant.md @@ -4,10 +4,9 @@ all software requirements, packages, operating system configuration, and users to provide the same development environment for everyone. -The [Vagrantfile] describes the type of machine required to build Tracee from -source and follow the [Getting Started](../index.md) guides. This allows -developers involved in the project to check out the code, run `vagrant up`, and -be on their way. +The [Vagrantfile](/Vagrantfile) describes the type of machine required to build Tracee from the [Getting Started](../index.md) guides. + +This allows developers involved in the project to check out the code, run `vagrant up`, and be on their way. ## Prerequisites @@ -15,314 +14,99 @@ be on their way. - [Hypervisor] supported by Vagrant, such as [VirtualBox] on a amd64 (Linux) machine or [Parallels] on an arm64 M1 (Darwin) machine. +## Clone the Tracee Repository -## Create Development Machine - -Clone and change directory to Tracee Git repository: - -```console -git clone --branch {{ git.tag }} https://github.com/aquasecurity/tracee.git -cd tracee -``` - -Create and configure development machine according to the `Vagrantfile`: - -```console -vagrant up -``` - -If everything goes well, you can SSH into a running development machine and -access its shell: - -```console -vagrant ssh -``` - -```text -vagrant@ubuntu-jammy:/vagrant$ -``` - -!!! tip - Provisioning from scratch take time, but once created you can reuse the - machine with `vagrant halt` and `vagrant up` commands. If something goes - wrong with your machine, there's also the `vagrant destroy` to destroy it - and start over again. - -Synced folders enable Vagrant to sync a folder on the host machine to the -development machine, allowing you to continue working on your project's files -on your host machine, but use the resources in the development machine to -compile or run Tracee. - -By default, Vagrant will share Tracee project directory (the directory with the -`Vagrantfile`) to `/vagrant`. To get started list files: - -```console -ls -l -``` - -```text -total 648 -drwxr-xr-x 1 vagrant vagrant 4096 Mar 22 23:43 3rdparty --rw-r--r-- 1 vagrant vagrant 11358 Mar 18 14:45 LICENSE --rw-r--r-- 1 vagrant vagrant 21821 Mar 27 13:40 Makefile --rw-r--r-- 1 vagrant vagrant 133 Mar 18 14:45 NOTICE --rw-r--r-- 1 vagrant vagrant 2643 Mar 29 18:30 RELEASING.md --rw-r--r-- 1 vagrant vagrant 2238 Mar 22 23:43 Readme.md --rw-r--r-- 1 vagrant vagrant 3337 Mar 22 23:43 Vagrantfile -drwxr-xr-x 1 vagrant vagrant 4096 Mar 29 18:05 brand -drwxr-xr-x 1 vagrant vagrant 4096 Mar 22 23:43 builder -drwxr-xr-x 1 vagrant vagrant 4096 Mar 22 23:43 cmd --rw-r--r-- 1 vagrant vagrant 415013 Mar 28 23:17 coverage.txt -drwxr-xr-x 1 vagrant vagrant 4096 Mar 18 14:45 deploy -drwxr-xr-x 1 vagrant vagrant 4096 Mar 29 18:15 dist -drwxr-xr-x 1 vagrant vagrant 4096 Mar 22 23:43 docs --rw-r--r-- 1 vagrant vagrant 164 Mar 18 14:45 embedded-ebpf.go --rw-r--r-- 1 vagrant vagrant 101 Mar 18 14:45 embedded.go -drwxr-xr-x 1 vagrant vagrant 4096 Mar 27 12:08 examples --rw-r--r-- 1 vagrant vagrant 5599 Mar 29 17:22 go.mod --rw-r--r-- 1 vagrant vagrant 77170 Mar 29 17:22 go.sum --rw-r--r-- 1 vagrant vagrant 40206 Mar 22 23:43 mkdocs.yml -drwxr-xr-x 1 vagrant vagrant 4096 Mar 22 23:43 packaging -drwxr-xr-x 1 vagrant vagrant 4096 Mar 22 23:43 pkg -drwxr-xr-x 1 vagrant vagrant 4096 Mar 18 14:45 signatures --rw-r--r-- 1 vagrant vagrant 157 Mar 22 23:43 staticcheck.conf -drwxr-xr-x 1 vagrant vagrant 4096 Mar 24 15:44 tests -drwxr-xr-x 1 vagrant vagrant 4096 Mar 22 23:43 types -``` - -As you can see the `/vagrant` directory contains source code of Tracee cloned -from GitHub. - -## Build and Run Tracee - -To build **tracee** executable binary, run the -default make target: +Clone the Tracee repository to your local machine. This repository contains the Vagrantfile. -```console -make +```bash +git clone https://github.com/aquasecurity/tracee.git ``` -Build targets are saved in the `/vagrant/dist` directory: +## Navigate to the Tracee Directory -```console -ls -l dist/ -``` - -```text -total 161096 -drwxr-xr-x 1 vagrant vagrant 4096 Mar 29 19:06 btfhub -drwxr-xr-x 1 vagrant vagrant 4096 Mar 29 19:06 libbpf -drwxr-xr-x 1 vagrant vagrant 4096 Mar 29 19:08 signatures --rwxr-xr-x 1 vagrant vagrant 62619312 Mar 29 19:08 tracee --rw-r--r-- 1 vagrant vagrant 10753624 Mar 29 19:06 tracee.bpf.o -``` +Open a terminal and navigate to the directory containing the `Vagrantfile` within the cloned Tracee repository (`tracee/`) -You can now run Tracee and see events printed to the standard output in a tabular format: - -```console -sudo ./dist/tracee +```bash +cd tracee ``` -```text -TIME UID COMM PID TID RET EVENT ARGS -19:10:09:453832 0 coredns 1 8 0 security_socket_connect sockfd: 13, remote_addr: map[sa_family:AF_INET sin_addr:0.0.0.0 sin_port:8080] -19:10:09:454179 0 coredns 1 9 0 security_socket_accept sockfd: 8, local_addr: map[sa_family:AF_INET6 sin6_addr::: sin6_flowinfo:0 sin6_port:8080 sin6_scopeid:0] -19:10:09:454265 0 coredns 1 9 0 security_socket_accept sockfd: 8, local_addr: map[sa_family:AF_INET6 sin6_addr::: sin6_flowinfo:0 sin6_port:8080 sin6_scopeid:0] -19:10:09:454478 0 coredns 1 14 0 net_packet_http_request metadata: {127.0.0.1 127.0.0.1 43306 8080 6 144 any}, http_request: &{GET HTTP/1.1 :8080 /health map[Accept-Encoding:[gzip] User-Agent:[Go-http-client/1.1]] 0} -19:10:09:454774 0 coredns 1 14 0 net_packet_http_response metadata: {127.0.0.1 127.0.0.1 8080 43306 6 170 any}, http_response: &{200 OK 200 HTTP/1.1 map[Content-Length:[2] Content-Type:[text/plain; charset=utf-8] Date:[Wed, 29 Mar 2023 19:10:09 GMT]] 2} -19:10:10:452992 0 coredns 1 14 0 security_socket_connect sockfd: 13, remote_addr: map[sa_family:AF_INET sin_addr:0.0.0.0 sin_port:8080] -19:10:10:453850 0 coredns 1 1 0 security_socket_accept sockfd: 8, local_addr: map[sa_family:AF_INET6 sin6_addr::: sin6_flowinfo:0 sin6_port:8080 sin6_scopeid:0] -19:10:10:453983 0 coredns 1 1 0 security_socket_accept sockfd: 8, local_addr: map[sa_family:AF_INET6 sin6_addr::: sin6_flowinfo:0 sin6_port:8080 sin6_scopeid:0] -19:10:10:454612 0 coredns 1 9 0 net_packet_http_request metadata: {127.0.0.1 127.0.0.1 43318 8080 6 144 any}, http_request: &{GET HTTP/1.1 :8080 /health map[Accept-Encoding:[gzip] User-Agent:[Go-http-client/1.1]] 0} -19:10:10:455114 0 coredns 1 9 0 net_packet_http_response metadata: {127.0.0.1 127.0.0.1 8080 43318 6 170 any}, http_response: &{200 OK 200 HTTP/1.1 map[Content-Length:[2] Content-Type:[text/plain; charset=utf-8] Date:[Wed, 29 Mar 2023 19:10:10 GMT]] 2} -``` +## Configure VM Type (Optional) -## Switch Between CO-RE and non CO-RE Linux Distribution +The VM can be provisioned for either a `dev` or `test` environment. The `dev` environment includes additional tools like MicroK8s, kubectl, and Helm. -By default, the development machine is running Ubuntu Linux 22.04 Jammy Jellyfish. -You can see that it has a BTF-enabled kernel by checking the existence of the -`/sys/kernel/btf/vmlinux` file. +- **Development Environment:** Full development environment (Default) -```ruby -Vagrant.configure("2") do |config| - # config.vm.box = "ubuntu/focal64" # Ubuntu 20.04 Focal Fossa (non CO-RE) - # config.vm.box = "ubuntu/hirsute64" # Ubuntu 21.04 Hirsute Hippo (CO-RE) - # config.vm.box = "ubuntu/impish64" # Ubuntu 21.10 Impish Indri (CO-RE) - config.vm.box = "ubuntu/jammy64" # Ubuntu 22.04 Jammy Jellyfish (CO-RE) -... -``` + ```bash + export VM_TYPE=dev + ``` -Sometimes you may want to test Tracee with a non CO-RE distribution. You can do -that by editing the Vagrantfile and modifying the `config.vm.box` property. For -example, you can switch to Ubuntu Linux 20.04 Focal Fossa as follows: - -```ruby -Vagrant.configure("2") do |config| - config.vm.box = "ubuntu/focal64" # Ubuntu 20.04 Focal Fossa (non CO-RE) - # config.vm.box = "ubuntu/hirsute64" # Ubuntu 21.04 Hirsute Hippo (CO-RE) - # config.vm.box = "ubuntu/impish64" # Ubuntu 21.10 Impish Indri (CO-RE) - # config.vm.box = "ubuntu/jammy64" # Ubuntu 22.04 Jammy Jellyfish (CO-RE) -... -``` - -This change requires re-provisioning the development machine: +- **Testing Environment:** Smaller vagrant machine without k8s cumbersome to avoid conflicts with specific tests. -```console -vagrant destroy -vagrant up -``` + ```bash + export VM_TYPE=test + ``` -!!! Attention - Ubuntu Focal distribution has introduced BTF information to their recent - kernels, allowing eBPF CO-RE capable code to run. If you're willing to test - non CO-RE kernels, make sure to use an older kernel that does not provide - the `/sys/kernel/btf/vmlinux` file. +## Configure Resource Allocation (Optional) -## Deploy Tracee with Postee on Kubernetes +Customize the VM's resources by setting the following environment variables: -The development machine described by Vagrantfile pre-installs [MicroK8s] Kubernetes cluster, which is suitable for testing Tracee. +- `VM_PROC`: Number of virtual processors. Defaults to half of the host's processors. Example: -```console -microk8s status -``` + ```bash + export VM_PROC=4 + ``` -```text -microk8s is running -high-availability: no - datastore master nodes: 127.0.0.1:19001 - datastore standby nodes: none -... -``` +- `VM_MEM`: Memory in gigabytes. Defaults to 8GB. Example: -There's also the [kubectl] command installed and configured to communicate with -the cluster: + ```bash + export VM_MEM=16 + ``` -```console -kubectl get nodes -o wide -``` +## Start the VM -``` -NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME -ubuntu-jammy Ready 40m v1.26.1 10.0.2.15 Ubuntu 22.04.2 LTS 5.15.0-69-generic containerd://1.6.8 -``` +Run the following command to start the VM: -Create a new namespace called `tracee-system`: + ```bash + vagrant up + ``` -```console -kubectl create ns tracee-system -``` +Vagrant will download the base box, provision the VM, and install all required dependencies. This process may take some time. -Create Postee Persistent Volumes and StatefulSet in the `tracee-system` -namespace: +## Accessing the VM -```console -kubectl apply -n tracee-system \ - -f https://raw.githubusercontent.com/aquasecurity/postee/v2.2.0/deploy/kubernetes/hostPath/postee-pv.yaml \ - -f https://raw.githubusercontent.com/aquasecurity/postee/v2.2.0/deploy/kubernetes/postee.yaml -``` +Once the VM is up and running, you can access it via SSH: -Create Tracee DaemonSet in the `tracee-system`, configuring it to send -detections to the standard output and send them over to Postee webhook on -http://postee-svc:8082: - -```console -helm install tracee ./deploy/helm/tracee \ - --namespace tracee-system \ - --set hostPID=true \ - --set webhook=http://postee-svc:8082 -``` - -!!! tip - To test code that hasn't been released yet do the following: - - 1. Build the `tracee:latest` container image from the current Git revision: - ```console - make -f builder/Makefile.tracee-container build-tracee - ``` - 2. Import the container image to MicroK8s registry: - ```console - docker image save -o /tmp/tracee-latest.tar tracee:latest - microk8s ctr images import /tmp/tracee-latest.tar - rm /tmp/tracee-latest.tar - ``` - 3. Create Tracee DaemonSet using `tracee:latest` as container image: - ```console - kubectl apply -n tracee-system -k deploy/kubernetes/tracee - ``` - -While Tracee pod is running, run `strace ls` command and observe detection -printed to the standard output. - -```console -kubectl logs -n tracee-system -f daemonset/tracee -``` - -```text -INFO: probing tracee capabilities... -INFO: starting tracee... -{"timestamp":1680119087787203746,"threadStartTime":1680119087787109775,"processorId":0,"processId":95599,"cgroupId":9789,"threadId":95599,"parentProcessId":95597,"hostProcessId":95599,"hostThreadId":95599,"hostParentProcessId":95597,"userId":1000,"mountNamespace":4026531841,"pidNamespace":4026531836,"processName":"strace","hostName":"ubuntu-jammy","containerId":"","containerImage":"","containerName":"","podName":"","podNamespace":"","podUID":"","podSandbox":false,"eventId":"6018","eventName":"Anti-Debugging detected","matchedScopes":1,"argsNum":0,"returnValue":0,"syscall":"","stackAddresses":null,"contextFlags":{"containerStarted":false,"isCompat":false},"args":[],"metadata":{"Version":"1","Description":"A process used anti-debugging techniques to block a debugger. Malware use anti-debugging to stay invisible and inhibit analysis of their behavior.","Tags":null,"Properties":{"Category":"defense-evasion","Kubernetes_Technique":"","Severity":1,"Technique":"Debugger Evasion","external_id":"T1622","id":"attack-pattern--e4dc8c01-417f-458d-9ee0-bb0617c1b391","signatureID":"TRC-102","signatureName":"Anti-Debugging detected"}}} +```bash +vagrant ssh ``` -If everything is configured properly, you can find the same detection in Postee -logs: +This will place you in the `/vagrant` directory inside the VM, which is synced with the Tracee directory on your host machine. -```console -kubectl -n tracee-system logs -f postee-0 -``` - -```text -2023/03/29 19:44:47 {"timestamp":1680119087787203746,"threadStartTime":1680119087787109775,"processorId":0,"processId":95599,"cgroupId":9789,"threadId":95599,"parentProcessId":95597,"hostProcessId":95599,"hostThreadId":95599,"hostParentProcessId":95597,"userId":1000,"mountNamespace":4026531841,"pidNamespace":4026531836,"processName":"strace","hostName":"ubuntu-jammy","containerId":"","containerImage":"","containerName":"","podName":"","podNamespace":"","podUID":"","podSandbox":false,"eventId":"6018","eventName":"Anti-Debugging detected","matchedScopes":1,"argsNum":0,"returnValue":0,"syscall":"","stackAddresses":null,"contextFlags":{"containerStarted":false,"isCompat":false},"args":[],"metadata":{"Version":"1","Description":"A process used anti-debugging techniques to block a debugger. Malware use anti-debugging to stay invisible and inhibit analysis of their behavior.","Tags":null,"Properties":{"Category":"defense-evasion","Kubernetes_Technique":"","Severity":1,"Technique":"Debugger Evasion","external_id":"T1622","id":"attack-pattern--e4dc8c01-417f-458d-9ee0-bb0617c1b391","signatureID":"TRC-102","signatureName":"Anti-Debugging detected"}}} -``` +## Build and Run Tracee -### Access Kubernetes Dashboard +You can now build Tracee within the VM using the provided Makefile. Consult the Tracee documentation for specific build instructions. +[Building Tracee Documentation](./building/building.md) -Use the following command to get the token required to log in to the -[Kubernetes Dashboard]: +## Stopping the VM -```console -kubectl -n kube-system describe secret \ - $(kubectl -n kube-system get secret | grep default-token | cut -d " " -f1) -``` +To stop the VM, use: -Forward port 10443 in the development machine to the Kubernetes Dashboard's -pod: + ```bash + vagrant halt + ``` -```console -kubectl port-forward --address 0.0.0.0 -n kube-system service/kubernetes-dashboard 10443:443 -``` +## Destroying the VM -Since port 10443 is forwarded to port 10443 on your host, you can open your -browser to [https://localhost:10443](https://localhost:10443) and access -Kubernetes Dashboard. +To completely remove the VM, use: -!!! warning - Modern browser usually block insecure localhost TLS connections. For Google - Chrome you may allow insecure TLS connections at - [chrome://flags/#allow-insecure-localhost](chrome://flags/#allow-insecure-localhost). + ```bash + vagrant destroy + ``` -## Preview Tracee Documentation +**Troubleshooting:** -You can run [MkDocs] server and preview documentation on your host: +- Shared Folder Issues: If you experience issues with the shared folder, ensure your virtualization software's Guest Additions are properly installed and that the shared folder settings in the Vagrantfile match your setup. -```console -make -f builder/Makefile.mkdocs -``` +- Networking Issues: If you have trouble accessing forwarded ports, check your firewall settings on both the host and guest machines. -The development machine is running the MkDocs server listening on port 8000, -which is forwarded to port 8000 on your host. Therefore, you can open your -browser to [http://localhost:8000](http://localhost:8000) and access -documentation pages. - -[Vagrant]: https://www.vagrantup.com/docs/installation -[HashiCorp Vagrant]: https://www.vagrantup.com -[Vagrantfile]: https://github.com/aquasecurity/tracee/blob/{{ git.tag }}/Vagrantfile -[Hypervisor]: https://www.vagrantup.com/docs/providers -[VirtualBox]: https://www.virtualbox.org -[Parallels]: https://www.parallels.com -[MicroK8s]: https://microk8s.io -[MicroK8s add-ons]: https://microk8s.io/docs/addons -[kubectl]: https://kubernetes.io/docs/tasks/tools/#kubectl -[Kubernetes Dashboard]: https://github.com/kubernetes/dashboard -[Postee]: https://github.com/aquasecurity/postee -[Persistent Volumes]: https://kubernetes.io/docs/concepts/storage/persistent-volumes/ -[MkDocs]: https://www.mkdocs.org +This setup provides a consistent and reproducible environment for developing and testing Tracee. Refer to the Tracee documentation for further details on building and using Tracee. diff --git a/docs/docs/advanced/data-sources/builtin/process-tree.md b/docs/docs/advanced/data-sources/builtin/process-tree.md index 8d0eb400c55d..5f44506c86ec 100644 --- a/docs/docs/advanced/data-sources/builtin/process-tree.md +++ b/docs/docs/advanced/data-sources/builtin/process-tree.md @@ -26,7 +26,6 @@ The process tree query the procfs upon initialization and during runtime to fill ## Command Line Option ```bash -$ tracee --proctree help Example: --proctree source=[none|events|signals|both] none | process tree is disabled (default). @@ -35,9 +34,8 @@ Example: both | process tree is built from both events and signals. --proctree process-cache=8192 | will cache up to 8192 processes in the tree (LRU cache). --proctree thread-cache=16384 | will cache up to 16384 threads in the tree (LRU cache). - --proctree process-cache-ttl=60 | will set the process cache element TTL to 60 seconds. - --proctree thread-cache-ttl=60 | will set the thread cache element TTL to 60 seconds. - --proctree disable-procfs-query | Will disable procfs quering during runtime + --proctree disable-procfs | will disable procfs entirely. + --proctree disable-procfs-query | will disable procfs quering during runtime. Use comma OR use the flag multiple times to choose multiple options: --proctree source=A,process-cache=B,thread-cache=C diff --git a/docs/docs/events/builtin/extra/stack_pivot.md b/docs/docs/events/builtin/extra/stack_pivot.md new file mode 100644 index 000000000000..f90217038dc2 --- /dev/null +++ b/docs/docs/events/builtin/extra/stack_pivot.md @@ -0,0 +1,50 @@ +# stack_pivot + +## Intro + +stack_pivot - An event reporting a syscall that was invoked while the user's stack pointer doesn't point to the stack. + +## Description + +All native code executed makes use of the stack, a region of memory used for storage of function-local data, like function parameters, return address, and local variables. + +A stack overflow vulnerability is a security vulnerability that allows an attacker to write data past the end of a stack allocated buffer, allowing him to overwrite other stack data. This kind of vulnerability could be exploited by overwriting the function return address to a location chosen by the attacker, causing the code at that location to run when the vulnerable function returns. An attacker can write multiple return addresses to the stack such that small code sequences, called gadgets, are executed in a chain dictated by the attacker. This exploitation method is called ROP (return oriented programming). + +One potential limitation of such an exploit is the amount of data the attacker is able to write to the stack - in some cases, it may not be enough to write the full sequence of gadget addresses required to achieve the attacker's goal. To overcome this limitation, the attacker can use the stack pivot technique. This technique involves a gadget that writes an attacker controlled value to the stack pointer, effectively moving the stack to a new location that the attacker is able to write to (and thus achieving a longer ROP chain). + +This event attempts to detect the usage of this technique by checking the stack pointer at the invocation of selected syscalls and detecting cases where it does not point to the original stack. + +This event relies on an event parameter to specify which syscalls should be monitored, to reduce overhead. An example command line usage of this event: + +`tracee --events stack_pivot.args.syscall=open,openat`. + +## Arguments + +- `syscall`:`int`[K] - the syscall which was invoked while the stack pointer doesn't point to the orignal stack. The syscall name is parsed if the `parse-arguments` option is specified. This argument is also used as a parameter to select which syscalls should be checked. +- `sp`:`void *`[K] - the stack pointer at the time of syscall invocation +- `vma_type`:`char *`[K] - a string describing the type of the VMA which contains the address that the stack pointer points to +- `vma_start`:`void *`[K] - the start address of the VMA which contains the address that the stack pointer points to +- `vma_size`:`unsigned long`[K] - the size of the VMA which contains the address that the stack pointer points to +- `vma_flags`:`unsigned long`[K] - the flags of the VMA which contains the address that the stack pointer points to. The flag names are parsed if the `parse-arguments` option is specified. + +## Hooks + +### Individual syscalls + +#### Type + +kprobe + +#### Purpose + +A kprobe is placed on each syscall that was selected using a parameter for this event. The kprobe function analyzes the location pointed to by the stack pointer. + +## Example Use Case + +Detect ROP exploits that use the stack pivot technique. + +## Issues + +The kernel manages the stack for the main thread of each process, but additional threads must create and manage their own stacks. The kernel has no notion of a thread stack, so in order to detect that an address belongs to a thread stack and avoid false positives, thread stacks are tracked by tracee by storing the memory region pointed to by the stack pointer at the time of a new thread's creation. This means that threads created before tracee started are not tracked, and we have no way to differentiate between a regular anonymous memory region and one allocated for the stack in such threads. To avoid false positives, anonymous memory regions are ignored for untracked threads, which may result in false negatives. + + diff --git a/docs/docs/events/builtin/extra/suspicious_syscall_source.md b/docs/docs/events/builtin/extra/suspicious_syscall_source.md index ba4d365253e4..b20b2e5100ef 100644 --- a/docs/docs/events/builtin/extra/suspicious_syscall_source.md +++ b/docs/docs/events/builtin/extra/suspicious_syscall_source.md @@ -24,7 +24,7 @@ To reduce noise in cases where code with significant syscall activity is being d * `syscall`:`int`[K] - the syscall which was invoked from an unusual location. The syscall name is parsed if the `parse-arguments` option is specified. This argument is also used as a parameter to select which syscalls should be checked. * `ip`:`void *`[K] - the address from which the syscall was invoked (instruction pointer of the instruction following the syscall instruction). -* `vma_type`:`char *`[K] - the type of the VMA which contains the code that triggered the syscall (one of *stack*/*heap*/*anonymous*) +* `vma_type`:`char *`[K] - a string describing the type of the VMA which contains the code that triggered the syscall * `vma_start`:`void *`[K] - the start address of the VMA which contains the code that triggered the syscall * `vma_size`:`unsigned long`[K] - the size of the VMA which contains the code that triggered the syscall * `vma_flags`:`unsigned long`[K] - the flags of the VMA which contains the code that triggered the syscall. The flag names are parsed if the `parse-arguments` option is specified. diff --git a/docs/man/scope.1 b/docs/man/scope.1 index 641f51bfc36e..4360d683022d 100644 --- a/docs/man/scope.1 +++ b/docs/man/scope.1 @@ -57,7 +57,7 @@ if starting with `*'. .PP NOTE: Expressions containing `*' token must be escaped! .PP -you can learn more about the wildcard in the \f[CR]event\f[R] section +you can learn more about the wildcard in the \f[CR]event\f[R] section. .SS BOOLEAN OPERATOR (PREPENDED) `!' .PP diff --git a/go.mod b/go.mod index 8155ba50e8de..2ba9eb0222b7 100644 --- a/go.mod +++ b/go.mod @@ -6,16 +6,16 @@ toolchain go1.22.4 require ( github.com/IBM/fluent-forward-go v0.2.2 - github.com/Masterminds/sprig/v3 v3.2.3 - github.com/aquasecurity/libbpfgo v0.7.0-libbpf-1.4.0.20240729111821-61d531acf4ca - github.com/aquasecurity/tracee/api v0.0.0-20241203172838-1f796cb64289 - github.com/aquasecurity/tracee/signatures/helpers v0.0.0-20241225084355-5b8f456dae7b - github.com/aquasecurity/tracee/types v0.0.0-20241008181102-d40bc1f81863 - github.com/containerd/containerd v1.7.21 + github.com/Masterminds/sprig/v3 v3.3.0 + github.com/aquasecurity/libbpfgo v0.8.0-libbpf-1.5.0.20250117141322-c0ac6035ae61 + github.com/aquasecurity/tracee/api v0.0.0-20250117110942-a403bd985f72 + github.com/aquasecurity/tracee/signatures/helpers v0.0.0-20250123101549-f57a1ebab93a + github.com/aquasecurity/tracee/types v0.0.0-20250117124739-92cb7e0f7155 + github.com/containerd/containerd v1.7.25 github.com/docker/docker v26.1.5+incompatible github.com/golang/protobuf v1.5.4 github.com/google/gopacket v1.1.19 - github.com/grafana/pyroscope-go v1.1.1 + github.com/grafana/pyroscope-go v1.2.0 github.com/hashicorp/golang-lru v1.0.2 github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/mennanov/fmutils v0.3.0 @@ -25,30 +25,31 @@ require ( github.com/sashabaranov/go-gpt3 v1.4.0 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.18.2 - github.com/stretchr/testify v1.9.0 - github.com/urfave/cli/v2 v2.27.2 + github.com/stretchr/testify v1.10.0 + github.com/urfave/cli/v2 v2.27.5 go.uber.org/goleak v1.3.0 go.uber.org/zap v1.27.0 golang.org/x/sys v0.28.0 - google.golang.org/grpc v1.66.0 - google.golang.org/protobuf v1.34.2 + google.golang.org/grpc v1.69.4 + google.golang.org/protobuf v1.36.3 gopkg.in/yaml.v2 v2.4.0 gotest.tools v2.2.0+incompatible k8s.io/api v0.30.1 k8s.io/apimachinery v0.30.1 k8s.io/client-go v0.30.1 k8s.io/cri-api v0.30.1 - kernel.org/pub/linux/libs/security/libcap/cap v1.2.70 + kernel.org/pub/linux/libs/security/libcap/cap v1.2.73 sigs.k8s.io/controller-runtime v0.18.2 ) require ( + dario.cat/mergo v1.0.1 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20231105174938-2b5cbb29f3e2 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/containerd/cgroups/v3 v3.0.3 // indirect - github.com/containerd/containerd/api v1.7.19 // indirect - github.com/containerd/errdefs v0.1.0 // indirect + github.com/containerd/containerd/api v1.8.0 // indirect + github.com/containerd/errdefs v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/containerd/typeurl/v2 v2.1.1 // indirect @@ -66,7 +67,7 @@ require ( github.com/google/gnostic-models v0.6.8 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/gorilla/websocket v1.5.1 // indirect - github.com/grafana/pyroscope-go/godeltaprof v0.1.7 // indirect + github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect @@ -92,21 +93,19 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/tinylib/msgp v1.1.9 // indirect - github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect + github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect - go.opentelemetry.io/otel v1.28.0 // indirect + go.opentelemetry.io/otel v1.31.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect - go.opentelemetry.io/otel/metric v1.28.0 // indirect - go.opentelemetry.io/otel/sdk v1.28.0 // indirect - go.opentelemetry.io/otel/trace v1.28.0 // indirect + go.opentelemetry.io/otel/metric v1.31.0 // indirect + go.opentelemetry.io/otel/trace v1.31.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect - golang.org/x/oauth2 v0.21.0 // indirect + golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/term v0.27.0 // indirect golang.org/x/time v0.6.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/genproto v0.0.0-20240515191416-fc5f0ca64291 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gotest.tools/v3 v3.4.0 // indirect @@ -121,15 +120,15 @@ require ( require ( github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver/v3 v3.2.1 + github.com/Masterminds/semver/v3 v3.3.1 github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/hcsshim v0.12.3 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/containerd/continuity v0.4.3 // indirect + github.com/containerd/continuity v0.4.4 // indirect github.com/containerd/fifo v1.1.0 // indirect github.com/containerd/ttrpc v1.2.5 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect @@ -138,7 +137,7 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/go-cmp v0.6.0 github.com/google/uuid v1.6.0 // indirect - github.com/huandu/xstrings v1.4.0 // indirect + github.com/huandu/xstrings v1.5.0 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.17.9 // indirect @@ -162,14 +161,14 @@ require ( github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/cast v1.7.0 // indirect go.opencensus.io v0.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.31.0 // indirect golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 - golang.org/x/net v0.28.0 // indirect + golang.org/x/net v0.33.0 // indirect golang.org/x/sync v0.10.0 // indirect golang.org/x/text v0.21.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - kernel.org/pub/linux/libs/security/libcap/psx v1.2.70 // indirect + kernel.org/pub/linux/libs/security/libcap/psx v1.2.73 // indirect ) diff --git a/go.sum b/go.sum index 61d0f531b95c..7a60e4812d32 100644 --- a/go.sum +++ b/go.sum @@ -376,6 +376,8 @@ cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1V cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M= cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= @@ -389,25 +391,24 @@ github.com/IBM/fluent-forward-go v0.2.2 h1:T48kAjSMOAqTcpd6zkzqLAFOWlYPYIbCFJcEj github.com/IBM/fluent-forward-go v0.2.2/go.mod h1:U1SVl6rVRGMC/QhCTZ3iQx4P/ykCeg1y6UoVnlz+OAY= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= -github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= -github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= -github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= -github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= +github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= +github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= +github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/hcsshim v0.12.3 h1:LS9NXqXhMoqNCplK1ApmVSfB4UnVLRDWRapB6EIlxE0= github.com/Microsoft/hcsshim v0.12.3/go.mod h1:Iyl1WVpZzr+UkzjekHZbV8o5Z9ZkxNGx6CtY2Qg/JVQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/aquasecurity/libbpfgo v0.7.0-libbpf-1.4.0.20240729111821-61d531acf4ca h1:OPbvwFFvR11c1bgOLhBq1R5Uk3hwUjHW2KfrdyJan9Y= -github.com/aquasecurity/libbpfgo v0.7.0-libbpf-1.4.0.20240729111821-61d531acf4ca/go.mod h1:UpO6kTehEgAGGKR2twztBxvzjTiLiV/cb2xmlYb+TfE= -github.com/aquasecurity/tracee/api v0.0.0-20241203172838-1f796cb64289 h1:mr7+agMcMRwn9vRwc44MaEFTUZnw0pvIbhteyANG38I= -github.com/aquasecurity/tracee/api v0.0.0-20241203172838-1f796cb64289/go.mod h1:Gn6xVkaBkVe1pOQ0++uuHl+lMMClv0TPY8mCQ6j88aA= -github.com/aquasecurity/tracee/signatures/helpers v0.0.0-20241225084355-5b8f456dae7b h1:eTIrU0vdn49P0LhtEypnSdGgoRzLvNPAGivGHPnCBXg= -github.com/aquasecurity/tracee/signatures/helpers v0.0.0-20241225084355-5b8f456dae7b/go.mod h1:DL+Q2DxyS7dpJGt4NVj26XbPiE2bjRK4vwqrmImr6Go= -github.com/aquasecurity/tracee/types v0.0.0-20241008181102-d40bc1f81863 h1:domVTTQICTuCvX+ZW5EjvdUBz8EH7FedBj5lRqwpgf4= -github.com/aquasecurity/tracee/types v0.0.0-20241008181102-d40bc1f81863/go.mod h1:Jwh9OOuiMHXDoGQY12N9ls5YB+j1FlRcXvFMvh1CmIU= +github.com/aquasecurity/libbpfgo v0.8.0-libbpf-1.5.0.20250117141322-c0ac6035ae61 h1:EdqmOm8rk/FMtYetPcceDdW27lZxXzpHZw8XpF3lG+g= +github.com/aquasecurity/libbpfgo v0.8.0-libbpf-1.5.0.20250117141322-c0ac6035ae61/go.mod h1:HB2DYWHuMraOB0IFcfjL8tsxN8TcFOdWKTrNqnHrTrs= +github.com/aquasecurity/tracee/api v0.0.0-20250117110942-a403bd985f72 h1:M1G3ttGcozWGfMMlD2nbdCgskGkAHSIVMsodRykOYSE= +github.com/aquasecurity/tracee/api v0.0.0-20250117110942-a403bd985f72/go.mod h1:mSYGLfhjpcLq78gjb4/XDQaY8DhfpDNAuLD0tvU2bZc= +github.com/aquasecurity/tracee/signatures/helpers v0.0.0-20250123101549-f57a1ebab93a h1:xCyaZJpgUgtpjKgrjQffKAjhz9jw3c0niMwtre2gh/E= +github.com/aquasecurity/tracee/signatures/helpers v0.0.0-20250123101549-f57a1ebab93a/go.mod h1:ea2x/5u4oOKJiuZsbPaa9WJlldZehprNsCTNhjeWYrs= +github.com/aquasecurity/tracee/types v0.0.0-20250117124739-92cb7e0f7155 h1:SOcc74U3FXyGJFfSV1QvI0I8VmXBHneq2y9suAdqmT4= +github.com/aquasecurity/tracee/types v0.0.0-20250117124739-92cb7e0f7155/go.mod h1:qG8nK8fjdHLTWAlTqDjCdxBuBUge2PyK5SHX/tfrxfY= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= @@ -433,14 +434,14 @@ github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/containerd/cgroups/v3 v3.0.3 h1:S5ByHZ/h9PMe5IOQoN7E+nMc2UcLEM/V48DGDJ9kip0= github.com/containerd/cgroups/v3 v3.0.3/go.mod h1:8HBe7V3aWGLFPd/k03swSIsGjZhHI2WzJmticMgVuz0= -github.com/containerd/containerd v1.7.21 h1:USGXRK1eOC/SX0L195YgxTHb0a00anxajOzgfN0qrCA= -github.com/containerd/containerd v1.7.21/go.mod h1:e3Jz1rYRUZ2Lt51YrH9Rz0zPyJBOlSvB3ghr2jbVD8g= -github.com/containerd/containerd/api v1.7.19 h1:VWbJL+8Ap4Ju2mx9c9qS1uFSB1OVYr5JJrW2yT5vFoA= -github.com/containerd/containerd/api v1.7.19/go.mod h1:fwGavl3LNwAV5ilJ0sbrABL44AQxmNjDRcwheXDb6Ig= -github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8= -github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= -github.com/containerd/errdefs v0.1.0 h1:m0wCRBiu1WJT/Fr+iOoQHMQS/eP5myQ8lCv4Dz5ZURM= -github.com/containerd/errdefs v0.1.0/go.mod h1:YgWiiHtLmSeBrvpw+UfPijzbLaB77mEG1WwJTDETIV0= +github.com/containerd/containerd v1.7.25 h1:khEQOAXOEJalRO228yzVsuASLH42vT7DIo9Ss+9SMFQ= +github.com/containerd/containerd v1.7.25/go.mod h1:tWfHzVI0azhw4CT2vaIjsb2CoV4LJ9PrMPaULAr21Ok= +github.com/containerd/containerd/api v1.8.0 h1:hVTNJKR8fMc/2Tiw60ZRijntNMd1U+JVMyTRdsD2bS0= +github.com/containerd/containerd/api v1.8.0/go.mod h1:dFv4lt6S20wTu/hMcP4350RL87qPWLVa/OHOwmmdnYc= +github.com/containerd/continuity v0.4.4 h1:/fNVfTJ7wIl/YPMHjf+5H32uFhl63JucB34PlCpMKII= +github.com/containerd/continuity v0.4.4/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= +github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4= +github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY= github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= @@ -451,8 +452,9 @@ github.com/containerd/ttrpc v1.2.5 h1:IFckT1EFQoFBMG4c3sMdT8EP3/aKfumK1msY+Ze4oL github.com/containerd/ttrpc v1.2.5/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= github.com/containerd/typeurl/v2 v2.1.1 h1:3Q4Pt7i8nYwy2KmQWIw2+1hTvwTE/6w9FqcttATPO/4= github.com/containerd/typeurl/v2 v2.1.1/go.mod h1:IDp2JFvbwZ31H8dQbEIY7sDl2L3o3HZj1hsSQlywkQ0= -github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= +github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -593,7 +595,6 @@ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -614,10 +615,10 @@ github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57Q github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= -github.com/grafana/pyroscope-go v1.1.1 h1:PQoUU9oWtO3ve/fgIiklYuGilvsm8qaGhlY4Vw6MAcQ= -github.com/grafana/pyroscope-go v1.1.1/go.mod h1:Mw26jU7jsL/KStNSGGuuVYdUq7Qghem5P8aXYXSXG88= -github.com/grafana/pyroscope-go/godeltaprof v0.1.7 h1:C11j63y7gymiW8VugJ9ZW0pWfxTZugdSJyC48olk5KY= -github.com/grafana/pyroscope-go/godeltaprof v0.1.7/go.mod h1:Tk376Nbldo4Cha9RgiU7ik8WKFkNpfds98aUzS8omLE= +github.com/grafana/pyroscope-go v1.2.0 h1:aILLKjTj8CS8f/24OPMGPewQSYlhmdQMBmol1d3KGj8= +github.com/grafana/pyroscope-go v1.2.0/go.mod h1:2GHr28Nr05bg2pElS+dDsc98f3JTUh2f6Fz1hWXrqwk= +github.com/grafana/pyroscope-go/godeltaprof v0.1.8 h1:iwOtYXeeVSAeYefJNaxDytgjKtUuKQbJqgAIjlnicKg= +github.com/grafana/pyroscope-go/godeltaprof v0.1.8/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= @@ -630,12 +631,10 @@ github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= -github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -650,7 +649,6 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.3/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= @@ -675,12 +673,10 @@ github.com/mennanov/fmutils v0.3.0 h1:2YSyrO8oOLQQwB/iKe+xDDGO6xCUHiIAj3gYhY7D4A github.com/mennanov/fmutils v0.3.0/go.mod h1:ph1jsu8gV1gUgMURCmfIVbXKG3O2/O5o/UbPbbqu8zs= github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= -github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= @@ -757,7 +753,6 @@ github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6g github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sashabaranov/go-gpt3 v1.4.0 h1:UqHYdXgJNtNvTtbzDnnQgkQ9TgTnHtCXx966uFTYXvU= github.com/sashabaranov/go-gpt3 v1.4.0/go.mod h1:BIZdbwdzxZbCrcKGMGH6u2eyGe1xFuX9Anmh3tCP8lQ= -github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= @@ -767,9 +762,8 @@ github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIK github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= -github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -779,8 +773,8 @@ github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMV github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= @@ -790,16 +784,17 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tinylib/msgp v1.1.9 h1:SHf3yoO2sGA0veCJeCBYLHuttAVFHGm2RHgNodW7wQU= github.com/tinylib/msgp v1.1.9/go.mod h1:BCXGB54lDD8qUEPmiG0cQQUANC4IUQyB2ItS2UDlO/k= -github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI= -github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM= -github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw= -github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk= +github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= +github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -817,18 +812,20 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= -go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= -go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= +go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= -go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= -go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= -go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= -go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= -go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= -go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= +go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= +go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= +go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= +go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= +go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= +go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= +go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= @@ -844,7 +841,6 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -932,9 +928,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -960,8 +955,8 @@ golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= -golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1043,13 +1038,11 @@ golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1306,10 +1299,10 @@ google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZV google.golang.org/genproto v0.0.0-20230202175211-008b39050e57/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/genproto v0.0.0-20240515191416-fc5f0ca64291 h1:CTZGpOdDJr2Jq+LcJ/mpjG8mClGy/uJdBBVYbS9g5lY= google.golang.org/genproto v0.0.0-20240515191416-fc5f0ca64291/go.mod h1:ch5ZrEj5+9MCxUeR3Gp3mCJ4u0eVpusYAmSr/mvpMSk= -google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0= -google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 h1:fVoAXEKA4+yufmbdVYv+SE73+cPZbbbe8paLsHfkK+U= +google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53/go.mod h1:riSXTwQ4+nqmPGtobMFyW5FqVAmIs0St6VPp4Ug7CE4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1346,8 +1339,8 @@ google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCD google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= -google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= -google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= +google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -1364,8 +1357,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= @@ -1378,7 +1371,6 @@ gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -1411,10 +1403,10 @@ k8s.io/kube-openapi v0.0.0-20240430033511-f0e62f92d13f h1:0LQagt0gDpKqvIkAMPaRGc k8s.io/kube-openapi v0.0.0-20240430033511-f0e62f92d13f/go.mod h1:S9tOR0FxgyusSNR+MboCuiDpVWkAifZvaYI1Q2ubgro= k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 h1:jgGTlFYnhF1PM1Ax/lAlxUPE+KfCIXHaathvJg1C3ak= k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -kernel.org/pub/linux/libs/security/libcap/cap v1.2.70 h1:QnLPkuDWWbD5C+3DUA2IUXai5TK6w2zff+MAGccqdsw= -kernel.org/pub/linux/libs/security/libcap/cap v1.2.70/go.mod h1:/iBwcj9nbLejQitYvUm9caurITQ6WyNHibJk6Q9fiS4= -kernel.org/pub/linux/libs/security/libcap/psx v1.2.70 h1:HsB2G/rEQiYyo1bGoQqHZ/Bvd6x1rERQTNdPr1FyWjI= -kernel.org/pub/linux/libs/security/libcap/psx v1.2.70/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24= +kernel.org/pub/linux/libs/security/libcap/cap v1.2.73 h1:Th2b8jljYqkyZKS3aD3N9VpYsQpHuXLgea+SZUIfODA= +kernel.org/pub/linux/libs/security/libcap/cap v1.2.73/go.mod h1:hbeKwKcboEsxARYmcy/AdPVN11wmT/Wnpgv4k4ftyqY= +kernel.org/pub/linux/libs/security/libcap/psx v1.2.73 h1:SEAEUiPVylTD4vqqi+vtGkSnXeP2FcRO3FoZB1MklMw= +kernel.org/pub/linux/libs/security/libcap/psx v1.2.73/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/mkdocs.yml b/mkdocs.yml index f1c334b28930..6bc6f4856113 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -624,8 +624,9 @@ nav: - Additional Resources: tutorials/additional-resources.md - Contributing: - Overview: contributing/overview.md - - Documentation: contributing/documentation.md - - Source Code Guidelines: contributing/guidelines.md + - Guidelines: contributing/guidelines.md + - Performence: contributing/performance.md + - Kubernetes: contributing/kubernetes.md - Setup Development Machine with Vagrant: contributing/setup-development-machine-with-vagrant.md - Building: - Building Tracee: contributing/building/building.md diff --git a/performance/benchmark/network/manifests/crd.yaml b/performance/benchmark/network/manifests/crd.yaml index b483eecacd69..027fc29f2fc2 100644 --- a/performance/benchmark/network/manifests/crd.yaml +++ b/performance/benchmark/network/manifests/crd.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.17.0 name: policies.tracee.aquasec.com spec: group: tracee.aquasec.com diff --git a/performance/dashboard/provisioning/dashboards/tracee.json b/performance/dashboard/provisioning/dashboards/tracee.json index 4a9344af0665..975eae0004cf 100644 --- a/performance/dashboard/provisioning/dashboards/tracee.json +++ b/performance/dashboard/provisioning/dashboards/tracee.json @@ -18,7 +18,7 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 0, - "id": 2, + "id": 1, "links": [], "panels": [ { @@ -773,7 +773,7 @@ "type": "stat" }, { - "collapsed": false, + "collapsed": true, "gridPos": { "h": 1, "w": 24, @@ -781,229 +781,604 @@ "y": 19 }, "id": 40, - "panels": [], - "title": "Perf Event Buffer (METRICS=1)", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PDC1078F23EBDF0E5" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "PDC1078F23EBDF0E5" }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 38, - "w": 8, - "x": 0, - "y": 20 - }, - "id": 39, - "interval": "10s", - "options": { - "displayMode": "gradient", - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": false + }, + "overrides": [] + }, + "gridPos": { + "h": 38, + "w": 8, + "x": 0, + "y": 1326 + }, + "id": 39, + "interval": "10s", + "options": { + "displayMode": "gradient", + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "maxVizHeight": 300, + "minVizHeight": 16, + "minVizWidth": 8, + "namePlacement": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showUnfilled": true, + "sizing": "auto", + "valueMode": "color" + }, + "pluginVersion": "11.4.0-202090", + "targets": [ + { + "disableTextWrap": false, + "editorMode": "code", + "exemplar": false, + "expr": "tracee_ebpf_bpf_perf_event_submit_attempts", + "format": "time_series", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{event_name}}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Submit Attempts", + "type": "bargauge" }, - "maxVizHeight": 300, - "minVizHeight": 16, - "minVizWidth": 8, - "namePlacement": "auto", - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "lastNotNull" + { + "datasource": { + "type": "prometheus", + "uid": "PDC1078F23EBDF0E5" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 38, + "w": 8, + "x": 8, + "y": 1326 + }, + "id": 41, + "interval": "10s", + "options": { + "displayMode": "gradient", + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "maxVizHeight": 300, + "minVizHeight": 16, + "minVizWidth": 8, + "namePlacement": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showUnfilled": true, + "sizing": "auto", + "valueMode": "color" + }, + "pluginVersion": "11.4.0-202090", + "targets": [ + { + "disableTextWrap": false, + "editorMode": "builder", + "expr": "tracee_ebpf_bpf_perf_event_submit_failures", + "fullMetaSearch": false, + "includeNullMetadata": true, + "legendFormat": "{{event_name}}", + "range": true, + "refId": "A", + "useBackend": false + } ], - "fields": "", - "values": false + "title": "Submit Failures", + "type": "bargauge" }, - "showUnfilled": true, - "sizing": "auto", - "valueMode": "color" - }, - "pluginVersion": "11.4.0-202090", - "targets": [ { - "disableTextWrap": false, - "editorMode": "code", - "exemplar": false, - "expr": "tracee_ebpf_bpf_perf_event_submit_attempts", - "format": "time_series", - "fullMetaSearch": false, - "includeNullMetadata": true, - "instant": false, - "legendFormat": "{{event_name}}", - "range": true, - "refId": "A", - "useBackend": false + "datasource": { + "type": "prometheus", + "uid": "PDC1078F23EBDF0E5" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red" + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 38, + "w": 8, + "x": 16, + "y": 1326 + }, + "id": 42, + "interval": "10s", + "options": { + "displayMode": "gradient", + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "maxVizHeight": 300, + "minVizHeight": 16, + "minVizWidth": 8, + "namePlacement": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showUnfilled": true, + "sizing": "auto", + "valueMode": "color" + }, + "pluginVersion": "11.4.0-202090", + "targets": [ + { + "disableTextWrap": false, + "editorMode": "code", + "expr": "tracee_ebpf_bpf_perf_event_submit_failures/tracee_ebpf_bpf_perf_event_submit_attempts", + "fullMetaSearch": false, + "includeNullMetadata": true, + "legendFormat": "{{event_name}}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Failures Ratio", + "type": "bargauge" } ], - "title": "Submit Attempts", - "type": "bargauge" + "title": "Perf Event Buffer (METRICS=1)", + "type": "row" }, { - "datasource": { - "type": "prometheus", - "uid": "PDC1078F23EBDF0E5" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "red", - "value": null - } - ] - } - }, - "overrides": [] - }, + "collapsed": true, "gridPos": { - "h": 38, - "w": 8, - "x": 8, + "h": 1, + "w": 24, + "x": 0, "y": 20 }, - "id": 41, - "interval": "10s", - "options": { - "displayMode": "gradient", - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": false + "id": 11, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "PROMETHEUS" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisGridShow": true, + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 8, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "area" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "transparent", + "value": null + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 0, + "y": 51 + }, + "id": 43, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.4.0-202090", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PROMETHEUS" + }, + "editorMode": "code", + "expr": "(rate(process_cpu_seconds_total{job=\"tracee\"}[$__rate_interval]) * 100)\n/\non()\ncount(node_cpu_seconds_total{mode=\"idle\"})", + "legendFormat": "Tracee", + "range": true, + "refId": "USER" + } + ], + "title": "CPU Time (Tracee)", + "transparent": true, + "type": "timeseries" }, - "maxVizHeight": 300, - "minVizHeight": 16, - "minVizWidth": 8, - "namePlacement": "auto", - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "lastNotNull" + { + "datasource": { + "type": "prometheus", + "uid": "PDC1078F23EBDF0E5" + }, + "description": "Measures the average percentage of CPU time used by the Tracee process relative to the total number of CPU cores available.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "fieldMinMax": false, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "orange", + "value": 2 + }, + { + "color": "red", + "value": 2.5 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 18, + "y": 51 + }, + "id": 48, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.4.0-202090", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PDC1078F23EBDF0E5" + }, + "editorMode": "code", + "expr": "avg(rate(process_cpu_seconds_total{job=\"tracee\"}[$__rate_interval]) * 100)\n/\ncount(node_cpu_seconds_total{mode=\"idle\"})", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } ], - "fields": "", - "values": false + "title": "CPU Time (Tracee - Avg %)", + "type": "stat" }, - "showUnfilled": true, - "sizing": "auto", - "valueMode": "color" - }, - "pluginVersion": "11.4.0-202090", - "targets": [ { - "disableTextWrap": false, - "editorMode": "builder", - "expr": "tracee_ebpf_bpf_perf_event_submit_failures", - "fullMetaSearch": false, - "includeNullMetadata": true, - "legendFormat": "{{event_name}}", - "range": true, - "refId": "A", - "useBackend": false - } - ], - "title": "Submit Failures", - "type": "bargauge" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PDC1078F23EBDF0E5" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" + "datasource": { + "type": "prometheus", + "uid": "PROMETHEUS" }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "red", - "value": null - } - ] + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisGridShow": true, + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 8, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineStyle": { + "dash": [ + 10, + 10 + ], + "fill": "dash" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "area" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "transparent", + "value": null + } + ] + }, + "unit": "percent" + }, + "overrides": [] }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 38, - "w": 8, - "x": 16, - "y": 20 - }, - "id": 42, - "interval": "10s", - "options": { - "displayMode": "gradient", - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": false - }, - "maxVizHeight": 300, - "minVizHeight": 16, - "minVizWidth": 8, - "namePlacement": "auto", - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "lastNotNull" + "gridPos": { + "h": 6, + "w": 18, + "x": 0, + "y": 57 + }, + "id": 45, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.4.0-202090", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PROMETHEUS" + }, + "editorMode": "code", + "expr": "(1 - avg(rate(node_cpu_seconds_total{mode=\"idle\"}[$__rate_interval]))) * 100", + "legendFormat": "All but idle", + "range": true, + "refId": "USER" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PROMETHEUS" + }, + "editorMode": "code", + "expr": "(rate(process_cpu_seconds_total{job=\"tracee\"}[$__rate_interval]) * 100)\n/\non()\ncount(node_cpu_seconds_total{mode=\"idle\"})", + "hide": false, + "legendFormat": "Tracee", + "range": true, + "refId": "A" + } ], - "fields": "", - "values": false + "title": "CPU Time (All but idle/Tracee)", + "transparent": true, + "type": "timeseries" }, - "showUnfilled": true, - "sizing": "auto", - "valueMode": "color" - }, - "pluginVersion": "11.4.0-202090", - "targets": [ { - "disableTextWrap": false, - "editorMode": "code", - "expr": "tracee_ebpf_bpf_perf_event_submit_failures/tracee_ebpf_bpf_perf_event_submit_attempts", - "fullMetaSearch": false, - "includeNullMetadata": true, - "legendFormat": "{{event_name}}", - "range": true, - "refId": "A", - "useBackend": false + "datasource": { + "type": "prometheus", + "uid": "PDC1078F23EBDF0E5" + }, + "description": "Average ratio of CPU usage by the Tracee process compared to the total non-idle CPU usage on the system.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "fieldMinMax": false, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 1 + }, + { + "color": "red", + "value": 2 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 18, + "y": 57 + }, + "id": 49, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.4.0-202090", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PDC1078F23EBDF0E5" + }, + "editorMode": "code", + "expr": "(\n (rate(process_cpu_seconds_total{job=\"tracee\"}[$__rate_interval]))\n /\n on()\n count(node_cpu_seconds_total{mode=\"idle\"})\n)\n/\n(\n 1 - avg(rate(node_cpu_seconds_total{mode=\"idle\"}[$__rate_interval]))\n)", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "CPU Time Share (Tracee vs Non-Idle)", + "type": "stat" } ], - "title": "Failures Ratio", - "type": "bargauge" + "title": "CPU", + "type": "row" }, { "collapsed": true, @@ -1011,7 +1386,7 @@ "h": 1, "w": 24, "x": 0, - "y": 58 + "y": 21 }, "id": 12, "panels": [ @@ -1078,7 +1453,7 @@ "h": 6, "w": 15, "x": 0, - "y": 153 + "y": 148 }, "id": 32, "options": { @@ -1143,7 +1518,7 @@ "h": 6, "w": 3, "x": 15, - "y": 153 + "y": 148 }, "id": 14, "options": { @@ -1206,7 +1581,7 @@ "h": 6, "w": 3, "x": 18, - "y": 153 + "y": 148 }, "id": 15, "options": { @@ -1271,7 +1646,7 @@ "h": 12, "w": 3, "x": 21, - "y": 153 + "y": 148 }, "id": 19, "options": { @@ -1399,7 +1774,7 @@ "h": 6, "w": 15, "x": 0, - "y": 159 + "y": 240 }, "id": 16, "options": { @@ -1464,7 +1839,7 @@ "h": 6, "w": 3, "x": 15, - "y": 159 + "y": 240 }, "id": 17, "options": { @@ -1527,7 +1902,7 @@ "h": 6, "w": 3, "x": 18, - "y": 159 + "y": 240 }, "id": 18, "options": { @@ -1629,7 +2004,7 @@ "h": 7, "w": 15, "x": 0, - "y": 165 + "y": 246 }, "id": 20, "options": { @@ -1720,7 +2095,7 @@ "h": 7, "w": 3, "x": 15, - "y": 165 + "y": 246 }, "id": 22, "options": { @@ -1820,7 +2195,7 @@ "h": 7, "w": 15, "x": 0, - "y": 172 + "y": 253 }, "id": 21, "options": { @@ -1885,7 +2260,7 @@ "h": 7, "w": 3, "x": 15, - "y": 172 + "y": 253 }, "id": 23, "options": { @@ -1931,7 +2306,7 @@ "h": 1, "w": 24, "x": 0, - "y": 59 + "y": 22 }, "id": 24, "panels": [ @@ -1999,7 +2374,7 @@ "h": 7, "w": 15, "x": 0, - "y": 453 + "y": 149 }, "id": 13, "options": { @@ -2064,7 +2439,7 @@ "h": 7, "w": 3, "x": 15, - "y": 453 + "y": 149 }, "id": 26, "options": { @@ -2164,7 +2539,7 @@ "h": 7, "w": 15, "x": 0, - "y": 460 + "y": 174 }, "id": 27, "options": { @@ -2229,7 +2604,7 @@ "h": 7, "w": 3, "x": 15, - "y": 460 + "y": 174 }, "id": 28, "options": { @@ -2275,16 +2650,16 @@ "h": 1, "w": 24, "x": 0, - "y": 60 + "y": 23 }, - "id": 11, + "id": 47, "panels": [ { "datasource": { "type": "prometheus", - "uid": "PROMETHEUS" + "uid": "PDC1078F23EBDF0E5" }, - "description": "Each line represents ONE SPECIFIC CPU percentage, and all of them are summing ALL USER CPU TIME in a stack.", + "description": "Each line represents ONE SPECIFIC CPU percentage.", "fieldConfig": { "defaults": { "color": { @@ -2300,7 +2675,7 @@ "barAlignment": 0, "barWidthFactor": 0.6, "drawStyle": "line", - "fillOpacity": 8, + "fillOpacity": 0, "gradientMode": "none", "hideFrom": { "legend": false, @@ -2309,6 +2684,9 @@ }, "insertNulls": false, "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, "lineWidth": 1, "pointSize": 5, "scaleDistribution": { @@ -2318,7 +2696,7 @@ "spanNulls": false, "stacking": { "group": "A", - "mode": "normal" + "mode": "none" }, "thresholdsStyle": { "mode": "area" @@ -2329,10 +2707,12 @@ "mode": "absolute", "steps": [ { - "color": "transparent" + "color": "transparent", + "value": null } ] - } + }, + "unit": "percent" }, "overrides": [] }, @@ -2340,9 +2720,9 @@ "h": 6, "w": 24, "x": 0, - "y": 454 + "y": 54 }, - "id": 5, + "id": 8, "options": { "legend": { "calcs": [], @@ -2363,22 +2743,22 @@ "uid": "PROMETHEUS" }, "editorMode": "code", - "expr": "rate(node_cpu_seconds_total{mode=\"user\"}[$__rate_interval])*100", - "legendFormat": "USER", + "expr": "(1 - rate(node_cpu_seconds_total{mode=\"idle\"}[$__rate_interval])) * 100", + "legendFormat": "Core {{cpu}}", "range": true, - "refId": "USER" + "refId": "Cores" } ], - "title": "USER PER CPU TIME STACKED", + "title": "CPU Time (All but idle - per core)", "transparent": true, "type": "timeseries" }, { "datasource": { "type": "prometheus", - "uid": "PDC1078F23EBDF0E5" + "uid": "PROMETHEUS" }, - "description": "Each line represents ONE SPECIFIC CPU percentage, and all of them are summing ALL SYSTEM CPU TIME in a stack.", + "description": "", "fieldConfig": { "defaults": { "color": { @@ -2423,10 +2803,12 @@ "mode": "absolute", "steps": [ { - "color": "transparent" + "color": "transparent", + "value": null } ] - } + }, + "unit": "percent" }, "overrides": [] }, @@ -2434,9 +2816,9 @@ "h": 6, "w": 24, "x": 0, - "y": 460 + "y": 60 }, - "id": 8, + "id": 44, "options": { "legend": { "calcs": [], @@ -2457,13 +2839,13 @@ "uid": "PROMETHEUS" }, "editorMode": "code", - "expr": "rate(node_cpu_seconds_total{mode=\"system\"}[$__rate_interval])*100", - "legendFormat": "SYSTEM", + "expr": "(1 - avg(rate(node_cpu_seconds_total{mode=\"idle\"}[$__rate_interval]))) * 100", + "legendFormat": "All but idle", "range": true, - "refId": "SYSTEM" + "refId": "USER" } ], - "title": "SYSTEM PERCPU TIME STACKED", + "title": "CPU Time (All but idle - avg)", "transparent": true, "type": "timeseries" }, @@ -2517,7 +2899,8 @@ "mode": "absolute", "steps": [ { - "color": "transparent" + "color": "transparent", + "value": null } ] } @@ -2528,7 +2911,7 @@ "h": 6, "w": 24, "x": 0, - "y": 466 + "y": 66 }, "id": 9, "options": { @@ -2618,12 +3001,12 @@ "refId": "FS CACHE + BUFFERS" } ], - "title": "HOST MEMORY IN GB", + "title": "Memory (GB)", "transparent": true, "type": "timeseries" } ], - "title": "System", + "title": "Host", "type": "row" } ], diff --git a/pkg/analyze/analyze.go b/pkg/analyze/analyze.go index 4380c99385a7..3eb841f5bb0b 100644 --- a/pkg/analyze/analyze.go +++ b/pkg/analyze/analyze.go @@ -192,9 +192,9 @@ func processLegacy(outF *os.File) func(finding *detect.Finding) { } type legacyOutput struct { - Data map[string]any - Event trace.Event - SigMetadata detect.SignatureMetadata + Data map[string]any `json:"Data,omitempty"` + Event trace.Event `json:"Context,omitempty"` + SigMetadata detect.SignatureMetadata `json:"SigMetadata,omitempty"` } func getSigsNames(signatures []detect.Signature) []string { diff --git a/pkg/bufferdecoder/decoder.go b/pkg/bufferdecoder/decoder.go index 4acb3f5407a5..8b7df285c02f 100644 --- a/pkg/bufferdecoder/decoder.go +++ b/pkg/bufferdecoder/decoder.go @@ -111,7 +111,7 @@ func (decoder *EbpfDecoder) DecodeArguments(args []trace.Argument, argnum int, e for i := 0; i < len(evtFields); i++ { if args[i].Value == nil { args[i].ArgMeta = evtFields[i] - args[i].Value = args[i].Zero + args[i].Value = evtFields[i].Zero } } return nil diff --git a/pkg/bufferdecoder/decoder_test.go b/pkg/bufferdecoder/decoder_test.go index 6d7900b6ff3c..d6bbf43ac63f 100644 --- a/pkg/bufferdecoder/decoder_test.go +++ b/pkg/bufferdecoder/decoder_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/assert" + "github.com/aquasecurity/tracee/pkg/events" "github.com/aquasecurity/tracee/types/trace" ) @@ -1005,3 +1006,80 @@ func BenchmarkBinaryMprotectWriteMeta(*testing.B) { binary.Read(binBuf, binary.LittleEndian, &s) } } + +func BenchmarkDecodeArguments(b *testing.B) { + /* + args := []trace.Argument{ + { + Name: "arg1", + Type: "u64", + Value: 1, + }, + { + Name: "arg2", + Type: "u64", + Value: 2, + }, + { + Name: "arg3", + Type: "u64", + Value: 3, + }, + ... + } + ****************** + buffer is the []byte representation of args instance + ****************** + */ + + buffer := []byte{ + 0, 1, 0, 0, 0, 0, 0, 0, 0, // arg1 + 1, 2, 0, 0, 0, 0, 0, 0, 0, // arg2 + 2, 3, 0, 0, 0, 0, 0, 0, 0, // arg3 + 3, 4, 0, 0, 0, 0, 0, 0, 0, // arg4 + 4, 5, 0, 0, 0, 0, 0, 0, 0, // arg5 + 5, 6, 0, 0, 0, 0, 0, 0, 0, // arg6 + 6, 7, 0, 0, 0, 0, 0, 0, 0, // arg7 + 7, 8, 0, 0, 0, 0, 0, 0, 0, // arg8 + } + evtFields := []trace.ArgMeta{ + {Name: "arg1", Type: "u64", Zero: 0}, + {Name: "arg2", Type: "u64", Zero: 0}, + {Name: "arg3", Type: "u64", Zero: 0}, + {Name: "arg4", Type: "u64", Zero: 0}, + {Name: "arg5", Type: "u64", Zero: 0}, + {Name: "arg6", Type: "u64", Zero: 0}, + {Name: "arg7", Type: "u64", Zero: 0}, + {Name: "arg8", Type: "u64", Zero: 0}, + } + + // decode half of the arguments leaving the rest to be populated as zero values + argnum := len(evtFields) / 2 + + evtVersion := events.NewVersion(1, 0, 0) + evtName := "test" + eventId := events.ID(0) + evtDef := events.NewDefinition( + eventId, + eventId+1000, + evtName, + evtVersion, + "", + "", + false, + false, + []string{}, + events.Dependencies{}, + evtFields, // fields + nil, + ) + + events.Core.AddBatch(map[events.ID]events.Definition{eventId: evtDef}) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + decoder := New(buffer) + args := make([]trace.Argument, len(evtFields)) + _ = decoder.DecodeArguments(args, argnum, evtFields, evtName, eventId) + } +} diff --git a/pkg/bufferdecoder/eventsreader.go b/pkg/bufferdecoder/eventsreader.go index 00c7a797cff9..13771b50f8de 100644 --- a/pkg/bufferdecoder/eventsreader.go +++ b/pkg/bufferdecoder/eventsreader.go @@ -255,9 +255,9 @@ func readSockaddrFromBuff(ebpfMsgDecoder *EbpfDecoder) (map[string]string, error } socketDomainArg, err := parsers.ParseSocketDomainArgument(uint64(family)) if err != nil { - socketDomainArg = parsers.AF_UNSPEC + socketDomainArg = parsers.AF_UNSPEC.String() } - res["sa_family"] = socketDomainArg.String() + res["sa_family"] = socketDomainArg switch family { case 1: // AF_UNIX /* diff --git a/pkg/cmd/flags/proctree.go b/pkg/cmd/flags/proctree.go index 020c86f369e6..596195799476 100644 --- a/pkg/cmd/flags/proctree.go +++ b/pkg/cmd/flags/proctree.go @@ -20,7 +20,8 @@ Example: both | process tree is built from both events and signals. --proctree process-cache=8192 | will cache up to 8192 processes in the tree (LRU cache). --proctree thread-cache=4096 | will cache up to 4096 threads in the tree (LRU cache). - --proctree disable-procfs-query | Will disable procfs queries during runtime + --proctree disable-procfs | will disable procfs entirely. + --proctree disable-procfs-query | will disable procfs quering during runtime. Use comma OR use the flag multiple times to choose multiple options: --proctree source=A,process-cache=B,thread-cache=C @@ -66,7 +67,9 @@ func PrepareProcTree(cacheSlice []string) (proctree.ProcTreeConfig, error) { default: return config, fmt.Errorf("unrecognized proctree source option: %v", option) } - cacheSet = true // at least the default ones + if config.Source != proctree.SourceNone { + cacheSet = true // at least the default ones + } continue } if strings.HasPrefix(value, "process-cache=") { @@ -93,7 +96,12 @@ func PrepareProcTree(cacheSlice []string) (proctree.ProcTreeConfig, error) { cacheSet = true continue } - if strings.HasPrefix(value, "disable-procfs-query") { + if value == "disable-procfs" { + config.ProcfsInitialization = false + config.ProcfsQuerying = false + continue + } + if value == "disable-procfs-query" { config.ProcfsQuerying = false continue } diff --git a/pkg/ebpf/c/common/context.h b/pkg/ebpf/c/common/context.h index 09841fbb85ff..5732173c198d 100644 --- a/pkg/ebpf/c/common/context.h +++ b/pkg/ebpf/c/common/context.h @@ -20,6 +20,7 @@ statfunc int init_program_data(program_data_t *, void *, u32); statfunc int init_tailcall_program_data(program_data_t *, void *); statfunc bool reset_event(event_data_t *, u32); statfunc void reset_event_args_buf(event_data_t *); +statfunc bool thread_stack_tracked(task_info_t *); // FUNCTIONS @@ -262,4 +263,9 @@ statfunc bool reset_event(event_data_t *event, u32 event_id) return true; } +statfunc bool thread_stack_tracked(task_info_t *task_info) +{ + return task_info->stack.start != 0 && task_info->stack.end != 0; +} + #endif diff --git a/pkg/ebpf/c/common/memory.h b/pkg/ebpf/c/common/memory.h index 6f8e0945c8f5..c9b94d3521bf 100644 --- a/pkg/ebpf/c/common/memory.h +++ b/pkg/ebpf/c/common/memory.h @@ -7,10 +7,14 @@ enum vma_type { - VMA_STACK, - VMA_HEAP, + VMA_FILE_BACKED, VMA_ANON, - VMA_OTHER + VMA_MAIN_STACK, + VMA_THREAD_STACK, + VMA_HEAP, + VMA_GOLANG_HEAP, + VMA_VDSO, + VMA_UNKNOWN, }; // PROTOTYPES @@ -22,11 +26,14 @@ statfunc unsigned long get_env_start_from_mm(struct mm_struct *); statfunc unsigned long get_env_end_from_mm(struct mm_struct *); statfunc unsigned long get_vma_flags(struct vm_area_struct *); statfunc struct vm_area_struct *find_vma(void *ctx, struct task_struct *task, u64 addr); -statfunc bool vma_is_stack(struct vm_area_struct *vma); -statfunc bool vma_is_heap(struct vm_area_struct *vma); +statfunc bool vma_is_file_backed(struct vm_area_struct *vma); +statfunc bool vma_is_main_stack(struct vm_area_struct *vma); +statfunc bool vma_is_main_heap(struct vm_area_struct *vma); statfunc bool vma_is_anon(struct vm_area_struct *vma); +statfunc bool vma_is_golang_heap(struct vm_area_struct *vma); +statfunc bool vma_is_thread_stack(task_info_t *task_info, struct vm_area_struct *vma); statfunc bool vma_is_vdso(struct vm_area_struct *vma); -statfunc enum vma_type get_vma_type(struct vm_area_struct *vma); +statfunc enum vma_type get_vma_type(task_info_t *task_info, struct vm_area_struct *vma); // FUNCTIONS @@ -121,7 +128,12 @@ statfunc struct vm_area_struct *find_vma(void *ctx, struct task_struct *task, u6 return vma; } -statfunc bool vma_is_stack(struct vm_area_struct *vma) +statfunc bool vma_is_file_backed(struct vm_area_struct *vma) +{ + return BPF_CORE_READ(vma, vm_file) != NULL; +} + +statfunc bool vma_is_main_stack(struct vm_area_struct *vma) { struct mm_struct *vm_mm = BPF_CORE_READ(vma, vm_mm); if (vm_mm == NULL) @@ -138,7 +150,7 @@ statfunc bool vma_is_stack(struct vm_area_struct *vma) return false; } -statfunc bool vma_is_heap(struct vm_area_struct *vma) +statfunc bool vma_is_main_heap(struct vm_area_struct *vma) { struct mm_struct *vm_mm = BPF_CORE_READ(vma, vm_mm); if (vm_mm == NULL) @@ -158,7 +170,54 @@ statfunc bool vma_is_heap(struct vm_area_struct *vma) statfunc bool vma_is_anon(struct vm_area_struct *vma) { - return BPF_CORE_READ(vma, vm_file) == NULL; + return !vma_is_file_backed(vma); +} + +// The golang heap consists of arenas which are memory regions mapped using mmap. +// When allocating areans, golang supplies mmap with an address hint, which is an +// address that the kernel should place the mapping at. +// Hints for x86_64 begin at 0xc000000000 and for ARM64 at 0x4000000000. +// From observation, when allocating arenas the MAP_FIXED flag is used which forces +// the kernel to use the specified address or fail the mapping, so it is safe to +// rely on the address pattern to determine if it belongs to a heap arena. +#define GOLANG_ARENA_HINT_MASK 0xffffffff00000000UL +#if defined(bpf_target_x86) + #define GOLANG_ARENA_HINT (0xc0UL << 32) +#elif defined(bpf_target_arm64) + #define GOLANG_ARENA_HINT (0x40UL << 32) +#else + #error Unsupported architecture +#endif +// We define a max hint that we assume golang allocations will never exceed. +// This translates to the address 0xff00000000. +// This means that we assume that a golang program will never allocate more than +// 256GB of memory on x86_64, or 768GB on ARM64. +#define GOLANG_ARENA_HINT_MAX (0xffUL << 32) + +statfunc bool vma_is_golang_heap(struct vm_area_struct *vma) +{ + u64 vm_start = BPF_CORE_READ(vma, vm_start); + + // Check if the VMA address is in the range provided by golang heap arena address hints. + // Of course, any program can also allocate memory at these addresses which will result + // in a false positive for this check, so any caller of this function must make sure + // that a false positive for this check is acceptable. + return (vm_start & GOLANG_ARENA_HINT_MASK) >= GOLANG_ARENA_HINT && + (vm_start & GOLANG_ARENA_HINT_MASK) <= GOLANG_ARENA_HINT_MAX; +} + +statfunc bool vma_is_thread_stack(task_info_t *task_info, struct vm_area_struct *vma) +{ + // Get the stack area for this task + address_range_t *stack = &task_info->stack; + if (stack->start == 0 && stack->end == 0) + // This thread's stack isn't tracked + return false; + + // Check if the VMA is **contained** in the thread stack range. + // We don't check exact address range match because a change to the permissions + // of part of the stack VMA will split it into multiple VMAs. + return BPF_CORE_READ(vma, vm_start) >= stack->start && BPF_CORE_READ(vma, vm_end) <= stack->end; } statfunc bool vma_is_vdso(struct vm_area_struct *vma) @@ -174,19 +233,57 @@ statfunc bool vma_is_vdso(struct vm_area_struct *vma) return strncmp("[vdso]", mapping_name, 7) == 0; } -statfunc enum vma_type get_vma_type(struct vm_area_struct *vma) +statfunc enum vma_type get_vma_type(task_info_t *task_info, struct vm_area_struct *vma) { - if (vma_is_stack(vma)) - return VMA_STACK; + // The check order is a balance between how expensive the check is and how likely it is to pass - if (vma_is_heap(vma)) + if (vma_is_file_backed(vma)) + return VMA_FILE_BACKED; + + if (vma_is_main_stack(vma)) + return VMA_MAIN_STACK; + + if (vma_is_main_heap(vma)) return VMA_HEAP; - if (vma_is_anon(vma) && !vma_is_vdso(vma)) { + if (vma_is_anon(vma)) { + if (vma_is_golang_heap(vma)) + return VMA_GOLANG_HEAP; + + if (vma_is_thread_stack(task_info, vma)) + return VMA_THREAD_STACK; + + if (vma_is_vdso(vma)) + return VMA_VDSO; + return VMA_ANON; } - return VMA_OTHER; + return VMA_UNKNOWN; +} + +statfunc const char *get_vma_type_str(enum vma_type vma_type) +{ + switch (vma_type) { + case VMA_FILE_BACKED: + return "file backed"; + case VMA_ANON: + return "anonymous"; + case VMA_MAIN_STACK: + return "main stack"; + case VMA_THREAD_STACK: + return "thread stack"; + case VMA_HEAP: + return "heap"; + case VMA_GOLANG_HEAP: + // Goroutine stacks are allocated on the golang heap + return "golang heap/stack"; + case VMA_VDSO: + return "vdso"; + case VMA_UNKNOWN: + default: + return "unknown"; + } } #endif diff --git a/pkg/ebpf/c/tracee.bpf.c b/pkg/ebpf/c/tracee.bpf.c index 323fea510d58..031447c5e2e4 100644 --- a/pkg/ebpf/c/tracee.bpf.c +++ b/pkg/ebpf/c/tracee.bpf.c @@ -569,6 +569,36 @@ int sys_dup_exit_tail(void *ctx) return 0; } +statfunc void update_thread_stack(void *ctx, task_info_t *task_info, struct task_struct *task) +{ + // Kernel threads and group leaders are not relevant, reset their stack area + if (get_task_flags(task) & PF_KTHREAD || BPF_CORE_READ(task, pid) == BPF_CORE_READ(task, tgid)) + task_info->stack = (address_range_t){0}; + + // Get user SP of new thread +#if defined(bpf_target_x86) + struct fork_frame *fork_frame = (struct fork_frame *) BPF_CORE_READ(task, thread.sp); + u64 thread_sp = BPF_CORE_READ(fork_frame, regs.sp); +#elif defined(bpf_target_arm64) + struct pt_regs *thread_regs = (struct pt_regs *) BPF_CORE_READ(task, thread.cpu_context.sp); + u64 thread_sp = BPF_CORE_READ(thread_regs, sp); +#else + #error Unsupported architecture +#endif + + // Find VMA which contains the SP. + // We subtract 1 fromt the SP because it may be just past the end of the VMA (top of the stack). + // For example: stack VMA mapped at 0x1000 with size 0x1000, + // SP is set to 0x2000 (which is not part of the VMA whose address range is 0x1000-0x1fff). + struct vm_area_struct *vma = find_vma(ctx, task, thread_sp - 1); + if (unlikely(vma == NULL)) + return; + + // Add the VMA address range to the task info + task_info->stack = + (address_range_t){.start = BPF_CORE_READ(vma, vm_start), .end = BPF_CORE_READ(vma, vm_end)}; +} + // trace/events/sched.h: TP_PROTO(struct task_struct *parent, struct task_struct *child) SEC("raw_tracepoint/sched_process_fork") int tracepoint__sched__sched_process_fork(struct bpf_raw_tracepoint_args *ctx) @@ -608,6 +638,11 @@ int tracepoint__sched__sched_process_fork(struct bpf_raw_tracepoint_args *ctx) task->context.host_tid = child_tid; task->context.start_time = child_start_time; + // Track thread stack if needed + if (event_is_selected(SUSPICIOUS_SYSCALL_SOURCE, p.event->context.policies_version) || + event_is_selected(STACK_PIVOT, p.event->context.policies_version)) + update_thread_stack(ctx, task, child); + // Update the proc_info_map with the new process's info (from parent) proc_info_t *c_proc_info = bpf_map_lookup_elem(&proc_info_map, &child_pid); @@ -1344,6 +1379,9 @@ int tracepoint__sched__sched_process_exec(struct bpf_raw_tracepoint_args *ctx) program_data_t p = {}; if (!init_program_data(&p, ctx, SCHED_PROCESS_EXEC)) return 0; + + // Reset thread stack area + p.task_info->stack = (address_range_t){0}; // Perform checks below before evaluate_scope_filters(), so tracee can filter by newly created containers // or processes. Assume that a new container, or pod, has started when a process of a newly @@ -1483,6 +1521,24 @@ int tracepoint__sched__sched_process_exit(struct bpf_raw_tracepoint_args *ctx) if (!init_program_data(&p, ctx, SCHED_PROCESS_EXIT)) return 0; + // The syscall number cannot be trusted in the following cases: + // + // 1. If the task was terminated due to a signal (PF_SIGNALED is set), the syscall + // context may be inconsistent. + // + // 2. If the task was not signaled: + // - A kernel thread (PF_KTHREAD is set) is not expected to have a valid syscall context, so + // the function init_program_data has already set its syscall number as NO_SYSCALL (-1). + // - If PF_KTHREAD is not set but the syscall value is negative, it may be due to + // an invalid or clobbered context. + // + // In any of these cases, we explicitly mark the syscall number as NO_SYSCALL (-1) to avoid + // misinterpretation. + int task_flags = get_task_flags(p.event->task); + if ((task_flags & PF_SIGNALED) || + (!(task_flags & PF_KTHREAD) && (p.event->context.syscall < 0))) + p.event->context.syscall = NO_SYSCALL; + // evaluate matched_policies before removing this pid from the maps evaluate_scope_filters(&p); @@ -5240,37 +5296,54 @@ int BPF_KPROBE(trace_chmod_common) return events_perf_submit(&p, 0); } -SEC("kprobe/suspicious_syscall_source") -int BPF_KPROBE(suspicious_syscall_source) +// +// Syscall checkers +// + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, MAX_EVENT_ID); + __type(key, u32); + __type(value, u32); +} suspicious_syscall_source_syscalls SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, MAX_EVENT_ID); + __type(key, u32); + __type(value, u32); +} stack_pivot_syscalls SEC(".maps"); + +statfunc void check_suspicious_syscall_source(void *ctx, struct pt_regs *regs, u32 syscall) { program_data_t p = {}; if (!init_program_data(&p, ctx, SUSPICIOUS_SYSCALL_SOURCE)) - return 0; + return; if (!evaluate_scope_filters(&p)) - return 0; + return; // Get instruction pointer - struct pt_regs *regs = ctx; - if (get_kconfig(ARCH_HAS_SYSCALL_WRAPPER)) - regs = (struct pt_regs *) PT_REGS_PARM1(ctx); u64 ip = PT_REGS_IP_CORE(regs); // Find VMA which contains the instruction pointer struct task_struct *task = (struct task_struct *) bpf_get_current_task(); if (unlikely(task == NULL)) - return 0; + return; struct vm_area_struct *vma = find_vma(ctx, task, ip); - if (vma == NULL) - return 0; + if (unlikely(vma == NULL)) + return; - // Get VMA type and make sure it's abnormal (stack/heap/anonymous VMA) - enum vma_type vma_type = get_vma_type(vma); - if (vma_type == VMA_OTHER) - return 0; + // If the VMA is file-backed, the syscall is determined to be legitimate + if (vma_is_file_backed(vma)) + return; - // Get syscall ID - u32 syscall = get_syscall_id_from_regs(regs); + // In 32-bit compat mode, syscalls may be invoked by calling into a VDSO provided + // syscall handler (the vsyscall mechanism). + // Even the 64-bit VDSO may invoke syscalls, as a fallback mechanism. + // In such cases, we don't know where was the code that called into the VDSO. + if (vma_is_vdso(vma)) + return; // Build a key that identifies the combination of syscall, // source VMA and process so we don't submit it multiple times @@ -5283,37 +5356,97 @@ int BPF_KPROBE(suspicious_syscall_source) // Try updating the map with the requirement that this key does not exist yet if ((int) bpf_map_update_elem(&syscall_source_map, &key, &val, BPF_NOEXIST) == -EEXIST) // This key already exists, no need to submit the same syscall-vma-process combination again - return 0; + return; - char *vma_type_str; + const char *vma_type_str = get_vma_type_str(get_vma_type(p.task_info, vma)); + unsigned long vma_start = BPF_CORE_READ(vma, vm_start); + unsigned long vma_size = BPF_CORE_READ(vma, vm_end) - vma_start; + unsigned long vma_flags = BPF_CORE_READ(vma, vm_flags); - switch (vma_type) { - case VMA_STACK: - vma_type_str = "stack"; - break; - case VMA_HEAP: - vma_type_str = "heap"; - break; - case VMA_ANON: - vma_type_str = "anonymous"; - break; - // shouldn't happen - default: - return 0; - } + save_to_submit_buf(&p.event->args_buf, &syscall, sizeof(syscall), 0); + save_to_submit_buf(&p.event->args_buf, &ip, sizeof(ip), 1); + save_str_to_buf(&p.event->args_buf, (void *) vma_type_str, 2); + save_to_submit_buf(&p.event->args_buf, &vma_start, sizeof(vma_start), 3); + save_to_submit_buf(&p.event->args_buf, &vma_size, sizeof(vma_size), 4); + save_to_submit_buf(&p.event->args_buf, &vma_flags, sizeof(vma_flags), 5); + + events_perf_submit(&p, 0); +} + +statfunc void check_stack_pivot(void *ctx, struct pt_regs *regs, u32 syscall) +{ + program_data_t p = {}; + + if (!init_program_data(&p, ctx, STACK_PIVOT)) + return; + + if (!evaluate_scope_filters(&p)) + return; + + // Get stack pointer + u64 sp = PT_REGS_SP_CORE(regs); + // Find VMA which contains the stack pointer + struct task_struct *task = (struct task_struct *) bpf_get_current_task(); + if (unlikely(task == NULL)) + return; + struct vm_area_struct *vma = find_vma(ctx, task, sp); + if (unlikely(vma == NULL)) + return; + + // Check if the stack pointer points to the stack region. + // + // Goroutine stacks are allocated on golang's heap, which means that an + // exploit performing a stack pivot on a go program will result in a false + // negative if the new stack location is on golang's heap. + // + // To identify thread stacks, they need to be tracked when new threads are + // created. This means that we cannot identify stacks of threads that were + // created before tracee started. To avoid false positives, we ignore events + // where the stack pointer's VMA might be a thread stack but it was not + // tracked for this thread. This may result in false negatives. + enum vma_type vma_type = get_vma_type(p.task_info, vma); + if (vma_type == VMA_MAIN_STACK || vma_type == VMA_GOLANG_HEAP || vma_type == VMA_THREAD_STACK || + (vma_type == VMA_ANON && !thread_stack_tracked(p.task_info))) + return; + + const char *vma_type_str = get_vma_type_str(vma_type); unsigned long vma_start = BPF_CORE_READ(vma, vm_start); unsigned long vma_size = BPF_CORE_READ(vma, vm_end) - vma_start; unsigned long vma_flags = BPF_CORE_READ(vma, vm_flags); save_to_submit_buf(&p.event->args_buf, &syscall, sizeof(syscall), 0); - save_to_submit_buf(&p.event->args_buf, &ip, sizeof(ip), 1); - save_str_to_buf(&p.event->args_buf, vma_type_str, 2); + save_to_submit_buf(&p.event->args_buf, &sp, sizeof(sp), 1); + save_str_to_buf(&p.event->args_buf, (void *) vma_type_str, 2); save_to_submit_buf(&p.event->args_buf, &vma_start, sizeof(vma_start), 3); save_to_submit_buf(&p.event->args_buf, &vma_size, sizeof(vma_size), 4); save_to_submit_buf(&p.event->args_buf, &vma_flags, sizeof(vma_flags), 5); events_perf_submit(&p, 0); +} + +SEC("kprobe/syscall_checker") +int BPF_KPROBE(syscall_checker) +{ + // Get user registers + struct pt_regs *regs = ctx; + if (get_kconfig(ARCH_HAS_SYSCALL_WRAPPER)) + regs = (struct pt_regs *) PT_REGS_PARM1(ctx); + + // Get syscall ID + u32 syscall = get_syscall_id_from_regs(regs); + if (is_compat((struct task_struct *) bpf_get_current_task())) { + u32 *id_64 = bpf_map_lookup_elem(&sys_32_to_64_map, &syscall); + if (id_64 == NULL) + return 0; + syscall = *id_64; + } + + if (bpf_map_lookup_elem(&suspicious_syscall_source_syscalls, &syscall) != NULL) + check_suspicious_syscall_source(ctx, regs, syscall); + + if (bpf_map_lookup_elem(&stack_pivot_syscalls, &syscall) != NULL) + check_stack_pivot(ctx, regs, syscall); return 0; } diff --git a/pkg/ebpf/c/types.h b/pkg/ebpf/c/types.h index fa4c0260e0bb..521f5593c4c1 100644 --- a/pkg/ebpf/c/types.h +++ b/pkg/ebpf/c/types.h @@ -125,6 +125,7 @@ enum event_id_e SECURITY_PATH_NOTIFY, SET_FS_PWD, SUSPICIOUS_SYSCALL_SOURCE, + STACK_PIVOT, HIDDEN_KERNEL_MODULE_SEEKER, MODULE_LOAD, MODULE_FREE, @@ -223,11 +224,18 @@ enum container_state_e CONTAINER_STARTED // a process in the cgroup executed a new binary }; +typedef struct { + u64 start; + u64 end; +} address_range_t; + typedef struct task_info { task_context_t context; syscall_data_t syscall_data; bool syscall_traced; // indicates that syscall_data is valid u8 container_state; // the state of the container the task resides in + address_range_t + stack; // stack area, only relevant for tasks that aren't group leaders (threads) } task_info_t; typedef struct file_id { diff --git a/pkg/ebpf/c/vmlinux.h b/pkg/ebpf/c/vmlinux.h index 41cbfd596728..747ffa35720f 100644 --- a/pkg/ebpf/c/vmlinux.h +++ b/pkg/ebpf/c/vmlinux.h @@ -256,6 +256,28 @@ typedef struct { uid_t val; } kuid_t; +#if defined(__TARGET_ARCH_x86) + +struct thread_struct { + unsigned long sp; +}; + +struct fork_frame { + struct pt_regs regs; +}; + +#elif defined(__TARGET_ARCH_arm64) + +struct cpu_context { + unsigned long sp; +}; + +struct thread_struct { + struct cpu_context cpu_context; +}; + +#endif + struct task_struct { struct thread_info thread_info; unsigned int flags; @@ -278,6 +300,7 @@ struct task_struct { struct signal_struct *signal; void *stack; struct sighand_struct *sighand; + struct thread_struct thread; }; typedef struct { diff --git a/pkg/ebpf/c/vmlinux_missing.h b/pkg/ebpf/c/vmlinux_missing.h index eb634720b3ff..8c1698485dff 100644 --- a/pkg/ebpf/c/vmlinux_missing.h +++ b/pkg/ebpf/c/vmlinux_missing.h @@ -48,7 +48,8 @@ #define ICMPV6_ECHO_REQUEST 128 -#define PF_KTHREAD 0x00200000 /* I am a kernel thread */ +#define PF_SIGNALED 0x00000400 /* Killed by a signal */ +#define PF_KTHREAD 0x00200000 /* I am a kernel thread */ #define TASK_COMM_LEN 16 diff --git a/pkg/ebpf/controlplane/controller.go b/pkg/ebpf/controlplane/controller.go index 2112d52af753..981496054078 100644 --- a/pkg/ebpf/controlplane/controller.go +++ b/pkg/ebpf/controlplane/controller.go @@ -3,6 +3,7 @@ package controlplane import ( "context" "fmt" + "sync" "time" "github.com/aquasecurity/libbpfgo" @@ -25,6 +26,7 @@ type Controller struct { lostSignalChan chan uint64 bpfModule *libbpfgo.Module signalBuffer *libbpfgo.PerfBuffer + signalPool *sync.Pool cgroupManager *containers.Containers processTree *proctree.ProcessTree enrichDisabled bool @@ -43,6 +45,11 @@ func NewController( signalChan: make(chan []byte, 100), lostSignalChan: make(chan uint64), bpfModule: bpfModule, + signalPool: &sync.Pool{ + New: func() interface{} { + return &signal{} + }, + }, cgroupManager: cgroupManager, processTree: procTree, enrichDisabled: enrichDisabled, @@ -69,16 +76,22 @@ func (ctrl *Controller) Run(ctx context.Context) { for { select { case signalData := <-ctrl.signalChan: - signal := signal{} + signal := ctrl.getSignalFromPool() + + // NOTE: override all the fields of the signal, to avoid any previous data. err := signal.Unmarshal(signalData) if err != nil { logger.Errorw("error unmarshaling signal ebpf buffer", "error", err) + ctrl.putSignalInPool(signal) continue } + err = ctrl.processSignal(signal) if err != nil { logger.Errorw("error processing control plane signal", "error", err) } + + ctrl.putSignalInPool(signal) case lost := <-ctrl.lostSignalChan: logger.Warnw(fmt.Sprintf("Lost %d control plane signals", lost)) case <-ctrl.ctx.Done(): @@ -93,8 +106,10 @@ func (ctrl *Controller) Stop() error { return nil } +// Private + // processSignal processes a signal from the control plane. -func (ctrl *Controller) processSignal(signal signal) error { +func (ctrl *Controller) processSignal(signal *signal) error { switch signal.id { case events.SignalCgroupMkdir: return ctrl.processCgroupMkdir(signal.args) @@ -111,7 +126,20 @@ func (ctrl *Controller) processSignal(signal signal) error { return nil } -// Private +// getSignalFromPool gets a signal from the pool. +// signal certainly contains old data, so it must be updated before use. +func (ctrl *Controller) getSignalFromPool() *signal { + // revive:disable:unchecked-type-assertion + sig := ctrl.signalPool.Get().(*signal) + // revive:enable:unchecked-type-assertion + + return sig +} + +// putSignalInPool puts a signal back in the pool. +func (ctrl *Controller) putSignalInPool(sig *signal) { + ctrl.signalPool.Put(sig) +} // debug prints the process tree every 5 seconds (for debugging purposes). func (ctrl *Controller) debug(enable bool) { diff --git a/pkg/ebpf/controlplane/processes.go b/pkg/ebpf/controlplane/processes.go index 200a2bb00fb7..5ae4f3679776 100644 --- a/pkg/ebpf/controlplane/processes.go +++ b/pkg/ebpf/controlplane/processes.go @@ -2,7 +2,6 @@ package controlplane import ( "github.com/aquasecurity/tracee/pkg/events/parse" - "github.com/aquasecurity/tracee/pkg/proctree" "github.com/aquasecurity/tracee/pkg/time" "github.com/aquasecurity/tracee/pkg/utils" "github.com/aquasecurity/tracee/types/trace" @@ -13,213 +12,221 @@ import ( // func (ctrl *Controller) procTreeForkProcessor(args []trace.Argument) error { - var errs []error - if ctrl.processTree == nil { return nil // process tree is disabled } + var err error + // NOTE: override all the fields of the forkFeed, to avoid any previous data. + forkFeed := ctrl.processTree.GetForkFeedFromPool() + defer ctrl.processTree.PutForkFeedInPool(forkFeed) + // NOTE: The "parent" related arguments can be ignored for process tree purposes. // Process & Event identification arguments - timestamp, err := parse.ArgVal[uint64](args, "timestamp") - errs = append(errs, err) + forkFeed.TimeStamp, err = parse.ArgVal[uint64](args, "timestamp") + if err != nil { + return err + } // Parent Process (Go up in hierarchy until parent is a process and not a lwp) - parentTid, err := parse.ArgVal[int32](args, "parent_process_tid") - errs = append(errs, err) - parentNsTid, err := parse.ArgVal[int32](args, "parent_process_ns_tid") - errs = append(errs, err) - parentPid, err := parse.ArgVal[int32](args, "parent_process_pid") - errs = append(errs, err) - parentNsPid, err := parse.ArgVal[int32](args, "parent_process_ns_pid") - errs = append(errs, err) - parentStartTime, err := parse.ArgVal[uint64](args, "parent_process_start_time") - errs = append(errs, err) + forkFeed.ParentTid, err = parse.ArgVal[int32](args, "parent_process_tid") + if err != nil { + return err + } + forkFeed.ParentNsTid, err = parse.ArgVal[int32](args, "parent_process_ns_tid") + if err != nil { + return err + } + forkFeed.ParentPid, err = parse.ArgVal[int32](args, "parent_process_pid") + if err != nil { + return err + } + forkFeed.ParentNsPid, err = parse.ArgVal[int32](args, "parent_process_ns_pid") + if err != nil { + return err + } + forkFeed.ParentStartTime, err = parse.ArgVal[uint64](args, "parent_process_start_time") + if err != nil { + return err + } // Thread Group Leader (might be the same as the "child", if "child" is a process) - leaderTid, err := parse.ArgVal[int32](args, "leader_tid") - errs = append(errs, err) - leaderNsTid, err := parse.ArgVal[int32](args, "leader_ns_tid") - errs = append(errs, err) - leaderPid, err := parse.ArgVal[int32](args, "leader_pid") - errs = append(errs, err) - leaderNsPid, err := parse.ArgVal[int32](args, "leader_ns_pid") - errs = append(errs, err) - leaderStartTime, err := parse.ArgVal[uint64](args, "leader_start_time") - errs = append(errs, err) + forkFeed.LeaderTid, err = parse.ArgVal[int32](args, "leader_tid") + if err != nil { + return err + } + forkFeed.LeaderNsTid, err = parse.ArgVal[int32](args, "leader_ns_tid") + if err != nil { + return err + } + forkFeed.LeaderPid, err = parse.ArgVal[int32](args, "leader_pid") + if err != nil { + return err + } + forkFeed.LeaderNsPid, err = parse.ArgVal[int32](args, "leader_ns_pid") + if err != nil { + return err + } + forkFeed.LeaderStartTime, err = parse.ArgVal[uint64](args, "leader_start_time") + if err != nil { + return err + } // Child (might be a process or a thread) - childTid, err := parse.ArgVal[int32](args, "child_tid") - errs = append(errs, err) - childNsTid, err := parse.ArgVal[int32](args, "child_ns_tid") - errs = append(errs, err) - childPid, err := parse.ArgVal[int32](args, "child_pid") - errs = append(errs, err) - childNsPid, err := parse.ArgVal[int32](args, "child_ns_pid") - errs = append(errs, err) - childStartTime, err := parse.ArgVal[uint64](args, "start_time") // child_start_time - errs = append(errs, err) - - // Handle errors - for _, err := range errs { - if err != nil { - return err - } - } - - timestamp = time.BootToEpochNS(timestamp) - childStartTime = time.BootToEpochNS(childStartTime) - parentStartTime = time.BootToEpochNS(parentStartTime) - leaderStartTime = time.BootToEpochNS(leaderStartTime) - - childHash := utils.HashTaskID(uint32(childTid), childStartTime) - parentHash := utils.HashTaskID(uint32(parentTid), parentStartTime) - leaderHash := utils.HashTaskID(uint32(leaderTid), leaderStartTime) - - return ctrl.processTree.FeedFromFork( - proctree.ForkFeed{ - TimeStamp: timestamp, - ChildHash: childHash, - ParentHash: parentHash, - LeaderHash: leaderHash, - ParentTid: parentTid, - ParentNsTid: parentNsTid, - ParentPid: parentPid, - ParentNsPid: parentNsPid, - ParentStartTime: parentStartTime, - LeaderTid: leaderTid, - LeaderNsTid: leaderNsTid, - LeaderPid: leaderPid, - LeaderNsPid: leaderNsPid, - LeaderStartTime: leaderStartTime, - ChildTid: childTid, - ChildNsTid: childNsTid, - ChildPid: childPid, - ChildNsPid: childNsPid, - ChildStartTime: childStartTime, - }, - ) + forkFeed.ChildTid, err = parse.ArgVal[int32](args, "child_tid") + if err != nil { + return err + } + forkFeed.ChildNsTid, err = parse.ArgVal[int32](args, "child_ns_tid") + if err != nil { + return err + } + forkFeed.ChildPid, err = parse.ArgVal[int32](args, "child_pid") + if err != nil { + return err + } + forkFeed.ChildNsPid, err = parse.ArgVal[int32](args, "child_ns_pid") + if err != nil { + return err + } + forkFeed.ChildStartTime, err = parse.ArgVal[uint64](args, "start_time") // child_start_time + if err != nil { + return err + } + + forkFeed.TimeStamp = time.BootToEpochNS(forkFeed.TimeStamp) + forkFeed.ChildStartTime = time.BootToEpochNS(forkFeed.ChildStartTime) + forkFeed.ParentStartTime = time.BootToEpochNS(forkFeed.ParentStartTime) + forkFeed.LeaderStartTime = time.BootToEpochNS(forkFeed.LeaderStartTime) + + forkFeed.ChildHash = utils.HashTaskID(uint32(forkFeed.ChildTid), forkFeed.ChildStartTime) + forkFeed.ParentHash = utils.HashTaskID(uint32(forkFeed.ParentTid), forkFeed.ParentStartTime) + forkFeed.LeaderHash = utils.HashTaskID(uint32(forkFeed.LeaderTid), forkFeed.LeaderStartTime) + + return ctrl.processTree.FeedFromFork(forkFeed) } func (ctrl *Controller) procTreeExecProcessor(args []trace.Argument) error { - var errs []error - if ctrl.processTree == nil { return nil // process tree is disabled } + var err error + + // NOTE: override all the fields of the execFeed, to avoid any previous data. + execFeed := ctrl.processTree.GetExecFeedFromPool() + defer ctrl.processTree.PutExecFeedInPool(execFeed) + // Process & Event identification arguments (won't exist for regular events) - timestamp, err := parse.ArgVal[uint64](args, "timestamp") - errs = append(errs, err) - taskHash, _ := parse.ArgVal[uint32](args, "task_hash") - errs = append(errs, err) - parentHash, _ := parse.ArgVal[uint32](args, "parent_hash") - errs = append(errs, err) - leaderHash, _ := parse.ArgVal[uint32](args, "leader_hash") - errs = append(errs, err) + execFeed.TimeStamp, err = parse.ArgVal[uint64](args, "timestamp") + if err != nil { + return err + } + execFeed.TaskHash, _ = parse.ArgVal[uint32](args, "task_hash") + execFeed.ParentHash, _ = parse.ArgVal[uint32](args, "parent_hash") + execFeed.LeaderHash, _ = parse.ArgVal[uint32](args, "leader_hash") // Executable - cmdPath, err := parse.ArgVal[string](args, "cmdpath") - errs = append(errs, err) - pathName, err := parse.ArgVal[string](args, "pathname") - errs = append(errs, err) - dev, err := parse.ArgVal[uint32](args, "dev") - errs = append(errs, err) - inode, err := parse.ArgVal[uint64](args, "inode") - errs = append(errs, err) - ctime, err := parse.ArgVal[uint64](args, "ctime") - errs = append(errs, err) - inodeMode, err := parse.ArgVal[uint16](args, "inode_mode") - errs = append(errs, err) - - // Binary Interpreter (or Loader): might come empty from the kernel - interPathName, _ := parse.ArgVal[string](args, "interpreter_pathname") - interDev, _ := parse.ArgVal[uint32](args, "interpreter_dev") - interInode, _ := parse.ArgVal[uint64](args, "interpreter_inode") - interCtime, _ := parse.ArgVal[uint64](args, "interpreter_ctime") + execFeed.CmdPath, err = parse.ArgVal[string](args, "cmdpath") + if err != nil { + return err + } + execFeed.PathName, err = parse.ArgVal[string](args, "pathname") + if err != nil { + return err + } + execFeed.Dev, err = parse.ArgVal[uint32](args, "dev") + if err != nil { + return err + } + execFeed.Inode, err = parse.ArgVal[uint64](args, "inode") + if err != nil { + return err + } + execFeed.Ctime, err = parse.ArgVal[uint64](args, "ctime") + if err != nil { + return err + } + execFeed.InodeMode, err = parse.ArgVal[uint16](args, "inode_mode") + if err != nil { + return err + } + + // // Binary Interpreter (or Loader): might come empty from the kernel + // InterpreterPath, _ := parse.ArgVal[string](args, "interpreter_pathname") + // InterpreterDev, _ := parse.ArgVal[uint32](args, "interpreter_dev") + // InterpreterInode, _ := parse.ArgVal[uint64](args, "interpreter_inode") + // InterpreterCtime, _ := parse.ArgVal[uint64](args, "interpreter_ctime") // Real Interpreter - interp, err := parse.ArgVal[string](args, "interp") - errs = append(errs, err) + execFeed.Interp, err = parse.ArgVal[string](args, "interp") + if err != nil { + return err + } // Others - stdinType, err := parse.ArgVal[uint16](args, "stdin_type") - errs = append(errs, err) - stdinPath, err := parse.ArgVal[string](args, "stdin_path") - errs = append(errs, err) - invokedFromKernel, err := parse.ArgVal[int32](args, "invoked_from_kernel") - errs = append(errs, err) - - // Handle errors - for _, err := range errs { - if err != nil { - return err - } - } - - return ctrl.processTree.FeedFromExec( - proctree.ExecFeed{ - TimeStamp: time.BootToEpochNS(timestamp), - TaskHash: taskHash, - ParentHash: parentHash, - LeaderHash: leaderHash, - CmdPath: cmdPath, - PathName: pathName, - Dev: dev, - Inode: inode, - Ctime: ctime, - InodeMode: inodeMode, - InterpreterPath: interPathName, - InterpreterDev: interDev, - InterpreterInode: interInode, - InterpreterCtime: interCtime, - Interp: interp, - StdinType: stdinType, - StdinPath: stdinPath, - InvokedFromKernel: invokedFromKernel, - }, - ) + execFeed.StdinType, err = parse.ArgVal[uint16](args, "stdin_type") + if err != nil { + return err + } + execFeed.StdinPath, err = parse.ArgVal[string](args, "stdin_path") + if err != nil { + return err + } + execFeed.InvokedFromKernel, err = parse.ArgVal[int32](args, "invoked_from_kernel") + if err != nil { + return err + } + + return ctrl.processTree.FeedFromExec(execFeed) } func (ctrl *Controller) procTreeExitProcessor(args []trace.Argument) error { - var errs []error - if ctrl.processTree == nil { return nil // process tree is disabled } + // NOTE: Currently FeedFromExit is only using TaskHash and TimeStamp from the ExitFeed. + // So the other fields are commented out for now. + // + // TODO: Analyze if the other fields will be needed in the future. + var err error + + // NOTE: override all the fields of the exitFeed, to avoid any previous data. + exitFeed := ctrl.processTree.GetExitFeedFromPool() + defer ctrl.processTree.PutExitFeedInPool(exitFeed) + // Process & Event identification arguments (won't exist for regular events) - timestamp, err := parse.ArgVal[uint64](args, "timestamp") - errs = append(errs, err) - taskHash, err := parse.ArgVal[uint32](args, "task_hash") - errs = append(errs, err) - parentHash, err := parse.ArgVal[uint32](args, "parent_hash") - errs = append(errs, err) - leaderHash, err := parse.ArgVal[uint32](args, "leader_hash") - errs = append(errs, err) - - // Exit logic arguments - exitCode, err := parse.ArgVal[int64](args, "exit_code") - errs = append(errs, err) - groupExit, err := parse.ArgVal[bool](args, "process_group_exit") - errs = append(errs, err) - - // Handle errors - for _, err := range errs { - if err != nil { - return err - } - } - - return ctrl.processTree.FeedFromExit( - proctree.ExitFeed{ - TimeStamp: time.BootToEpochNS(timestamp), // time of exit is already a times)p - TaskHash: taskHash, - ParentHash: parentHash, - LeaderHash: leaderHash, - ExitCode: exitCode, - Group: groupExit, - }, - ) + exitFeed.TimeStamp, err = parse.ArgVal[uint64](args, "timestamp") + if err != nil { + return err + } + // time of exit is already a timestamp) + exitFeed.TimeStamp = time.BootToEpochNS(exitFeed.TimeStamp) + + exitFeed.TaskHash, err = parse.ArgVal[uint32](args, "task_hash") + if err != nil { + return err + } + // exitFeed.ParentHash, err = parse.ArgVal[uint32](args, "parent_hash") + // if err != nil { + // return err + // } + // exitFeed.LeaderHash, err = parse.ArgVal[uint32](args, "leader_hash") + // if err != nil { + // return err + // } + + // // Exit logic arguments + // exitFeed.ExitCode, err = parse.ArgVal[int64](args, "exit_code") + // if err != nil { + // return err + // } + // exitFeed.Group, err = parse.ArgVal[bool](args, "process_group_exit") + // if err != nil { + // return err + // } + + return ctrl.processTree.FeedFromExit(exitFeed) } diff --git a/pkg/ebpf/controlplane/processes_bench_test.go b/pkg/ebpf/controlplane/processes_bench_test.go new file mode 100644 index 000000000000..7354f611cc67 --- /dev/null +++ b/pkg/ebpf/controlplane/processes_bench_test.go @@ -0,0 +1,109 @@ +package controlplane + +import ( + "context" + "testing" + + "github.com/aquasecurity/tracee/pkg/proctree" + "github.com/aquasecurity/tracee/types/trace" +) + +func Benchmark_procTreeForkProcessor(b *testing.B) { + ctrl := &Controller{} + ctrl.processTree, _ = proctree.NewProcessTree( + context.Background(), + proctree.ProcTreeConfig{ + Source: proctree.SourceBoth, + ProcessCacheSize: proctree.DefaultProcessCacheSize, + ThreadCacheSize: proctree.DefaultThreadCacheSize, + }, + ) + + args := []trace.Argument{ + {ArgMeta: trace.ArgMeta{Name: "timestamp"}, Value: uint64(1)}, + {ArgMeta: trace.ArgMeta{Name: "parent_process_tid"}, Value: int32(1)}, + {ArgMeta: trace.ArgMeta{Name: "parent_process_ns_tid"}, Value: int32(1)}, + {ArgMeta: trace.ArgMeta{Name: "parent_process_pid"}, Value: int32(1)}, + {ArgMeta: trace.ArgMeta{Name: "parent_process_ns_pid"}, Value: int32(1)}, + {ArgMeta: trace.ArgMeta{Name: "parent_process_start_time"}, Value: uint64(1)}, + {ArgMeta: trace.ArgMeta{Name: "leader_tid"}, Value: int32(1)}, + {ArgMeta: trace.ArgMeta{Name: "leader_ns_tid"}, Value: int32(1)}, + {ArgMeta: trace.ArgMeta{Name: "leader_pid"}, Value: int32(1)}, + {ArgMeta: trace.ArgMeta{Name: "leader_ns_pid"}, Value: int32(1)}, + {ArgMeta: trace.ArgMeta{Name: "leader_start_time"}, Value: uint64(1)}, + {ArgMeta: trace.ArgMeta{Name: "child_tid"}, Value: int32(2)}, + {ArgMeta: trace.ArgMeta{Name: "child_ns_tid"}, Value: int32(2)}, + {ArgMeta: trace.ArgMeta{Name: "child_pid"}, Value: int32(2)}, + {ArgMeta: trace.ArgMeta{Name: "child_ns_pid"}, Value: int32(2)}, + {ArgMeta: trace.ArgMeta{Name: "start_time"}, Value: uint64(2)}, + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = ctrl.procTreeForkProcessor(args) + } +} + +func Benchmark_procTreeExecProcessor(b *testing.B) { + ctrl := &Controller{} + ctrl.processTree, _ = proctree.NewProcessTree( + context.Background(), + proctree.ProcTreeConfig{ + Source: proctree.SourceBoth, + ProcessCacheSize: proctree.DefaultProcessCacheSize, + ThreadCacheSize: proctree.DefaultThreadCacheSize, + }, + ) + + args := []trace.Argument{ + {ArgMeta: trace.ArgMeta{Name: "timestamp"}, Value: uint64(1)}, + {ArgMeta: trace.ArgMeta{Name: "task_hash"}, Value: uint32(1)}, + {ArgMeta: trace.ArgMeta{Name: "parent_hash"}, Value: uint32(1)}, + {ArgMeta: trace.ArgMeta{Name: "leader_hash"}, Value: uint32(1)}, + {ArgMeta: trace.ArgMeta{Name: "cmdpath"}, Value: "/bin/bash"}, + {ArgMeta: trace.ArgMeta{Name: "pathname"}, Value: "/bin/bash"}, + {ArgMeta: trace.ArgMeta{Name: "dev"}, Value: uint32(1)}, + {ArgMeta: trace.ArgMeta{Name: "inode"}, Value: uint64(1)}, + {ArgMeta: trace.ArgMeta{Name: "ctime"}, Value: uint64(1)}, + {ArgMeta: trace.ArgMeta{Name: "inode_mode"}, Value: uint16(1)}, + // {ArgMeta: trace.ArgMeta{Name: "interpreter_pathname"}, Value: "/lib64/ld-linux-x86-64.so.2"}, + // {ArgMeta: trace.ArgMeta{Name: "interpreter_dev"}, Value: uint32(1)}, + // {ArgMeta: trace.ArgMeta{Name: "interpreter_inode"}, Value: uint64(1)}, + // {ArgMeta: trace.ArgMeta{Name: "interpreter_ctime"}, Value: uint64(1)}, + {ArgMeta: trace.ArgMeta{Name: "interp"}, Value: "/lib64/ld-linux-x86-64.so.2"}, + {ArgMeta: trace.ArgMeta{Name: "stdin_type"}, Value: uint16(1)}, + {ArgMeta: trace.ArgMeta{Name: "stdin_path"}, Value: "/dev/null"}, + {ArgMeta: trace.ArgMeta{Name: "invoked_from_kernel"}, Value: int32(1)}, + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = ctrl.procTreeExecProcessor(args) + } +} + +func Benchmark_procTreeExitProcessor(b *testing.B) { + ctrl := &Controller{} + ctrl.processTree, _ = proctree.NewProcessTree( + context.Background(), + proctree.ProcTreeConfig{ + Source: proctree.SourceBoth, + ProcessCacheSize: proctree.DefaultProcessCacheSize, + ThreadCacheSize: proctree.DefaultThreadCacheSize, + }, + ) + + args := []trace.Argument{ + {ArgMeta: trace.ArgMeta{Name: "timestamp"}, Value: uint64(1)}, + {ArgMeta: trace.ArgMeta{Name: "task_hash"}, Value: uint32(1)}, + {ArgMeta: trace.ArgMeta{Name: "parent_hash"}, Value: uint32(1)}, + {ArgMeta: trace.ArgMeta{Name: "leader_hash"}, Value: uint32(1)}, + {ArgMeta: trace.ArgMeta{Name: "exit_code"}, Value: int64(1)}, + {ArgMeta: trace.ArgMeta{Name: "process_group_exit"}, Value: true}, + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = ctrl.procTreeExitProcessor(args) + } +} diff --git a/pkg/ebpf/controlplane/signal.go b/pkg/ebpf/controlplane/signal.go index 8836d05d13c4..33360d82c981 100644 --- a/pkg/ebpf/controlplane/signal.go +++ b/pkg/ebpf/controlplane/signal.go @@ -19,20 +19,25 @@ func (sig *signal) Unmarshal(buffer []byte) error { if err != nil { return errfmt.Errorf("failed to decode signal event ID: %v", err) } + sig.id = events.ID(eventIdUint32) + var argnum uint8 err = ebpfDecoder.DecodeUint8(&argnum) if err != nil { return errfmt.Errorf("failed to decode signal argnum: %v", err) } - if !events.Core.IsDefined(sig.id) { - return errfmt.Errorf("failed to get event %d configuration", sig.id) - } eventDefinition := events.Core.GetDefinitionByID(sig.id) + if eventDefinition.NotValid() { + return errfmt.Errorf("%d is not a valid event id", sig.id) + } + evtFields := eventDefinition.GetFields() evtName := eventDefinition.GetName() + sig.args = make([]trace.Argument, len(evtFields)) + err = ebpfDecoder.DecodeArguments(sig.args, int(argnum), evtFields, evtName, sig.id) if err != nil { return errfmt.Errorf("failed to decode signal arguments: %v", err) diff --git a/pkg/ebpf/event_parameters.go b/pkg/ebpf/event_parameters.go index a148ae33e080..d8ab21a546e7 100644 --- a/pkg/ebpf/event_parameters.go +++ b/pkg/ebpf/event_parameters.go @@ -2,9 +2,12 @@ package ebpf import ( "fmt" + "math" "strconv" + "unsafe" "github.com/aquasecurity/tracee/pkg/ebpf/probes" + "github.com/aquasecurity/tracee/pkg/errfmt" "github.com/aquasecurity/tracee/pkg/events" "github.com/aquasecurity/tracee/pkg/filters" "github.com/aquasecurity/tracee/pkg/logger" @@ -13,7 +16,8 @@ import ( type eventParameterHandler func(t *Tracee, eventParams []map[string]filters.Filter[*filters.StringFilter]) error var eventParameterHandlers = map[events.ID]eventParameterHandler{ - events.SuspiciousSyscallSource: attachSuspiciousSyscallSourceProbes, + events.SuspiciousSyscallSource: prepareSuspiciousSyscallSource, + events.StackPivot: prepareStackPivot, } // handleEventParameters performs initialization actions according to event parameters, @@ -36,11 +40,13 @@ func (t *Tracee) handleEventParameters() error { eventParams := make([]map[string]filters.Filter[*filters.StringFilter], 0) for iterator := t.policyManager.CreateAllIterator(); iterator.HasNext(); { policy := iterator.Next() - policyParams := policy.Rules[eventID].DataFilter.GetFieldFilters() - if len(policyParams) == 0 { - continue + if rule, ok := policy.Rules[eventID]; ok { + policyParams := rule.DataFilter.GetFieldFilters() + if len(policyParams) == 0 { + continue + } + eventParams = append(eventParams, policyParams) } - eventParams = append(eventParams, policyParams) } if len(eventParams) == 0 { // No parameters for this event @@ -57,51 +63,97 @@ func (t *Tracee) handleEventParameters() error { return nil } -func attachSuspiciousSyscallSourceProbes(t *Tracee, eventParams []map[string]filters.Filter[*filters.StringFilter]) error { - // Get syscalls to trace - syscalls := make(map[string]struct{}, 0) +type syscallInfo struct { + id events.ID + name string +} + +func getSyscallsFromParams(eventParams []map[string]filters.Filter[*filters.StringFilter], syscallArgName string) ([]syscallInfo, error) { + syscalls := []syscallInfo{} + for _, policyParams := range eventParams { - syscallsParam, ok := policyParams["syscall"].(*filters.StringFilter) + syscallsParam, ok := policyParams[syscallArgName].(*filters.StringFilter) if !ok { - return nil + return syscalls, errfmt.Errorf("invalid argument name '%s'", syscallArgName) } + for _, entry := range syscallsParam.Equal() { - syscallID, err := strconv.Atoi(entry) + syscallIDInt, err := strconv.Atoi(entry) if err != nil { - return err + return syscalls, errfmt.WrapError(err) } - if !events.Core.IsDefined(events.ID(syscallID)) { - return fmt.Errorf("syscall id %d is not defined", syscallID) + if syscallIDInt > math.MaxInt32 { + return syscalls, errfmt.Errorf("invalid syscall ID %d", syscallIDInt) } + syscallID := events.ID(syscallIDInt) - syscallName := events.Core.GetDefinitionByID(events.ID(syscallID)).GetName() - syscalls[syscallName] = struct{}{} + syscallDef := events.Core.GetDefinitionByID(events.ID(syscallID)) + if syscallDef.NotValid() { + return syscalls, errfmt.Errorf("syscall id %d is not valid", syscallID) + } + + syscalls = append(syscalls, syscallInfo{ + id: syscallID, + name: syscallDef.GetName(), + }) } } - // Create probe group - probeMap := make(map[probes.Handle]probes.Probe) - i := 0 - for syscallName := range syscalls { - probeMap[probes.Handle(i)] = probes.NewTraceProbe(probes.SyscallEnter, syscallName, "suspicious_syscall_source") - i++ + return syscalls, nil +} + +func registerSyscallChecker(t *Tracee, eventParams []map[string]filters.Filter[*filters.StringFilter], + syscallArgName string, selectedSyscallsMapName string) error { + // Create probe group if needed + probeGroupName := "syscall_checkers" + probeGroup, ok := t.extraProbes[probeGroupName] + if !ok { + probeGroup = probes.NewProbeGroup(t.bpfModule, map[probes.Handle]probes.Probe{}) + t.extraProbes[probeGroupName] = probeGroup + } + + // Get list of syscalls to be checked + syscalls, err := getSyscallsFromParams(eventParams, syscallArgName) + if err != nil { + return errfmt.WrapError(err) } - probeGroupName := "suspicious_syscall_source" - probeGroup := probes.NewProbeGroup(t.bpfModule, probeMap) - if _, exists := t.extraProbes[probeGroupName]; exists { - return fmt.Errorf("probe group %s already exists", probeGroupName) + + // Get map of syscalls to be checked + syscallsMap, err := t.bpfModule.GetMap(selectedSyscallsMapName) + if err != nil { + return errfmt.WrapError(err) } - t.extraProbes[probeGroupName] = probeGroup - - // Attach probes - i = 0 - for syscallName := range syscalls { - if err := probeGroup.Attach(probes.Handle(i), t.kernelSymbols); err != nil { - // Report attachment errors but don't fail, because it may be a syscall that doesn't exist on this system - logger.Warnw("Failed to attach suspicious_syscall_source kprobe", "syscall", syscallName, "error", err) + + for _, syscall := range syscalls { + // Register and attach a probe for this syscall, if not registered already + handle := probes.Handle(syscall.id) + if !probeGroup.HandleExists(handle) { + probe := probes.NewTraceProbe(probes.SyscallEnter, syscall.name, "syscall_checker") + if err := probeGroup.AddProbe(handle, probe); err != nil { + return errfmt.WrapError(err) + } + if err := probeGroup.Attach(handle, t.getKernelSymbols()); err != nil { + // Report attachment errors but don't fail, because it may be a syscall that doesn't exist on this system + logger.Warnw("Failed to attach syscall checker kprobe", "syscall", syscall.name, "error", err) + continue + } + } + + // Update syscalls to check map with this syscall + id := uint32(syscall.id) + val := uint32(1) + if err := syscallsMap.Update(unsafe.Pointer(&id), unsafe.Pointer(&val)); err != nil { + return errfmt.WrapError(err) } - i++ } return nil } + +func prepareSuspiciousSyscallSource(t *Tracee, eventParams []map[string]filters.Filter[*filters.StringFilter]) error { + return registerSyscallChecker(t, eventParams, "syscall", "suspicious_syscall_source_syscalls") +} + +func prepareStackPivot(t *Tracee, eventParams []map[string]filters.Filter[*filters.StringFilter]) error { + return registerSyscallChecker(t, eventParams, "syscall", "stack_pivot_syscalls") +} diff --git a/pkg/ebpf/events_pipeline.go b/pkg/ebpf/events_pipeline.go index fa42d474484b..f043b07aa82e 100644 --- a/pkg/ebpf/events_pipeline.go +++ b/pkg/ebpf/events_pipeline.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/binary" + "fmt" "slices" "strconv" "sync" @@ -176,11 +177,12 @@ func (t *Tracee) decodeEvents(ctx context.Context, sourceChan chan []byte) (<-ch continue } eventId := events.ID(eCtx.EventID) - if !events.Core.IsDefined(eventId) { + eventDefinition := events.Core.GetDefinitionByID(eventId) + if eventDefinition.NotValid() { t.handleError(errfmt.Errorf("failed to get configuration of event %d", eventId)) continue } - eventDefinition := events.Core.GetDefinitionByID(eventId) + evtFields := eventDefinition.GetFields() evtName := eventDefinition.GetName() args := make([]trace.Argument, len(evtFields)) @@ -217,12 +219,18 @@ func (t *Tracee) decodeEvents(ctx context.Context, sourceChan chan []byte) (<-ch // For 32-bit (compat) processes, the syscall ID gets translated in eBPF to the event ID of its // 64-bit counterpart, or if it's a 32-bit exclusive syscall, to the event ID corresponding to it. id := events.ID(eCtx.Syscall) - if events.Core.IsDefined(id) { - syscall = events.Core.GetDefinitionByID(id).GetName() - } else { - // This should never fail, as the translation used in eBPF relies on the same event definitions - logger.Errorw("No syscall event with id %d", id) + syscallDef := events.Core.GetDefinitionByID(id) + if syscallDef.NotValid() { + commStr := string(eCtx.Comm[:bytes.IndexByte(eCtx.Comm[:], 0)]) + utsNameStr := string(eCtx.UtsName[:bytes.IndexByte(eCtx.UtsName[:], 0)]) + logger.Debugw( + fmt.Sprintf("Event %s with an invalid syscall id %d", evtName, id), + "Comm", commStr, + "UtsName", utsNameStr, + "EventContext", eCtx, + ) } + syscall = syscallDef.GetName() } // get an event pointer from the pool diff --git a/pkg/ebpf/hooked_syscall_table.go b/pkg/ebpf/hooked_syscall_table.go index a7382dd677ad..ae2a1ff94d57 100644 --- a/pkg/ebpf/hooked_syscall_table.go +++ b/pkg/ebpf/hooked_syscall_table.go @@ -160,11 +160,11 @@ func (t *Tracee) getSyscallNameByKerVer(restrictions []events.KernelRestrictions // populateExpectedSyscallTableArray fills the expected values of the syscall table func (t *Tracee) populateExpectedSyscallTableArray(tableMap *bpf.BPFMap) error { // Get address to the function that defines the not implemented sys call - niSyscallSymbol, err := t.kernelSymbols.GetSymbolByOwnerAndName("system", events.SyscallPrefix+"ni_syscall") + niSyscallSymbol, err := t.getKernelSymbols().GetSymbolByOwnerAndName("system", events.SyscallPrefix+"ni_syscall") if err != nil { e := err // RHEL 8.x uses sys_ni_syscall instead of __arch_ni_syscall - niSyscallSymbol, err = t.kernelSymbols.GetSymbolByOwnerAndName("system", "sys_ni_syscall") + niSyscallSymbol, err = t.getKernelSymbols().GetSymbolByOwnerAndName("system", "sys_ni_syscall") if err != nil { logger.Debugw("hooked_syscall: syscall symbol not found", "name", "sys_ni_syscall") return e @@ -188,7 +188,7 @@ func (t *Tracee) populateExpectedSyscallTableArray(tableMap *bpf.BPFMap) error { continue } - kernelSymbol, err := t.kernelSymbols.GetSymbolByOwnerAndName("system", events.SyscallPrefix+syscallName) + kernelSymbol, err := t.getKernelSymbols().GetSymbolByOwnerAndName("system", events.SyscallPrefix+syscallName) if err != nil { logger.Warnw(fmt.Sprintf("hooked_syscall: Unable to locate syscall symbol... permanently skipping hook check for syscall ID %d", index)) zero := 0 diff --git a/pkg/ebpf/ksymbols.go b/pkg/ebpf/ksymbols.go index 64fa239a696a..b82f4b4d1b0c 100644 --- a/pkg/ebpf/ksymbols.go +++ b/pkg/ebpf/ksymbols.go @@ -45,7 +45,7 @@ func (t *Tracee) UpdateKallsyms() error { // For every ksymbol required by tracee ... for _, required := range allReqSymbols { // ... get the symbol address from the kallsyms file ... - symbol, err := t.kernelSymbols.GetSymbolByOwnerAndName(globalSymbolOwner, required) + symbol, err := t.getKernelSymbols().GetSymbolByOwnerAndName(globalSymbolOwner, required) if err != nil { logger.Debugw("failed to get symbol", "symbol", required, "error", err) continue diff --git a/pkg/ebpf/probes/probe_group.go b/pkg/ebpf/probes/probe_group.go index 4578f319571c..ee0d353c9ed9 100644 --- a/pkg/ebpf/probes/probe_group.go +++ b/pkg/ebpf/probes/probe_group.go @@ -29,6 +29,27 @@ func NewProbeGroup(m *bpf.Module, p map[Handle]Probe) *ProbeGroup { } } +func (p *ProbeGroup) HandleExists(handle Handle) bool { + p.probesLock.Lock() + defer p.probesLock.Unlock() + + _, ok := p.probes[handle] + return ok +} + +func (p *ProbeGroup) AddProbe(handle Handle, probe Probe) error { + if p.HandleExists(handle) { + return errfmt.Errorf("probe handle (%d) already exists", handle) + } + + p.probesLock.Lock() + defer p.probesLock.Unlock() + + p.probes[handle] = probe + + return nil +} + // GetProbe returns a probe type by its handle. func (p *ProbeGroup) GetProbeType(handle Handle) ProbeType { p.probesLock.Lock() diff --git a/pkg/ebpf/probes/trace.go b/pkg/ebpf/probes/trace.go index ef67ac7351fd..f5df4d127267 100644 --- a/pkg/ebpf/probes/trace.go +++ b/pkg/ebpf/probes/trace.go @@ -111,7 +111,7 @@ func (p *TraceProbe) attach(module *bpf.Module, args ...interface{}) error { var err error var link *bpf.BPFLink var attachFunc func(uint64) (*bpf.BPFLink, error) - var syms []environment.KernelSymbol + var syms []*environment.KernelSymbol // https://github.com/aquasecurity/tracee/issues/3653#issuecomment-1832642225 // // After commit b022f0c7e404 ('tracing/kprobes: Return EADDRNOTAVAIL diff --git a/pkg/ebpf/processor_funcs.go b/pkg/ebpf/processor_funcs.go index 253b16fd5116..c5e817451df3 100644 --- a/pkg/ebpf/processor_funcs.go +++ b/pkg/ebpf/processor_funcs.go @@ -21,6 +21,7 @@ import ( "github.com/aquasecurity/tracee/pkg/logger" "github.com/aquasecurity/tracee/pkg/time" "github.com/aquasecurity/tracee/pkg/utils" + "github.com/aquasecurity/tracee/pkg/utils/environment" "github.com/aquasecurity/tracee/types/trace" ) @@ -233,10 +234,11 @@ func (t *Tracee) processDoInitModule(event *trace.Event) error { err := capabilities.GetInstance().EBPF( func() error { - err := t.kernelSymbols.Refresh() + newKernelSymbols, err := environment.NewKernelSymbolTable(true, true, t.requiredKsyms...) if err != nil { return errfmt.WrapError(err) } + t.setKernelSymbols(newKernelSymbols) return t.UpdateKallsyms() }, ) @@ -281,7 +283,7 @@ func (t *Tracee) processHookedProcFops(event *trace.Event) error { if addr == 0 { // address is in text segment, marked as 0 continue } - hookingFunction := utils.ParseSymbol(addr, t.kernelSymbols) + hookingFunction := t.getKernelSymbols().GetPotentiallyHiddenSymbolByAddr(addr)[0] if hookingFunction.Owner == "system" { continue } @@ -326,7 +328,7 @@ func (t *Tracee) processPrintMemDump(event *trace.Event) error { } addressUint64 := uint64(address) - symbol := utils.ParseSymbol(addressUint64, t.kernelSymbols) + symbol := t.getKernelSymbols().GetPotentiallyHiddenSymbolByAddr(addressUint64)[0] var utsName unix.Utsname arch := "" if err := unix.Uname(&utsName); err != nil { diff --git a/pkg/ebpf/processor_proctree.go b/pkg/ebpf/processor_proctree.go index 559fb4f063a5..acb2e981994a 100644 --- a/pkg/ebpf/processor_proctree.go +++ b/pkg/ebpf/processor_proctree.go @@ -5,7 +5,6 @@ import ( "github.com/aquasecurity/tracee/pkg/events/parse" "github.com/aquasecurity/tracee/pkg/logger" - "github.com/aquasecurity/tracee/pkg/proctree" traceetime "github.com/aquasecurity/tracee/pkg/time" "github.com/aquasecurity/tracee/pkg/utils" "github.com/aquasecurity/tracee/types/trace" @@ -17,91 +16,94 @@ import ( // procTreeForkProcessor handles process fork events. func (t *Tracee) procTreeForkProcessor(event *trace.Event) error { - var errs []error - if t.processTree == nil { return fmt.Errorf("process tree is disabled") } + var err error + // NOTE: override all the fields of the forkFeed, to avoid any previous data. + forkFeed := t.processTree.GetForkFeedFromPool() + defer t.processTree.PutForkFeedInPool(forkFeed) + // NOTE: The "parent" related arguments can be ignored for process tree purposes. // Parent Process (Go up in hierarchy until parent is a process and not a lwp) - parentTid, err := parse.ArgVal[int32](event.Args, "parent_process_tid") - errs = append(errs, err) - parentNsTid, err := parse.ArgVal[int32](event.Args, "parent_process_ns_tid") - errs = append(errs, err) - parentPid, err := parse.ArgVal[int32](event.Args, "parent_process_pid") - errs = append(errs, err) - parentNsPid, err := parse.ArgVal[int32](event.Args, "parent_process_ns_pid") - errs = append(errs, err) - parentStartTime, err := parse.ArgVal[uint64](event.Args, "parent_process_start_time") - errs = append(errs, err) + forkFeed.ParentTid, err = parse.ArgVal[int32](event.Args, "parent_process_tid") + if err != nil { + return err + } + forkFeed.ParentNsTid, err = parse.ArgVal[int32](event.Args, "parent_process_ns_tid") + if err != nil { + return err + } + forkFeed.ParentPid, err = parse.ArgVal[int32](event.Args, "parent_process_pid") + if err != nil { + return err + } + forkFeed.ParentNsPid, err = parse.ArgVal[int32](event.Args, "parent_process_ns_pid") + if err != nil { + return err + } + forkFeed.ParentStartTime, err = parse.ArgVal[uint64](event.Args, "parent_process_start_time") + if err != nil { + return err + } // Thread Group Leader (might be the same as the "child", if "child" is a process) - leaderTid, err := parse.ArgVal[int32](event.Args, "leader_tid") - errs = append(errs, err) - leaderNsTid, err := parse.ArgVal[int32](event.Args, "leader_ns_tid") - errs = append(errs, err) - leaderPid, err := parse.ArgVal[int32](event.Args, "leader_pid") - errs = append(errs, err) - leaderNsPid, err := parse.ArgVal[int32](event.Args, "leader_ns_pid") - errs = append(errs, err) - leaderStartTime, err := parse.ArgVal[uint64](event.Args, "leader_start_time") - errs = append(errs, err) + forkFeed.LeaderTid, err = parse.ArgVal[int32](event.Args, "leader_tid") + if err != nil { + return err + } + forkFeed.LeaderNsTid, err = parse.ArgVal[int32](event.Args, "leader_ns_tid") + if err != nil { + return err + } + forkFeed.LeaderPid, err = parse.ArgVal[int32](event.Args, "leader_pid") + if err != nil { + return err + } + forkFeed.LeaderNsPid, err = parse.ArgVal[int32](event.Args, "leader_ns_pid") + if err != nil { + return err + } + forkFeed.LeaderStartTime, err = parse.ArgVal[uint64](event.Args, "leader_start_time") + if err != nil { + return err + } // Child (might be a process or a thread) - childTid, err := parse.ArgVal[int32](event.Args, "child_tid") - errs = append(errs, err) - childNsTid, err := parse.ArgVal[int32](event.Args, "child_ns_tid") - errs = append(errs, err) - childPid, err := parse.ArgVal[int32](event.Args, "child_pid") - errs = append(errs, err) - childNsPid, err := parse.ArgVal[int32](event.Args, "child_ns_pid") - errs = append(errs, err) - childStartTime, err := parse.ArgVal[uint64](event.Args, "start_time") // child_start_time - errs = append(errs, err) - - // Deal with errors - for _, err := range errs { - if err != nil { - return err - } + forkFeed.ChildTid, err = parse.ArgVal[int32](event.Args, "child_tid") + if err != nil { + return err + } + forkFeed.ChildNsTid, err = parse.ArgVal[int32](event.Args, "child_ns_tid") + if err != nil { + return err + } + forkFeed.ChildPid, err = parse.ArgVal[int32](event.Args, "child_pid") + if err != nil { + return err } + forkFeed.ChildNsPid, err = parse.ArgVal[int32](event.Args, "child_ns_pid") + if err != nil { + return err + } + forkFeed.ChildStartTime, err = parse.ArgVal[uint64](event.Args, "start_time") // child_start_time + if err != nil { + return err + } + forkFeed.TimeStamp = forkFeed.ChildStartTime // event timestamp is the same // Calculate hashes - childHash := utils.HashTaskID(uint32(childTid), uint64(childStartTime)) - parentHash := utils.HashTaskID(uint32(parentTid), uint64(parentStartTime)) - leaderHash := utils.HashTaskID(uint32(leaderTid), uint64(leaderStartTime)) - - return t.processTree.FeedFromFork( - proctree.ForkFeed{ - TimeStamp: childStartTime, // event timestamp is the same - ChildHash: childHash, - ParentHash: parentHash, - LeaderHash: leaderHash, - ParentTid: parentTid, - ParentNsTid: parentNsTid, - ParentPid: parentPid, - ParentNsPid: parentNsPid, - ParentStartTime: parentStartTime, - LeaderTid: leaderTid, - LeaderNsTid: leaderNsTid, - LeaderPid: leaderPid, - LeaderNsPid: leaderNsPid, - LeaderStartTime: leaderStartTime, - ChildTid: childTid, - ChildNsTid: childNsTid, - ChildPid: childPid, - ChildNsPid: childNsPid, - ChildStartTime: childStartTime, - }, - ) + forkFeed.ParentHash = utils.HashTaskID(uint32(forkFeed.ParentTid), uint64(forkFeed.ParentStartTime)) + forkFeed.ChildHash = utils.HashTaskID(uint32(forkFeed.ChildTid), uint64(forkFeed.ChildStartTime)) + forkFeed.LeaderHash = utils.HashTaskID(uint32(forkFeed.LeaderTid), uint64(forkFeed.LeaderStartTime)) + + return t.processTree.FeedFromFork(forkFeed) } // procTreeExecProcessor handles process exec events. func (t *Tracee) procTreeExecProcessor(event *trace.Event) error { - var errs []error - if t.processTree == nil { return fmt.Errorf("process tree is disabled") } @@ -109,76 +111,74 @@ func (t *Tracee) procTreeExecProcessor(event *trace.Event) error { return nil // chek FeedFromExec for TODO of execve() handled by threads } - timestamp := uint64(event.Timestamp) - taskHash := utils.HashTaskID(uint32(event.HostThreadID), uint64(event.ThreadStartTime)) + var err error + + // NOTE: override all the fields of the execFeed, to avoid any previous data. + execFeed := t.processTree.GetExecFeedFromPool() + defer t.processTree.PutExecFeedInPool(execFeed) // Executable - cmdPath, err := parse.ArgVal[string](event.Args, "cmdpath") - errs = append(errs, err) - pathName, err := parse.ArgVal[string](event.Args, "pathname") - errs = append(errs, err) - dev, err := parse.ArgVal[uint32](event.Args, "dev") - errs = append(errs, err) - inode, err := parse.ArgVal[uint64](event.Args, "inode") - errs = append(errs, err) - ctime, err := parse.ArgVal[uint64](event.Args, "ctime") - errs = append(errs, err) - inodeMode, err := parse.ArgVal[uint16](event.Args, "inode_mode") - errs = append(errs, err) - - // Binary Interpreter (or Loader): might come empty from the kernel - interPathName, _ := parse.ArgVal[string](event.Args, "interpreter_pathname") - interDev, _ := parse.ArgVal[uint32](event.Args, "interpreter_dev") - interInode, _ := parse.ArgVal[uint64](event.Args, "interpreter_inode") - interCtime, _ := parse.ArgVal[uint64](event.Args, "interpreter_ctime") + execFeed.CmdPath, err = parse.ArgVal[string](event.Args, "cmdpath") + if err != nil { + return err + } + execFeed.PathName, err = parse.ArgVal[string](event.Args, "pathname") + if err != nil { + return err + } + execFeed.Dev, err = parse.ArgVal[uint32](event.Args, "dev") + if err != nil { + return err + } + execFeed.Inode, err = parse.ArgVal[uint64](event.Args, "inode") + if err != nil { + return err + } + execFeed.Ctime, err = parse.ArgVal[uint64](event.Args, "ctime") + if err != nil { + return err + } + execFeed.InodeMode, err = parse.ArgVal[uint16](event.Args, "inode_mode") + if err != nil { + return err + } + + // // Binary Interpreter (or Loader): might come empty from the kernel + // execFeed.InterpreterPath, _ := parse.ArgVal[string](event.Args, "interpreter_pathname") + // execFeed.InterpreterDev, _ := parse.ArgVal[uint32](event.Args, "interpreter_dev") + // execFeed.InterpreterInode, _ := parse.ArgVal[uint64](event.Args, "interpreter_inode") + // execFeed.InterpreterCtime, _ := parse.ArgVal[uint64](event.Args, "interpreter_ctime") // Real Interpreter - interp, err := parse.ArgVal[string](event.Args, "interp") - errs = append(errs, err) + execFeed.Interp, err = parse.ArgVal[string](event.Args, "interp") + if err != nil { + return err + } // Others - stdinType, err := parse.ArgVal[uint16](event.Args, "stdin_type") - errs = append(errs, err) - stdinPath, err := parse.ArgVal[string](event.Args, "stdin_path") - errs = append(errs, err) - invokedFromKernel, err := parse.ArgVal[int32](event.Args, "invoked_from_kernel") - errs = append(errs, err) - - // Handle errors - for _, err := range errs { - if err != nil { - return err - } + execFeed.StdinType, err = parse.ArgVal[uint16](event.Args, "stdin_type") + if err != nil { + return err } + execFeed.StdinPath, err = parse.ArgVal[string](event.Args, "stdin_path") + if err != nil { + return err + } + execFeed.InvokedFromKernel, err = parse.ArgVal[int32](event.Args, "invoked_from_kernel") + if err != nil { + return err + } + + execFeed.TimeStamp = uint64(event.Timestamp) + execFeed.TaskHash = utils.HashTaskID(uint32(event.HostThreadID), uint64(event.ThreadStartTime)) + execFeed.ParentHash = 0 // regular pipeline does not have parent hash + execFeed.LeaderHash = 0 // regular pipeline does not have leader hash - return t.processTree.FeedFromExec( - proctree.ExecFeed{ - TimeStamp: timestamp, - TaskHash: taskHash, - ParentHash: 0, // regular pipeline does not have parent hash - LeaderHash: 0, // regular pipeline does not have leader hash - CmdPath: cmdPath, - PathName: pathName, - Dev: dev, - Inode: inode, - Ctime: ctime, - InodeMode: inodeMode, - InterpreterPath: interPathName, - InterpreterDev: interDev, - InterpreterInode: interInode, - InterpreterCtime: interCtime, - Interp: interp, - StdinType: stdinType, - StdinPath: stdinPath, - InvokedFromKernel: invokedFromKernel, - }, - ) + return t.processTree.FeedFromExec(execFeed) } // procTreeExitProcessor handles process exit events. func (t *Tracee) procTreeExitProcessor(event *trace.Event) error { - var errs []error - if t.processTree == nil { return fmt.Errorf("process tree is disabled") } @@ -186,32 +186,32 @@ func (t *Tracee) procTreeExitProcessor(event *trace.Event) error { return nil // chek FeedFromExec for TODO of execve() handled by threads } - timestamp := uint64(event.Timestamp) - taskHash := utils.HashTaskID(uint32(event.HostThreadID), uint64(event.ThreadStartTime)) - - // Exit logic arguments - exitCode, err := parse.ArgVal[int64](event.Args, "exit_code") - errs = append(errs, err) - groupExit, err := parse.ArgVal[bool](event.Args, "process_group_exit") - errs = append(errs, err) - - // Handle errors - for _, err := range errs { - if err != nil { - return err - } - } - - return t.processTree.FeedFromExit( - proctree.ExitFeed{ - TimeStamp: timestamp, // time of exit is already a timestamp - TaskHash: taskHash, - ParentHash: 0, // regular pipeline does not have parent hash - LeaderHash: 0, // regular pipeline does not have leader hash - ExitCode: exitCode, - Group: groupExit, - }, - ) + // NOTE: Currently FeedFromExit is only using TaskHash and TimeStamp from the ExitFeed. + // So the other fields are commented out for now. + // + // TODO: Analyze if the other fields will be needed in the future. + // var err error + + // NOTE: override all the fields of the exitFeed, to avoid any previous data. + exitFeed := t.processTree.GetExitFeedFromPool() + defer t.processTree.PutExitFeedInPool(exitFeed) + + // // Exit logic arguments + // exitFeed.ExitCode, err = parse.ArgVal[int64](event.Args, "exit_code") + // if err != nil { + // return err + // } + // exitFeed.Group, err = parse.ArgVal[bool](event.Args, "process_group_exit") + // if err != nil { + // return err + // } + + exitFeed.TimeStamp = uint64(event.Timestamp) // time of exit is already a timestamp + exitFeed.TaskHash = utils.HashTaskID(uint32(event.HostThreadID), uint64(event.ThreadStartTime)) + // exitFeed.ParentHash = 0 // regular pipeline does not have parent hash + // exitFeed.LeaderHash = 0 // regular pipeline does not have leader hash + + return t.processTree.FeedFromExit(exitFeed) } // diff --git a/pkg/ebpf/processor_proctree_bench_test.go b/pkg/ebpf/processor_proctree_bench_test.go new file mode 100644 index 000000000000..7e75654b2a7b --- /dev/null +++ b/pkg/ebpf/processor_proctree_bench_test.go @@ -0,0 +1,106 @@ +package ebpf + +import ( + "context" + "testing" + + "github.com/aquasecurity/tracee/pkg/proctree" + "github.com/aquasecurity/tracee/types/trace" +) + +func Benchmark_procTreeForkProcessor(b *testing.B) { + t := &Tracee{} + t.processTree, _ = proctree.NewProcessTree( + context.Background(), + proctree.ProcTreeConfig{ + Source: proctree.SourceBoth, + ProcessCacheSize: proctree.DefaultProcessCacheSize, + ThreadCacheSize: proctree.DefaultThreadCacheSize, + }, + ) + + event := &trace.Event{ + Args: []trace.Argument{ + {ArgMeta: trace.ArgMeta{Name: "parent_process_tid"}, Value: int32(1)}, + {ArgMeta: trace.ArgMeta{Name: "parent_process_ns_tid"}, Value: int32(1)}, + {ArgMeta: trace.ArgMeta{Name: "parent_process_pid"}, Value: int32(1)}, + {ArgMeta: trace.ArgMeta{Name: "parent_process_ns_pid"}, Value: int32(1)}, + {ArgMeta: trace.ArgMeta{Name: "parent_process_start_time"}, Value: uint64(1)}, + {ArgMeta: trace.ArgMeta{Name: "leader_tid"}, Value: int32(1)}, + {ArgMeta: trace.ArgMeta{Name: "leader_ns_tid"}, Value: int32(1)}, + {ArgMeta: trace.ArgMeta{Name: "leader_pid"}, Value: int32(1)}, + {ArgMeta: trace.ArgMeta{Name: "leader_ns_pid"}, Value: int32(1)}, + {ArgMeta: trace.ArgMeta{Name: "leader_start_time"}, Value: uint64(1)}, + {ArgMeta: trace.ArgMeta{Name: "child_tid"}, Value: int32(2)}, + {ArgMeta: trace.ArgMeta{Name: "child_ns_tid"}, Value: int32(2)}, + {ArgMeta: trace.ArgMeta{Name: "child_pid"}, Value: int32(2)}, + {ArgMeta: trace.ArgMeta{Name: "child_ns_pid"}, Value: int32(2)}, + {ArgMeta: trace.ArgMeta{Name: "start_time"}, Value: uint64(2)}, + }, + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = t.procTreeForkProcessor(event) + } +} + +func Benchmark_procTreeExecProcessor(b *testing.B) { + t := &Tracee{} + t.processTree, _ = proctree.NewProcessTree( + context.Background(), + proctree.ProcTreeConfig{ + Source: proctree.SourceBoth, + ProcessCacheSize: proctree.DefaultProcessCacheSize, + ThreadCacheSize: proctree.DefaultThreadCacheSize, + }, + ) + + event := &trace.Event{ + Args: []trace.Argument{ + {ArgMeta: trace.ArgMeta{Name: "cmdpath"}, Value: "/bin/bash"}, + {ArgMeta: trace.ArgMeta{Name: "pathname"}, Value: "/bin/bash"}, + {ArgMeta: trace.ArgMeta{Name: "dev"}, Value: uint32(1)}, + {ArgMeta: trace.ArgMeta{Name: "inode"}, Value: uint64(1)}, + {ArgMeta: trace.ArgMeta{Name: "ctime"}, Value: uint64(1)}, + {ArgMeta: trace.ArgMeta{Name: "inode_mode"}, Value: uint16(1)}, + // {ArgMeta: trace.ArgMeta{Name: "interpreter_pathname"}, Value: "/lib64/ld-linux-x86-64.so.2"}, + // {ArgMeta: trace.ArgMeta{Name: "interpreter_dev"}, Value: uint32(1)}, + // {ArgMeta: trace.ArgMeta{Name: "interpreter_inode"}, Value: uint64(1)}, + // {ArgMeta: trace.ArgMeta{Name: "interpreter_ctime"}, Value: uint64(1)}, + {ArgMeta: trace.ArgMeta{Name: "interp"}, Value: "/lib64/ld-linux-x86-64.so.2"}, + {ArgMeta: trace.ArgMeta{Name: "stdin_type"}, Value: uint16(1)}, + {ArgMeta: trace.ArgMeta{Name: "stdin_path"}, Value: "/dev/null"}, + {ArgMeta: trace.ArgMeta{Name: "invoked_from_kernel"}, Value: int32(1)}, + }, + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = t.procTreeExecProcessor(event) + } +} + +func Benchmark_procTreeExitProcessor(b *testing.B) { + t := &Tracee{} + t.processTree, _ = proctree.NewProcessTree( + context.Background(), + proctree.ProcTreeConfig{ + Source: proctree.SourceBoth, + ProcessCacheSize: proctree.DefaultProcessCacheSize, + ThreadCacheSize: proctree.DefaultThreadCacheSize, + }, + ) + + event := &trace.Event{ + Args: []trace.Argument{ + {ArgMeta: trace.ArgMeta{Name: "exit_code"}, Value: int64(1)}, + {ArgMeta: trace.ArgMeta{Name: "process_group_exit"}, Value: true}, + }, + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = t.procTreeExitProcessor(event) + } +} diff --git a/pkg/ebpf/signature_engine.go b/pkg/ebpf/signature_engine.go index 7fe3cb7d2a4c..053dc2af684e 100644 --- a/pkg/ebpf/signature_engine.go +++ b/pkg/ebpf/signature_engine.go @@ -82,28 +82,16 @@ func (t *Tracee) engineEvents(ctx context.Context, in <-chan *trace.Event) (<-ch // arguments parsing) can affect engine stage. eventCopy := *event - // if t.config.Output.ParseArguments { - // // shallow clone the event arguments before parsing them (new slice is created), - // // to keep the eventCopy with raw arguments. - // eventCopy.Args = slices.Clone(event.Args) - - // err := t.parseArguments(event) - // if err != nil { - // t.handleError(err) - // return - // } - // } - - // This is a workaround to keep working with parsed arguments in the engine stage. - // Once fully migrated, this should be reverted to the commented code above - eventCopy.Args = slices.Clone(event.Args) - err := t.parseArguments(&eventCopy) - if err != nil { - t.handleError(err) - return - } if t.config.Output.ParseArguments { - event.Args = slices.Clone(eventCopy.Args) + // shallow clone the event arguments before parsing them (new slice is created), + // to keep the eventCopy with raw arguments. + eventCopy.Args = slices.Clone(event.Args) + + err := t.parseArguments(event) + if err != nil { + t.handleError(err) + return + } } // pass the event to the sink stage, if the event is also marked as emit diff --git a/pkg/ebpf/tracee.go b/pkg/ebpf/tracee.go index 7167d6b13ef9..c588978caae5 100644 --- a/pkg/ebpf/tracee.go +++ b/pkg/ebpf/tracee.go @@ -3,14 +3,12 @@ package ebpf import ( gocontext "context" "encoding/binary" - "errors" "fmt" "os" "strconv" "strings" "sync" "sync/atomic" - "syscall" "unsafe" "kernel.org/pub/linux/libs/security/libcap/cap" @@ -78,9 +76,8 @@ type Tracee struct { writtenFiles map[string]string netCapturePcap *pcaps.Pcaps // Internal Data - readFiles map[string]string - pidsInMntns bucketscache.BucketsCache // first n PIDs in each mountns - kernelSymbols *environment.KernelSymbolTable + readFiles map[string]string + pidsInMntns bucketscache.BucketsCache // first n PIDs in each mountns // eBPF bpfModule *bpf.Module defaultProbes *probes.ProbeGroup @@ -123,6 +120,9 @@ type Tracee struct { policyManager *policy.Manager // The dependencies of events used by Tracee eventsDependencies *dependencies.Manager + // A reference to a environment.KernelSymbolTable that might change at runtime. + // This should only be accessed using t.getKernelSymbols() and t.setKernelSymbols() + kernelSymbols atomic.Pointer[environment.KernelSymbolTable] // Ksymbols needed to be kept alive in table. // This does not mean they are required for tracee to function. // TODO: remove this in favor of dependency manager nodes @@ -137,6 +137,14 @@ func (t *Tracee) Engine() *engine.Engine { return t.sigEngine } +func (t *Tracee) getKernelSymbols() *environment.KernelSymbolTable { + return t.kernelSymbols.Load() +} + +func (t *Tracee) setKernelSymbols(kernelSymbols *environment.KernelSymbolTable) { + t.kernelSymbols.Store(kernelSymbols) +} + // New creates a new Tracee instance based on a given valid Config. It is expected that it won't // cause external system side effects (reads, writes, etc). func New(cfg config.Config) (*Tracee, error) { @@ -362,12 +370,13 @@ func (t *Tracee) Init(ctx gocontext.Context) error { err = capabilities.GetInstance().Specific( func() error { - t.kernelSymbols, err = environment.NewKernelSymbolTable( - environment.WithRequiredSymbols(t.requiredKsyms), - ) - // Cleanup memory in list - t.requiredKsyms = []string{} - return err + // t.requiredKsyms may contain non-data symbols, but it doesn't affect the validity of this call + kernelSymbols, err := environment.NewKernelSymbolTable(true, true, t.requiredKsyms...) + if err != nil { + return err + } + t.setKernelSymbols(kernelSymbols) + return nil }, cap.SYSLOG, ) @@ -384,17 +393,24 @@ func (t *Tracee) Init(ctx gocontext.Context) error { usedClockID := traceetime.CLOCK_BOOTTIME err = capabilities.GetInstance().EBPF( func() error { + // Since this code is running with sufficient capabilities, we can safely trust the result of `BPFHelperIsSupported`. + // If the helper is reported as supported (`supported == true`), it is assumed to be reliable for use. + // If `supported == false`, it indicates that the helper for getting BOOTTIME is not available. + // The `innerErr` provides information about errors that occurred during the check, regardless of whether `supported` + // is true or false. + // For a full explanation of the caveats and behavior, refer to: + // https://github.com/aquasecurity/libbpfgo/blob/eb576c71ece75930a693b8b0687c5d052a5dbd56/libbpfgo.go#L99-L119 supported, innerErr := bpf.BPFHelperIsSupported(bpf.BPFProgTypeKprobe, bpf.BPFFuncKtimeGetBootNs) - // only report if operation not permitted - if errors.Is(innerErr, syscall.EPERM) { - return innerErr - } - - // If BPFFuncKtimeGetBootNs is not available, eBPF will generate events based on monotonic time. + // Use CLOCK_MONOTONIC only when the helper is explicitly unsupported if !supported { usedClockID = traceetime.CLOCK_MONOTONIC } + + if innerErr != nil { + logger.Debugw("Detect clock timing", "warn", innerErr) + } + return nil }) if err != nil { @@ -604,13 +620,13 @@ func (t *Tracee) initDerivationTable() error { events.SyscallTableCheck: { events.HookedSyscall: { Enabled: shouldSubmit(events.SyscallTableCheck), - DeriveFunction: derive.DetectHookedSyscall(t.kernelSymbols), + DeriveFunction: derive.DetectHookedSyscall(t.getKernelSymbols()), }, }, events.PrintNetSeqOps: { events.HookedSeqOps: { Enabled: shouldSubmit(events.HookedSeqOps), - DeriveFunction: derive.HookedSeqOps(t.kernelSymbols), + DeriveFunction: derive.HookedSeqOps(t.getKernelSymbols()), }, }, events.HiddenKernelModuleSeeker: { @@ -913,18 +929,12 @@ func getUnavailbaleKsymbols(ksymbols []events.KSymbol, kernelSymbols *environmen var unavailableSymbols []events.KSymbol for _, ksymbol := range ksymbols { - sym, err := kernelSymbols.GetSymbolByName(ksymbol.GetSymbolName()) + _, err := kernelSymbols.GetSymbolByName(ksymbol.GetSymbolName()) if err != nil { // If the symbol is not found, it means it's unavailable. unavailableSymbols = append(unavailableSymbols, ksymbol) continue } - for _, s := range sym { - if s.Address == 0 { - // Same if the symbol is found but its address is 0. - unavailableSymbols = append(unavailableSymbols, ksymbol) - } - } } return unavailableSymbols } @@ -944,7 +954,7 @@ func (t *Tracee) validateKallsymsDependencies() { } validateEvent := func(eventId events.ID) bool { - missingDepSyms := getUnavailbaleKsymbols(evtDefSymDeps(eventId), t.kernelSymbols) + missingDepSyms := getUnavailbaleKsymbols(evtDefSymDeps(eventId), t.getKernelSymbols()) shouldFailEvent := false for _, symDep := range missingDepSyms { if symDep.IsRequired() { @@ -1159,7 +1169,7 @@ func (t *Tracee) attachEvent(id events.ID) error { return err } for _, probe := range depsNode.GetDependencies().GetProbes() { - err := t.defaultProbes.Attach(probe.GetHandle(), t.cgroups, t.kernelSymbols) + err := t.defaultProbes.Attach(probe.GetHandle(), t.cgroups, t.getKernelSymbols()) if err == nil { continue } @@ -1192,7 +1202,7 @@ func (t *Tracee) attachProbes() error { logger.Errorw("Got node from type not requested") return nil } - err := t.defaultProbes.Attach(probeNode.GetHandle(), t.cgroups, t.kernelSymbols) + err := t.defaultProbes.Attach(probeNode.GetHandle(), t.cgroups, t.getKernelSymbols()) if err != nil { return []dependencies.Action{dependencies.NewCancelNodeAddAction(err)} } @@ -1575,12 +1585,11 @@ func (t *Tracee) getSelfLoadedPrograms(kprobesOnly bool) map[string]int { uniqueHooksMap := map[probeMapKey]struct{}{} for _, tr := range t.policyManager.EventsSelected() { - if !events.Core.IsDefined(tr) { + definition := events.Core.GetDefinitionByID(tr) + if definition.NotValid() { continue } - definition := events.Core.GetDefinitionByID(tr) - for _, depProbes := range definition.GetDependencies().GetProbes() { currProbe := t.defaultProbes.GetProbeByHandle(depProbes.GetHandle()) name := "" @@ -1722,7 +1731,7 @@ func (t *Tracee) triggerSeqOpsIntegrityCheck(event trace.Event) { } var seqOpsPointers [len(derive.NetSeqOps)]uint64 for i, seqName := range derive.NetSeqOps { - seqOpsStruct, err := t.kernelSymbols.GetSymbolByOwnerAndName("system", seqName) + seqOpsStruct, err := t.getKernelSymbols().GetSymbolByOwnerAndName("system", seqName) if err != nil { continue } @@ -1816,7 +1825,7 @@ func (t *Tracee) triggerMemDump(event trace.Event) []error { continue } - symbol, err := t.kernelSymbols.GetSymbolByOwnerAndName(owner, name) + symbol, err := t.getKernelSymbols().GetSymbolByOwnerAndName(owner, name) if err != nil { if owner != "system" { errs = append(errs, errfmt.Errorf("policy %d: invalid symbols provided to print_mem_dump event: %s - %v", p.ID, field, err)) @@ -1828,7 +1837,7 @@ func (t *Tracee) triggerMemDump(event trace.Event) []error { prefixes := []string{"sys_", "__x64_sys_", "__arm64_sys_"} var errSyscall error for _, prefix := range prefixes { - symbol, errSyscall = t.kernelSymbols.GetSymbolByOwnerAndName(owner, prefix+name) + symbol, errSyscall = t.getKernelSymbols().GetSymbolByOwnerAndName(owner, prefix+name) if errSyscall == nil { err = nil break diff --git a/pkg/events/core.go b/pkg/events/core.go index 146b58bd63ba..7cb67f000b3e 100644 --- a/pkg/events/core.go +++ b/pkg/events/core.go @@ -107,6 +107,7 @@ const ( SecurityPathNotify SetFsPwd SuspiciousSyscallSource + StackPivot HiddenKernelModuleSeeker ModuleLoad ModuleFree @@ -13066,7 +13067,13 @@ var CoreEvents = map[ID]Definition{ id: SuspiciousSyscallSource, id32Bit: Sys32Undefined, name: "suspicious_syscall_source", - sets: []string{}, + dependencies: Dependencies{ + probes: []Probe{ + {handle: probes.SchedProcessFork, required: false}, // for thread stack tracking + {handle: probes.SchedProcessExec, required: false}, // for thread stack tracking + }, + }, + sets: []string{}, fields: []trace.ArgMeta{ {Type: "int", Name: "syscall"}, {Type: "void*", Name: "ip"}, @@ -13076,6 +13083,26 @@ var CoreEvents = map[ID]Definition{ {Type: "unsigned long", Name: "vma_flags"}, }, }, + StackPivot: { + id: StackPivot, + id32Bit: Sys32Undefined, + name: "stack_pivot", + dependencies: Dependencies{ + probes: []Probe{ + {handle: probes.SchedProcessFork, required: false}, // for thread stack tracking + {handle: probes.SchedProcessExec, required: false}, // for thread stack tracking + }, + }, + sets: []string{}, + fields: []trace.ArgMeta{ + {Type: "int", Name: "syscall"}, + {Type: "void*", Name: "sp"}, + {Type: "char*", Name: "vma_type"}, + {Type: "void*", Name: "vma_start"}, + {Type: "unsigned long", Name: "vma_size"}, + {Type: "unsigned long", Name: "vma_flags"}, + }, + }, // // Begin of Signal Events (Control Plane) // diff --git a/pkg/events/definition.go b/pkg/events/definition.go index e0c3c6780aee..1a5cd8288bc2 100644 --- a/pkg/events/definition.go +++ b/pkg/events/definition.go @@ -114,3 +114,7 @@ func (d Definition) IsNetwork() bool { func (d Definition) GetProperties() map[string]interface{} { return d.properties } + +func (d Definition) NotValid() bool { + return d.id == Undefined || d.id == Unsupported +} diff --git a/pkg/events/definition_group.go b/pkg/events/definition_group.go index 365fcbd817f6..dfe0d8a468b6 100644 --- a/pkg/events/definition_group.go +++ b/pkg/events/definition_group.go @@ -61,10 +61,12 @@ func (d *DefinitionGroup) GetDefinitions() []Definition { func (d *DefinitionGroup) GetDefinitionIDByName(givenName string) (ID, bool) { d.mutex.RLock() defer d.mutex.RUnlock() + id, found := d.getDefinitionIDByName(givenName) if !found { logger.Debugw("definition name not found", "name", givenName) } + return id, found } @@ -80,7 +82,6 @@ func (d *DefinitionGroup) getDefinitionIDByName(givenName string) (ID, bool) { } // GetDefinitionByID returns a definition by its ID. -// NOTE: should be used together with IsDefined when definition might not exist. func (d *DefinitionGroup) GetDefinitionByID(givenDef ID) Definition { d.mutex.RLock() defer d.mutex.RUnlock() @@ -88,14 +89,42 @@ func (d *DefinitionGroup) GetDefinitionByID(givenDef ID) Definition { def, ok := d.definitions[givenDef] if !ok { logger.Debugw("definition id not found", "id", givenDef) - return Definition{id: Undefined} + return Definition{ + id: Undefined, + name: "Undefined", + } } return def } +// GetDefinitionByName returns a definition by its name. +func (d *DefinitionGroup) GetDefinitionByName(givenName string) Definition { + d.mutex.RLock() + defer d.mutex.RUnlock() + + def, _ := d.getDefinitionByName(givenName) + return def +} + +// getDefinitionByName returns a definition by its name (no locking). +func (d *DefinitionGroup) getDefinitionByName(givenName string) (Definition, bool) { + for _, def := range d.definitions { + if def.GetName() == givenName { + return def, true + } + } + + return Definition{ + id: Undefined, + name: "Undefined", + }, false +} + // IsDefined returns true if the definition exists in the definition group. -// NOTE: needed as GetDefinitionByID() is used as GetDefinitionByID().Method() multiple times. +// This method only verifies the existence of a definition. +// To retrieve the Definition, use GetDefinitionByID and check its validity with +// the NotValid method. func (d *DefinitionGroup) IsDefined(givenDef ID) bool { d.mutex.RLock() defer d.mutex.RUnlock() diff --git a/pkg/events/definition_group_test.go b/pkg/events/definition_group_test.go index 4ba495ec379f..678442045c87 100644 --- a/pkg/events/definition_group_test.go +++ b/pkg/events/definition_group_test.go @@ -84,6 +84,32 @@ func TestDefinitionGroup_GetDefinitionIDByName(t *testing.T) { require.Equal(t, id, id2) } +// TestGetDefinitionByName tests that GetDefinitionByName returns a definition by its name. +func TestDefinitionGroup_GetDefinitionByName(t *testing.T) { + t.Parallel() + + defGroup := NewDefinitionGroup() + + id1 := ID(1) + id2 := ID(2) + + def1 := NewDefinition(id1, id1+1000, "def1", version, "", "", false, false, []string{}, Dependencies{}, nil, nil) + def2 := NewDefinition(id2, id2+1000, "def2", version, "", "", false, false, []string{}, Dependencies{}, nil, nil) + + defGroup.AddBatch(map[ID]Definition{id1: def1, id2: def2}) + + // found definition + + def := defGroup.GetDefinitionByName("def1") + require.Equal(t, def.GetID(), id1) + require.Equal(t, def.GetName(), "def1") + + // definition not found (undefined) + + def = defGroup.GetDefinitionByName("def3") + require.Equal(t, def.GetID(), Undefined) +} + // TestDefinitionGroup_GetDefinitionByID tests that GetDefinitionByID returns a definition by its ID. func TestDefinitionGroup_GetDefinitionByID(t *testing.T) { t.Parallel() diff --git a/pkg/events/derive/hooked_seq_ops.go b/pkg/events/derive/hooked_seq_ops.go index 08af6ba5fe5f..6fe24d56f796 100644 --- a/pkg/events/derive/hooked_seq_ops.go +++ b/pkg/events/derive/hooked_seq_ops.go @@ -4,7 +4,6 @@ import ( "github.com/aquasecurity/tracee/pkg/errfmt" "github.com/aquasecurity/tracee/pkg/events" "github.com/aquasecurity/tracee/pkg/events/parse" - "github.com/aquasecurity/tracee/pkg/utils" "github.com/aquasecurity/tracee/pkg/utils/environment" "github.com/aquasecurity/tracee/types/trace" ) @@ -43,7 +42,7 @@ func deriveHookedSeqOpsArgs(kernelSymbols *environment.KernelSymbolTable) derive if addr == 0 { continue } - hookingFunction := utils.ParseSymbol(addr, kernelSymbols) + hookingFunction := kernelSymbols.GetPotentiallyHiddenSymbolByAddr(addr)[0] seqOpsStruct := NetSeqOps[i/4] seqOpsFunc := NetSeqOpsFuncs[i%4] hookedSeqOps[seqOpsStruct+"_"+seqOpsFunc] = diff --git a/pkg/events/derive/hooked_syscall.go b/pkg/events/derive/hooked_syscall.go index 9160e274f346..a31057f2fef8 100644 --- a/pkg/events/derive/hooked_syscall.go +++ b/pkg/events/derive/hooked_syscall.go @@ -28,19 +28,19 @@ func InitHookedSyscall() error { } func DetectHookedSyscall(kernelSymbols *environment.KernelSymbolTable) DeriveFunction { - return deriveSingleEvent(events.HookedSyscall, deriveDetectHookedSyscallArgs(kernelSymbols)) + return deriveMultipleEvents(events.HookedSyscall, deriveDetectHookedSyscallArgs(kernelSymbols)) } -func deriveDetectHookedSyscallArgs(kernelSymbols *environment.KernelSymbolTable) deriveArgsFunction { - return func(event trace.Event) ([]interface{}, error) { +func deriveDetectHookedSyscallArgs(kernelSymbols *environment.KernelSymbolTable) multiDeriveArgsFunction { + return func(event trace.Event) ([][]interface{}, []error) { syscallId, err := parse.ArgVal[int32](event.Args, "syscall_id") if err != nil { - return nil, errfmt.Errorf("error parsing syscall_id arg: %v", err) + return nil, []error{errfmt.Errorf("error parsing syscall_id arg: %v", err)} } address, err := parse.ArgVal[uint64](event.Args, "syscall_address") if err != nil { - return nil, errfmt.Errorf("error parsing syscall_address arg: %v", err) + return nil, []error{errfmt.Errorf("error parsing syscall_address arg: %v", err)} } alreadyReportedAddress, found := reportedHookedSyscalls.Get(syscallId) @@ -50,18 +50,20 @@ func deriveDetectHookedSyscallArgs(kernelSymbols *environment.KernelSymbolTable) reportedHookedSyscalls.Add(syscallId, address) // Upsert - hookedFuncName := "" - hookedOwner := "" - hookedFuncSymbol, err := kernelSymbols.GetSymbolByAddr(address) - if err == nil { - hookedFuncName = hookedFuncSymbol[0].Name - hookedOwner = hookedFuncSymbol[0].Owner - } - syscallName := convertToSyscallName(syscallId) hexAddress := fmt.Sprintf("%x", address) - return []interface{}{syscallName, hexAddress, hookedFuncName, hookedOwner}, nil + hookedFuncSymbols, err := kernelSymbols.GetSymbolByAddr(address) + if err != nil { + return [][]interface{}{{syscallName, hexAddress, "", ""}}, nil + } + + events := make([][]interface{}, 0) + for _, symbol := range hookedFuncSymbols { + events = append(events, []interface{}{syscallName, hexAddress, symbol.Name, symbol.Owner}) + } + + return events, nil } } diff --git a/pkg/events/parse/params.go b/pkg/events/parse/params.go index e2550a602496..78767dd8ff54 100644 --- a/pkg/events/parse/params.go +++ b/pkg/events/parse/params.go @@ -8,22 +8,37 @@ import ( ) func ArgVal[T any](args []trace.Argument, argName string) (T, error) { - for _, arg := range args { - if arg.Name == argName { - val, ok := arg.Value.(T) - if !ok { - zeroVal := *new(T) - return zeroVal, errfmt.Errorf( - "argument %s is not of type %T, is of type %T", - argName, - zeroVal, - arg.Value, - ) - } - return val, nil + var ok bool + var val T + var foundAndNotOk bool + + var i int + for i = range len(args) { + if args[i].Name != argName { + continue + } + + val, ok = args[i].Value.(T) + if !ok { + foundAndNotOk = true + break } + + return val, nil + } + + var zeroVal T + if foundAndNotOk { + return zeroVal, + errfmt.Errorf( + "argument %s is not of type %T, is of type %T", + argName, + zeroVal, + args[i].Value, + ) } - return *new(T), errfmt.Errorf("argument %s not found", argName) + + return zeroVal, errfmt.Errorf("argument %s not found", argName) } func ArgZeroValueFromType(t string) interface{} { diff --git a/pkg/events/parse/params_bench_test.go b/pkg/events/parse/params_bench_test.go new file mode 100644 index 000000000000..6eca4f7bce18 --- /dev/null +++ b/pkg/events/parse/params_bench_test.go @@ -0,0 +1,63 @@ +package parse + +import ( + "testing" + + "github.com/aquasecurity/tracee/types/trace" +) + +var args = []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "valid_arg1", + Type: "int", + }, + Value: int32(1878), + }, + { + ArgMeta: trace.ArgMeta{ + Name: "valid_arg2", + Type: "int", + }, + Value: int32(1878), + }, + { + ArgMeta: trace.ArgMeta{ + Name: "invalid_val_type", // in the middle of the list + Type: "int", + }, + Value: int64(1878), + }, + { + ArgMeta: trace.ArgMeta{ + Name: "valid_arg3", + Type: "int", + }, + Value: int32(1878), + }, +} + +func BenchmarkArgVal(b *testing.B) { + b.Run("int32", func(b *testing.B) { + b.Run("valid_args", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _, _ = ArgVal[int32](args, "valid_arg1") + _, _ = ArgVal[int32](args, "valid_arg2") + _, _ = ArgVal[int32](args, "valid_arg3") + } + }) + b.Run("invalid_val_type", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _, _ = ArgVal[int32](args, "invalid_val_type") + } + }) + b.Run("not_found_arg", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _, _ = ArgVal[int32](args, "not_found_arg") + } + }) + }) +} diff --git a/pkg/events/parse_args.go b/pkg/events/parse_args.go index e57433cdb7e0..a899afe6e25a 100644 --- a/pkg/events/parse_args.go +++ b/pkg/events/parse_args.go @@ -23,7 +23,8 @@ func ParseArgs(event *trace.Event) error { } } - switch ID(event.EventID) { + evtID := ID(event.EventID) + switch evtID { case MemProtAlert: if alertArg := GetArg(event, "alert"); alertArg != nil { if alert, isUint32 := alertArg.Value.(uint32); isUint32 { @@ -83,8 +84,8 @@ func ParseArgs(event *trace.Event) error { } case Prctl: if optArg := GetArg(event, "option"); optArg != nil { - if opt, isInt32 := optArg.Value.(int32); isInt32 { - parsePrctlOption(optArg, uint64(opt)) + if option, isInt32 := optArg.Value.(int32); isInt32 { + parsePrctlOption(optArg, uint64(option)) } } case Socketcall: @@ -115,16 +116,27 @@ func ParseArgs(event *trace.Event) error { parseSocketType(typeArg, uint64(typ)) } } - case Access, Faccessat: + case Access: if modeArg := GetArg(event, "mode"); modeArg != nil { if mode, isInt32 := modeArg.Value.(int32); isInt32 { parseAccessMode(modeArg, uint64(mode)) } } + case Faccessat: + if modeArg := GetArg(event, "mode"); modeArg != nil { + if mode, isInt32 := modeArg.Value.(int32); isInt32 { + parseAccessMode(modeArg, uint64(mode)) + } + } + if flagsArg := GetArg(event, "flags"); flagsArg != nil { + if flags, isInt32 := flagsArg.Value.(int32); isInt32 { + parseFaccessatFlag(flagsArg, uint64(flags)) + } + } case Execveat: if flagsArg := GetArg(event, "flags"); flagsArg != nil { if flags, isInt32 := flagsArg.Value.(int32); isInt32 { - parseExecFlag(flagsArg, uint64(flags)) + parseExecveatFlag(flagsArg, uint64(flags)) } } case Open, Openat, SecurityFileOpen: @@ -139,6 +151,13 @@ func ParseArgs(event *trace.Event) error { parseInodeMode(modeArg, uint64(mode)) } } + if evtID == Fchmodat { + if flagsArg := GetArg(event, "flags"); flagsArg != nil { + if flags, isInt32 := flagsArg.Value.(int32); isInt32 { + parseFchmodatFlag(flagsArg, uint64(flags)) + } + } + } case Clone: if flagsArg := GetArg(event, "flags"); flagsArg != nil { if flags, isUint64 := flagsArg.Value.(uint64); isUint64 { @@ -220,15 +239,14 @@ func ParseArgs(event *trace.Event) error { parseFsNotifyObjType(objTypeArg, uint64(objType)) } } - case SuspiciousSyscallSource: + case SuspiciousSyscallSource, StackPivot: if syscallArg := GetArg(event, "syscall"); syscallArg != nil { if id, isInt32 := syscallArg.Value.(int32); isInt32 { - if Core.IsDefined(ID(id)) { - eventDefinition := Core.GetDefinitionByID(ID(id)) - if eventDefinition.IsSyscall() { - syscallArg.Value = eventDefinition.GetName() - syscallArg.Type = "string" - } + eventDefinition := Core.GetDefinitionByID(ID(id)) + // no need to check for NotValid() since it is syscall only if it's a valid event + if eventDefinition.IsSyscall() { + syscallArg.Value = eventDefinition.GetName() + syscallArg.Type = "string" } } } @@ -257,6 +275,12 @@ func ParseArgsFDs(event *trace.Event, origTimestamp uint64, fdArgPathMap *bpf.BP } } + if dirfdArg := GetArg(event, "dirfd"); dirfdArg != nil { + if dirfd, isInt32 := dirfdArg.Value.(int32); isInt32 { + parseDirfdAt(dirfdArg, uint64(dirfd)) + } + } + return nil } diff --git a/pkg/events/parse_args_helpers.go b/pkg/events/parse_args_helpers.go index 8e0279b58406..4c440f1f23a9 100644 --- a/pkg/events/parse_args_helpers.go +++ b/pkg/events/parse_args_helpers.go @@ -1,11 +1,23 @@ package events import ( + "strconv" + + "golang.org/x/sys/unix" + "github.com/aquasecurity/tracee/pkg/events/parsers" "github.com/aquasecurity/tracee/pkg/logger" "github.com/aquasecurity/tracee/types/trace" ) +func parseDirfdAt(arg *trace.Argument, dirfd uint64) { + if int32(dirfd) == unix.AT_FDCWD { + arg.Type = "string" + arg.Value = "AT_FDCWD" + return + } +} + func parseMMapProt(arg *trace.Argument, prot uint64) { mmapProtArgument := parsers.ParseMmapProt(prot) arg.Type = "string" @@ -16,17 +28,17 @@ func parseSocketDomainArgument(arg *trace.Argument, domain uint64) { arg.Type = "string" socketDomainArgument, err := parsers.ParseSocketDomainArgument(domain) if err != nil { - arg.Value = "" + arg.Value = strconv.FormatUint(domain, 10) return } - arg.Value = socketDomainArgument.String() + arg.Value = socketDomainArgument } func parseSocketType(arg *trace.Argument, typ uint64) { arg.Type = "string" socketTypeArgument, err := parsers.ParseSocketType(typ) if err != nil { - arg.Value = "" + arg.Value = strconv.FormatUint(typ, 10) return } arg.Value = socketTypeArgument.String() @@ -36,7 +48,7 @@ func parseInodeMode(arg *trace.Argument, mode uint64) { arg.Type = "string" inodeModeArgument, err := parsers.ParseInodeMode(mode) if err != nil { - arg.Value = "" + arg.Value = strconv.FormatUint(mode, 10) return } arg.Value = inodeModeArgument.String() @@ -46,7 +58,7 @@ func parseBPFProgType(arg *trace.Argument, progType uint64) { arg.Type = "string" bpfProgTypeArgument, err := parsers.ParseBPFProgType(progType) if err != nil { - arg.Value = "" + arg.Value = strconv.FormatUint(progType, 10) return } arg.Value = bpfProgTypeArgument.String() @@ -56,10 +68,10 @@ func parseCapability(arg *trace.Argument, capability uint64) { arg.Type = "string" capabilityFlagArgument, err := parsers.ParseCapability(capability) if err != nil { - arg.Value = "" + arg.Value = strconv.FormatUint(capability, 10) return } - arg.Value = capabilityFlagArgument.String() + arg.Value = capabilityFlagArgument } func parseMemProtAlert(arg *trace.Argument, alert uint32) { @@ -74,12 +86,13 @@ func parseSyscall(arg *trace.Argument, id int32) { // NOTE: This might cause data races in the future if the map is modified. // One solution to keep better CPU time is to segregate the map into two maps: // one for proper core (read-only) events and another for the dynamic events. + arg.Type = "string" def, ok := CoreEvents[ID(id)] if !ok || !def.IsSyscall() { + arg.Value = strconv.FormatInt(int64(id), 10) return } - arg.Type = "string" arg.Value = def.GetName() } @@ -87,87 +100,107 @@ func parsePtraceRequestArgument(arg *trace.Argument, req uint64) { arg.Type = "string" ptraceRequestArgument, err := parsers.ParsePtraceRequestArgument(req) if err != nil { - arg.Value = "" + arg.Value = strconv.FormatUint(req, 10) return } - arg.Value = ptraceRequestArgument.String() + arg.Value = ptraceRequestArgument } -func parsePrctlOption(arg *trace.Argument, opt uint64) { +func parsePrctlOption(arg *trace.Argument, option uint64) { arg.Type = "string" - prctlOptionArgument, err := parsers.ParsePrctlOption(opt) + prctlOptionArgument, err := parsers.ParsePrctlOption(option) if err != nil { - arg.Value = "" + arg.Value = strconv.FormatUint(option, 10) return } - arg.Value = prctlOptionArgument.String() + arg.Value = prctlOptionArgument } func parseSocketcallCall(arg *trace.Argument, call uint64) { arg.Type = "string" - socketcallArgument, err := parsers.ParseSocketcallCall(call) + socketCallArgument, err := parsers.ParseSocketcallCall(call) if err != nil { - arg.Value = "" + arg.Value = strconv.FormatUint(call, 10) return } - arg.Value = socketcallArgument.String() + arg.Value = socketCallArgument } func parseAccessMode(arg *trace.Argument, mode uint64) { arg.Type = "string" accessModeArgument, err := parsers.ParseAccessMode(mode) if err != nil { - arg.Value = "" + arg.Value = strconv.FormatUint(mode, 10) return } - arg.Value = accessModeArgument.String() + arg.Value = accessModeArgument } -func parseExecFlag(arg *trace.Argument, flags uint64) { +func parseFaccessatFlag(arg *trace.Argument, flags uint64) { arg.Type = "string" - execFlagArgument, err := parsers.ParseExecFlag(flags) + faccessatFlagArgument, err := parsers.ParseFaccessatFlag(flags) if err != nil { - arg.Value = "" + arg.Value = strconv.FormatUint(flags, 10) return } - arg.Value = execFlagArgument.String() + arg.Value = faccessatFlagArgument +} + +func parseFchmodatFlag(arg *trace.Argument, flags uint64) { + arg.Type = "string" + fchmodatFlagArgument, err := parsers.ParseFchmodatFlag(flags) + if err != nil { + arg.Value = strconv.FormatUint(flags, 10) + return + } + arg.Value = fchmodatFlagArgument +} + +func parseExecveatFlag(arg *trace.Argument, flags uint64) { + arg.Type = "string" + execFlagArgument, err := parsers.ParseExecveatFlag(flags) + if err != nil { + arg.Value = strconv.FormatUint(flags, 10) + return + } + arg.Value = execFlagArgument } func parseOpenFlagArgument(arg *trace.Argument, flags uint64) { arg.Type = "string" openFlagArgument, err := parsers.ParseOpenFlagArgument(flags) if err != nil { - arg.Value = "" + arg.Value = strconv.FormatUint(flags, 10) return } - arg.Value = openFlagArgument.String() + arg.Value = openFlagArgument } func parseCloneFlags(arg *trace.Argument, flags uint64) { arg.Type = "string" cloneFlagArgument, err := parsers.ParseCloneFlags(flags) if err != nil { - arg.Value = "" + arg.Value = strconv.FormatUint(flags, 10) return } - arg.Value = cloneFlagArgument.String() + arg.Value = cloneFlagArgument } func parseBPFCmd(arg *trace.Argument, cmd uint64) { arg.Type = "string" bpfCommandArgument, err := parsers.ParseBPFCmd(cmd) if err != nil { - arg.Value = "" + arg.Value = strconv.FormatUint(cmd, 10) return } - arg.Value = bpfCommandArgument.String() + arg.Value = bpfCommandArgument } func parseSocketLevel(arg *trace.Argument, level uint64) { arg.Type = "string" socketLevelArgument, err := parsers.ParseSocketLevel(level) if err != nil { - arg.Value = "" + arg.Value = strconv.FormatUint(level, 10) return } arg.Value = socketLevelArgument.String() @@ -185,7 +218,7 @@ func parseGetSocketOption(arg *trace.Argument, opt uint64, evtID ID) { if err == nil { arg.Value = optionNameArgument.String() } else { - arg.Value = "" + arg.Value = strconv.FormatUint(opt, 10) } } @@ -193,7 +226,7 @@ func parseFsNotifyObjType(arg *trace.Argument, objType uint64) { arg.Type = "string" fsNotifyObjTypeArgument, err := parsers.ParseFsNotifyObjType(objType) if err != nil { - arg.Value = "" + arg.Value = strconv.FormatUint(objType, 10) return } arg.Value = fsNotifyObjTypeArgument.String() @@ -206,6 +239,7 @@ func parseBpfHelpersUsage(arg *trace.Argument, helpersList []uint64) { // helper number is used. get its name from libbpfgo bpfHelper, err := parsers.ParseBPFFunc(uint64(i)) if err != nil { + usedHelpers = append(usedHelpers, strconv.FormatInt(int64(i), 10)) continue } usedHelpers = append(usedHelpers, bpfHelper.String()) @@ -235,9 +269,8 @@ func parseBpfAttachType(arg *trace.Argument, attachType int32) { case 5: attTypeName = "uretprobe" default: - arg.Value = "" + attTypeName = strconv.FormatInt(int64(attachType), 10) logger.Errorw("Unknown attach_type got from bpf_attach event") - return } arg.Value = attTypeName diff --git a/pkg/events/parse_args_helpers_test.go b/pkg/events/parse_args_helpers_test.go new file mode 100644 index 000000000000..fa0d0eb2ffec --- /dev/null +++ b/pkg/events/parse_args_helpers_test.go @@ -0,0 +1,1385 @@ +package events + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/tracee/pkg/events/parsers" + "github.com/aquasecurity/tracee/types/trace" +) + +func TestParseArgsHelpers(t *testing.T) { + t.Parallel() + TestParseMMapProt(t) + TestParseSocketDomainArgument(t) + TestParseSocketType(t) + TestParseInodeMode(t) + TestParseBPFProgType(t) + TestParseCapability(t) + TestParseMemProtAlert(t) + TestParseSyscall(t) + TestParsePtraceRequestArgument(t) + TestParsePrctlOption(t) + TestParseSocketcallCall(t) + TestParseAccessMode(t) + TestParseExecveatFlag(t) + TestParseOpenFlagArgument(t) + TestParseCloneFlags(t) + TestParseBPFCmd(t) + TestParseSocketLevel(t) + TestParseGetSocketOption(t) + TestParseFsNotifyObjType(t) + TestParseBpfHelpersUsage(t) + TestParseBpfAttachType(t) +} +func TestParseMMapProt(t *testing.T) { + // No need to add other test cases because there isn't a case where parseMMapProt fail + testCases := []struct { + name string + args []trace.Argument + expectedArgs []trace.Argument + }{{ + name: "normal flow", + args: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "prot", + Type: "int", + }, + Value: parsers.PROT_READ.Value(), + }, + }, + expectedArgs: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "prot", + Type: "string", + }, + Value: "PROT_READ", + }, + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + t.Run(testCase.name, func(t *testing.T) { + event := &trace.Event{ + Args: testCase.args, + } + parseMMapProt(GetArg(event, "prot"), testCase.args[0].Value.(uint64)) + for _, expArg := range testCase.expectedArgs { + arg := GetArg(event, expArg.Name) + assert.Equal(t, expArg, *arg) + } + }) + } +} +func TestParseSocketDomainArgument(t *testing.T) { + testCases := []struct { + name string + args []trace.Argument + expectedArgs []trace.Argument + }{ + { + name: "normal flow", + args: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "domain", + Type: "int", + }, + Value: parsers.AF_INET.Value(), + }, + }, + expectedArgs: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "domain", + Type: "string", + }, + Value: "AF_INET", + }, + }, + }, + { + name: "invalid domain", + args: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "domain", + Type: "int", + }, + Value: uint64(12345), + }, + }, + expectedArgs: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "domain", + Type: "string", + }, + Value: "12345", + }, + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + t.Run(testCase.name, func(t *testing.T) { + event := &trace.Event{ + Args: testCase.args, + } + parseSocketDomainArgument(GetArg(event, "domain"), testCase.args[0].Value.(uint64)) + for _, expArg := range testCase.expectedArgs { + arg := GetArg(event, expArg.Name) + assert.Equal(t, expArg, *arg) + } + }) + } +} +func TestParseSocketType(t *testing.T) { + testCases := []struct { + eventId int + name string + args []trace.Argument + expectedArgs []trace.Argument + }{ + { + name: "normal flow", + eventId: int(Socket), + args: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "type", + Type: "int", + }, + Value: int32(parsers.SOCK_STREAM.Value()), + }, + }, + expectedArgs: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "type", + Type: "string", + }, + Value: "SOCK_STREAM", + }, + }, + }, + { + name: "invalid type", + eventId: int(Socket), + args: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "type", + Type: "int", + }, + Value: int32(12345), + }, + }, + expectedArgs: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "type", + Type: "string", + }, + Value: "12345", + }, + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + t.Run(testCase.name, func(t *testing.T) { + event := &trace.Event{ + EventID: testCase.eventId, + Args: testCase.args, + } + err := ParseArgs(event) + require.NoError(t, err) + for _, expArg := range testCase.expectedArgs { + arg := GetArg(event, expArg.Name) + assert.Equal(t, expArg, *arg) + } + }) + } +} +func TestParseInodeMode(t *testing.T) { + testCases := []struct { + name string + args []trace.Argument + expectedArgs []trace.Argument + }{ + { + name: "normal flow", + args: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "mode", + Type: "int", + }, + Value: parsers.S_IFSOCK.Value(), + }, + }, + expectedArgs: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "mode", + Type: "string", + }, + Value: "S_IFSOCK", + }, + }, + }, + { + name: "invalid mode", + args: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "mode", + Type: "int", + }, + Value: uint64(0), + }, + }, + expectedArgs: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "mode", + Type: "string", + }, + Value: "", + }, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + event := &trace.Event{ + Args: testCase.args, + } + parseInodeMode(GetArg(event, "mode"), testCase.args[0].Value.(uint64)) + for _, expArg := range testCase.expectedArgs { + arg := GetArg(event, expArg.Name) + assert.Equal(t, expArg, *arg) + } + }) + } +} +func TestParseBPFProgType(t *testing.T) { + testCases := []struct { + name string + args []trace.Argument + expectedArgs []trace.Argument + }{ + { + name: "normal flow", + args: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "type", + Type: "int", + }, + Value: parsers.BPFProgTypeUnspec.Value(), + }, + }, + expectedArgs: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "type", + Type: "string", + }, + Value: "BPF_PROG_TYPE_UNSPEC", + }, + }, + }, + { + name: "invalid type", + args: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "type", + Type: "int", + }, + Value: uint64(12345), + }, + }, + expectedArgs: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "type", + Type: "string", + }, + Value: "12345", + }, + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + t.Run(testCase.name, func(t *testing.T) { + event := &trace.Event{ + Args: testCase.args, + } + parseBPFProgType(GetArg(event, "type"), testCase.args[0].Value.(uint64)) + for _, expArg := range testCase.expectedArgs { + arg := GetArg(event, expArg.Name) + assert.Equal(t, expArg, *arg) + } + }) + } +} +func TestParseCapability(t *testing.T) { + testCases := []struct { + name string + args []trace.Argument + expectedArgs []trace.Argument + }{ + { + name: "normal flow", + args: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "capability", + Type: "int", + }, + Value: parsers.CAP_CHOWN.Value(), + }, + }, + expectedArgs: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "capability", + Type: "string", + }, + Value: "CAP_CHOWN", + }, + }, + }, + { + name: "invalid capability", + args: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "capability", + Type: "int", + }, + Value: uint64(12345), + }, + }, + expectedArgs: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "capability", + Type: "string", + }, + Value: "12345", + }, + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + t.Run(testCase.name, func(t *testing.T) { + event := &trace.Event{ + Args: testCase.args, + } + parseCapability(GetArg(event, "capability"), testCase.args[0].Value.(uint64)) + for _, expArg := range testCase.expectedArgs { + arg := GetArg(event, expArg.Name) + assert.Equal(t, expArg, *arg) + } + }) + } +} +func TestParseMemProtAlert(t *testing.T) { + testCases := []struct { + name string + args []trace.Argument + expectedArgs []trace.Argument + }{ + { + name: "normal flow", + args: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "alert", + Type: "int", + }, + Value: uint32(trace.ProtAlertMmapWX), + }, + }, + expectedArgs: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "alert", + Type: "string", + }, + Value: "Mmaped region with W+E permissions!", + }, + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + t.Run(testCase.name, func(t *testing.T) { + event := &trace.Event{ + Args: testCase.args, + } + parseMemProtAlert(GetArg(event, "alert"), testCase.args[0].Value.(uint32)) + for _, expArg := range testCase.expectedArgs { + arg := GetArg(event, expArg.Name) + assert.Equal(t, expArg, *arg) + } + }) + } +} +func TestParseSyscall(t *testing.T) { + testCases := []struct { + name string + args []trace.Argument + expectedArgs []trace.Argument + }{ + { + name: "normal flow", + args: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "id", + Type: "int", + }, + Value: int32(Ptrace), + }, + }, + expectedArgs: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "id", + Type: "string", + }, + Value: "ptrace", + }, + }, + }, + { + name: "invalid syscall", + args: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "id", + Type: "int", + }, + Value: int32(12345), + }, + }, + expectedArgs: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "id", + Type: "string", + }, + Value: "12345", + }, + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + t.Run(testCase.name, func(t *testing.T) { + event := &trace.Event{ + Args: testCase.args, + } + parseSyscall(GetArg(event, "id"), testCase.args[0].Value.(int32)) + for _, expArg := range testCase.expectedArgs { + arg := GetArg(event, expArg.Name) + assert.Equal(t, expArg, *arg) + } + }) + } +} +func TestParsePtraceRequestArgument(t *testing.T) { + testCases := []struct { + name string + args []trace.Argument + expectedArgs []trace.Argument + }{ + { + name: "normal flow", + args: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "req", + Type: "int", + }, + Value: parsers.PTRACE_PEEKTEXT.Value(), + }, + }, + expectedArgs: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "req", + Type: "string", + }, + Value: "PTRACE_PEEKTEXT", + }, + }, + }, + { + name: "invalid req", + args: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "req", + Type: "int", + }, + Value: uint64(12345), + }, + }, + expectedArgs: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "req", + Type: "string", + }, + Value: "12345", + }, + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + t.Run(testCase.name, func(t *testing.T) { + event := &trace.Event{ + Args: testCase.args, + } + parsePtraceRequestArgument(GetArg(event, "req"), testCase.args[0].Value.(uint64)) + for _, expArg := range testCase.expectedArgs { + arg := GetArg(event, expArg.Name) + assert.Equal(t, expArg, *arg) + } + }) + } +} +func TestParsePrctlOption(t *testing.T) { + testCases := []struct { + name string + args []trace.Argument + expectedArgs []trace.Argument + }{ + { + name: "normal flow", + args: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "opt", + Type: "int", + }, + Value: parsers.PR_SET_NO_NEW_PRIVS.Value(), + }, + }, + expectedArgs: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "opt", + Type: "string", + }, + Value: "PR_SET_NO_NEW_PRIVS", + }, + }, + }, + { + name: "invalid opt", + args: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "opt", + Type: "int", + }, + Value: uint64(12345), + }, + }, + expectedArgs: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "opt", + Type: "string", + }, + Value: "12345", + }, + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + t.Run(testCase.name, func(t *testing.T) { + event := &trace.Event{ + Args: testCase.args, + } + parsePrctlOption(GetArg(event, "opt"), testCase.args[0].Value.(uint64)) + for _, expArg := range testCase.expectedArgs { + arg := GetArg(event, expArg.Name) + assert.Equal(t, expArg, *arg) + } + }) + } +} +func TestParseSocketcallCall(t *testing.T) { + testCases := []struct { + name string + args []trace.Argument + expectedArgs []trace.Argument + }{ + { + name: "normal flow", + args: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "call", + Type: "int", + }, + Value: parsers.SYS_SOCKET.Value(), + }, + }, + expectedArgs: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "call", + Type: "string", + }, + Value: "SYS_SOCKET", + }, + }, + }, + { + name: "invalid call", + args: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "call", + Type: "int", + }, + Value: uint64(12345), + }, + }, + expectedArgs: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "call", + Type: "string", + }, + Value: "12345", + }, + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + t.Run(testCase.name, func(t *testing.T) { + event := &trace.Event{ + Args: testCase.args, + } + parseSocketcallCall(GetArg(event, "call"), testCase.args[0].Value.(uint64)) + for _, expArg := range testCase.expectedArgs { + arg := GetArg(event, expArg.Name) + assert.Equal(t, expArg, *arg) + } + }) + } +} +func TestParseAccessMode(t *testing.T) { + testcase := []struct { + name string + args []trace.Argument + expectedArgs []trace.Argument + }{ + // TODO: remove comment when tracee handles an empty flag, will be fixed in #4279 + // { + // name: "normal flow", + // args: []trace.Argument{ + // { + // ArgMeta: trace.ArgMeta{ + // Name: "mode", + // Type: "int", + // }, + // Value: parsers.F_OK.Value(), + // }, + // }, + // expectedArgs: []trace.Argument{ + // { + // ArgMeta: trace.ArgMeta{ + // Name: "mode", + // Type: "string", + // }, + // Value: "F_OK", + // }, + // }, + // }, + { + name: "multiple flow", + args: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "mode", + Type: "int", + }, + Value: parsers.X_OK.Value() | parsers.R_OK.Value() | parsers.W_OK.Value(), + }, + }, + expectedArgs: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "mode", + Type: "string", + }, + Value: "X_OK|W_OK|R_OK", + }, + }, + }, + } + + for _, testCase := range testcase { + testCase := testCase + + t.Run(testCase.name, func(t *testing.T) { + event := &trace.Event{ + Args: testCase.args, + } + parseAccessMode(GetArg(event, "mode"), testCase.args[0].Value.(uint64)) + for _, expArg := range testCase.expectedArgs { + arg := GetArg(event, expArg.Name) + assert.Equal(t, expArg, *arg) + } + }) + } +} +func TestParseExecveatFlag(t *testing.T) { + testCases := []struct { + name string + args []trace.Argument + expectedArgs []trace.Argument + }{ + { + name: "normal flow", + args: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "flags", + Type: "int", + }, + Value: parsers.AT_SYMLINK_NOFOLLOW.Value(), + }, + }, + expectedArgs: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "flags", + Type: "string", + }, + Value: "AT_SYMLINK_NOFOLLOW", + }, + }, + }, + { + name: "invalid flags", + args: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "flags", + Type: "int", + }, + Value: uint64(100), + }, + }, + expectedArgs: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "flags", + Type: "string", + }, + Value: "100", + }, + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + t.Run(testCase.name, func(t *testing.T) { + event := &trace.Event{ + Args: testCase.args, + } + parseExecveatFlag(GetArg(event, "flags"), testCase.args[0].Value.(uint64)) + for _, expArg := range testCase.expectedArgs { + arg := GetArg(event, expArg.Name) + assert.Equal(t, expArg, *arg) + } + }) + } +} +func TestParseOpenFlagArgument(t *testing.T) { + testCases := []struct { + name string + args []trace.Argument + expectedArgs []trace.Argument + }{ + // TODO: remove comment when tracee handles an empty flag, will be fixed in #4279 + // { + // name: "normal flow", + // args: []trace.Argument{ + // { + // ArgMeta: trace.ArgMeta{ + // Name: "flags", + // Type: "int", + // }, + // Value: parsers.O_RDONLY.Value(), + // }, + // }, + // expectedArgs: []trace.Argument{ + // { + // ArgMeta: trace.ArgMeta{ + // Name: "flags", + // Type: "string", + // }, + // Value: "O_RDONLY", + // }, + // }, + // }, + { + name: "multiple flags", + args: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "flags", + Type: "int", + }, + Value: parsers.O_RDWR.Value() | parsers.O_CREAT.Value(), + }, + }, + expectedArgs: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "flags", + Type: "string", + }, + Value: "O_RDWR|O_CREAT", + }, + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + t.Run(testCase.name, func(t *testing.T) { + event := &trace.Event{ + Args: testCase.args, + } + parseOpenFlagArgument(GetArg(event, "flags"), testCase.args[0].Value.(uint64)) + for _, expArg := range testCase.expectedArgs { + arg := GetArg(event, expArg.Name) + assert.Equal(t, expArg, *arg) + } + }) + } +} +func TestParseCloneFlags(t *testing.T) { + testCases := []struct { + name string + args []trace.Argument + expectedArgs []trace.Argument + }{ + { + name: "normal flow", + args: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "flags", + Type: "int", + }, + Value: parsers.CLONE_VM.Value(), + }, + }, + expectedArgs: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "flags", + Type: "string", + }, + Value: "CLONE_VM", + }, + }, + }, + { + name: "multiple flags", + args: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "flags", + Type: "int", + }, + Value: parsers.CLONE_VM.Value() | parsers.CLONE_FS.Value(), + }, + }, + expectedArgs: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "flags", + Type: "string", + }, + Value: "CLONE_VM|CLONE_FS", + }, + }, + }, + { + name: "zero flags", + args: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "flags", + Type: "int", + }, + Value: uint64(0), + }, + }, + expectedArgs: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "flags", + Type: "string", + }, + Value: "", + }, + }, + }, + } + for _, testCase := range testCases { + testCase := testCase + + t.Run(testCase.name, func(t *testing.T) { + event := &trace.Event{ + Args: testCase.args, + } + parseCloneFlags(GetArg(event, "flags"), testCase.args[0].Value.(uint64)) + for _, expArg := range testCase.expectedArgs { + arg := GetArg(event, expArg.Name) + assert.Equal(t, expArg, *arg) + } + }) + } +} +func TestParseBPFCmd(t *testing.T) { + testCases := []struct { + name string + args []trace.Argument + expectedArgs []trace.Argument + }{ + { + name: "normal flow", + args: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "cmd", + Type: "int", + }, + Value: parsers.BPF_PROG_LOAD.Value(), + }, + }, + expectedArgs: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "cmd", + Type: "string", + }, + Value: "BPF_PROG_LOAD", + }, + }, + }, + { + name: "invalid cmd", + args: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "cmd", + Type: "int", + }, + Value: uint64(12345), + }, + }, + expectedArgs: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "cmd", + Type: "string", + }, + Value: "12345", + }, + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + t.Run(testCase.name, func(t *testing.T) { + event := &trace.Event{ + Args: testCase.args, + } + parseBPFCmd(GetArg(event, "cmd"), testCase.args[0].Value.(uint64)) + for _, expArg := range testCase.expectedArgs { + arg := GetArg(event, expArg.Name) + assert.Equal(t, expArg, *arg) + } + }) + } +} +func TestParseSocketLevel(t *testing.T) { + testCases := []struct { + name string + args []trace.Argument + expectedArgs []trace.Argument + }{ + { + name: "normal flow", + args: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "level", + Type: "int", + }, + Value: parsers.SOL_SOCKET.Value(), + }, + }, + expectedArgs: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "level", + Type: "string", + }, + Value: "SOL_SOCKET", + }, + }, + }, + { + name: "invalid level", + args: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "level", + Type: "int", + }, + Value: uint64(12345), + }, + }, + expectedArgs: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "level", + Type: "string", + }, + Value: "12345", + }, + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + t.Run(testCase.name, func(t *testing.T) { + event := &trace.Event{ + Args: testCase.args, + } + parseSocketLevel(GetArg(event, "level"), testCase.args[0].Value.(uint64)) + for _, expArg := range testCase.expectedArgs { + arg := GetArg(event, expArg.Name) + assert.Equal(t, expArg, *arg) + } + }) + } +} +func TestParseGetSocketOption(t *testing.T) { + testCases := []struct { + name string + args []trace.Argument + expectedArgs []trace.Argument + }{ + { + name: "normal flow", + args: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "optname", + Type: "int", + }, + Value: parsers.SO_LOCK_FILTER.Value(), + }, + }, + expectedArgs: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "optname", + Type: "string", + }, + Value: "SO_LOCK_FILTER", + }, + }, + }, + { + name: "invalid flow", + args: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "optname", + Type: "int", + }, + Value: uint64(12345), + }, + }, + expectedArgs: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "optname", + Type: "string", + }, + Value: "12345", + }, + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + t.Run(testCase.name, func(t *testing.T) { + event := &trace.Event{ + Args: testCase.args, + } + parseGetSocketOption(GetArg(event, "optname"), testCase.args[0].Value.(uint64), Getsockopt) + for _, expArg := range testCase.expectedArgs { + arg := GetArg(event, expArg.Name) + assert.Equal(t, expArg, *arg) + } + }) + } +} +func TestParseFsNotifyObjType(t *testing.T) { + testCases := []struct { + name string + args []trace.Argument + expectedArgs []trace.Argument + }{ + { + name: "normal flow", + args: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "objType", + Type: "int", + }, + Value: parsers.FSNOTIFY_OBJ_TYPE_INODE.Value(), + }, + }, + expectedArgs: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "objType", + Type: "string", + }, + Value: "FSNOTIFY_OBJ_TYPE_INODE", + }, + }, + }, + { + name: "invalid objType", + args: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "objType", + Type: "int", + }, + Value: uint64(12345), + }, + }, + expectedArgs: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "objType", + Type: "string", + }, + Value: "12345", + }, + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + t.Run(testCase.name, func(t *testing.T) { + event := &trace.Event{ + Args: testCase.args, + } + parseFsNotifyObjType(GetArg(event, "objType"), testCase.args[0].Value.(uint64)) + for _, expArg := range testCase.expectedArgs { + arg := GetArg(event, expArg.Name) + assert.Equal(t, expArg, *arg) + } + }) + } +} +func TestParseBpfHelpersUsage(t *testing.T) { + var emptyValue []string + testCases := []struct { + name string + args []trace.Argument + expectedArgs []trace.Argument + }{ + { + name: "normal flow", + args: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "helpersList", + Type: "int", + }, + Value: []uint64{1, 2, 3}, + }, + }, + expectedArgs: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "helpersList", + Type: "const char**", + }, + Value: []string{"unspec", "xdp_adjust_tail", "sk_cgroup_id", "sk_ancestor_cgroup_id"}, + }, + }, + }, + { + name: "invalid helpersList", + args: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "helpersList", + Type: "int", + }, + Value: []uint64{0}, + }, + }, + expectedArgs: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "helpersList", + Type: "const char**", + }, + Value: emptyValue, + }, + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + t.Run(testCase.name, func(t *testing.T) { + event := &trace.Event{ + Args: testCase.args, + } + parseBpfHelpersUsage(GetArg(event, "helpersList"), testCase.args[0].Value.([]uint64)) + for _, expArg := range testCase.expectedArgs { + arg := GetArg(event, expArg.Name) + assert.Equal(t, expArg, *arg) + } + }) + } +} +func TestParseBpfAttachType(t *testing.T) { + testCases := []struct { + name string + args []trace.Argument + expectedArgs []trace.Argument + }{ + { + name: "normal flow", + args: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "attachType", + Type: "int", + }, + Value: int32(0), + }, + }, + expectedArgs: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "attachType", + Type: "string", + }, + Value: "raw_tracepoint", + }, + }, + }, + { + name: "invalid attachType", + args: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "attachType", + Type: "int", + }, + Value: int32(12345), + }, + }, + expectedArgs: []trace.Argument{ + { + ArgMeta: trace.ArgMeta{ + Name: "attachType", + Type: "string", + }, + Value: "12345", + }, + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + t.Run(testCase.name, func(t *testing.T) { + event := &trace.Event{ + Args: testCase.args, + } + parseBpfAttachType(GetArg(event, "attachType"), testCase.args[0].Value.(int32)) + for _, expArg := range testCase.expectedArgs { + arg := GetArg(event, expArg.Name) + assert.Equal(t, expArg, *arg) + } + }) + } +} diff --git a/pkg/events/parsers/data_parsers.go b/pkg/events/parsers/data_parsers.go index 061fb0f18910..8caa375a0010 100644 --- a/pkg/events/parsers/data_parsers.go +++ b/pkg/events/parsers/data_parsers.go @@ -13,11 +13,27 @@ import ( "github.com/aquasecurity/tracee/pkg/utils/environment" ) -type SystemFunctionArgument interface { +// Deprecated: use concrete SystemFunctionArgument type instead +// TODO: remove this interface after all consumers are updated +type systemFunctionArgument interface { fmt.Stringer Value() uint64 } +var _ = systemFunctionArgument(SystemFunctionArgument{}) + +type SystemFunctionArgument struct { + rawValue uint64 + stringValue string +} + +func (a SystemFunctionArgument) Value() uint64 { + return a.rawValue +} +func (a SystemFunctionArgument) String() string { + return a.stringValue +} + // optionsAreContainedInArgument checks whether the argument (rawArgument) // contains all of the 'options' such as with flags passed to the clone flag. // This function takes an arbitrary number of uint64. @@ -40,1239 +56,865 @@ func optionIsContainedInArgument(rawArgument uint64, option uint64) bool { return rawArgument&option == option } -type CloneFlagArgument struct { - rawValue uint64 - stringValue string +// buildStringFromValues builds a string from the values of the arguments +// that are present in the rawArgument bitmask. +func buildStringFromValues(sb *strings.Builder, argValues []SystemFunctionArgument, rawArgument uint64) { + for _, arg := range argValues { + if optionIsContainedInArgument(rawArgument, arg.Value()) { + if sb.Len() > 0 { + sb.WriteByte('|') + } + sb.WriteString(arg.String()) + } + } } -// revive:disable +// +// Parsers +// + +// Always use raw values for constants instead of relying on Go's unix/syscall library constants. +// These constants are derived from UAPI definitions but may vary based on predefined macros +// (e.g., _LARGEFILE64_SOURCE, _FILE_OFFSET_BITS) during compilation. For instance, `O_LARGEFILE` +// is defined as 0x8000 (00100000) in some C headers but as 0x0 in Go's unix package. +// To avoid discrepancies, always verify the Linux kernel headers for the correct values. var ( - // These values are copied from uapi/linux/sched.h - CLONE_VM CloneFlagArgument = CloneFlagArgument{rawValue: 0x00000100, stringValue: "CLONE_VM"} - CLONE_FS CloneFlagArgument = CloneFlagArgument{rawValue: 0x00000200, stringValue: "CLONE_FS"} - CLONE_FILES CloneFlagArgument = CloneFlagArgument{rawValue: 0x00000400, stringValue: "CLONE_FILES"} - CLONE_SIGHAND CloneFlagArgument = CloneFlagArgument{rawValue: 0x00000800, stringValue: "CLONE_SIGHAND"} - CLONE_PIDFD CloneFlagArgument = CloneFlagArgument{rawValue: 0x00001000, stringValue: "CLONE_PIDFD"} - CLONE_PTRACE CloneFlagArgument = CloneFlagArgument{rawValue: 0x00002000, stringValue: "CLONE_PTRACE"} - CLONE_VFORK CloneFlagArgument = CloneFlagArgument{rawValue: 0x00004000, stringValue: "CLONE_VFORK"} - CLONE_PARENT CloneFlagArgument = CloneFlagArgument{rawValue: 0x00008000, stringValue: "CLONE_PARENT"} - CLONE_THREAD CloneFlagArgument = CloneFlagArgument{rawValue: 0x00010000, stringValue: "CLONE_THREAD"} - CLONE_NEWNS CloneFlagArgument = CloneFlagArgument{rawValue: 0x00020000, stringValue: "CLONE_NEWNS"} - CLONE_SYSVSEM CloneFlagArgument = CloneFlagArgument{rawValue: 0x00040000, stringValue: "CLONE_SYSVSEM"} - CLONE_SETTLS CloneFlagArgument = CloneFlagArgument{rawValue: 0x00080000, stringValue: "CLONE_SETTLS"} - CLONE_PARENT_SETTID CloneFlagArgument = CloneFlagArgument{rawValue: 0x00100000, stringValue: "CLONE_PARENT_SETTID"} - CLONE_CHILD_CLEARTID CloneFlagArgument = CloneFlagArgument{rawValue: 0x00200000, stringValue: "CLONE_CHILD_CLEARTID"} - CLONE_DETACHED CloneFlagArgument = CloneFlagArgument{rawValue: 0x00400000, stringValue: "CLONE_DETACHED"} - CLONE_UNTRACED CloneFlagArgument = CloneFlagArgument{rawValue: 0x00800000, stringValue: "CLONE_UNTRACED"} - CLONE_CHILD_SETTID CloneFlagArgument = CloneFlagArgument{rawValue: 0x01000000, stringValue: "CLONE_CHILD_SETTID"} - CLONE_NEWCGROUP CloneFlagArgument = CloneFlagArgument{rawValue: 0x02000000, stringValue: "CLONE_NEWCGROUP"} - CLONE_NEWUTS CloneFlagArgument = CloneFlagArgument{rawValue: 0x04000000, stringValue: "CLONE_NEWUTS"} - CLONE_NEWIPC CloneFlagArgument = CloneFlagArgument{rawValue: 0x08000000, stringValue: "CLONE_NEWIPC"} - CLONE_NEWUSER CloneFlagArgument = CloneFlagArgument{rawValue: 0x10000000, stringValue: "CLONE_NEWUSER"} - CLONE_NEWPID CloneFlagArgument = CloneFlagArgument{rawValue: 0x20000000, stringValue: "CLONE_NEWPID"} - CLONE_NEWNET CloneFlagArgument = CloneFlagArgument{rawValue: 0x40000000, stringValue: "CLONE_NEWNET"} - CLONE_IO CloneFlagArgument = CloneFlagArgument{rawValue: 0x80000000, stringValue: "CLONE_IO"} + // from linux/sched.h + CLONE_VM = SystemFunctionArgument{rawValue: 0x00000100, stringValue: "CLONE_VM"} + CLONE_FS = SystemFunctionArgument{rawValue: 0x00000200, stringValue: "CLONE_FS"} + CLONE_FILES = SystemFunctionArgument{rawValue: 0x00000400, stringValue: "CLONE_FILES"} + CLONE_SIGHAND = SystemFunctionArgument{rawValue: 0x00000800, stringValue: "CLONE_SIGHAND"} + CLONE_PIDFD = SystemFunctionArgument{rawValue: 0x00001000, stringValue: "CLONE_PIDFD"} + CLONE_PTRACE = SystemFunctionArgument{rawValue: 0x00002000, stringValue: "CLONE_PTRACE"} + CLONE_VFORK = SystemFunctionArgument{rawValue: 0x00004000, stringValue: "CLONE_VFORK"} + CLONE_PARENT = SystemFunctionArgument{rawValue: 0x00008000, stringValue: "CLONE_PARENT"} + CLONE_THREAD = SystemFunctionArgument{rawValue: 0x00010000, stringValue: "CLONE_THREAD"} + CLONE_NEWNS = SystemFunctionArgument{rawValue: 0x00020000, stringValue: "CLONE_NEWNS"} + CLONE_SYSVSEM = SystemFunctionArgument{rawValue: 0x00040000, stringValue: "CLONE_SYSVSEM"} + CLONE_SETTLS = SystemFunctionArgument{rawValue: 0x00080000, stringValue: "CLONE_SETTLS"} + CLONE_PARENT_SETTID = SystemFunctionArgument{rawValue: 0x00100000, stringValue: "CLONE_PARENT_SETTID"} + CLONE_CHILD_CLEARTID = SystemFunctionArgument{rawValue: 0x00200000, stringValue: "CLONE_CHILD_CLEARTID"} + CLONE_DETACHED = SystemFunctionArgument{rawValue: 0x00400000, stringValue: "CLONE_DETACHED"} + CLONE_UNTRACED = SystemFunctionArgument{rawValue: 0x00800000, stringValue: "CLONE_UNTRACED"} + CLONE_CHILD_SETTID = SystemFunctionArgument{rawValue: 0x01000000, stringValue: "CLONE_CHILD_SETTID"} + CLONE_NEWCGROUP = SystemFunctionArgument{rawValue: 0x02000000, stringValue: "CLONE_NEWCGROUP"} + CLONE_NEWUTS = SystemFunctionArgument{rawValue: 0x04000000, stringValue: "CLONE_NEWUTS"} + CLONE_NEWIPC = SystemFunctionArgument{rawValue: 0x08000000, stringValue: "CLONE_NEWIPC"} + CLONE_NEWUSER = SystemFunctionArgument{rawValue: 0x10000000, stringValue: "CLONE_NEWUSER"} + CLONE_NEWPID = SystemFunctionArgument{rawValue: 0x20000000, stringValue: "CLONE_NEWPID"} + CLONE_NEWNET = SystemFunctionArgument{rawValue: 0x40000000, stringValue: "CLONE_NEWNET"} + CLONE_IO = SystemFunctionArgument{rawValue: 0x80000000, stringValue: "CLONE_IO"} ) -// revive:enable - -func (c CloneFlagArgument) Value() uint64 { return c.rawValue } -func (c CloneFlagArgument) String() string { return c.stringValue } - -func ParseCloneFlags(rawValue uint64) (CloneFlagArgument, error) { - if rawValue == 0 { - return CloneFlagArgument{}, nil +var cloneFlagsValues = []SystemFunctionArgument{ + CLONE_VM, + CLONE_FS, + CLONE_FILES, + CLONE_SIGHAND, + CLONE_PIDFD, + CLONE_PTRACE, + CLONE_VFORK, + CLONE_PARENT, + CLONE_THREAD, + CLONE_NEWNS, + CLONE_SYSVSEM, + CLONE_SETTLS, + CLONE_PARENT_SETTID, + CLONE_CHILD_CLEARTID, + CLONE_DETACHED, + CLONE_UNTRACED, + CLONE_CHILD_SETTID, + CLONE_NEWCGROUP, + CLONE_NEWUTS, + CLONE_NEWIPC, + CLONE_NEWUSER, + CLONE_NEWPID, + CLONE_NEWNET, + CLONE_IO, +} + +// ParseCloneFlags parses the `flags` bitmask argument of the `clone` syscall. +func ParseCloneFlags(flags uint64) (string, error) { + if flags == 0 { + return "", nil } - var f []string - if optionIsContainedInArgument(rawValue, CLONE_VM.Value()) { - f = append(f, CLONE_VM.String()) - } - if optionIsContainedInArgument(rawValue, CLONE_FS.Value()) { - f = append(f, CLONE_FS.String()) - } - if optionIsContainedInArgument(rawValue, CLONE_FILES.Value()) { - f = append(f, CLONE_FILES.String()) - } - if optionIsContainedInArgument(rawValue, CLONE_SIGHAND.Value()) { - f = append(f, CLONE_SIGHAND.String()) - } - if optionIsContainedInArgument(rawValue, CLONE_PIDFD.Value()) { - f = append(f, CLONE_PIDFD.String()) - } - if optionIsContainedInArgument(rawValue, CLONE_PTRACE.Value()) { - f = append(f, CLONE_PTRACE.String()) - } - if optionIsContainedInArgument(rawValue, CLONE_VFORK.Value()) { - f = append(f, CLONE_VFORK.String()) - } - if optionIsContainedInArgument(rawValue, CLONE_PARENT.Value()) { - f = append(f, CLONE_PARENT.String()) - } - if optionIsContainedInArgument(rawValue, CLONE_THREAD.Value()) { - f = append(f, CLONE_THREAD.String()) - } - if optionIsContainedInArgument(rawValue, CLONE_NEWNS.Value()) { - f = append(f, CLONE_NEWNS.String()) - } - if optionIsContainedInArgument(rawValue, CLONE_SYSVSEM.Value()) { - f = append(f, CLONE_SYSVSEM.String()) - } - if optionIsContainedInArgument(rawValue, CLONE_SETTLS.Value()) { - f = append(f, CLONE_SETTLS.String()) - } - if optionIsContainedInArgument(rawValue, CLONE_PARENT_SETTID.Value()) { - f = append(f, CLONE_PARENT_SETTID.String()) - } - if optionIsContainedInArgument(rawValue, CLONE_CHILD_CLEARTID.Value()) { - f = append(f, CLONE_CHILD_CLEARTID.String()) - } - if optionIsContainedInArgument(rawValue, CLONE_DETACHED.Value()) { - f = append(f, CLONE_DETACHED.String()) - } - if optionIsContainedInArgument(rawValue, CLONE_UNTRACED.Value()) { - f = append(f, CLONE_UNTRACED.String()) - } - if optionIsContainedInArgument(rawValue, CLONE_CHILD_SETTID.Value()) { - f = append(f, CLONE_CHILD_SETTID.String()) - } - if optionIsContainedInArgument(rawValue, CLONE_NEWCGROUP.Value()) { - f = append(f, CLONE_NEWCGROUP.String()) - } - if optionIsContainedInArgument(rawValue, CLONE_NEWUTS.Value()) { - f = append(f, CLONE_NEWUTS.String()) - } - if optionIsContainedInArgument(rawValue, CLONE_NEWIPC.Value()) { - f = append(f, CLONE_NEWIPC.String()) - } - if optionIsContainedInArgument(rawValue, CLONE_NEWUSER.Value()) { - f = append(f, CLONE_NEWUSER.String()) - } - if optionIsContainedInArgument(rawValue, CLONE_NEWPID.Value()) { - f = append(f, CLONE_NEWPID.String()) - } - if optionIsContainedInArgument(rawValue, CLONE_NEWNET.Value()) { - f = append(f, CLONE_NEWNET.String()) - } - if optionIsContainedInArgument(rawValue, CLONE_IO.Value()) { - f = append(f, CLONE_IO.String()) - } - if len(f) == 0 { - return CloneFlagArgument{}, fmt.Errorf("no valid clone flag values present in raw value: 0x%x", rawValue) - } + var sb strings.Builder + buildStringFromValues(&sb, cloneFlagsValues, flags) - return CloneFlagArgument{stringValue: strings.Join(f, "|"), rawValue: rawValue}, nil -} + if sb.Len() == 0 { + return "", fmt.Errorf("no valid clone flag values present in flags value: 0x%x", flags) + } -type OpenFlagArgument struct { - rawValue uint64 - stringValue string + return sb.String(), nil } -// revive:disable - var ( - // These values are copied from uapi/asm-generic/fcntl.h - O_ACCMODE OpenFlagArgument = OpenFlagArgument{rawValue: 00000003, stringValue: "O_ACCMODE"} - O_RDONLY OpenFlagArgument = OpenFlagArgument{rawValue: 00000000, stringValue: "O_RDONLY"} - O_WRONLY OpenFlagArgument = OpenFlagArgument{rawValue: 00000001, stringValue: "O_WRONLY"} - O_RDWR OpenFlagArgument = OpenFlagArgument{rawValue: 00000002, stringValue: "O_RDWR"} - O_CREAT OpenFlagArgument = OpenFlagArgument{rawValue: 00000100, stringValue: "O_CREAT"} - O_EXCL OpenFlagArgument = OpenFlagArgument{rawValue: 00000200, stringValue: "O_EXCL"} - O_NOCTTY OpenFlagArgument = OpenFlagArgument{rawValue: 00000400, stringValue: "O_NOCTTY"} - O_TRUNC OpenFlagArgument = OpenFlagArgument{rawValue: 00001000, stringValue: "O_TRUNC"} - O_APPEND OpenFlagArgument = OpenFlagArgument{rawValue: 00002000, stringValue: "O_APPEND"} - O_NONBLOCK OpenFlagArgument = OpenFlagArgument{rawValue: 00004000, stringValue: "O_NONBLOCK"} - O_DSYNC OpenFlagArgument = OpenFlagArgument{rawValue: 00010000, stringValue: "O_DSYNC"} - O_SYNC OpenFlagArgument = OpenFlagArgument{rawValue: 04010000, stringValue: "O_SYNC"} - FASYNC OpenFlagArgument = OpenFlagArgument{rawValue: 00020000, stringValue: "FASYNC"} - O_DIRECT OpenFlagArgument = OpenFlagArgument{rawValue: 00040000, stringValue: "O_DIRECT"} - O_LARGEFILE OpenFlagArgument = OpenFlagArgument{rawValue: 00100000, stringValue: "O_LARGEFILE"} - O_DIRECTORY OpenFlagArgument = OpenFlagArgument{rawValue: 00200000, stringValue: "O_DIRECTORY"} - O_NOFOLLOW OpenFlagArgument = OpenFlagArgument{rawValue: 00400000, stringValue: "O_NOFOLLOW"} - O_NOATIME OpenFlagArgument = OpenFlagArgument{rawValue: 01000000, stringValue: "O_NOATIME"} - O_CLOEXEC OpenFlagArgument = OpenFlagArgument{rawValue: 02000000, stringValue: "O_CLOEXEC"} - O_PATH OpenFlagArgument = OpenFlagArgument{rawValue: 040000000, stringValue: "O_PATH"} - O_TMPFILE OpenFlagArgument = OpenFlagArgument{rawValue: 020000000, stringValue: "O_TMPFILE"} + // from asm-generic/fcntl.h + // NOT sequential values + O_ACCMODE = SystemFunctionArgument{rawValue: 00000003, stringValue: "O_ACCMODE"} + O_RDONLY = SystemFunctionArgument{rawValue: 00000000, stringValue: "O_RDONLY"} + O_WRONLY = SystemFunctionArgument{rawValue: 00000001, stringValue: "O_WRONLY"} + O_RDWR = SystemFunctionArgument{rawValue: 00000002, stringValue: "O_RDWR"} + O_CREAT = SystemFunctionArgument{rawValue: 00000100, stringValue: "O_CREAT"} + O_EXCL = SystemFunctionArgument{rawValue: 00000200, stringValue: "O_EXCL"} + O_NOCTTY = SystemFunctionArgument{rawValue: 00000400, stringValue: "O_NOCTTY"} + O_TRUNC = SystemFunctionArgument{rawValue: 00001000, stringValue: "O_TRUNC"} + O_APPEND = SystemFunctionArgument{rawValue: 00002000, stringValue: "O_APPEND"} + O_NONBLOCK = SystemFunctionArgument{rawValue: 00004000, stringValue: "O_NONBLOCK"} + O_DSYNC = SystemFunctionArgument{rawValue: 00010000, stringValue: "O_DSYNC"} + O_SYNC = SystemFunctionArgument{rawValue: 04010000, stringValue: "O_SYNC"} + FASYNC = SystemFunctionArgument{rawValue: 00020000, stringValue: "FASYNC"} + O_DIRECT = SystemFunctionArgument{rawValue: 00040000, stringValue: "O_DIRECT"} + // gap + O_DIRECTORY = SystemFunctionArgument{rawValue: 00200000, stringValue: "O_DIRECTORY"} + O_NOFOLLOW = SystemFunctionArgument{rawValue: 00400000, stringValue: "O_NOFOLLOW"} + O_NOATIME = SystemFunctionArgument{rawValue: 01000000, stringValue: "O_NOATIME"} + O_CLOEXEC = SystemFunctionArgument{rawValue: 02000000, stringValue: "O_CLOEXEC"} + O_PATH = SystemFunctionArgument{rawValue: 040000000, stringValue: "O_PATH"} + O_TMPFILE = SystemFunctionArgument{rawValue: 020000000, stringValue: "O_TMPFILE"} ) -// revive:enable - -func (o OpenFlagArgument) Value() uint64 { return o.rawValue } -func (o OpenFlagArgument) String() string { return o.stringValue } - -// ParseOpenFlagArgument parses the `flags` bitmask argument of the `open` syscall +// ParseOpenFlagArgument parses the `flags` bitmask argument of the `open` syscall. // http://man7.org/linux/man-pages/man2/open.2.html // https://elixir.bootlin.com/linux/v5.5.3/source/include/uapi/asm-generic/fcntl.h -func ParseOpenFlagArgument(rawValue uint64) (OpenFlagArgument, error) { - if rawValue == 0 { - return OpenFlagArgument{}, nil +func ParseOpenFlagArgument(flags uint64) (string, error) { + if flags == 0 { + return O_RDONLY.String(), nil } - var f []string + + var sb strings.Builder // access mode switch { - case optionIsContainedInArgument(rawValue, O_WRONLY.Value()): - f = append(f, O_WRONLY.String()) - case optionIsContainedInArgument(rawValue, O_RDWR.Value()): - f = append(f, O_RDWR.String()) + case optionIsContainedInArgument(flags, O_WRONLY.Value()): + sb.WriteString(O_WRONLY.String()) + case optionIsContainedInArgument(flags, O_RDWR.Value()): + sb.WriteString(O_RDWR.String()) default: - f = append(f, O_RDONLY.String()) + sb.WriteString(O_RDONLY.String()) } // file creation and status flags - if optionIsContainedInArgument(rawValue, O_CREAT.Value()) { - f = append(f, O_CREAT.String()) - } - if optionIsContainedInArgument(rawValue, O_EXCL.Value()) { - f = append(f, O_EXCL.String()) - } - if optionIsContainedInArgument(rawValue, O_NOCTTY.Value()) { - f = append(f, O_NOCTTY.String()) - } - if optionIsContainedInArgument(rawValue, O_TRUNC.Value()) { - f = append(f, O_TRUNC.String()) - } - if optionIsContainedInArgument(rawValue, O_APPEND.Value()) { - f = append(f, O_APPEND.String()) - } - if optionIsContainedInArgument(rawValue, O_NONBLOCK.Value()) { - f = append(f, O_NONBLOCK.String()) - } - if optionIsContainedInArgument(rawValue, O_SYNC.Value()) { - f = append(f, O_SYNC.String()) - } - if optionIsContainedInArgument(rawValue, FASYNC.Value()) { - f = append(f, FASYNC.String()) - } - if optionIsContainedInArgument(rawValue, O_LARGEFILE.Value()) { - f = append(f, O_LARGEFILE.String()) - } - if optionIsContainedInArgument(rawValue, O_DIRECTORY.Value()) { - f = append(f, O_DIRECTORY.String()) - } - if optionIsContainedInArgument(rawValue, O_NOFOLLOW.Value()) { - f = append(f, O_NOFOLLOW.String()) - } - if optionIsContainedInArgument(rawValue, O_CLOEXEC.Value()) { - f = append(f, O_CLOEXEC.String()) - } - if optionIsContainedInArgument(rawValue, O_DIRECT.Value()) { - f = append(f, O_DIRECT.String()) - } - if optionIsContainedInArgument(rawValue, O_NOATIME.Value()) { - f = append(f, O_NOATIME.String()) - } - if optionIsContainedInArgument(rawValue, O_PATH.Value()) { - f = append(f, O_PATH.String()) - } - if optionIsContainedInArgument(rawValue, O_TMPFILE.Value()) { - f = append(f, O_TMPFILE.String()) - } - - if len(f) == 0 { - return OpenFlagArgument{}, fmt.Errorf("no valid open flag values present in raw value: 0x%x", rawValue) + for _, a := range openFlagsValues { + if optionIsContainedInArgument(flags, a.Value()) { + sb.WriteByte('|') + sb.WriteString(a.String()) + } } - return OpenFlagArgument{rawValue: rawValue, stringValue: strings.Join(f, "|")}, nil -} - -type AccessModeArgument struct { - rawValue uint64 - stringValue string + return sb.String(), nil } -// revive:disable - var ( - F_OK AccessModeArgument = AccessModeArgument{rawValue: 0, stringValue: "F_OK"} - X_OK AccessModeArgument = AccessModeArgument{rawValue: 1, stringValue: "X_OK"} - W_OK AccessModeArgument = AccessModeArgument{rawValue: 2, stringValue: "W_OK"} - R_OK AccessModeArgument = AccessModeArgument{rawValue: 4, stringValue: "R_OK"} + // from fcntl.h + F_OK = SystemFunctionArgument{rawValue: 0, stringValue: "F_OK"} + X_OK = SystemFunctionArgument{rawValue: 1, stringValue: "X_OK"} + W_OK = SystemFunctionArgument{rawValue: 2, stringValue: "W_OK"} + R_OK = SystemFunctionArgument{rawValue: 4, stringValue: "R_OK"} ) -// revive:enable - -func (a AccessModeArgument) Value() uint64 { return a.rawValue } - -func (a AccessModeArgument) String() string { return a.stringValue } +var accessModeValues = []SystemFunctionArgument{ + // F_OK, // special case checked before the loop in ParseAccessMode + X_OK, + W_OK, + R_OK, +} -// ParseAccessMode parses the mode from the `access` system call +// ParseAccessMode parses the mode from the `access` system call. // http://man7.org/linux/man-pages/man2/access.2.html -func ParseAccessMode(rawValue uint64) (AccessModeArgument, error) { - if rawValue == 0 { - return AccessModeArgument{}, nil - } - var f []string - if rawValue == 0x0 { - f = append(f, F_OK.String()) - } else { - if optionIsContainedInArgument(rawValue, R_OK.Value()) { - f = append(f, R_OK.String()) - } - if optionIsContainedInArgument(rawValue, W_OK.Value()) { - f = append(f, W_OK.String()) - } - if optionIsContainedInArgument(rawValue, X_OK.Value()) { - f = append(f, X_OK.String()) - } +func ParseAccessMode(mode uint64) (string, error) { + if mode == 0 { + return F_OK.String(), nil } - if len(f) == 0 { - return AccessModeArgument{}, fmt.Errorf("no valid access mode values present in raw value: 0x%x", rawValue) - } + var sb strings.Builder - return AccessModeArgument{stringValue: strings.Join(f, "|"), rawValue: rawValue}, nil -} + buildStringFromValues(&sb, accessModeValues, mode) -type ExecFlagArgument struct { - rawValue uint64 - stringValue string -} + if sb.Len() == 0 { + return "", fmt.Errorf("no valid access mode values present in mode value: 0x%x", mode) + } -// revive:disable + return sb.String(), nil +} var ( - AT_SYMLINK_NOFOLLOW ExecFlagArgument = ExecFlagArgument{stringValue: "AT_SYMLINK_NOFOLLOW", rawValue: 0x100} - AT_EACCESS ExecFlagArgument = ExecFlagArgument{stringValue: "AT_EACCESS", rawValue: 0x200} - AT_REMOVEDIR ExecFlagArgument = ExecFlagArgument{stringValue: "AT_REMOVEDIR", rawValue: 0x200} - AT_SYMLINK_FOLLOW ExecFlagArgument = ExecFlagArgument{stringValue: "AT_SYMLINK_FOLLOW", rawValue: 0x400} - AT_NO_AUTOMOUNT ExecFlagArgument = ExecFlagArgument{stringValue: "AT_NO_AUTOMOUNT", rawValue: 0x800} - AT_EMPTY_PATH ExecFlagArgument = ExecFlagArgument{stringValue: "AT_EMPTY_PATH", rawValue: 0x1000} - AT_STATX_SYNC_TYPE ExecFlagArgument = ExecFlagArgument{stringValue: "AT_STATX_SYNC_TYPE", rawValue: 0x6000} - AT_STATX_SYNC_AS_STAT ExecFlagArgument = ExecFlagArgument{stringValue: "AT_STATX_SYNC_AS_STAT", rawValue: 0x0000} - AT_STATX_FORCE_SYNC ExecFlagArgument = ExecFlagArgument{stringValue: "AT_STATX_FORCE_SYNC", rawValue: 0x2000} - AT_STATX_DONT_SYNC ExecFlagArgument = ExecFlagArgument{stringValue: "AT_STATX_DONT_SYNC", rawValue: 0x4000} - AT_RECURSIVE ExecFlagArgument = ExecFlagArgument{stringValue: "AT_RECURSIVE", rawValue: 0x8000} + // from linux/fcntl.h + AT_SYMLINK_NOFOLLOW = SystemFunctionArgument{rawValue: 0x100, stringValue: "AT_SYMLINK_NOFOLLOW"} + AT_EACCESS = SystemFunctionArgument{rawValue: 0x200, stringValue: "AT_EACCESS"} + AT_REMOVEDIR = SystemFunctionArgument{rawValue: 0x200, stringValue: "AT_REMOVEDIR"} + AT_SYMLINK_FOLLOW = SystemFunctionArgument{rawValue: 0x400, stringValue: "AT_SYMLINK_FOLLOW"} + AT_NO_AUTOMOUNT = SystemFunctionArgument{rawValue: 0x800, stringValue: "AT_NO_AUTOMOUNT"} + AT_EMPTY_PATH = SystemFunctionArgument{rawValue: 0x1000, stringValue: "AT_EMPTY_PATH"} + AT_STATX_SYNC_TYPE = SystemFunctionArgument{rawValue: 0x6000, stringValue: "AT_STATX_SYNC_TYPE"} + AT_STATX_SYNC_AS_STAT = SystemFunctionArgument{rawValue: 0x0000, stringValue: "AT_STATX_SYNC_AS_STAT"} + AT_STATX_FORCE_SYNC = SystemFunctionArgument{rawValue: 0x2000, stringValue: "AT_STATX_FORCE_SYNC"} + AT_STATX_DONT_SYNC = SystemFunctionArgument{rawValue: 0x4000, stringValue: "AT_STATX_DONT_SYNC"} + AT_RECURSIVE = SystemFunctionArgument{rawValue: 0x8000, stringValue: "AT_RECURSIVE"} ) -// revive:enable - -func (e ExecFlagArgument) Value() uint64 { return e.rawValue } -func (e ExecFlagArgument) String() string { return e.stringValue } +var faccessatFlagsValues = []SystemFunctionArgument{ + AT_EACCESS, + AT_EMPTY_PATH, + AT_SYMLINK_NOFOLLOW, +} -func ParseExecFlag(rawValue uint64) (ExecFlagArgument, error) { - if rawValue == 0 { - return ExecFlagArgument{}, nil +// ParseFaccessatFlag parses the `flags` bitmask argument of the `faccessat` syscall. +func ParseFaccessatFlag(flags uint64) (string, error) { + if flags == 0 { + return "", nil } - var f []string - if optionIsContainedInArgument(rawValue, AT_EMPTY_PATH.Value()) { - f = append(f, AT_EMPTY_PATH.String()) - } - if optionIsContainedInArgument(rawValue, AT_SYMLINK_NOFOLLOW.Value()) { - f = append(f, AT_SYMLINK_NOFOLLOW.String()) - } - if optionIsContainedInArgument(rawValue, AT_EACCESS.Value()) { - f = append(f, AT_EACCESS.String()) - } - if optionIsContainedInArgument(rawValue, AT_REMOVEDIR.Value()) { - f = append(f, AT_REMOVEDIR.String()) - } - if optionIsContainedInArgument(rawValue, AT_NO_AUTOMOUNT.Value()) { - f = append(f, AT_NO_AUTOMOUNT.String()) - } - if optionIsContainedInArgument(rawValue, AT_STATX_SYNC_TYPE.Value()) { - f = append(f, AT_STATX_SYNC_TYPE.String()) - } - if optionIsContainedInArgument(rawValue, AT_STATX_FORCE_SYNC.Value()) { - f = append(f, AT_STATX_FORCE_SYNC.String()) - } - if optionIsContainedInArgument(rawValue, AT_STATX_DONT_SYNC.Value()) { - f = append(f, AT_STATX_DONT_SYNC.String()) - } - if optionIsContainedInArgument(rawValue, AT_RECURSIVE.Value()) { - f = append(f, AT_RECURSIVE.String()) - } - if len(f) == 0 { - return ExecFlagArgument{}, fmt.Errorf("no valid exec flag values present in raw value: 0x%x", rawValue) + var sb strings.Builder + + buildStringFromValues(&sb, faccessatFlagsValues, flags) + + if sb.Len() == 0 { + return "", fmt.Errorf("no valid faccessat flag values present in flags value: 0x%x", flags) } - return ExecFlagArgument{stringValue: strings.Join(f, "|"), rawValue: rawValue}, nil + + return sb.String(), nil } -type CapabilityFlagArgument uint64 +var fchmodatFlagsValues = []SystemFunctionArgument{ + AT_SYMLINK_NOFOLLOW, +} -const ( - CAP_CHOWN CapabilityFlagArgument = iota - CAP_DAC_OVERRIDE - CAP_DAC_READ_SEARCH - CAP_FOWNER - CAP_FSETID - CAP_KILL - CAP_SETGID - CAP_SETUID - CAP_SETPCAP - CAP_LINUX_IMMUTABLE - CAP_NET_BIND_SERVICE - CAP_NET_BROADCAST - CAP_NET_ADMIN - CAP_NET_RAW - CAP_IPC_LOCK - CAP_IPC_OWNER - CAP_SYS_MODULE - CAP_SYS_RAWIO - CAP_SYS_CHROOT - CAP_SYS_PTRACE - CAP_SYS_PACCT - CAP_SYS_ADMIN - CAP_SYS_BOOT - CAP_SYS_NICE - CAP_SYS_RESOURCE - CAP_SYS_TIME - CAP_SYS_TTY_CONFIG - CAP_MKNOD - CAP_LEASE - CAP_AUDIT_WRITE - CAP_AUDIT_CONTROL - CAP_SETFCAP - CAP_MAC_OVERRIDE - CAP_MAC_ADMIN - CAP_SYSLOG - CAP_WAKE_ALARM - CAP_BLOCK_SUSPEND - CAP_AUDIT_READ -) +func ParseFchmodatFlag(flags uint64) (string, error) { + if flags == 0 { + return "", nil + } -func (c CapabilityFlagArgument) Value() uint64 { return uint64(c) } - -var capFlagStringMap = map[CapabilityFlagArgument]string{ - CAP_CHOWN: "CAP_CHOWN", - CAP_DAC_OVERRIDE: "CAP_DAC_OVERRIDE", - CAP_DAC_READ_SEARCH: "CAP_DAC_READ_SEARCH", - CAP_FOWNER: "CAP_FOWNER", - CAP_FSETID: "CAP_FSETID", - CAP_KILL: "CAP_KILL", - CAP_SETGID: "CAP_SETGID", - CAP_SETUID: "CAP_SETUID", - CAP_SETPCAP: "CAP_SETPCAP", - CAP_LINUX_IMMUTABLE: "CAP_LINUX_IMMUTABLE", - CAP_NET_BIND_SERVICE: "CAP_NET_BIND_SERVICE", - CAP_NET_BROADCAST: "CAP_NET_BROADCAST", - CAP_NET_ADMIN: "CAP_NET_ADMIN", - CAP_NET_RAW: "CAP_NET_RAW", - CAP_IPC_LOCK: "CAP_IPC_LOCK", - CAP_IPC_OWNER: "CAP_IPC_OWNER", - CAP_SYS_MODULE: "CAP_SYS_MODULE", - CAP_SYS_RAWIO: "CAP_SYS_RAWIO", - CAP_SYS_CHROOT: "CAP_SYS_CHROOT", - CAP_SYS_PTRACE: "CAP_SYS_PTRACE", - CAP_SYS_PACCT: "CAP_SYS_PACCT", - CAP_SYS_ADMIN: "CAP_SYS_ADMIN", - CAP_SYS_BOOT: "CAP_SYS_BOOT", - CAP_SYS_NICE: "CAP_SYS_NICE", - CAP_SYS_RESOURCE: "CAP_SYS_RESOURCE", - CAP_SYS_TIME: "CAP_SYS_TIME", - CAP_SYS_TTY_CONFIG: "CAP_SYS_TTY_CONFIG", - CAP_MKNOD: "CAP_MKNOD", - CAP_LEASE: "CAP_LEASE", - CAP_AUDIT_WRITE: "CAP_AUDIT_WRITE", - CAP_AUDIT_CONTROL: "CAP_AUDIT_CONTROL", - CAP_SETFCAP: "CAP_SETFCAP", - CAP_MAC_OVERRIDE: "CAP_MAC_OVERRIDE", - CAP_MAC_ADMIN: "CAP_MAC_ADMIN", - CAP_SYSLOG: "CAP_SYSLOG", - CAP_WAKE_ALARM: "CAP_WAKE_ALARM", - CAP_BLOCK_SUSPEND: "CAP_BLOCK_SUSPEND", - CAP_AUDIT_READ: "CAP_AUDIT_READ", -} - -func (c CapabilityFlagArgument) String() string { - var res string + var sb strings.Builder - if capName, ok := capFlagStringMap[c]; ok { - res = capName - } else { - res = strconv.Itoa(int(c)) + buildStringFromValues(&sb, fchmodatFlagsValues, flags) + + if sb.Len() == 0 { + return "", fmt.Errorf("no valid fchmodat flag values present in flags value: 0x%x", flags) } - return res + + return sb.String(), nil } -var capabilitiesMap = map[uint64]CapabilityFlagArgument{ - CAP_CHOWN.Value(): CAP_CHOWN, - CAP_DAC_OVERRIDE.Value(): CAP_DAC_OVERRIDE, - CAP_DAC_READ_SEARCH.Value(): CAP_DAC_READ_SEARCH, - CAP_FOWNER.Value(): CAP_FOWNER, - CAP_FSETID.Value(): CAP_FSETID, - CAP_KILL.Value(): CAP_KILL, - CAP_SETGID.Value(): CAP_SETGID, - CAP_SETUID.Value(): CAP_SETUID, - CAP_SETPCAP.Value(): CAP_SETPCAP, - CAP_LINUX_IMMUTABLE.Value(): CAP_LINUX_IMMUTABLE, - CAP_NET_BIND_SERVICE.Value(): CAP_NET_BIND_SERVICE, - CAP_NET_BROADCAST.Value(): CAP_NET_BROADCAST, - CAP_NET_ADMIN.Value(): CAP_NET_ADMIN, - CAP_NET_RAW.Value(): CAP_NET_RAW, - CAP_IPC_LOCK.Value(): CAP_IPC_LOCK, - CAP_IPC_OWNER.Value(): CAP_IPC_OWNER, - CAP_SYS_MODULE.Value(): CAP_SYS_MODULE, - CAP_SYS_RAWIO.Value(): CAP_SYS_RAWIO, - CAP_SYS_CHROOT.Value(): CAP_SYS_CHROOT, - CAP_SYS_PTRACE.Value(): CAP_SYS_PTRACE, - CAP_SYS_PACCT.Value(): CAP_SYS_PACCT, - CAP_SYS_ADMIN.Value(): CAP_SYS_ADMIN, - CAP_SYS_BOOT.Value(): CAP_SYS_BOOT, - CAP_SYS_NICE.Value(): CAP_SYS_NICE, - CAP_SYS_RESOURCE.Value(): CAP_SYS_RESOURCE, - CAP_SYS_TIME.Value(): CAP_SYS_TIME, - CAP_SYS_TTY_CONFIG.Value(): CAP_SYS_TTY_CONFIG, - CAP_MKNOD.Value(): CAP_MKNOD, - CAP_LEASE.Value(): CAP_LEASE, - CAP_AUDIT_WRITE.Value(): CAP_AUDIT_WRITE, - CAP_AUDIT_CONTROL.Value(): CAP_AUDIT_CONTROL, - CAP_SETFCAP.Value(): CAP_SETFCAP, - CAP_MAC_OVERRIDE.Value(): CAP_MAC_OVERRIDE, - CAP_MAC_ADMIN.Value(): CAP_MAC_ADMIN, - CAP_SYSLOG.Value(): CAP_SYSLOG, - CAP_WAKE_ALARM.Value(): CAP_WAKE_ALARM, - CAP_BLOCK_SUSPEND.Value(): CAP_BLOCK_SUSPEND, - CAP_AUDIT_READ.Value(): CAP_AUDIT_READ, +var execveAtFlagsValues = []SystemFunctionArgument{ + AT_SYMLINK_NOFOLLOW, + AT_EMPTY_PATH, } -// ParseCapability parses the `capability` bitmask argument of the -// `cap_capable` function -func ParseCapability(rawValue uint64) (CapabilityFlagArgument, error) { - v, ok := capabilitiesMap[rawValue] - if !ok { - return 0, fmt.Errorf("not a valid capability value: %d", rawValue) +// ParseExecveatFlag parses the `flags` bitmask argument of the `execveat` syscall. +// http://man7.org/linux/man-pages/man2/execveat.2.html +func ParseExecveatFlag(flags uint64) (string, error) { + if flags == 0 { + return "", nil } - return v, nil -} -type PrctlOptionArgument uint64 + var sb strings.Builder -const ( - PR_SET_PDEATHSIG PrctlOptionArgument = iota + 1 - PR_GET_PDEATHSIG - PR_GET_DUMPABLE - PR_SET_DUMPABLE - PR_GET_UNALIGN - PR_SET_UNALIGN - PR_GET_KEEPCAPS - PR_SET_KEEPCAPS - PR_GET_FPEMU - PR_SET_FPEMU - PR_GET_FPEXC - PR_SET_FPEXC - PR_GET_TIMING - PR_SET_TIMING - PR_SET_NAME - PR_GET_NAME - PR_GET_ENDIAN - PR_SET_ENDIAN - PR_GET_SECCOMP - PR_SET_SECCOMP - PR_CAPBSET_READ - PR_CAPBSET_DROP - PR_GET_TSC - PR_SET_TSC - PR_GET_SECUREBITS - PR_SET_SECUREBITS - PR_SET_TIMERSLACK - PR_GET_TIMERSLACK - PR_TASK_PERF_EVENTS_DISABLE - PR_TASK_PERF_EVENTS_ENABLE - PR_MCE_KILL - PR_MCE_KILL_GET - PR_SET_MM - PR_SET_CHILD_SUBREAPER - PR_GET_CHILD_SUBREAPER - PR_SET_NO_NEW_PRIVS - PR_GET_NO_NEW_PRIVS - PR_GET_TID_ADDRESS - PR_SET_THP_DISABLE - PR_GET_THP_DISABLE - PR_MPX_ENABLE_MANAGEMENT - PR_MPX_DISABLE_MANAGEMENT - PR_SET_FP_MODE - PR_GET_FP_MODE - PR_CAP_AMBIENT - PR_SVE_SET_VL - PR_SVE_GET_VL - PR_GET_SPECULATION_CTRL - PR_SET_SPECULATION_CTRL - PR_PAC_RESET_KEYS - PR_SET_TAGGED_ADDR_CTRL - PR_GET_TAGGED_ADDR_CTRL -) + buildStringFromValues(&sb, execveAtFlagsValues, flags) -func (p PrctlOptionArgument) Value() uint64 { return uint64(p) } - -var prctlOptionStringMap = map[PrctlOptionArgument]string{ - PR_SET_PDEATHSIG: "PR_SET_PDEATHSIG", - PR_GET_PDEATHSIG: "PR_GET_PDEATHSIG", - PR_GET_DUMPABLE: "PR_GET_DUMPABLE", - PR_SET_DUMPABLE: "PR_SET_DUMPABLE", - PR_GET_UNALIGN: "PR_GET_UNALIGN", - PR_SET_UNALIGN: "PR_SET_UNALIGN", - PR_GET_KEEPCAPS: "PR_GET_KEEPCAPS", - PR_SET_KEEPCAPS: "PR_SET_KEEPCAPS", - PR_GET_FPEMU: "PR_GET_FPEMU", - PR_SET_FPEMU: "PR_SET_FPEMU", - PR_GET_FPEXC: "PR_GET_FPEXC", - PR_SET_FPEXC: "PR_SET_FPEXC", - PR_GET_TIMING: "PR_GET_TIMING", - PR_SET_TIMING: "PR_SET_TIMING", - PR_SET_NAME: "PR_SET_NAME", - PR_GET_NAME: "PR_GET_NAME", - PR_GET_ENDIAN: "PR_GET_ENDIAN", - PR_SET_ENDIAN: "PR_SET_ENDIAN", - PR_GET_SECCOMP: "PR_GET_SECCOMP", - PR_SET_SECCOMP: "PR_SET_SECCOMP", - PR_CAPBSET_READ: "PR_CAPBSET_READ", - PR_CAPBSET_DROP: "PR_CAPBSET_DROP", - PR_GET_TSC: "PR_GET_TSC", - PR_SET_TSC: "PR_SET_TSC", - PR_GET_SECUREBITS: "PR_GET_SECUREBITS", - PR_SET_SECUREBITS: "PR_SET_SECUREBITS", - PR_SET_TIMERSLACK: "PR_SET_TIMERSLACK", - PR_GET_TIMERSLACK: "PR_GET_TIMERSLACK", - PR_TASK_PERF_EVENTS_DISABLE: "PR_TASK_PERF_EVENTS_DISABLE", - PR_TASK_PERF_EVENTS_ENABLE: "PR_TASK_PERF_EVENTS_ENABLE", - PR_MCE_KILL: "PR_MCE_KILL", - PR_MCE_KILL_GET: "PR_MCE_KILL_GET", - PR_SET_MM: "PR_SET_MM", - PR_SET_CHILD_SUBREAPER: "PR_SET_CHILD_SUBREAPER", - PR_GET_CHILD_SUBREAPER: "PR_GET_CHILD_SUBREAPER", - PR_SET_NO_NEW_PRIVS: "PR_SET_NO_NEW_PRIVS", - PR_GET_NO_NEW_PRIVS: "PR_GET_NO_NEW_PRIVS", - PR_GET_TID_ADDRESS: "PR_GET_TID_ADDRESS", - PR_SET_THP_DISABLE: "PR_SET_THP_DISABLE", - PR_GET_THP_DISABLE: "PR_GET_THP_DISABLE", - PR_MPX_ENABLE_MANAGEMENT: "PR_MPX_ENABLE_MANAGEMENT", - PR_MPX_DISABLE_MANAGEMENT: "PR_MPX_DISABLE_MANAGEMENT", - PR_SET_FP_MODE: "PR_SET_FP_MODE", - PR_GET_FP_MODE: "PR_GET_FP_MODE", - PR_CAP_AMBIENT: "PR_CAP_AMBIENT", - PR_SVE_SET_VL: "PR_SVE_SET_VL", - PR_SVE_GET_VL: "PR_SVE_GET_VL", - PR_GET_SPECULATION_CTRL: "PR_GET_SPECULATION_CTRL", - PR_SET_SPECULATION_CTRL: "PR_SET_SPECULATION_CTRL", - PR_PAC_RESET_KEYS: "PR_PAC_RESET_KEYS", - PR_SET_TAGGED_ADDR_CTRL: "PR_SET_TAGGED_ADDR_CTRL", - PR_GET_TAGGED_ADDR_CTRL: "PR_GET_TAGGED_ADDR_CTRL", -} - -func (p PrctlOptionArgument) String() string { - var res string - if opName, ok := prctlOptionStringMap[p]; ok { - res = opName - } else { - res = strconv.Itoa(int(p)) + if sb.Len() == 0 { + return "", fmt.Errorf("no valid execveat flag values present in flags value: 0x%x", flags) } - return res + return sb.String(), nil } -var prctlOptionsMap = map[uint64]PrctlOptionArgument{ - PR_SET_PDEATHSIG.Value(): PR_SET_PDEATHSIG, - PR_GET_PDEATHSIG.Value(): PR_GET_PDEATHSIG, - PR_GET_DUMPABLE.Value(): PR_GET_DUMPABLE, - PR_SET_DUMPABLE.Value(): PR_SET_DUMPABLE, - PR_GET_UNALIGN.Value(): PR_GET_UNALIGN, - PR_SET_UNALIGN.Value(): PR_SET_UNALIGN, - PR_GET_KEEPCAPS.Value(): PR_GET_KEEPCAPS, - PR_SET_KEEPCAPS.Value(): PR_SET_KEEPCAPS, - PR_GET_FPEMU.Value(): PR_GET_FPEMU, - PR_SET_FPEMU.Value(): PR_SET_FPEMU, - PR_GET_FPEXC.Value(): PR_GET_FPEXC, - PR_SET_FPEXC.Value(): PR_SET_FPEXC, - PR_GET_TIMING.Value(): PR_GET_TIMING, - PR_SET_TIMING.Value(): PR_SET_TIMING, - PR_SET_NAME.Value(): PR_SET_NAME, - PR_GET_NAME.Value(): PR_GET_NAME, - PR_GET_ENDIAN.Value(): PR_GET_ENDIAN, - PR_SET_ENDIAN.Value(): PR_SET_ENDIAN, - PR_GET_SECCOMP.Value(): PR_GET_SECCOMP, - PR_SET_SECCOMP.Value(): PR_SET_SECCOMP, - PR_CAPBSET_READ.Value(): PR_CAPBSET_READ, - PR_CAPBSET_DROP.Value(): PR_CAPBSET_DROP, - PR_GET_TSC.Value(): PR_GET_TSC, - PR_SET_TSC.Value(): PR_SET_TSC, - PR_GET_SECUREBITS.Value(): PR_GET_SECUREBITS, - PR_SET_SECUREBITS.Value(): PR_SET_SECUREBITS, - PR_SET_TIMERSLACK.Value(): PR_SET_TIMERSLACK, - PR_GET_TIMERSLACK.Value(): PR_GET_TIMERSLACK, - PR_TASK_PERF_EVENTS_DISABLE.Value(): PR_TASK_PERF_EVENTS_DISABLE, - PR_TASK_PERF_EVENTS_ENABLE.Value(): PR_TASK_PERF_EVENTS_ENABLE, - PR_MCE_KILL.Value(): PR_MCE_KILL, - PR_MCE_KILL_GET.Value(): PR_MCE_KILL_GET, - PR_SET_MM.Value(): PR_SET_MM, - PR_SET_CHILD_SUBREAPER.Value(): PR_SET_CHILD_SUBREAPER, - PR_GET_CHILD_SUBREAPER.Value(): PR_GET_CHILD_SUBREAPER, - PR_SET_NO_NEW_PRIVS.Value(): PR_SET_NO_NEW_PRIVS, - PR_GET_NO_NEW_PRIVS.Value(): PR_GET_NO_NEW_PRIVS, - PR_GET_TID_ADDRESS.Value(): PR_GET_TID_ADDRESS, - PR_SET_THP_DISABLE.Value(): PR_SET_THP_DISABLE, - PR_GET_THP_DISABLE.Value(): PR_GET_THP_DISABLE, - PR_MPX_ENABLE_MANAGEMENT.Value(): PR_MPX_ENABLE_MANAGEMENT, - PR_MPX_DISABLE_MANAGEMENT.Value(): PR_MPX_DISABLE_MANAGEMENT, - PR_SET_FP_MODE.Value(): PR_SET_FP_MODE, - PR_GET_FP_MODE.Value(): PR_GET_FP_MODE, - PR_CAP_AMBIENT.Value(): PR_CAP_AMBIENT, - PR_SVE_SET_VL.Value(): PR_SVE_SET_VL, - PR_SVE_GET_VL.Value(): PR_SVE_GET_VL, - PR_GET_SPECULATION_CTRL.Value(): PR_GET_SPECULATION_CTRL, - PR_SET_SPECULATION_CTRL.Value(): PR_SET_SPECULATION_CTRL, - PR_PAC_RESET_KEYS.Value(): PR_PAC_RESET_KEYS, - PR_SET_TAGGED_ADDR_CTRL.Value(): PR_SET_TAGGED_ADDR_CTRL, - PR_GET_TAGGED_ADDR_CTRL.Value(): PR_GET_TAGGED_ADDR_CTRL, -} +var ( + // from linux/capability.h + // sequential values starting from 0 + CAP_CHOWN = SystemFunctionArgument{rawValue: 0, stringValue: "CAP_CHOWN"} + CAP_DAC_OVERRIDE = SystemFunctionArgument{rawValue: 1, stringValue: "CAP_DAC_OVERRIDE"} + CAP_DAC_READ_SEARCH = SystemFunctionArgument{rawValue: 2, stringValue: "CAP_DAC_READ_SEARCH"} + CAP_FOWNER = SystemFunctionArgument{rawValue: 3, stringValue: "CAP_FOWNER"} + CAP_FSETID = SystemFunctionArgument{rawValue: 4, stringValue: "CAP_FSETID"} + CAP_KILL = SystemFunctionArgument{rawValue: 5, stringValue: "CAP_KILL"} + CAP_SETGID = SystemFunctionArgument{rawValue: 6, stringValue: "CAP_SETGID"} + CAP_SETUID = SystemFunctionArgument{rawValue: 7, stringValue: "CAP_SETUID"} + CAP_SETPCAP = SystemFunctionArgument{rawValue: 8, stringValue: "CAP_SETPCAP"} + CAP_LINUX_IMMUTABLE = SystemFunctionArgument{rawValue: 9, stringValue: "CAP_LINUX_IMMUTABLE"} + CAP_NET_BIND_SERVICE = SystemFunctionArgument{rawValue: 10, stringValue: "CAP_NET_BIND_SERVICE"} + CAP_NET_BROADCAST = SystemFunctionArgument{rawValue: 11, stringValue: "CAP_NET_BROADCAST"} + CAP_NET_ADMIN = SystemFunctionArgument{rawValue: 12, stringValue: "CAP_NET_ADMIN"} + CAP_NET_RAW = SystemFunctionArgument{rawValue: 13, stringValue: "CAP_NET_RAW"} + CAP_IPC_LOCK = SystemFunctionArgument{rawValue: 14, stringValue: "CAP_IPC_LOCK"} + CAP_IPC_OWNER = SystemFunctionArgument{rawValue: 15, stringValue: "CAP_IPC_OWNER"} + CAP_SYS_MODULE = SystemFunctionArgument{rawValue: 16, stringValue: "CAP_SYS_MODULE"} + CAP_SYS_RAWIO = SystemFunctionArgument{rawValue: 17, stringValue: "CAP_SYS_RAWIO"} + CAP_SYS_CHROOT = SystemFunctionArgument{rawValue: 18, stringValue: "CAP_SYS_CHROOT"} + CAP_SYS_PTRACE = SystemFunctionArgument{rawValue: 19, stringValue: "CAP_SYS_PTRACE"} + CAP_SYS_PACCT = SystemFunctionArgument{rawValue: 20, stringValue: "CAP_SYS_PACCT"} + CAP_SYS_ADMIN = SystemFunctionArgument{rawValue: 21, stringValue: "CAP_SYS_ADMIN"} + CAP_SYS_BOOT = SystemFunctionArgument{rawValue: 22, stringValue: "CAP_SYS_BOOT"} + CAP_SYS_NICE = SystemFunctionArgument{rawValue: 23, stringValue: "CAP_SYS_NICE"} + CAP_SYS_RESOURCE = SystemFunctionArgument{rawValue: 24, stringValue: "CAP_SYS_RESOURCE"} + CAP_SYS_TIME = SystemFunctionArgument{rawValue: 25, stringValue: "CAP_SYS_TIME"} + CAP_SYS_TTY_CONFIG = SystemFunctionArgument{rawValue: 26, stringValue: "CAP_SYS_TTY_CONFIG"} + CAP_MKNOD = SystemFunctionArgument{rawValue: 27, stringValue: "CAP_MKNOD"} + CAP_LEASE = SystemFunctionArgument{rawValue: 28, stringValue: "CAP_LEASE"} + CAP_AUDIT_WRITE = SystemFunctionArgument{rawValue: 29, stringValue: "CAP_AUDIT_WRITE"} + CAP_AUDIT_CONTROL = SystemFunctionArgument{rawValue: 30, stringValue: "CAP_AUDIT_CONTROL"} + CAP_SETFCAP = SystemFunctionArgument{rawValue: 31, stringValue: "CAP_SETFCAP"} + CAP_MAC_OVERRIDE = SystemFunctionArgument{rawValue: 32, stringValue: "CAP_MAC_OVERRIDE"} + CAP_MAC_ADMIN = SystemFunctionArgument{rawValue: 33, stringValue: "CAP_MAC_ADMIN"} + CAP_SYSLOG = SystemFunctionArgument{rawValue: 34, stringValue: "CAP_SYSLOG"} + CAP_WAKE_ALARM = SystemFunctionArgument{rawValue: 35, stringValue: "CAP_WAKE_ALARM"} + CAP_BLOCK_SUSPEND = SystemFunctionArgument{rawValue: 36, stringValue: "CAP_BLOCK_SUSPEND"} + CAP_AUDIT_READ = SystemFunctionArgument{rawValue: 37, stringValue: "CAP_AUDIT_READ"} + CAP_PERFMON = SystemFunctionArgument{rawValue: 38, stringValue: "CAP_PERFMON"} + CAP_BPF = SystemFunctionArgument{rawValue: 39, stringValue: "CAP_BPF"} + CAP_CHECKPOINT_RESTORE = SystemFunctionArgument{rawValue: 40, stringValue: "CAP_CHECKPOINT_RESTORE"} +) -// ParsePrctlOption parses the `option` argument of the `prctl` syscall -// http://man7.org/linux/man-pages/man2/prctl.2.html -func ParsePrctlOption(rawValue uint64) (PrctlOptionArgument, error) { - v, ok := prctlOptionsMap[rawValue] - if !ok { - return 0, fmt.Errorf("not a valid prctl option value: %d", rawValue) - } - return v, nil +var capabilityValues = []SystemFunctionArgument{ + CAP_CHOWN, + CAP_DAC_OVERRIDE, + CAP_DAC_READ_SEARCH, + CAP_FOWNER, + CAP_FSETID, + CAP_KILL, + CAP_SETGID, + CAP_SETUID, + CAP_SETPCAP, + CAP_LINUX_IMMUTABLE, + CAP_NET_BIND_SERVICE, + CAP_NET_BROADCAST, + CAP_NET_ADMIN, + CAP_NET_RAW, + CAP_IPC_LOCK, + CAP_IPC_OWNER, + CAP_SYS_MODULE, + CAP_SYS_RAWIO, + CAP_SYS_CHROOT, + CAP_SYS_PTRACE, + CAP_SYS_PACCT, + CAP_SYS_ADMIN, + CAP_SYS_BOOT, + CAP_SYS_NICE, + CAP_SYS_RESOURCE, + CAP_SYS_TIME, + CAP_SYS_TTY_CONFIG, + CAP_MKNOD, + CAP_LEASE, + CAP_AUDIT_WRITE, + CAP_AUDIT_CONTROL, + CAP_SETFCAP, + CAP_MAC_OVERRIDE, + CAP_MAC_ADMIN, + CAP_SYSLOG, + CAP_WAKE_ALARM, + CAP_BLOCK_SUSPEND, + CAP_AUDIT_READ, + CAP_PERFMON, + CAP_BPF, + CAP_CHECKPOINT_RESTORE, } -type BPFCommandArgument uint64 - -const ( - BPF_MAP_CREATE BPFCommandArgument = iota - BPF_MAP_LOOKUP_ELEM - BPF_MAP_UPDATE_ELEM - BPF_MAP_DELETE_ELEM - BPF_MAP_GET_NEXT_KEY - BPF_PROG_LOAD - BPF_OBJ_PIN - BPF_OBJ_GET - BPF_PROG_ATTACH - BPF_PROG_DETACH - BPF_PROG_TEST_RUN - BPF_PROG_GET_NEXT_ID - BPF_MAP_GET_NEXT_ID - BPF_PROG_GET_FD_BY_ID - BPF_MAP_GET_FD_BY_ID - BPF_OBJ_GET_INFO_BY_FD - BPF_PROG_QUERY - BPF_RAW_TRACEPOINT_OPEN - BPF_BTF_LOAD - BPF_BTF_GET_FD_BY_ID - BPF_TASK_FD_QUERY - BPF_MAP_LOOKUP_AND_DELETE_ELEM - BPF_MAP_FREEZE - BPF_BTF_GET_NEXT_ID - BPF_MAP_LOOKUP_BATCH - BPF_MAP_LOOKUP_AND_DELETE_BATCH - BPF_MAP_UPDATE_BATCH - BPF_MAP_DELETE_BATCH - BPF_LINK_CREATE - BPF_LINK_UPDATE - BPF_LINK_GET_FD_BY_ID - BPF_LINK_GET_NEXT_ID - BPF_ENABLE_STATS - BPF_ITER_CREATE - BPF_LINK_DETACH +var ( + CAP_FIRST_CAP = CAP_CHOWN.Value() + CAP_LAST_CAP = CAP_CHECKPOINT_RESTORE.Value() ) -func (b BPFCommandArgument) Value() uint64 { return uint64(b) } - -var bpfCmdStringMap = map[BPFCommandArgument]string{ - BPF_MAP_CREATE: "BPF_MAP_CREATE", - BPF_MAP_LOOKUP_ELEM: "BPF_MAP_LOOKUP_ELEM", - BPF_MAP_UPDATE_ELEM: "BPF_MAP_UPDATE_ELEM", - BPF_MAP_DELETE_ELEM: "BPF_MAP_DELETE_ELEM", - BPF_MAP_GET_NEXT_KEY: "BPF_MAP_GET_NEXT_KEY", - BPF_PROG_LOAD: "BPF_PROG_LOAD", - BPF_OBJ_PIN: "BPF_OBJ_PIN", - BPF_OBJ_GET: "BPF_OBJ_GET", - BPF_PROG_ATTACH: "BPF_PROG_ATTACH", - BPF_PROG_DETACH: "BPF_PROG_DETACH", - BPF_PROG_TEST_RUN: "BPF_PROG_TEST_RUN", - BPF_PROG_GET_NEXT_ID: "BPF_PROG_GET_NEXT_ID", - BPF_MAP_GET_NEXT_ID: "BPF_MAP_GET_NEXT_ID", - BPF_PROG_GET_FD_BY_ID: "BPF_PROG_GET_FD_BY_ID", - BPF_MAP_GET_FD_BY_ID: "BPF_MAP_GET_FD_BY_ID", - BPF_OBJ_GET_INFO_BY_FD: "BPF_OBJ_GET_INFO_BY_FD", - BPF_PROG_QUERY: "BPF_PROG_QUERY", - BPF_RAW_TRACEPOINT_OPEN: "BPF_RAW_TRACEPOINT_OPEN", - BPF_BTF_LOAD: "BPF_BTF_LOAD", - BPF_BTF_GET_FD_BY_ID: "BPF_BTF_GET_FD_BY_ID", - BPF_TASK_FD_QUERY: "BPF_TASK_FD_QUERY", - BPF_MAP_LOOKUP_AND_DELETE_ELEM: "BPF_MAP_LOOKUP_AND_DELETE_ELEM", - BPF_MAP_FREEZE: "BPF_MAP_FREEZE", - BPF_BTF_GET_NEXT_ID: "BPF_BTF_GET_NEXT_ID", - BPF_MAP_LOOKUP_BATCH: "BPF_MAP_LOOKUP_BATCH", - BPF_MAP_LOOKUP_AND_DELETE_BATCH: "BPF_MAP_LOOKUP_AND_DELETE_BATCH", - BPF_MAP_UPDATE_BATCH: "BPF_MAP_UPDATE_BATCH", - BPF_MAP_DELETE_BATCH: "BPF_MAP_DELETE_BATCH", - BPF_LINK_CREATE: "BPF_LINK_CREATE", - BPF_LINK_UPDATE: "BPF_LINK_UPDATE", - BPF_LINK_GET_FD_BY_ID: "BPF_LINK_GET_FD_BY_ID", - BPF_LINK_GET_NEXT_ID: "BPF_LINK_GET_NEXT_ID", - BPF_ENABLE_STATS: "BPF_ENABLE_STATS", - BPF_ITER_CREATE: "BPF_ITER_CREATE", - BPF_LINK_DETACH: "BPF_LINK_DETACH", -} - -// String parses the `cmd` argument of the `bpf` syscall -// https://man7.org/linux/man-pages/man2/bpf.2.html -func (b BPFCommandArgument) String() string { - var res string - if cmdName, ok := bpfCmdStringMap[b]; ok { - res = cmdName - } else { - res = strconv.Itoa(int(b)) +// ParseCapability parses the `capability` bitmask argument of the +// `cap_capable` function +func ParseCapability(cap uint64) (string, error) { + if cap > CAP_LAST_CAP { + return "", fmt.Errorf("not a valid capability value: %d", cap) } - return res + idx := int(cap - CAP_FIRST_CAP) + return capabilityValues[idx].String(), nil } -var bpfCmdMap = map[uint64]BPFCommandArgument{ - BPF_MAP_CREATE.Value(): BPF_MAP_CREATE, - BPF_MAP_LOOKUP_ELEM.Value(): BPF_MAP_LOOKUP_ELEM, - BPF_MAP_UPDATE_ELEM.Value(): BPF_MAP_UPDATE_ELEM, - BPF_MAP_DELETE_ELEM.Value(): BPF_MAP_DELETE_ELEM, - BPF_MAP_GET_NEXT_KEY.Value(): BPF_MAP_GET_NEXT_KEY, - BPF_PROG_LOAD.Value(): BPF_PROG_LOAD, - BPF_OBJ_PIN.Value(): BPF_OBJ_PIN, - BPF_OBJ_GET.Value(): BPF_OBJ_GET, - BPF_PROG_ATTACH.Value(): BPF_PROG_ATTACH, - BPF_PROG_DETACH.Value(): BPF_PROG_DETACH, - BPF_PROG_TEST_RUN.Value(): BPF_PROG_TEST_RUN, - BPF_PROG_GET_NEXT_ID.Value(): BPF_PROG_GET_NEXT_ID, - BPF_MAP_GET_NEXT_ID.Value(): BPF_MAP_GET_NEXT_ID, - BPF_PROG_GET_FD_BY_ID.Value(): BPF_PROG_GET_FD_BY_ID, - BPF_MAP_GET_FD_BY_ID.Value(): BPF_MAP_GET_FD_BY_ID, - BPF_OBJ_GET_INFO_BY_FD.Value(): BPF_OBJ_GET_INFO_BY_FD, - BPF_PROG_QUERY.Value(): BPF_PROG_QUERY, - BPF_RAW_TRACEPOINT_OPEN.Value(): BPF_RAW_TRACEPOINT_OPEN, - BPF_BTF_LOAD.Value(): BPF_BTF_LOAD, - BPF_BTF_GET_FD_BY_ID.Value(): BPF_BTF_GET_FD_BY_ID, - BPF_TASK_FD_QUERY.Value(): BPF_TASK_FD_QUERY, - BPF_MAP_LOOKUP_AND_DELETE_ELEM.Value(): BPF_MAP_LOOKUP_AND_DELETE_ELEM, - BPF_MAP_FREEZE.Value(): BPF_MAP_FREEZE, - BPF_BTF_GET_NEXT_ID.Value(): BPF_BTF_GET_NEXT_ID, - BPF_MAP_LOOKUP_BATCH.Value(): BPF_MAP_LOOKUP_BATCH, - BPF_MAP_LOOKUP_AND_DELETE_BATCH.Value(): BPF_MAP_LOOKUP_AND_DELETE_BATCH, - BPF_MAP_UPDATE_BATCH.Value(): BPF_MAP_UPDATE_BATCH, - BPF_MAP_DELETE_BATCH.Value(): BPF_MAP_DELETE_BATCH, - BPF_LINK_CREATE.Value(): BPF_LINK_CREATE, - BPF_LINK_UPDATE.Value(): BPF_LINK_UPDATE, - BPF_LINK_GET_FD_BY_ID.Value(): BPF_LINK_GET_FD_BY_ID, - BPF_LINK_GET_NEXT_ID.Value(): BPF_LINK_GET_NEXT_ID, - BPF_ENABLE_STATS.Value(): BPF_ENABLE_STATS, - BPF_ITER_CREATE.Value(): BPF_ITER_CREATE, - BPF_LINK_DETACH.Value(): BPF_LINK_DETACH, +var ( + // from linux/prctl.h + // NOT sequential values + PR_SET_PDEATHSIG = SystemFunctionArgument{rawValue: 1, stringValue: "PR_SET_PDEATHSIG"} + PR_GET_PDEATHSIG = SystemFunctionArgument{rawValue: 2, stringValue: "PR_GET_PDEATHSIG"} + PR_GET_DUMPABLE = SystemFunctionArgument{rawValue: 3, stringValue: "PR_GET_DUMPABLE"} + PR_SET_DUMPABLE = SystemFunctionArgument{rawValue: 4, stringValue: "PR_SET_DUMPABLE"} + PR_GET_UNALIGN = SystemFunctionArgument{rawValue: 5, stringValue: "PR_GET_UNALIGN"} + PR_SET_UNALIGN = SystemFunctionArgument{rawValue: 6, stringValue: "PR_SET_UNALIGN"} + PR_GET_KEEPCAPS = SystemFunctionArgument{rawValue: 7, stringValue: "PR_GET_KEEPCAPS"} + PR_SET_KEEPCAPS = SystemFunctionArgument{rawValue: 8, stringValue: "PR_SET_KEEPCAPS"} + PR_GET_FPEMU = SystemFunctionArgument{rawValue: 9, stringValue: "PR_GET_FPEMU"} + PR_SET_FPEMU = SystemFunctionArgument{rawValue: 10, stringValue: "PR_SET_FPEMU"} + PR_GET_FPEXC = SystemFunctionArgument{rawValue: 11, stringValue: "PR_GET_FPEXC"} + PR_SET_FPEXC = SystemFunctionArgument{rawValue: 12, stringValue: "PR_SET_FPEXC"} + PR_GET_TIMING = SystemFunctionArgument{rawValue: 13, stringValue: "PR_GET_TIMING"} + PR_SET_TIMING = SystemFunctionArgument{rawValue: 14, stringValue: "PR_SET_TIMING"} + PR_SET_NAME = SystemFunctionArgument{rawValue: 15, stringValue: "PR_SET_NAME"} + PR_GET_NAME = SystemFunctionArgument{rawValue: 16, stringValue: "PR_GET_NAME"} + // gap + PR_GET_ENDIAN = SystemFunctionArgument{rawValue: 19, stringValue: "PR_GET_ENDIAN"} + PR_SET_ENDIAN = SystemFunctionArgument{rawValue: 20, stringValue: "PR_SET_ENDIAN"} + PR_GET_SECCOMP = SystemFunctionArgument{rawValue: 21, stringValue: "PR_GET_SECCOMP"} + PR_SET_SECCOMP = SystemFunctionArgument{rawValue: 22, stringValue: "PR_SET_SECCOMP"} + PR_CAPBSET_READ = SystemFunctionArgument{rawValue: 23, stringValue: "PR_CAPBSET_READ"} + PR_CAPBSET_DROP = SystemFunctionArgument{rawValue: 24, stringValue: "PR_CAPBSET_DROP"} + PR_GET_TSC = SystemFunctionArgument{rawValue: 25, stringValue: "PR_GET_TSC"} + PR_SET_TSC = SystemFunctionArgument{rawValue: 26, stringValue: "PR_SET_TSC"} + PR_GET_SECUREBITS = SystemFunctionArgument{rawValue: 27, stringValue: "PR_GET_SECUREBITS"} + PR_SET_SECUREBITS = SystemFunctionArgument{rawValue: 28, stringValue: "PR_SET_SECUREBITS"} + PR_SET_TIMERSLACK = SystemFunctionArgument{rawValue: 29, stringValue: "PR_SET_TIMERSLACK"} + PR_GET_TIMERSLACK = SystemFunctionArgument{rawValue: 30, stringValue: "PR_GET_TIMERSLACK"} + PR_TASK_PERF_EVENTS_DISABLE = SystemFunctionArgument{rawValue: 31, stringValue: "PR_TASK_PERF_EVENTS_DISABLE"} + PR_TASK_PERF_EVENTS_ENABLE = SystemFunctionArgument{rawValue: 32, stringValue: "PR_TASK_PERF_EVENTS_ENABLE"} + PR_MCE_KILL = SystemFunctionArgument{rawValue: 33, stringValue: "PR_MCE_KILL"} + PR_MCE_KILL_GET = SystemFunctionArgument{rawValue: 34, stringValue: "PR_MCE_KILL_GET"} + PR_SET_MM = SystemFunctionArgument{rawValue: 35, stringValue: "PR_SET_MM"} + + // unordered + PR_SET_PTRACER = SystemFunctionArgument{rawValue: 0x59616d61, stringValue: "PR_SET_PTRACER"} + + PR_SET_CHILD_SUBREAPER = SystemFunctionArgument{rawValue: 36, stringValue: "PR_SET_CHILD_SUBREAPER"} + PR_GET_CHILD_SUBREAPER = SystemFunctionArgument{rawValue: 37, stringValue: "PR_GET_CHILD_SUBREAPER"} + PR_SET_NO_NEW_PRIVS = SystemFunctionArgument{rawValue: 38, stringValue: "PR_SET_NO_NEW_PRIVS"} + PR_GET_NO_NEW_PRIVS = SystemFunctionArgument{rawValue: 39, stringValue: "PR_GET_NO_NEW_PRIVS"} + PR_GET_TID_ADDRESS = SystemFunctionArgument{rawValue: 40, stringValue: "PR_GET_TID_ADDRESS"} + PR_SET_THP_DISABLE = SystemFunctionArgument{rawValue: 41, stringValue: "PR_SET_THP_DISABLE"} + PR_GET_THP_DISABLE = SystemFunctionArgument{rawValue: 42, stringValue: "PR_GET_THP_DISABLE"} + PR_MPX_ENABLE_MANAGEMENT = SystemFunctionArgument{rawValue: 43, stringValue: "PR_MPX_ENABLE_MANAGEMENT"} + PR_MPX_DISABLE_MANAGEMENT = SystemFunctionArgument{rawValue: 44, stringValue: "PR_MPX_DISABLE_MANAGEMENT"} + PR_SET_FP_MODE = SystemFunctionArgument{rawValue: 45, stringValue: "PR_SET_FP_MODE"} + PR_GET_FP_MODE = SystemFunctionArgument{rawValue: 46, stringValue: "PR_GET_FP_MODE"} + PR_CAP_AMBIENT = SystemFunctionArgument{rawValue: 47, stringValue: "PR_CAP_AMBIENT"} + // gap + PR_SVE_SET_VL = SystemFunctionArgument{rawValue: 50, stringValue: "PR_SVE_SET_VL"} + PR_SVE_GET_VL = SystemFunctionArgument{rawValue: 51, stringValue: "PR_SVE_GET_VL"} + PR_GET_SPECULATION_CTRL = SystemFunctionArgument{rawValue: 52, stringValue: "PR_GET_SPECULATION_CTRL"} + PR_SET_SPECULATION_CTRL = SystemFunctionArgument{rawValue: 53, stringValue: "PR_SET_SPECULATION_CTRL"} + PR_PAC_RESET_KEYS = SystemFunctionArgument{rawValue: 54, stringValue: "PR_PAC_RESET_KEYS"} + PR_SET_TAGGED_ADDR_CTRL = SystemFunctionArgument{rawValue: 55, stringValue: "PR_SET_TAGGED_ADDR_CTRL"} + PR_GET_TAGGED_ADDR_CTRL = SystemFunctionArgument{rawValue: 56, stringValue: "PR_GET_TAGGED_ADDR_CTRL"} + PR_SET_IO_FLUSHER = SystemFunctionArgument{rawValue: 57, stringValue: "PR_SET_IO_FLUSHER"} + PR_GET_IO_FLUSHER = SystemFunctionArgument{rawValue: 58, stringValue: "PR_GET_IO_FLUSHER"} + PR_SET_SYSCALL_USER_DISPATCH = SystemFunctionArgument{rawValue: 59, stringValue: "PR_SET_SYSCALL_USER_DISPATCH"} + PR_PAC_SET_ENABLED_KEYS = SystemFunctionArgument{rawValue: 60, stringValue: "PR_PAC_SET_ENABLED_KEYS"} + PR_PAC_GET_ENABLED_KEYS = SystemFunctionArgument{rawValue: 61, stringValue: "PR_PAC_GET_ENABLED_KEYS"} + PR_SCHED_CORE = SystemFunctionArgument{rawValue: 62, stringValue: "PR_SCHED_CORE"} + PR_SME_SET_VL = SystemFunctionArgument{rawValue: 63, stringValue: "PR_SME_SET_VL"} + PR_SME_GET_VL = SystemFunctionArgument{rawValue: 64, stringValue: "PR_SME_GET_VL"} + PR_SET_MDWE = SystemFunctionArgument{rawValue: 65, stringValue: "PR_SET_MDWE"} + PR_GET_MDWE = SystemFunctionArgument{rawValue: 66, stringValue: "PR_GET_MDWE"} + PR_SET_MEMORY_MERGE = SystemFunctionArgument{rawValue: 67, stringValue: "PR_SET_MEMORY_MERGE"} + PR_GET_MEMORY_MERGE = SystemFunctionArgument{rawValue: 68, stringValue: "PR_GET_MEMORY_MERGE"} + + // architecure specific (not supported) + // #define PR_RISCV_V_SET_CONTROL 69 + // #define PR_RISCV_V_GET_CONTROL 70 + // #define PR_RISCV_SET_ICACHE_FLUSH_CTX 71 + // #define PR_PPC_GET_DEXCR 72 + // #define PR_PPC_SET_DEXCR 73 +) + +var prctlOptionValues = []SystemFunctionArgument{ + PR_SET_PDEATHSIG, + PR_GET_PDEATHSIG, + PR_GET_DUMPABLE, + PR_SET_DUMPABLE, + PR_GET_UNALIGN, + PR_SET_UNALIGN, + PR_GET_KEEPCAPS, + PR_SET_KEEPCAPS, + PR_GET_FPEMU, + PR_SET_FPEMU, + PR_GET_FPEXC, + PR_SET_FPEXC, + PR_GET_TIMING, + PR_SET_TIMING, + PR_SET_NAME, + PR_GET_NAME, + PR_GET_ENDIAN, + PR_SET_ENDIAN, + PR_GET_SECCOMP, + PR_SET_SECCOMP, + PR_CAPBSET_READ, + PR_CAPBSET_DROP, + PR_GET_TSC, + PR_SET_TSC, + PR_GET_SECUREBITS, + PR_SET_SECUREBITS, + PR_SET_TIMERSLACK, + PR_GET_TIMERSLACK, + PR_TASK_PERF_EVENTS_DISABLE, + PR_TASK_PERF_EVENTS_ENABLE, + PR_MCE_KILL, + PR_MCE_KILL_GET, + PR_SET_MM, + PR_SET_CHILD_SUBREAPER, + PR_GET_CHILD_SUBREAPER, + PR_SET_NO_NEW_PRIVS, + PR_GET_NO_NEW_PRIVS, + PR_GET_TID_ADDRESS, + PR_SET_THP_DISABLE, + PR_GET_THP_DISABLE, + PR_MPX_ENABLE_MANAGEMENT, + PR_MPX_DISABLE_MANAGEMENT, + PR_SET_FP_MODE, + PR_GET_FP_MODE, + PR_CAP_AMBIENT, + PR_SVE_SET_VL, + PR_SVE_GET_VL, + PR_GET_SPECULATION_CTRL, + PR_SET_SPECULATION_CTRL, + PR_PAC_RESET_KEYS, + PR_SET_TAGGED_ADDR_CTRL, + PR_GET_TAGGED_ADDR_CTRL, + PR_SET_IO_FLUSHER, + PR_GET_IO_FLUSHER, + PR_SET_SYSCALL_USER_DISPATCH, + PR_PAC_SET_ENABLED_KEYS, + PR_PAC_GET_ENABLED_KEYS, + PR_SCHED_CORE, + PR_SME_SET_VL, + PR_SME_GET_VL, + PR_SET_MDWE, + PR_GET_MDWE, + PR_SET_MEMORY_MERGE, + PR_GET_MEMORY_MERGE, } -// ParseBPFCmd parses the raw value of the `cmd` argument of the `bpf` syscall -// https://man7.org/linux/man-pages/man2/bpf.2.html -func ParseBPFCmd(cmd uint64) (BPFCommandArgument, error) { - v, ok := bpfCmdMap[cmd] - if !ok { - return 0, fmt.Errorf("not a valid BPF command argument: %d", cmd) +// ParsePrctlOption parses the `option` argument of the `prctl` syscall +// http://man7.org/linux/man-pages/man2/prctl.2.html +func ParsePrctlOption(option uint64) (string, error) { + for idx := range prctlOptionValues { + if option == prctlOptionValues[idx].Value() { + return prctlOptionValues[idx].String(), nil + } } - return v, nil + + return "", fmt.Errorf("not a valid prctl option value: %d", option) } -type PtraceRequestArgument uint64 +var ( + // from linux/bpf.h + // sequential values starting from 0 + BPF_MAP_CREATE = SystemFunctionArgument{rawValue: 0, stringValue: "BPF_MAP_CREATE"} + BPF_MAP_LOOKUP_ELEM = SystemFunctionArgument{rawValue: 1, stringValue: "BPF_MAP_LOOKUP_ELEM"} + BPF_MAP_UPDATE_ELEM = SystemFunctionArgument{rawValue: 2, stringValue: "BPF_MAP_UPDATE_ELEM"} + BPF_MAP_DELETE_ELEM = SystemFunctionArgument{rawValue: 3, stringValue: "BPF_MAP_DELETE_ELEM"} + BPF_MAP_GET_NEXT_KEY = SystemFunctionArgument{rawValue: 4, stringValue: "BPF_MAP_GET_NEXT_KEY"} + BPF_PROG_LOAD = SystemFunctionArgument{rawValue: 5, stringValue: "BPF_PROG_LOAD"} + BPF_OBJ_PIN = SystemFunctionArgument{rawValue: 6, stringValue: "BPF_OBJ_PIN"} + BPF_OBJ_GET = SystemFunctionArgument{rawValue: 7, stringValue: "BPF_OBJ_GET"} + BPF_PROG_ATTACH = SystemFunctionArgument{rawValue: 8, stringValue: "BPF_PROG_ATTACH"} + BPF_PROG_DETACH = SystemFunctionArgument{rawValue: 9, stringValue: "BPF_PROG_DETACH"} + BPF_PROG_TEST_RUN = SystemFunctionArgument{rawValue: 10, stringValue: "BPF_PROG_TEST_RUN"} + BPF_PROG_GET_NEXT_ID = SystemFunctionArgument{rawValue: 11, stringValue: "BPF_PROG_GET_NEXT_ID"} + BPF_MAP_GET_NEXT_ID = SystemFunctionArgument{rawValue: 12, stringValue: "BPF_MAP_GET_NEXT_ID"} + BPF_PROG_GET_FD_BY_ID = SystemFunctionArgument{rawValue: 13, stringValue: "BPF_PROG_GET_FD_BY_ID"} + BPF_MAP_GET_FD_BY_ID = SystemFunctionArgument{rawValue: 14, stringValue: "BPF_MAP_GET_FD_BY_ID"} + BPF_OBJ_GET_INFO_BY_FD = SystemFunctionArgument{rawValue: 15, stringValue: "BPF_OBJ_GET_INFO_BY_FD"} + BPF_PROG_QUERY = SystemFunctionArgument{rawValue: 16, stringValue: "BPF_PROG_QUERY"} + BPF_RAW_TRACEPOINT_OPEN = SystemFunctionArgument{rawValue: 17, stringValue: "BPF_RAW_TRACEPOINT_OPEN"} + BPF_BTF_LOAD = SystemFunctionArgument{rawValue: 18, stringValue: "BPF_BTF_LOAD"} + BPF_BTF_GET_FD_BY_ID = SystemFunctionArgument{rawValue: 19, stringValue: "BPF_BTF_GET_FD_BY_ID"} + BPF_TASK_FD_QUERY = SystemFunctionArgument{rawValue: 20, stringValue: "BPF_TASK_FD_QUERY"} + BPF_MAP_LOOKUP_AND_DELETE_ELEM = SystemFunctionArgument{rawValue: 21, stringValue: "BPF_MAP_LOOKUP_AND_DELETE_ELEM"} + BPF_MAP_FREEZE = SystemFunctionArgument{rawValue: 22, stringValue: "BPF_MAP_FREEZE"} + BPF_BTF_GET_NEXT_ID = SystemFunctionArgument{rawValue: 23, stringValue: "BPF_BTF_GET_NEXT_ID"} + BPF_MAP_LOOKUP_BATCH = SystemFunctionArgument{rawValue: 24, stringValue: "BPF_MAP_LOOKUP_BATCH"} + BPF_MAP_LOOKUP_AND_DELETE_BATCH = SystemFunctionArgument{rawValue: 25, stringValue: "BPF_MAP_LOOKUP_AND_DELETE_BATCH"} + BPF_MAP_UPDATE_BATCH = SystemFunctionArgument{rawValue: 26, stringValue: "BPF_MAP_UPDATE_BATCH"} + BPF_MAP_DELETE_BATCH = SystemFunctionArgument{rawValue: 27, stringValue: "BPF_MAP_DELETE_BATCH"} + BPF_LINK_CREATE = SystemFunctionArgument{rawValue: 28, stringValue: "BPF_LINK_CREATE"} + BPF_LINK_UPDATE = SystemFunctionArgument{rawValue: 29, stringValue: "BPF_LINK_UPDATE"} + BPF_LINK_GET_FD_BY_ID = SystemFunctionArgument{rawValue: 30, stringValue: "BPF_LINK_GET_FD_BY_ID"} + BPF_LINK_GET_NEXT_ID = SystemFunctionArgument{rawValue: 31, stringValue: "BPF_LINK_GET_NEXT_ID"} + BPF_ENABLE_STATS = SystemFunctionArgument{rawValue: 32, stringValue: "BPF_ENABLE_STATS"} + BPF_ITER_CREATE = SystemFunctionArgument{rawValue: 33, stringValue: "BPF_ITER_CREATE"} + BPF_LINK_DETACH = SystemFunctionArgument{rawValue: 34, stringValue: "BPF_LINK_DETACH"} + BPF_PROG_BIND_MAP = SystemFunctionArgument{rawValue: 35, stringValue: "BPF_PROG_BIND_MAP"} + BPF_TOKEN_CREATE = SystemFunctionArgument{rawValue: 36, stringValue: "BPF_TOKEN_CREATE"} +) -// revive:disable +var bpfCmdValues = []SystemFunctionArgument{ + BPF_MAP_CREATE, + BPF_MAP_LOOKUP_ELEM, + BPF_MAP_UPDATE_ELEM, + BPF_MAP_DELETE_ELEM, + BPF_MAP_GET_NEXT_KEY, + BPF_PROG_LOAD, + BPF_OBJ_PIN, + BPF_OBJ_GET, + BPF_PROG_ATTACH, + BPF_PROG_DETACH, + BPF_PROG_TEST_RUN, + BPF_PROG_GET_NEXT_ID, + BPF_MAP_GET_NEXT_ID, + BPF_PROG_GET_FD_BY_ID, + BPF_MAP_GET_FD_BY_ID, + BPF_OBJ_GET_INFO_BY_FD, + BPF_PROG_QUERY, + BPF_RAW_TRACEPOINT_OPEN, + BPF_BTF_LOAD, + BPF_BTF_GET_FD_BY_ID, + BPF_TASK_FD_QUERY, + BPF_MAP_LOOKUP_AND_DELETE_ELEM, + BPF_MAP_FREEZE, + BPF_BTF_GET_NEXT_ID, + BPF_MAP_LOOKUP_BATCH, + BPF_MAP_LOOKUP_AND_DELETE_BATCH, + BPF_MAP_UPDATE_BATCH, + BPF_MAP_DELETE_BATCH, + BPF_LINK_CREATE, + BPF_LINK_UPDATE, + BPF_LINK_GET_FD_BY_ID, + BPF_LINK_GET_NEXT_ID, + BPF_ENABLE_STATS, + BPF_ITER_CREATE, + BPF_LINK_DETACH, + BPF_PROG_BIND_MAP, + BPF_TOKEN_CREATE, +} var ( - PTRACE_TRACEME PtraceRequestArgument = 0 - PTRACE_PEEKTEXT PtraceRequestArgument = 1 - PTRACE_PEEKDATA PtraceRequestArgument = 2 - PTRACE_PEEKUSER PtraceRequestArgument = 3 - PTRACE_POKETEXT PtraceRequestArgument = 4 - PTRACE_POKEDATA PtraceRequestArgument = 5 - PTRACE_POKEUSER PtraceRequestArgument = 6 - PTRACE_CONT PtraceRequestArgument = 7 - PTRACE_KILL PtraceRequestArgument = 8 - PTRACE_SINGLESTEP PtraceRequestArgument = 9 - PTRACE_GETREGS PtraceRequestArgument = 12 - PTRACE_SETREGS PtraceRequestArgument = 13 - PTRACE_GETFPREGS PtraceRequestArgument = 14 - PTRACE_SETFPREGS PtraceRequestArgument = 15 - PTRACE_ATTACH PtraceRequestArgument = 16 - PTRACE_DETACH PtraceRequestArgument = 17 - PTRACE_GETFPXREGS PtraceRequestArgument = 18 - PTRACE_SETFPXREGS PtraceRequestArgument = 19 - PTRACE_SYSCALL PtraceRequestArgument = 24 - PTRACE_SETOPTIONS PtraceRequestArgument = 0x4200 - PTRACE_GETEVENTMSG PtraceRequestArgument = 0x4201 - PTRACE_GETSIGINFO PtraceRequestArgument = 0x4202 - PTRACE_SETSIGINFO PtraceRequestArgument = 0x4203 - PTRACE_GETREGSET PtraceRequestArgument = 0x4204 - PTRACE_SETREGSET PtraceRequestArgument = 0x4205 - PTRACE_SEIZE PtraceRequestArgument = 0x4206 - PTRACE_INTERRUPT PtraceRequestArgument = 0x4207 - PTRACE_LISTEN PtraceRequestArgument = 0x4208 - PTRACE_PEEKSIGINFO PtraceRequestArgument = 0x4209 - PTRACE_GETSIGMASK PtraceRequestArgument = 0x420a - PTRACE_SETSIGMASK PtraceRequestArgument = 0x420b - PTRACE_SECCOMP_GET_FILTER PtraceRequestArgument = 0x420c - PTRACE_SECCOMP_GET_METADATA PtraceRequestArgument = 0x420d - PTRACE_GET_SYSCALL_INFO PtraceRequestArgument = 0x420e + BPF_FIRST_BPF = BPF_MAP_CREATE.Value() + BPF_LAST_BPF = BPF_TOKEN_CREATE.Value() ) -// revive:enable - -func (p PtraceRequestArgument) Value() uint64 { return uint64(p) } - -var ptraceRequestStringMap = map[PtraceRequestArgument]string{ - PTRACE_TRACEME: "PTRACE_TRACEME", - PTRACE_PEEKTEXT: "PTRACE_PEEKTEXT", - PTRACE_PEEKDATA: "PTRACE_PEEKDATA", - PTRACE_PEEKUSER: "PTRACE_PEEKUSER", - PTRACE_POKETEXT: "PTRACE_POKETEXT", - PTRACE_POKEDATA: "PTRACE_POKEDATA", - PTRACE_POKEUSER: "PTRACE_POKEUSER", - PTRACE_CONT: "PTRACE_CONT", - PTRACE_KILL: "PTRACE_KILL", - PTRACE_SINGLESTEP: "PTRACE_SINGLESTEP", - PTRACE_GETREGS: "PTRACE_GETREGS", - PTRACE_SETREGS: "PTRACE_SETREGS", - PTRACE_GETFPREGS: "PTRACE_GETFPREGS", - PTRACE_SETFPREGS: "PTRACE_SETFPREGS", - PTRACE_ATTACH: "PTRACE_ATTACH", - PTRACE_DETACH: "PTRACE_DETACH", - PTRACE_GETFPXREGS: "PTRACE_GETFPXREGS", - PTRACE_SETFPXREGS: "PTRACE_SETFPXREGS", - PTRACE_SYSCALL: "PTRACE_SYSCALL", - PTRACE_SETOPTIONS: "PTRACE_SETOPTIONS", - PTRACE_GETEVENTMSG: "PTRACE_GETEVENTMSG", - PTRACE_GETSIGINFO: "PTRACE_GETSIGINFO", - PTRACE_SETSIGINFO: "PTRACE_SETSIGINFO", - PTRACE_GETREGSET: "PTRACE_GETREGSET", - PTRACE_SETREGSET: "PTRACE_SETREGSET", - PTRACE_SEIZE: "PTRACE_SEIZE", - PTRACE_INTERRUPT: "PTRACE_INTERRUPT", - PTRACE_LISTEN: "PTRACE_LISTEN", - PTRACE_PEEKSIGINFO: "PTRACE_PEEKSIGINFO", - PTRACE_GETSIGMASK: "PTRACE_GETSIGMASK", - PTRACE_SETSIGMASK: "PTRACE_SETSIGMASK", - PTRACE_SECCOMP_GET_FILTER: "PTRACE_SECCOMP_GET_FILTER", - PTRACE_SECCOMP_GET_METADATA: "PTRACE_SECCOMP_GET_METADATA", - PTRACE_GET_SYSCALL_INFO: "PTRACE_GET_SYSCALL_INFO", -} - -func (p PtraceRequestArgument) String() string { - var res string - if reqName, ok := ptraceRequestStringMap[p]; ok { - res = reqName - } else { - res = strconv.Itoa(int(p)) +// ParseBPFCmd parses the raw value of the `cmd` argument of the `bpf` syscall +// https://man7.org/linux/man-pages/man2/bpf.2.html +func ParseBPFCmd(cmd uint64) (string, error) { + if cmd > BPF_LAST_BPF { + return "", fmt.Errorf("not a valid bpf command value: %d", cmd) } - return res -} - -var ptraceRequestArgMap = map[uint64]PtraceRequestArgument{ - PTRACE_TRACEME.Value(): PTRACE_TRACEME, - PTRACE_PEEKTEXT.Value(): PTRACE_PEEKTEXT, - PTRACE_PEEKDATA.Value(): PTRACE_PEEKDATA, - PTRACE_PEEKUSER.Value(): PTRACE_PEEKUSER, - PTRACE_POKETEXT.Value(): PTRACE_POKETEXT, - PTRACE_POKEDATA.Value(): PTRACE_POKEDATA, - PTRACE_POKEUSER.Value(): PTRACE_POKEUSER, - PTRACE_CONT.Value(): PTRACE_CONT, - PTRACE_KILL.Value(): PTRACE_KILL, - PTRACE_SINGLESTEP.Value(): PTRACE_SINGLESTEP, - PTRACE_GETREGS.Value(): PTRACE_GETREGS, - PTRACE_SETREGS.Value(): PTRACE_SETREGS, - PTRACE_GETFPREGS.Value(): PTRACE_GETFPREGS, - PTRACE_SETFPREGS.Value(): PTRACE_SETFPREGS, - PTRACE_ATTACH.Value(): PTRACE_ATTACH, - PTRACE_DETACH.Value(): PTRACE_DETACH, - PTRACE_GETFPXREGS.Value(): PTRACE_GETFPXREGS, - PTRACE_SETFPXREGS.Value(): PTRACE_SETFPXREGS, - PTRACE_SYSCALL.Value(): PTRACE_SYSCALL, - PTRACE_SETOPTIONS.Value(): PTRACE_SETOPTIONS, - PTRACE_GETEVENTMSG.Value(): PTRACE_GETEVENTMSG, - PTRACE_GETSIGINFO.Value(): PTRACE_GETSIGINFO, - PTRACE_SETSIGINFO.Value(): PTRACE_SETSIGINFO, - PTRACE_GETREGSET.Value(): PTRACE_GETREGSET, - PTRACE_SETREGSET.Value(): PTRACE_SETREGSET, - PTRACE_SEIZE.Value(): PTRACE_SEIZE, - PTRACE_INTERRUPT.Value(): PTRACE_INTERRUPT, - PTRACE_LISTEN.Value(): PTRACE_LISTEN, - PTRACE_PEEKSIGINFO.Value(): PTRACE_PEEKSIGINFO, - PTRACE_GETSIGMASK.Value(): PTRACE_GETSIGMASK, - PTRACE_SETSIGMASK.Value(): PTRACE_SETSIGMASK, - PTRACE_SECCOMP_GET_FILTER.Value(): PTRACE_SECCOMP_GET_FILTER, - PTRACE_SECCOMP_GET_METADATA.Value(): PTRACE_SECCOMP_GET_METADATA, - PTRACE_GET_SYSCALL_INFO.Value(): PTRACE_GET_SYSCALL_INFO, -} - -func ParsePtraceRequestArgument(rawValue uint64) (PtraceRequestArgument, error) { - if reqName, ok := ptraceRequestArgMap[rawValue]; ok { - return reqName, nil - } - return 0, fmt.Errorf("not a valid ptrace request value: %d", rawValue) + idx := int(cmd - BPF_FIRST_BPF) + return bpfCmdValues[idx].String(), nil } -type SocketcallCallArgument uint64 - -const ( - SYS_SOCKET SocketcallCallArgument = iota + 1 - SYS_BIND - SYS_CONNECT - SYS_LISTEN - SYS_ACCEPT - SYS_GETSOCKNAME - SYS_GETPEERNAME - SYS_SOCKETPAIR - SYS_SEND - SYS_RECV - SYS_SENDTO - SYS_RECVFROM - SYS_SHUTDOWN - SYS_SETSOCKOPT - SYS_GETSOCKOPT - SYS_SENDMSG - SYS_RECVMSG - SYS_ACCEPT4 - SYS_RECVMMSG - SYS_SENDMMSG +var ( + // from linux/ptrace.h and sys/ptrace.h + // NOT sequential values + PTRACE_TRACEME = SystemFunctionArgument{rawValue: 0, stringValue: "PTRACE_TRACEME"} + PTRACE_PEEKTEXT = SystemFunctionArgument{rawValue: 1, stringValue: "PTRACE_PEEKTEXT"} + PTRACE_PEEKDATA = SystemFunctionArgument{rawValue: 2, stringValue: "PTRACE_PEEKDATA"} + PTRACE_PEEKUSR = SystemFunctionArgument{rawValue: 3, stringValue: "PTRACE_PEEKUSER"} + PTRACE_POKETEXT = SystemFunctionArgument{rawValue: 4, stringValue: "PTRACE_POKETEXT"} + PTRACE_POKEDATA = SystemFunctionArgument{rawValue: 5, stringValue: "PTRACE_POKEDATA"} + PTRACE_POKEUSR = SystemFunctionArgument{rawValue: 6, stringValue: "PTRACE_POKEUSER"} + PTRACE_CONT = SystemFunctionArgument{rawValue: 7, stringValue: "PTRACE_CONT"} + PTRACE_KILL = SystemFunctionArgument{rawValue: 8, stringValue: "PTRACE_KILL"} + PTRACE_SINGLESTEP = SystemFunctionArgument{rawValue: 9, stringValue: "PTRACE_SINGLESTEP"} + // gap + PTRACE_ATTACH = SystemFunctionArgument{rawValue: 16, stringValue: "PTRACE_ATTACH"} + PTRACE_DETACH = SystemFunctionArgument{rawValue: 17, stringValue: "PTRACE_DETACH"} + // gap + PTRACE_SYSCALL = SystemFunctionArgument{rawValue: 24, stringValue: "PTRACE_SYSCALL"} + // gap + PTRACE_SYSEMU = SystemFunctionArgument{rawValue: 31, stringValue: "PTRACE_SYSEMU"} + PTRACE_SYSEMU_SINGLESTEP = SystemFunctionArgument{rawValue: 32, stringValue: "PTRACE_SYSEMU_SINGLESTEP"} + // gap + PTRACE_SETOPTIONS = SystemFunctionArgument{rawValue: 0x4200, stringValue: "PTRACE_SETOPTIONS"} + PTRACE_GETEVENTMSG = SystemFunctionArgument{rawValue: 0x4201, stringValue: "PTRACE_GETEVENTMSG"} + PTRACE_GETSIGINFO = SystemFunctionArgument{rawValue: 0x4202, stringValue: "PTRACE_GETSIGINFO"} + PTRACE_SETSIGINFO = SystemFunctionArgument{rawValue: 0x4203, stringValue: "PTRACE_SETSIGINFO"} + PTRACE_GETREGSET = SystemFunctionArgument{rawValue: 0x4204, stringValue: "PTRACE_GETREGSET"} + PTRACE_SETREGSET = SystemFunctionArgument{rawValue: 0x4205, stringValue: "PTRACE_SETREGSET"} + PTRACE_SEIZE = SystemFunctionArgument{rawValue: 0x4206, stringValue: "PTRACE_SEIZE"} + PTRACE_INTERRUPT = SystemFunctionArgument{rawValue: 0x4207, stringValue: "PTRACE_INTERRUPT"} + PTRACE_LISTEN = SystemFunctionArgument{rawValue: 0x4208, stringValue: "PTRACE_LISTEN"} + PTRACE_PEEKSIGINFO = SystemFunctionArgument{rawValue: 0x4209, stringValue: "PTRACE_PEEKSIGINFO"} + PTRACE_GETSIGMASK = SystemFunctionArgument{rawValue: 0x420a, stringValue: "PTRACE_GETSIGMASK"} + PTRACE_SETSIGMASK = SystemFunctionArgument{rawValue: 0x420b, stringValue: "PTRACE_SETSIGMASK"} + PTRACE_SECCOMP_GET_FILTER = SystemFunctionArgument{rawValue: 0x420c, stringValue: "PTRACE_SECCOMP_GET_FILTER"} + PTRACE_SECCOMP_GET_METADATA = SystemFunctionArgument{rawValue: 0x420d, stringValue: "PTRACE_SECCOMP_GET_METADATA"} + PTRACE_GET_SYSCALL_INFO = SystemFunctionArgument{rawValue: 0x420e, stringValue: "PTRACE_GET_SYSCALL_INFO"} + PTRACE_GET_RSEQ_CONFIGURATION = SystemFunctionArgument{rawValue: 0x420f, stringValue: "PTRACE_GET_RSEQ_CONFIGURATION"} ) -func (s SocketcallCallArgument) Value() uint64 { - return uint64(s) -} - -var socketcallCallStringMap = map[SocketcallCallArgument]string{ - SYS_SOCKET: "SYS_SOCKET", - SYS_BIND: "SYS_BIND", - SYS_CONNECT: "SYS_CONNECT", - SYS_LISTEN: "SYS_LISTEN", - SYS_ACCEPT: "SYS_ACCEPT", - SYS_GETSOCKNAME: "SYS_GETSOCKNAME", - SYS_GETPEERNAME: "SYS_GETPEERNAME", - SYS_SOCKETPAIR: "SYS_SOCKETPAIR", - SYS_SEND: "SYS_SEND", - SYS_RECV: "SYS_RECV", - SYS_SENDTO: "SYS_SENDTO", - SYS_RECVFROM: "SYS_RECVFROM", - SYS_SHUTDOWN: "SYS_SHUTDOWN", - SYS_SETSOCKOPT: "SYS_SETSOCKOPT", - SYS_GETSOCKOPT: "SYS_GETSOCKOPT", - SYS_SENDMSG: "SYS_SENDMSG", - SYS_RECVMSG: "SYS_RECVMSG", - SYS_ACCEPT4: "SYS_ACCEPT4", - SYS_RECVMMSG: "SYS_RECVMMSG", - SYS_SENDMMSG: "SYS_SENDMMSG", -} - -func (s SocketcallCallArgument) String() string { - var res string - - if sdName, ok := socketcallCallStringMap[s]; ok { - res = sdName - } else { - res = strconv.Itoa(int(s)) +func ParsePtraceRequestArgument(request uint64) (string, error) { + for idx := range ptraceRequestValues { + if ptraceRequestValues[idx].Value() == request { + return ptraceRequestValues[idx].String(), nil + } } - return res + return "", fmt.Errorf("not a valid ptrace request value: %d", request) } -var socketcallCallMap = map[uint64]SocketcallCallArgument{ - SYS_SOCKET.Value(): SYS_SOCKET, - SYS_BIND.Value(): SYS_BIND, - SYS_CONNECT.Value(): SYS_CONNECT, - SYS_LISTEN.Value(): SYS_LISTEN, - SYS_ACCEPT.Value(): SYS_ACCEPT, - SYS_GETSOCKNAME.Value(): SYS_GETSOCKNAME, - SYS_GETPEERNAME.Value(): SYS_GETPEERNAME, - SYS_SOCKETPAIR.Value(): SYS_SOCKETPAIR, - SYS_SEND.Value(): SYS_SEND, - SYS_RECV.Value(): SYS_RECV, - SYS_SENDTO.Value(): SYS_SENDTO, - SYS_RECVFROM.Value(): SYS_RECVFROM, - SYS_SHUTDOWN.Value(): SYS_SHUTDOWN, - SYS_SETSOCKOPT.Value(): SYS_SETSOCKOPT, - SYS_GETSOCKOPT.Value(): SYS_GETSOCKOPT, - SYS_SENDMSG.Value(): SYS_SENDMSG, - SYS_RECVMSG.Value(): SYS_RECVMSG, - SYS_ACCEPT4.Value(): SYS_ACCEPT4, - SYS_RECVMMSG.Value(): SYS_RECVMMSG, - SYS_SENDMMSG.Value(): SYS_SENDMMSG, +var ( + // from linux/net.h + // sequential values starting from 1 + SYS_SOCKET = SystemFunctionArgument{rawValue: 1, stringValue: "SYS_SOCKET"} + SYS_BIND = SystemFunctionArgument{rawValue: 2, stringValue: "SYS_BIND"} + SYS_CONNECT = SystemFunctionArgument{rawValue: 3, stringValue: "SYS_CONNECT"} + SYS_LISTEN = SystemFunctionArgument{rawValue: 4, stringValue: "SYS_LISTEN"} + SYS_ACCEPT = SystemFunctionArgument{rawValue: 5, stringValue: "SYS_ACCEPT"} + SYS_GETSOCKNAME = SystemFunctionArgument{rawValue: 6, stringValue: "SYS_GETSOCKNAME"} + SYS_GETPEERNAME = SystemFunctionArgument{rawValue: 7, stringValue: "SYS_GETPEERNAME"} + SYS_SOCKETPAIR = SystemFunctionArgument{rawValue: 8, stringValue: "SYS_SOCKETPAIR"} + SYS_SEND = SystemFunctionArgument{rawValue: 9, stringValue: "SYS_SEND"} + SYS_RECV = SystemFunctionArgument{rawValue: 10, stringValue: "SYS_RECV"} + SYS_SENDTO = SystemFunctionArgument{rawValue: 11, stringValue: "SYS_SENDTO"} + SYS_RECVFROM = SystemFunctionArgument{rawValue: 12, stringValue: "SYS_RECVFROM"} + SYS_SHUTDOWN = SystemFunctionArgument{rawValue: 13, stringValue: "SYS_SHUTDOWN"} + SYS_SETSOCKOPT = SystemFunctionArgument{rawValue: 14, stringValue: "SYS_SETSOCKOPT"} + SYS_GETSOCKOPT = SystemFunctionArgument{rawValue: 15, stringValue: "SYS_GETSOCKOPT"} + SYS_SENDMSG = SystemFunctionArgument{rawValue: 16, stringValue: "SYS_SENDMSG"} + SYS_RECVMSG = SystemFunctionArgument{rawValue: 17, stringValue: "SYS_RECVMSG"} + SYS_ACCEPT4 = SystemFunctionArgument{rawValue: 18, stringValue: "SYS_ACCEPT4"} + SYS_RECVMMSG = SystemFunctionArgument{rawValue: 19, stringValue: "SYS_RECVMMSG"} + SYS_SENDMMSG = SystemFunctionArgument{rawValue: 20, stringValue: "SYS_SENDMMSG"} +) + +var sysNetValues = []SystemFunctionArgument{ + SYS_SOCKET, + SYS_BIND, + SYS_CONNECT, + SYS_LISTEN, + SYS_ACCEPT, + SYS_GETSOCKNAME, + SYS_GETPEERNAME, + SYS_SOCKETPAIR, + SYS_SEND, + SYS_RECV, + SYS_SENDTO, + SYS_RECVFROM, + SYS_SHUTDOWN, + SYS_SETSOCKOPT, + SYS_GETSOCKOPT, + SYS_SENDMSG, + SYS_RECVMSG, + SYS_ACCEPT4, + SYS_RECVMMSG, + SYS_SENDMMSG, } +var ( + SYS_NET_FIRST = SYS_SOCKET.Value() + SYS_NET_LAST = SYS_SENDMMSG.Value() +) + // ParseSocketcallCall parses the `call` argument of the `socketcall` syscall // http://man7.org/linux/man-pages/man2/socketcall.2.html // https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/net.h -func ParseSocketcallCall(rawValue uint64) (SocketcallCallArgument, error) { - if v, ok := socketcallCallMap[rawValue]; ok { - return v, nil +func ParseSocketcallCall(call uint64) (string, error) { + if call > SYS_NET_LAST { + return "", fmt.Errorf("not a valid socketcall call value: %d", call) } - return 0, fmt.Errorf("not a valid socketcall call value: %d", rawValue) + idx := int(call - SYS_NET_FIRST) + return sysNetValues[idx].String(), nil } -type SocketDomainArgument uint64 - -const ( - AF_UNSPEC SocketDomainArgument = iota - AF_UNIX - AF_INET - AF_AX25 - AF_IPX - AF_APPLETALK - AF_NETROM - AF_BRIDGE - AF_ATMPVC - AF_X25 - AF_INET6 - AF_ROSE - AF_DECnet - AF_NETBEUI - AF_SECURITY - AF_KEY - AF_NETLINK - AF_PACKET - AF_ASH - AF_ECONET - AF_ATMSVC - AF_RDS - AF_SNA - AF_IRDA - AF_PPPOX - AF_WANPIPE - AF_LLC - AF_IB - AF_MPLS - AF_CAN - AF_TIPC - AF_BLUETOOTH - AF_IUCV - AF_RXRPC - AF_ISDN - AF_PHONET - AF_IEEE802154 - AF_CAIF - AF_ALG - AF_NFC - AF_VSOCK - AF_KCM - AF_QIPCRTR - AF_SMC - AF_XDP +var ( + // from bits/socket.h via sys/socket.h + // sequential values starting from 0 + AF_UNSPEC = SystemFunctionArgument{rawValue: 0, stringValue: "AF_UNSPEC"} + AF_UNIX = SystemFunctionArgument{rawValue: 1, stringValue: "AF_UNIX"} + AF_INET = SystemFunctionArgument{rawValue: 2, stringValue: "AF_INET"} + AF_AX25 = SystemFunctionArgument{rawValue: 3, stringValue: "AF_AX25"} + AF_IPX = SystemFunctionArgument{rawValue: 4, stringValue: "AF_IPX"} + AF_APPLETALK = SystemFunctionArgument{rawValue: 5, stringValue: "AF_APPLETALK"} + AF_NETROM = SystemFunctionArgument{rawValue: 6, stringValue: "AF_NETROM"} + AF_BRIDGE = SystemFunctionArgument{rawValue: 7, stringValue: "AF_BRIDGE"} + AF_ATMPVC = SystemFunctionArgument{rawValue: 8, stringValue: "AF_ATMPVC"} + AF_X25 = SystemFunctionArgument{rawValue: 9, stringValue: "AF_X25"} + AF_INET6 = SystemFunctionArgument{rawValue: 10, stringValue: "AF_INET6"} + AF_ROSE = SystemFunctionArgument{rawValue: 11, stringValue: "AF_ROSE"} + AF_DECnet = SystemFunctionArgument{rawValue: 12, stringValue: "AF_DECnet"} + AF_NETBEUI = SystemFunctionArgument{rawValue: 13, stringValue: "AF_NETBEUI"} + AF_SECURITY = SystemFunctionArgument{rawValue: 14, stringValue: "AF_SECURITY"} + AF_KEY = SystemFunctionArgument{rawValue: 15, stringValue: "AF_KEY"} + AF_NETLINK = SystemFunctionArgument{rawValue: 16, stringValue: "AF_NETLINK"} + AF_PACKET = SystemFunctionArgument{rawValue: 17, stringValue: "AF_PACKET"} + AF_ASH = SystemFunctionArgument{rawValue: 18, stringValue: "AF_ASH"} + AF_ECONET = SystemFunctionArgument{rawValue: 19, stringValue: "AF_ECONET"} + AF_ATMSVC = SystemFunctionArgument{rawValue: 20, stringValue: "AF_ATMSVC"} + AF_RDS = SystemFunctionArgument{rawValue: 21, stringValue: "AF_RDS"} + AF_SNA = SystemFunctionArgument{rawValue: 22, stringValue: "AF_SNA"} + AF_IRDA = SystemFunctionArgument{rawValue: 23, stringValue: "AF_IRDA"} + AF_PPPOX = SystemFunctionArgument{rawValue: 24, stringValue: "AF_PPPOX"} + AF_WANPIPE = SystemFunctionArgument{rawValue: 25, stringValue: "AF_WANPIPE"} + AF_LLC = SystemFunctionArgument{rawValue: 26, stringValue: "AF_LLC"} + AF_IB = SystemFunctionArgument{rawValue: 27, stringValue: "AF_IB"} + AF_MPLS = SystemFunctionArgument{rawValue: 28, stringValue: "AF_MPLS"} + AF_CAN = SystemFunctionArgument{rawValue: 29, stringValue: "AF_CAN"} + AF_TIPC = SystemFunctionArgument{rawValue: 30, stringValue: "AF_TIPC"} + AF_BLUETOOTH = SystemFunctionArgument{rawValue: 31, stringValue: "AF_BLUETOOTH"} + AF_IUCV = SystemFunctionArgument{rawValue: 32, stringValue: "AF_IUCV"} + AF_RXRPC = SystemFunctionArgument{rawValue: 33, stringValue: "AF_RXRPC"} + AF_ISDN = SystemFunctionArgument{rawValue: 34, stringValue: "AF_ISDN"} + AF_PHONET = SystemFunctionArgument{rawValue: 35, stringValue: "AF_PHONET"} + AF_IEEE802154 = SystemFunctionArgument{rawValue: 36, stringValue: "AF_IEEE802154"} + AF_CAIF = SystemFunctionArgument{rawValue: 37, stringValue: "AF_CAIF"} + AF_ALG = SystemFunctionArgument{rawValue: 38, stringValue: "AF_ALG"} + AF_NFC = SystemFunctionArgument{rawValue: 39, stringValue: "AF_NFC"} + AF_VSOCK = SystemFunctionArgument{rawValue: 40, stringValue: "AF_VSOCK"} + AF_KCM = SystemFunctionArgument{rawValue: 41, stringValue: "AF_KCM"} + AF_QIPCRTR = SystemFunctionArgument{rawValue: 42, stringValue: "AF_QIPCRTR"} + AF_SMC = SystemFunctionArgument{rawValue: 43, stringValue: "AF_SMC"} + AF_XDP = SystemFunctionArgument{rawValue: 44, stringValue: "AF_XDP"} + AF_MCTP = SystemFunctionArgument{rawValue: 45, stringValue: "AF_MCTP"} ) -func (s SocketDomainArgument) Value() uint64 { return uint64(s) } - -var socketDomainStringMap = map[SocketDomainArgument]string{ - AF_UNSPEC: "AF_UNSPEC", - AF_UNIX: "AF_UNIX", - AF_INET: "AF_INET", - AF_AX25: "AF_AX25", - AF_IPX: "AF_IPX", - AF_APPLETALK: "AF_APPLETALK", - AF_NETROM: "AF_NETROM", - AF_BRIDGE: "AF_BRIDGE", - AF_ATMPVC: "AF_ATMPVC", - AF_X25: "AF_X25", - AF_INET6: "AF_INET6", - AF_ROSE: "AF_ROSE", - AF_DECnet: "AF_DECnet", - AF_NETBEUI: "AF_NETBEUI", - AF_SECURITY: "AF_SECURITY", - AF_KEY: "AF_KEY", - AF_NETLINK: "AF_NETLINK", - AF_PACKET: "AF_PACKET", - AF_ASH: "AF_ASH", - AF_ECONET: "AF_ECONET", - AF_ATMSVC: "AF_ATMSVC", - AF_RDS: "AF_RDS", - AF_SNA: "AF_SNA", - AF_IRDA: "AF_IRDA", - AF_PPPOX: "AF_PPPOX", - AF_WANPIPE: "AF_WANPIPE", - AF_LLC: "AF_LLC", - AF_IB: "AF_IB", - AF_MPLS: "AF_MPLS", - AF_CAN: "AF_CAN", - AF_TIPC: "AF_TIPC", - AF_BLUETOOTH: "AF_BLUETOOTH", - AF_IUCV: "AF_IUCV", - AF_RXRPC: "AF_RXRPC", - AF_ISDN: "AF_ISDN", - AF_PHONET: "AF_PHONET", - AF_IEEE802154: "AF_IEEE802154", - AF_CAIF: "AF_CAIF", - AF_ALG: "AF_ALG", - AF_NFC: "AF_NFC", - AF_VSOCK: "AF_VSOCK", - AF_KCM: "AF_KCM", - AF_QIPCRTR: "AF_QIPCRTR", - AF_SMC: "AF_SMC", - AF_XDP: "AF_XDP", -} - -func (s SocketDomainArgument) String() string { - var res string - - if sdName, ok := socketDomainStringMap[s]; ok { - res = sdName - } else { - res = strconv.Itoa(int(s)) - } - - return res +var socketDomainValues = []SystemFunctionArgument{ + AF_UNSPEC, + AF_UNIX, + AF_INET, + AF_AX25, + AF_IPX, + AF_APPLETALK, + AF_NETROM, + AF_BRIDGE, + AF_ATMPVC, + AF_X25, + AF_INET6, + AF_ROSE, + AF_DECnet, + AF_NETBEUI, + AF_SECURITY, + AF_KEY, + AF_NETLINK, + AF_PACKET, + AF_ASH, + AF_ECONET, + AF_ATMSVC, + AF_RDS, + AF_SNA, + AF_IRDA, + AF_PPPOX, + AF_WANPIPE, + AF_LLC, + AF_IB, + AF_MPLS, + AF_CAN, + AF_TIPC, + AF_BLUETOOTH, + AF_IUCV, + AF_RXRPC, + AF_ISDN, + AF_PHONET, + AF_IEEE802154, + AF_CAIF, + AF_ALG, + AF_NFC, + AF_VSOCK, + AF_KCM, + AF_QIPCRTR, + AF_SMC, + AF_XDP, + AF_MCTP, } -var socketDomainMap = map[uint64]SocketDomainArgument{ - AF_UNSPEC.Value(): AF_UNSPEC, - AF_UNIX.Value(): AF_UNIX, - AF_INET.Value(): AF_INET, - AF_AX25.Value(): AF_AX25, - AF_IPX.Value(): AF_IPX, - AF_APPLETALK.Value(): AF_APPLETALK, - AF_NETROM.Value(): AF_NETROM, - AF_BRIDGE.Value(): AF_BRIDGE, - AF_ATMPVC.Value(): AF_ATMPVC, - AF_X25.Value(): AF_X25, - AF_INET6.Value(): AF_INET6, - AF_ROSE.Value(): AF_ROSE, - AF_DECnet.Value(): AF_DECnet, - AF_NETBEUI.Value(): AF_NETBEUI, - AF_SECURITY.Value(): AF_SECURITY, - AF_KEY.Value(): AF_KEY, - AF_NETLINK.Value(): AF_NETLINK, - AF_PACKET.Value(): AF_PACKET, - AF_ASH.Value(): AF_ASH, - AF_ECONET.Value(): AF_ECONET, - AF_ATMSVC.Value(): AF_ATMSVC, - AF_RDS.Value(): AF_RDS, - AF_SNA.Value(): AF_SNA, - AF_IRDA.Value(): AF_IRDA, - AF_PPPOX.Value(): AF_PPPOX, - AF_WANPIPE.Value(): AF_WANPIPE, - AF_LLC.Value(): AF_LLC, - AF_IB.Value(): AF_IB, - AF_MPLS.Value(): AF_MPLS, - AF_CAN.Value(): AF_CAN, - AF_TIPC.Value(): AF_TIPC, - AF_BLUETOOTH.Value(): AF_BLUETOOTH, - AF_IUCV.Value(): AF_IUCV, - AF_RXRPC.Value(): AF_RXRPC, - AF_ISDN.Value(): AF_ISDN, - AF_PHONET.Value(): AF_PHONET, - AF_IEEE802154.Value(): AF_IEEE802154, - AF_CAIF.Value(): AF_CAIF, - AF_ALG.Value(): AF_ALG, - AF_NFC.Value(): AF_NFC, - AF_VSOCK.Value(): AF_VSOCK, - AF_KCM.Value(): AF_KCM, - AF_QIPCRTR.Value(): AF_QIPCRTR, - AF_SMC.Value(): AF_SMC, - AF_XDP.Value(): AF_XDP, -} +var ( + AF_SOCKET_FIRST = AF_UNSPEC.Value() + AF_SOCKET_LAST = AF_MCTP.Value() +) // ParseSocketDomainArgument parses the `domain` bitmask argument of the `socket` syscall // http://man7.org/linux/man-pages/man2/socket.2.html -func ParseSocketDomainArgument(rawValue uint64) (SocketDomainArgument, error) { - v, ok := socketDomainMap[rawValue] - if !ok { - return 0, fmt.Errorf("not a valid argument: %d", rawValue) +func ParseSocketDomainArgument(domain uint64) (string, error) { + if domain > AF_SOCKET_LAST { + return "", fmt.Errorf("not a valid socket domain value: %d", domain) } - return v, nil + + idx := int(domain - AF_SOCKET_FIRST) + return socketDomainValues[idx].String(), nil } type SocketTypeArgument struct { @@ -3274,7 +2916,7 @@ const gupFlagsChangeVersion = "6.3.0" // ParseGUPFlagsCurrentOS parse the GUP flags received according to current machine OS version. // It uses optimizations to perform better than ParseGUPFlagsForOS -func ParseGUPFlagsCurrentOS(rawValue uint64) (SystemFunctionArgument, error) { +func ParseGUPFlagsCurrentOS(rawValue uint64) (systemFunctionArgument, error) { const ( newVersionsParsing = iota legacyParsing @@ -3313,7 +2955,7 @@ func ParseGUPFlagsCurrentOS(rawValue uint64) (SystemFunctionArgument, error) { } // ParseGUPFlagsForOS parse the GUP flags received according to given OS version. -func ParseGUPFlagsForOS(osInfo *environment.OSInfo, rawValue uint64) (SystemFunctionArgument, error) { +func ParseGUPFlagsForOS(osInfo *environment.OSInfo, rawValue uint64) (systemFunctionArgument, error) { compare, err := osInfo.CompareOSBaseKernelRelease(gupFlagsChangeVersion) if err != nil { return nil, fmt.Errorf( diff --git a/pkg/events/parsers/data_parsers_amd64.go b/pkg/events/parsers/data_parsers_amd64.go index 4372fba616c3..6a5fb1f7ef45 100644 --- a/pkg/events/parsers/data_parsers_amd64.go +++ b/pkg/events/parsers/data_parsers_amd64.go @@ -1,9 +1,116 @@ +//go:build amd64 +// +build amd64 + package parsers import ( "golang.org/x/sys/unix" ) +var ( + // from asm-generic/fcntl.h + // NOT sequential values + // gap + O_LARGEFILE = SystemFunctionArgument{rawValue: 00100000, stringValue: "O_LARGEFILE"} +) + +var openFlagsValues = []SystemFunctionArgument{ + // O_ACCMODE, // macro for access mode, so not included + + // special cases checked before the loop in ParseOpenFlagArgument + // O_RDONLY, + // O_WRONLY, + // O_RDWR, + O_CREAT, + O_EXCL, + O_NOCTTY, + O_TRUNC, + O_APPEND, + O_NONBLOCK, + O_DSYNC, + O_SYNC, + FASYNC, + O_DIRECT, + O_LARGEFILE, + O_DIRECTORY, + O_NOFOLLOW, + O_NOATIME, + O_CLOEXEC, + O_PATH, + O_TMPFILE, +} + +var ( + // from linux/ptrace.h and sys/ptrace.h + // NOT sequential values + // gap + PTRACE_GETREGS = SystemFunctionArgument{rawValue: 12, stringValue: "PTRACE_GETREGS"} + PTRACE_SETREGS = SystemFunctionArgument{rawValue: 13, stringValue: "PTRACE_SETREGS"} + PTRACE_GETFPREGS = SystemFunctionArgument{rawValue: 14, stringValue: "PTRACE_GETFPREGS"} + PTRACE_SETFPREGS = SystemFunctionArgument{rawValue: 15, stringValue: "PTRACE_SETFPREGS"} + // gap + PTRACE_GETFPXREGS = SystemFunctionArgument{rawValue: 18, stringValue: "PTRACE_GETFPXREGS"} + PTRACE_SETFPXREGS = SystemFunctionArgument{rawValue: 19, stringValue: "PTRACE_SETFPXREGS"} + // gap + PTRACE_GET_THREAD_AREA = SystemFunctionArgument{rawValue: 25, stringValue: "PTRACE_GET_THREAD_AREA"} + PTRACE_SET_THREAD_AREA = SystemFunctionArgument{rawValue: 26, stringValue: "PTRACE_SET_THREAD_AREA"} + // gap + PTRACE_ARCH_PRCTL = SystemFunctionArgument{rawValue: 30, stringValue: "PTRACE_ARCH_PRCTL"} + // gap + PTRACE_SINGLEBLOCK = SystemFunctionArgument{rawValue: 33, stringValue: "PTRACE_SINGLEBLOCK"} + // gap + PTRACE_SET_SYSCALL_USER_DISPATCH_CONFIG = SystemFunctionArgument{rawValue: 0x4210, stringValue: "PTRACE_SET_SYSCALL_USER_DISPATCH_CONFIG"} + PTRACE_GET_SYSCALL_USER_DISPATCH_CONFIG = SystemFunctionArgument{rawValue: 0x4211, stringValue: "PTRACE_GET_SYSCALL_USER_DISPATCH_CONFIG"} +) + +var ptraceRequestValues = []SystemFunctionArgument{ + PTRACE_TRACEME, + PTRACE_PEEKTEXT, + PTRACE_PEEKDATA, + PTRACE_PEEKUSR, + PTRACE_POKETEXT, + PTRACE_POKEDATA, + PTRACE_POKEUSR, + PTRACE_CONT, + PTRACE_KILL, + PTRACE_SINGLESTEP, + PTRACE_GETREGS, + PTRACE_SETREGS, + PTRACE_GETFPREGS, + PTRACE_SETFPREGS, + PTRACE_ATTACH, + PTRACE_DETACH, + PTRACE_GETFPXREGS, + PTRACE_SETFPXREGS, + PTRACE_SYSCALL, + PTRACE_GET_THREAD_AREA, + PTRACE_SET_THREAD_AREA, + PTRACE_ARCH_PRCTL, + PTRACE_SYSEMU, + PTRACE_SYSEMU_SINGLESTEP, + PTRACE_SINGLEBLOCK, + // PTRACE_PEEKMTETAGS, + // PTRACE_POKEMTETAGS, + PTRACE_SETOPTIONS, + PTRACE_GETEVENTMSG, + PTRACE_GETSIGINFO, + PTRACE_SETSIGINFO, + PTRACE_GETREGSET, + PTRACE_SETREGSET, + PTRACE_SEIZE, + PTRACE_INTERRUPT, + PTRACE_LISTEN, + PTRACE_PEEKSIGINFO, + PTRACE_GETSIGMASK, + PTRACE_SETSIGMASK, + PTRACE_SECCOMP_GET_FILTER, + PTRACE_SECCOMP_GET_METADATA, + PTRACE_GET_SYSCALL_INFO, + PTRACE_GET_RSEQ_CONFIGURATION, + PTRACE_SET_SYSCALL_USER_DISPATCH_CONFIG, + PTRACE_GET_SYSCALL_USER_DISPATCH_CONFIG, +} + var ( Map32bit = MmapFlagArgument{rawValue: unix.MAP_32BIT, stringValue: "MAP_32BIT"} ) diff --git a/pkg/events/parsers/data_parsers_arm64.go b/pkg/events/parsers/data_parsers_arm64.go new file mode 100644 index 000000000000..8d9d8c17a7ba --- /dev/null +++ b/pkg/events/parsers/data_parsers_arm64.go @@ -0,0 +1,93 @@ +//go:build arm64 +// +build arm64 + +package parsers + +var ( + // from asm/fcntl.h + // NOT sequential values + // gap + O_LARGEFILE = SystemFunctionArgument{rawValue: 0400000, stringValue: "O_LARGEFILE"} +) + +var openFlagsValues = []SystemFunctionArgument{ + // O_ACCMODE, // macro for access mode, so not included + + // special cases checked before the loop in ParseOpenFlagArgument + // O_RDONLY, + // O_WRONLY, + // O_RDWR, + O_CREAT, + O_EXCL, + O_NOCTTY, + O_TRUNC, + O_APPEND, + O_NONBLOCK, + O_DSYNC, + O_SYNC, + FASYNC, + O_DIRECT, + O_LARGEFILE, + O_DIRECTORY, + O_NOFOLLOW, + O_NOATIME, + O_CLOEXEC, + O_PATH, + O_TMPFILE, +} + +var ( + // from linux/ptrace.h and sys/ptrace.h + // NOT sequential values + // gap + PTRACE_PEEKMTETAGS = SystemFunctionArgument{rawValue: 33, stringValue: "PTRACE_PEEKMTETAGS"} + PTRACE_POKEMTETAGS = SystemFunctionArgument{rawValue: 34, stringValue: "PTRACE_POKEMTETAGS"} +) + +var ptraceRequestValues = []SystemFunctionArgument{ + PTRACE_TRACEME, + PTRACE_PEEKTEXT, + PTRACE_PEEKDATA, + PTRACE_PEEKUSR, + PTRACE_POKETEXT, + PTRACE_POKEDATA, + PTRACE_POKEUSR, + PTRACE_CONT, + PTRACE_KILL, + PTRACE_SINGLESTEP, + // PTRACE_GETREGS, + // PTRACE_SETREGS, + // PTRACE_GETFPREGS, + // PTRACE_SETFPREGS, + PTRACE_ATTACH, + PTRACE_DETACH, + // PTRACE_GETFPXREGS, + // PTRACE_SETFPXREGS, + PTRACE_SYSCALL, + // PTRACE_GET_THREAD_AREA, + // PTRACE_SET_THREAD_AREA, + // PTRACE_ARCH_PRCTL, + PTRACE_SYSEMU, + PTRACE_SYSEMU_SINGLESTEP, + // PTRACE_SINGLEBLOCK, + PTRACE_PEEKMTETAGS, + PTRACE_POKEMTETAGS, + PTRACE_SETOPTIONS, + PTRACE_GETEVENTMSG, + PTRACE_GETSIGINFO, + PTRACE_SETSIGINFO, + PTRACE_GETREGSET, + PTRACE_SETREGSET, + PTRACE_SEIZE, + PTRACE_INTERRUPT, + PTRACE_LISTEN, + PTRACE_PEEKSIGINFO, + PTRACE_GETSIGMASK, + PTRACE_SETSIGMASK, + PTRACE_SECCOMP_GET_FILTER, + PTRACE_SECCOMP_GET_METADATA, + PTRACE_GET_SYSCALL_INFO, + PTRACE_GET_RSEQ_CONFIGURATION, + // PTRACE_SET_SYSCALL_USER_DISPATCH_CONFIG, + // PTRACE_GET_SYSCALL_USER_DISPATCH_CONFIG, +} diff --git a/pkg/events/parsers/data_parsers_bench_test.go b/pkg/events/parsers/data_parsers_bench_test.go index c3bbe09db8c8..5df807b6f8b5 100644 --- a/pkg/events/parsers/data_parsers_bench_test.go +++ b/pkg/events/parsers/data_parsers_bench_test.go @@ -24,6 +24,43 @@ func BenchmarkParseMmapProt(b *testing.B) { } } +var parseCloneFlagsBenchTestArgs = []struct { + rawArgument uint64 +}{ + { + rawArgument: CLONE_VM.Value(), + }, + { + rawArgument: CLONE_FS.Value(), + }, + { + rawArgument: CLONE_VM.Value() | CLONE_FS.Value(), + }, + { + rawArgument: CLONE_VM.Value() | CLONE_FS.Value() | CLONE_FILES.Value(), + }, + { + rawArgument: CLONE_VM.Value() | CLONE_FILES.Value() | CLONE_THREAD.Value(), + }, + { + rawArgument: CLONE_VM.Value() | CLONE_FS.Value() | CLONE_FILES.Value() | CLONE_SIGHAND.Value(), + }, + { + rawArgument: CLONE_VM.Value() | CLONE_FS.Value() | CLONE_FILES.Value() | CLONE_SIGHAND.Value() | CLONE_PTRACE.Value(), + }, + { + rawArgument: CLONE_VM.Value() | CLONE_FS.Value() | CLONE_FILES.Value() | CLONE_SIGHAND.Value() | CLONE_PTRACE.Value() | CLONE_VFORK.Value(), + }, +} + +func BenchmarkParseCloneFlags(b *testing.B) { + for i := 0; i < b.N; i++ { + for _, tt := range parseCloneFlagsBenchTestArgs { + _, _ = ParseCloneFlags(tt.rawArgument) + } + } +} + var optionsAreContainedInArgumentTestTable = []struct { rawArgument uint64 options []uint64 diff --git a/pkg/events/parsers/data_parsers_test.go b/pkg/events/parsers/data_parsers_test.go index 70edb89f70fe..f0387fac6429 100644 --- a/pkg/events/parsers/data_parsers_test.go +++ b/pkg/events/parsers/data_parsers_test.go @@ -132,6 +132,210 @@ func Test_optionIsContainedInArgument(t *testing.T) { } } +func TestParseCloneFlags(t *testing.T) { + testCases := []struct { + name string + rawArgument uint64 + expectedSting string + expectedError bool + }{ + { + name: "No value", + rawArgument: 0, + expectedSting: "", + expectedError: false, + }, + { + name: "Single value", + rawArgument: CLONE_CHILD_CLEARTID.Value(), + expectedSting: "CLONE_CHILD_CLEARTID", + expectedError: false, + }, + { + name: "Multiple values", + rawArgument: CLONE_VM.Value() | CLONE_FS.Value(), + expectedSting: "CLONE_VM|CLONE_FS", + expectedError: false, + }, + { + name: "Non existing value", + rawArgument: 1, + expectedSting: "", + expectedError: true, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + opt, err := ParseCloneFlags(testCase.rawArgument) + if testCase.expectedError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + assert.Equal(t, testCase.expectedSting, opt) + }) + } +} + +func TestParseOpenFlagArgument(t *testing.T) { + tests := []struct { + name string + rawArgument uint64 + expectedString string + expectedErr bool + }{ + { + name: "Test O_RDONLY", + rawArgument: 0, + expectedString: O_RDONLY.String(), + }, + { + name: "Test O_WRONLY", + rawArgument: O_WRONLY.Value(), + expectedString: O_WRONLY.String(), + }, + { + name: "Test O_RDWR", + rawArgument: O_RDWR.Value(), + expectedString: O_RDWR.String(), + }, + { + name: "Test O_CREAT", + rawArgument: O_CREAT.Value(), + expectedString: O_RDONLY.String() + "|" + O_CREAT.String(), + }, + { + name: "Test O_RDWR | O_CREAT", + rawArgument: O_RDWR.Value() | O_CREAT.Value(), + expectedString: O_RDWR.String() + "|" + O_CREAT.String(), + }, + { + name: "Test O_WRONLY | O_CREAT | O_EXCL", + rawArgument: O_WRONLY.Value() | O_CREAT.Value() | O_EXCL.Value(), + expectedString: O_WRONLY.String() + "|" + O_CREAT.String() + "|" + O_EXCL.String(), + }, + { + name: "Test O_RDWR | O_CREAT | O_DSYNC | O_LARGEFILE", + rawArgument: O_RDWR.Value() | O_CREAT.Value() | O_DSYNC.Value() | O_LARGEFILE.Value(), + expectedString: O_RDWR.String() + "|" + O_CREAT.String() + "|" + O_DSYNC.String() + "|" + O_LARGEFILE.String(), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + opt, err := ParseOpenFlagArgument(tt.rawArgument) + if tt.expectedErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + assert.Equal(t, tt.expectedString, opt) + }) + } +} + +func TestParseAccessMode(t *testing.T) { + tests := []struct { + name string + rawArgument uint64 + expectedString string + expectedErr bool + }{ + { + name: "Test F_OK", + rawArgument: F_OK.Value(), + expectedString: F_OK.String(), + }, + { + name: "Test X_OK", + rawArgument: X_OK.Value(), + expectedString: X_OK.String(), + }, + { + name: "Test W_OK", + rawArgument: W_OK.Value(), + expectedString: W_OK.String(), + }, + { + name: "Test R_OK", + rawArgument: R_OK.Value(), + expectedString: R_OK.String(), + }, + { + name: "Test W_OK | R_OK", + rawArgument: W_OK.Value() | R_OK.Value(), + expectedString: W_OK.String() + "|" + R_OK.String(), + }, + { + name: "Test X_OK | W_OK | R_OK", + rawArgument: X_OK.Value() | W_OK.Value() | R_OK.Value(), + expectedString: X_OK.String() + "|" + W_OK.String() + "|" + R_OK.String(), + }, + { + name: "Test Invalid", + rawArgument: 0xff000000, + expectedString: "", + expectedErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + opt, err := ParseAccessMode(tt.rawArgument) + if tt.expectedErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + assert.Equal(t, tt.expectedString, opt) + }) + } +} + +func TestParseExecveatFlag(t *testing.T) { + tests := []struct { + name string + rawArgument uint64 + expectedString string + expectedErr bool + }{ + { + name: "Test AT_SYMLINK_NOFOLLOW", + rawArgument: AT_SYMLINK_NOFOLLOW.Value(), + expectedString: AT_SYMLINK_NOFOLLOW.String(), + }, + { + name: "Test AT_EMPTY_PATH", + rawArgument: AT_EMPTY_PATH.Value(), + expectedString: AT_EMPTY_PATH.String(), + }, + { + name: "Test AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH", + rawArgument: AT_SYMLINK_NOFOLLOW.Value() | AT_EMPTY_PATH.Value(), + expectedString: AT_SYMLINK_NOFOLLOW.String() + "|" + AT_EMPTY_PATH.String(), + }, + { + name: "Test Invalid", + rawArgument: 0xff000000, + expectedString: "", + expectedErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + opt, err := ParseExecveatFlag(tt.rawArgument) + if tt.expectedErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + assert.Equal(t, tt.expectedString, opt) + }) + } +} + func TestParseSetSocketOption(t *testing.T) { testCases := []struct { name string diff --git a/pkg/filters/binary.go b/pkg/filters/binary.go index 79ac768052f5..e048bf54d4e5 100644 --- a/pkg/filters/binary.go +++ b/pkg/filters/binary.go @@ -1,6 +1,7 @@ package filters import ( + "math" "strconv" "strings" @@ -33,6 +34,9 @@ func getHostMntNS() (uint32, error) { if err != nil { return 0, errfmt.WrapError(err) } + if ns < 0 || ns > math.MaxUint32 { + return 0, errfmt.Errorf("invalid mnt namespace %d", ns) + } return uint32(ns), nil } @@ -84,6 +88,9 @@ func (f *BinaryFilter) Parse(operatorAndValues string) error { if err != nil { return InvalidValue(val) } + if mntNS < 0 || mntNS > math.MaxUint32 { + return InvalidValue(val) + } bin.MntNS = uint32(mntNS) } } else { diff --git a/pkg/filters/data.go b/pkg/filters/data.go index 48cc2f315fc0..a347d33a8939 100644 --- a/pkg/filters/data.go +++ b/pkg/filters/data.go @@ -2,6 +2,7 @@ package filters import ( "fmt" + "math" "strconv" "strings" @@ -173,25 +174,35 @@ func (f *DataFilter) Parse(id events.ID, fieldName string, operatorAndValues str case events.SysEnter, events.SysExit, - events.SuspiciousSyscallSource: + events.SuspiciousSyscallSource, + events.StackPivot: if fieldName == "syscall" { // handle either syscall name or syscall id _, err := strconv.Atoi(val) - if err != nil { - // if val is a syscall name, then we need to convert it to a syscall id - syscallID, ok := events.Core.GetDefinitionIDByName(val) - if !ok { - return val, errfmt.Errorf("invalid syscall name: %s", val) - } - val = strconv.Itoa(int(syscallID)) + if err == nil { + return val, nil // val might already be a syscall id } + + // val might be a syscall name, then we need to convert it to a syscall id + syscallID, ok := events.Core.GetDefinitionIDByName(val) + if !ok { + return val, errfmt.Errorf("invalid syscall name: %s", val) + } + val = strconv.Itoa(int(syscallID)) } + case events.HookedSyscall: if fieldName == "syscall" { // handle either syscall name or syscall id dataEventID, err := strconv.Atoi(val) - if err == nil { - // if val is a syscall id, then we need to convert it to a syscall name - val = events.Core.GetDefinitionByID(events.ID(dataEventID)).GetName() + // check if dataEventID is a syscall id + if err != nil { + return val, nil // val might already be a syscall name } + if dataEventID < 0 || dataEventID > math.MaxInt32 { + return val, errfmt.Errorf("invalid syscall id: %s", val) + } + + // val might be a syscall id, then we need to convert it to a syscall name + val = events.Core.GetDefinitionByID(events.ID(dataEventID)).GetName() } } diff --git a/pkg/k8s/apis/tracee.aquasec.com/v1beta1/tracee_types.go b/pkg/k8s/apis/tracee.aquasec.com/v1beta1/tracee_types.go index 6da612ab5aa5..5040001d50c5 100644 --- a/pkg/k8s/apis/tracee.aquasec.com/v1beta1/tracee_types.go +++ b/pkg/k8s/apis/tracee.aquasec.com/v1beta1/tracee_types.go @@ -4,6 +4,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// +kubebuilder:object:generate=false + // PolicyInterface is the interface of the policy object, // it is used to allow tracee to support policies coming from kubernetes, // or directly from the filesystem. diff --git a/pkg/policy/v1beta1/policy_file.go b/pkg/policy/v1beta1/policy_file.go index 8c5103493627..05f2e067d45c 100644 --- a/pkg/policy/v1beta1/policy_file.go +++ b/pkg/policy/v1beta1/policy_file.go @@ -269,13 +269,11 @@ func validateEventData(policyName, eventName, dataName string) error { dataName = s[0] - eventDefID, ok := events.Core.GetDefinitionIDByName(eventName) - if !ok { + eventDefinition := events.Core.GetDefinitionByName(eventName) + if eventDefinition.NotValid() { return errfmt.Errorf("policy %s, event %s is not valid", policyName, eventName) } - eventDefinition := events.Core.GetDefinitionByID(eventDefID) - for _, set := range eventDefinition.GetSets() { if set == "signatures" { // no sig event validation (arguments are dynamic) return nil diff --git a/pkg/proctree/fileinfo.go b/pkg/proctree/fileinfo.go index a41defbedf9a..5a2c6a0bd6bd 100644 --- a/pkg/proctree/fileinfo.go +++ b/pkg/proctree/fileinfo.go @@ -24,20 +24,20 @@ type FileInfoFeed struct { // FileInfo represents a file. type FileInfo struct { - feed *changelog.Changelog[FileInfoFeed] + feed *changelog.Changelog[*FileInfoFeed] mutex *sync.RWMutex } // NewFileInfo creates a new file. func NewFileInfo() *FileInfo { return &FileInfo{ - feed: changelog.NewChangelog[FileInfoFeed](3), + feed: changelog.NewChangelog[*FileInfoFeed](3), mutex: &sync.RWMutex{}, } } // NewFileInfoFeed creates a new file with values from the given feed. -func NewFileInfoFeed(feed FileInfoFeed) *FileInfo { +func NewFileInfoFeed(feed *FileInfoFeed) *FileInfo { new := NewFileInfo() new.setFeed(feed) @@ -51,7 +51,7 @@ func NewFileInfoFeed(feed FileInfoFeed) *FileInfo { // Multiple values at once (using a feed structure) // SetFeed sets the values of the file from a feed at the current time. -func (fi *FileInfo) SetFeed(feed FileInfoFeed) { +func (fi *FileInfo) SetFeed(feed *FileInfoFeed) { fi.mutex.Lock() defer fi.mutex.Unlock() @@ -59,7 +59,7 @@ func (fi *FileInfo) SetFeed(feed FileInfoFeed) { } // SetFeedAt sets the values of the file from a feed at the given time. -func (fi *FileInfo) SetFeedAt(feed FileInfoFeed, targetTime time.Time) { +func (fi *FileInfo) SetFeedAt(feed *FileInfoFeed, targetTime time.Time) { fi.mutex.Lock() defer fi.mutex.Unlock() @@ -68,7 +68,7 @@ func (fi *FileInfo) SetFeedAt(feed FileInfoFeed, targetTime time.Time) { // private setters -func (fi *FileInfo) setFeed(feed FileInfoFeed) { +func (fi *FileInfo) setFeed(feed *FileInfoFeed) { fi.setFeedAt(feed, time.Now()) } @@ -76,7 +76,7 @@ func (fi *FileInfo) setFeed(feed FileInfoFeed) { // managing memory more responsibly. const MaxPathLen = 1024 -func (fi *FileInfo) setFeedAt(feed FileInfoFeed, targetTime time.Time) { +func (fi *FileInfo) setFeedAt(feed *FileInfoFeed, targetTime time.Time) { atFeed := fi.getFeedAt(targetTime) if feed.Path != "" { @@ -115,7 +115,7 @@ func (fi *FileInfo) GetFeed() FileInfoFeed { fi.mutex.RLock() defer fi.mutex.RUnlock() - return fi.getFeed() + return *fi.getFeed() // return a copy } // GetFeedAt returns the values of the file as a feed at the given time. @@ -123,7 +123,7 @@ func (fi *FileInfo) GetFeedAt(targetTime time.Time) FileInfoFeed { fi.mutex.RLock() defer fi.mutex.RUnlock() - return fi.getFeedAt(targetTime) + return *fi.getFeedAt(targetTime) // return a copy } // Single values @@ -210,10 +210,20 @@ func (fi *FileInfo) GetInodeModeAt(targetTime time.Time) int { // private getters -func (fi *FileInfo) getFeed() FileInfoFeed { - return fi.feed.GetCurrent() +func (fi *FileInfo) getFeed() *FileInfoFeed { + feed := fi.feed.GetCurrent() + if feed == nil { + feed = &FileInfoFeed{} + } + + return feed } -func (fi *FileInfo) getFeedAt(targetTime time.Time) FileInfoFeed { - return fi.feed.Get(targetTime) +func (fi *FileInfo) getFeedAt(targetTime time.Time) *FileInfoFeed { + feed := fi.feed.Get(targetTime) + if feed == nil { + feed = &FileInfoFeed{} + } + + return feed } diff --git a/pkg/proctree/process.go b/pkg/proctree/process.go index 6b8b1a631e44..05e8ffdcc9af 100644 --- a/pkg/proctree/process.go +++ b/pkg/proctree/process.go @@ -2,6 +2,7 @@ package proctree import ( "sync" + "sync/atomic" ) // @@ -10,31 +11,18 @@ import ( // Process represents a process. type Process struct { - processHash uint32 // hash of process + processHash uint32 // hash of process (immutable, so no need of concurrency control) parentHash uint32 // hash of parent - info *TaskInfo // task info - executable *FileInfo // executable info - children map[uint32]struct{} // hash of childrens + info *TaskInfo // task info (immutable pointer) + executable *FileInfo // executable info (immutable pointer) + children map[uint32]struct{} // hash of children threads map[uint32]struct{} // hash of threads // Control fields mutex *sync.RWMutex // mutex to protect the process } -// NewProcess creates a new process. -func NewProcess(hash uint32) *Process { - return &Process{ - processHash: hash, - parentHash: 0, - info: NewTaskInfo(), - executable: NewFileInfo(), - children: make(map[uint32]struct{}), - threads: make(map[uint32]struct{}), - mutex: &sync.RWMutex{}, - } -} - -// NewProcessWithInfo creates a new thread with an initialized task info. -func NewProcessWithInfo(hash uint32, info *TaskInfo) *Process { +// NewProcess creates a new thread with an initialized task info. +func NewProcess(hash uint32, info *TaskInfo) *Process { return &Process{ processHash: hash, parentHash: 0, @@ -50,39 +38,29 @@ func NewProcessWithInfo(hash uint32, info *TaskInfo) *Process { // GetHash returns the hash of the process. func (p *Process) GetHash() uint32 { - p.mutex.RLock() - defer p.mutex.RUnlock() - return p.processHash + return p.processHash // immutable } // GetParentHash returns the hash of the parent. func (p *Process) GetParentHash() uint32 { - p.mutex.RLock() - defer p.mutex.RUnlock() - return p.parentHash + return atomic.LoadUint32(&p.parentHash) } // GetInfo returns a instanced task info. func (p *Process) GetInfo() *TaskInfo { - p.mutex.RLock() - defer p.mutex.RUnlock() - return p.info + return p.info // immutable pointer } // GetExecutable returns a instanced executable info. func (p *Process) GetExecutable() *FileInfo { - p.mutex.RLock() - defer p.mutex.RUnlock() - return p.executable + return p.executable // immutable pointer } // Setters // SetParentHash sets the hash of the parent. func (p *Process) SetParentHash(parentHash uint32) { - p.mutex.Lock() - defer p.mutex.Unlock() - p.parentHash = parentHash + atomic.StoreUint32(&p.parentHash, parentHash) } // @@ -93,26 +71,28 @@ func (p *Process) SetParentHash(parentHash uint32) { func (p *Process) AddChild(childHash uint32) { p.mutex.Lock() defer p.mutex.Unlock() - p.addChild(childHash) + + p.children[childHash] = struct{}{} } // AddThread adds a thread to the process. func (p *Process) AddThread(threadHash uint32) { p.mutex.Lock() defer p.mutex.Unlock() - p.addThread(threadHash) + + p.threads[threadHash] = struct{}{} } // GetChildren returns the children of the process. func (p *Process) GetChildren() []uint32 { p.mutex.RLock() defer p.mutex.RUnlock() - children := make([]uint32, len(p.children)) - i := 0 + + children := make([]uint32, 0, len(p.children)) for child := range p.children { - children[i] = child - i++ + children = append(children, child) } + return children } @@ -120,12 +100,12 @@ func (p *Process) GetChildren() []uint32 { func (p *Process) GetThreads() []uint32 { p.mutex.RLock() defer p.mutex.RUnlock() - threads := make([]uint32, len(p.threads)) - i := 0 + + threads := make([]uint32, 0, len(p.threads)) for thread := range p.threads { - threads[i] = thread - i++ + threads = append(threads, thread) } + return threads } @@ -133,36 +113,14 @@ func (p *Process) GetThreads() []uint32 { func (p *Process) DelChild(childHash uint32) { p.mutex.Lock() defer p.mutex.Unlock() - p.delChild(childHash) + + delete(p.children, childHash) } // DelThread deletes a thread from the process. func (p *Process) DelThread(threadHash uint32) { p.mutex.Lock() defer p.mutex.Unlock() - p.delThread(threadHash) -} - -// addChild adds a child to the process. -func (p *Process) addChild(childHash uint32) { - if _, ok := p.children[childHash]; !ok { - p.children[childHash] = struct{}{} - } -} - -// addThread adds a thread to the process. -func (p *Process) addThread(threadHash uint32) { - if _, ok := p.threads[threadHash]; !ok { - p.threads[threadHash] = struct{}{} - } -} - -// delChild deletes a child from the process. -func (p *Process) delChild(childHash uint32) { - delete(p.children, childHash) -} -// delThread deletes a thread from the process. -func (p *Process) delThread(threadHash uint32) { delete(p.threads, threadHash) } diff --git a/pkg/proctree/proctree.go b/pkg/proctree/proctree.go index 7017ffaf427d..9ed389c10344 100644 --- a/pkg/proctree/proctree.go +++ b/pkg/proctree/proctree.go @@ -35,10 +35,8 @@ import ( // const ( - DefaultProcessCacheSize = 16384 - DefaultThreadCacheSize = 32768 - DefaultProcessCacheTTL = time.Second * 120 - DefaultThreadCacheTTL = time.Second * 120 + DefaultProcessCacheSize = 10928 + DefaultThreadCacheSize = 21856 ) type SourceType int @@ -80,6 +78,13 @@ type ProcessTree struct { procfsOnce *sync.Once // busy loop debug message throttling ctx context.Context // context for the process tree procfsQuery bool + + // pools + forkFeedPool *sync.Pool // pool of ForkFeed instances + execFeedPool *sync.Pool // pool of ExecFeed instances + exitFeedPool *sync.Pool // pool of ExitFeed instances + taskInfoFeedPool *sync.Pool // pool of TaskInfoFeed instances + fileInfoFeedPool *sync.Pool // pool of FileInfoFeed instances } // NewProcessTree creates a new process tree. @@ -146,6 +151,31 @@ func NewProcessTree(ctx context.Context, config ProcTreeConfig) (*ProcessTree, e procfsOnce: new(sync.Once), ctx: ctx, procfsQuery: config.ProcfsQuerying, + forkFeedPool: &sync.Pool{ + New: func() interface{} { + return &ForkFeed{} + }, + }, + execFeedPool: &sync.Pool{ + New: func() interface{} { + return &ExecFeed{} + }, + }, + exitFeedPool: &sync.Pool{ + New: func() interface{} { + return &ExitFeed{} + }, + }, + taskInfoFeedPool: &sync.Pool{ + New: func() interface{} { + return &TaskInfoFeed{} + }, + }, + fileInfoFeedPool: &sync.Pool{ + New: func() interface{} { + return &FileInfoFeed{} + }, + }, } if config.ProcfsInitialization { @@ -163,10 +193,6 @@ func NewProcessTree(ctx context.Context, config ProcTreeConfig) (*ProcessTree, e // GetProcessByHash returns a process by its hash. func (pt *ProcessTree) GetProcessByHash(hash uint32) (*Process, bool) { process, ok := pt.processes.Get(hash) - if !ok { - return nil, false - } - return process, ok } @@ -174,20 +200,25 @@ func (pt *ProcessTree) GetProcessByHash(hash uint32) (*Process, bool) { func (pt *ProcessTree) GetOrCreateProcessByHash(hash uint32) *Process { process, ok := pt.processes.Get(hash) if !ok { + var taskInfo *TaskInfo + // Each process must have a thread with thread ID matching its process ID. // Both share the same info as both represent the same task in the kernel. thread, ok := pt.threads.Get(hash) - if !ok { - process = NewProcess(hash) // create a new process - thread = NewThreadWithInfo(hash, process.GetInfo()) - pt.threads.Add(hash, thread) + if ok { + taskInfo = thread.GetInfo() } else { - process = NewProcessWithInfo(hash, thread.GetInfo()) + taskInfo = NewTaskInfo() + thread = NewThread(hash, taskInfo) // create a new thread + pt.threads.Add(hash, thread) } - pt.processes.Add(hash, process) - process.AddThread(hash) + thread.SetLeaderHash(hash) + process = NewProcess(hash, taskInfo) // create a new process + process.AddThread(hash) + pt.processes.Add(hash, process) + return process } @@ -208,17 +239,89 @@ func (pt *ProcessTree) GetThreadByHash(hash uint32) (*Thread, bool) { func (pt *ProcessTree) GetOrCreateThreadByHash(hash uint32) *Thread { thread, ok := pt.threads.Get(hash) if !ok { + var taskInfo *TaskInfo + // Create a new thread // If the thread is a leader task, sync its info with the process instance info. process, ok := pt.processes.Get(hash) if ok { - thread = NewThreadWithInfo(hash, process.GetInfo()) + taskInfo = process.GetInfo() } else { - thread = NewThread(hash) + taskInfo = NewTaskInfo() } + + thread = NewThread(hash, taskInfo) // create a new thread pt.threads.Add(hash, thread) + return thread } return thread // return an existing thread } + +// Pools + +// GetForkFeedFromPool returns a ForkFeed from the pool, or creates a new one if the pool is empty. +// ForkFeed certainly contains old data, so it must be updated before use. +func (pt *ProcessTree) GetForkFeedFromPool() *ForkFeed { + // revive:disable:unchecked-type-assertion + return pt.forkFeedPool.Get().(*ForkFeed) + // revive:enable:unchecked-type-assertion +} + +// PutForkFeedInPool returns a ForkFeed to the pool. +func (pt *ProcessTree) PutForkFeedInPool(forkFeed *ForkFeed) { + pt.forkFeedPool.Put(forkFeed) +} + +// GetExecFeedFromPool returns a ExecFeed from the pool, or creates a new one if the pool is empty. +// ExecFeed certainly contains old data, so it must be updated before use. +func (pt *ProcessTree) GetExecFeedFromPool() *ExecFeed { + // revive:disable:unchecked-type-assertion + return pt.execFeedPool.Get().(*ExecFeed) + // revive:enable:unchecked-type-assertion +} + +// PutExecFeedInPool returns a ExecFeed to the pool. +func (pt *ProcessTree) PutExecFeedInPool(execFeed *ExecFeed) { + pt.execFeedPool.Put(execFeed) +} + +// GetExitFeedFromPool returns a ExitFeed from the pool, or creates a new one if the pool is empty. +// ExitFeed certainly contains old data, so it must be updated before use. +func (pt *ProcessTree) GetExitFeedFromPool() *ExitFeed { + // revive:disable:unchecked-type-assertion + return pt.exitFeedPool.Get().(*ExitFeed) + // revive:enable:unchecked-type-assertion +} + +// PutExitFeedInPool returns a ExitFeed to the pool. +func (pt *ProcessTree) PutExitFeedInPool(exitFeed *ExitFeed) { + pt.exitFeedPool.Put(exitFeed) +} + +// GetTaskInfoFeedFromPool returns a TaskInfoFeed from the pool, or creates a new one if the pool is empty. +// TaskInfoFeed certainly contains old data, so it must be updated before use. +func (pt *ProcessTree) GetTaskInfoFeedFromPool() *TaskInfoFeed { + // revive:disable:unchecked-type-assertion + return pt.taskInfoFeedPool.Get().(*TaskInfoFeed) + // revive:enable:unchecked-type-assertion +} + +// PutTaskInfoFeedInPool returns a TaskInfoFeed to the pool. +func (pt *ProcessTree) PutTaskInfoFeedInPool(taskInfoFeed *TaskInfoFeed) { + pt.taskInfoFeedPool.Put(taskInfoFeed) +} + +// GetFileInfoFeedFromPool returns a FileInfoFeed from the pool, or creates a new one if the pool is empty. +// FileInfoFeed certainly contains old data, so it must be updated before use. +func (pt *ProcessTree) GetFileInfoFeedFromPool() *FileInfoFeed { + // revive:disable:unchecked-type-assertion + return pt.fileInfoFeedPool.Get().(*FileInfoFeed) + // revive:enable:unchecked-type-assertion +} + +// PutFileInfoFeedInPool returns a FileInfoFeed to the pool. +func (pt *ProcessTree) PutFileInfoFeedInPool(fileInfoFeed *FileInfoFeed) { + pt.fileInfoFeedPool.Put(fileInfoFeed) +} diff --git a/pkg/proctree/proctree_feed.go b/pkg/proctree/proctree_feed.go index 9714abfb4ad0..2f045a04f976 100644 --- a/pkg/proctree/proctree_feed.go +++ b/pkg/proctree/proctree_feed.go @@ -2,6 +2,7 @@ package proctree import ( "path/filepath" + "time" "github.com/aquasecurity/tracee/pkg/errfmt" "github.com/aquasecurity/tracee/pkg/logger" @@ -34,8 +35,95 @@ type ForkFeed struct { ChildStartTime uint64 } +func (pt *ProcessTree) setParentFeed( + parent *Process, + forkFeed *ForkFeed, + feedTimeStamp time.Time, +) { + // NOTE: override all the fields of the taskInfoFeed, to avoid any previous data. + taskInfoFeed := pt.GetTaskInfoFeedFromPool() + + taskInfoFeed.Name = "" // do not change the parent name + taskInfoFeed.Tid = int(forkFeed.ParentTid) + taskInfoFeed.Pid = int(forkFeed.ParentPid) + taskInfoFeed.NsTid = int(forkFeed.ParentNsTid) + taskInfoFeed.NsPid = int(forkFeed.ParentNsPid) + taskInfoFeed.StartTimeNS = forkFeed.ParentStartTime + taskInfoFeed.PPid = -1 // do not change the parent ppid + taskInfoFeed.NsPPid = -1 // do not change the parent nsppid + taskInfoFeed.Uid = -1 // do not change the parent uid + taskInfoFeed.Gid = -1 // do not change the parent gid + taskInfoFeed.ExitTimeNS = 0 + + parent.GetInfo().SetFeedAt(taskInfoFeed, feedTimeStamp) + + // Release the feed back to the pool as soon as it is not needed anymore + pt.PutTaskInfoFeedInPool(taskInfoFeed) + + if pt.procfsQuery { + pt.FeedFromProcFSAsync(int(forkFeed.ParentPid)) // try to enrich ppid and name from procfs + } +} + +func (pt *ProcessTree) setLeaderFeed( + leader, parent *Process, + forkFeed *ForkFeed, + feedTimeStamp time.Time, +) { + // NOTE: override all the fields of the taskInfoFeed, to avoid any previous data. + taskInfoFeed := pt.GetTaskInfoFeedFromPool() + + taskInfoFeed.Name = parent.GetInfo().GetName() + taskInfoFeed.Tid = int(forkFeed.LeaderTid) + taskInfoFeed.Pid = int(forkFeed.LeaderPid) + taskInfoFeed.NsTid = int(forkFeed.LeaderNsTid) + taskInfoFeed.NsPid = int(forkFeed.LeaderNsPid) + taskInfoFeed.StartTimeNS = forkFeed.LeaderStartTime + taskInfoFeed.PPid = int(forkFeed.ParentPid) + taskInfoFeed.NsPPid = int(forkFeed.ParentNsPid) + taskInfoFeed.Uid = -1 // do not change the leader uid + taskInfoFeed.Gid = -1 // do not change the leader gid + taskInfoFeed.ExitTimeNS = 0 + + leader.GetInfo().SetFeedAt(taskInfoFeed, feedTimeStamp) + + // Release the feed back to the pool as soon as it is not needed anymore + pt.PutTaskInfoFeedInPool(taskInfoFeed) + + if pt.procfsQuery { + pt.FeedFromProcFSAsync(int(forkFeed.LeaderPid)) // try to enrich name from procfs if needed + } +} + +func (pt *ProcessTree) setThreadFeed( + thread *Thread, + leader *Process, + forkFeed *ForkFeed, + feedTimeStamp time.Time, +) { + // NOTE: override all the fields of the taskInfoFeed, to avoid any previous data. + taskInfoFeed := pt.GetTaskInfoFeedFromPool() + + taskInfoFeed.Name = leader.GetInfo().GetName() + taskInfoFeed.Tid = int(forkFeed.ChildTid) + taskInfoFeed.Pid = int(forkFeed.ChildPid) + taskInfoFeed.NsTid = int(forkFeed.ChildNsTid) + taskInfoFeed.NsPid = int(forkFeed.ChildNsPid) + taskInfoFeed.StartTimeNS = forkFeed.ChildStartTime + taskInfoFeed.PPid = int(forkFeed.ParentPid) + taskInfoFeed.NsPPid = int(forkFeed.ParentNsPid) + taskInfoFeed.Uid = -1 // do not change the thread uid + taskInfoFeed.Gid = -1 // do not change the thread gid + taskInfoFeed.ExitTimeNS = 0 + + thread.GetInfo().SetFeedAt(taskInfoFeed, feedTimeStamp) + + // Release the feed back to the pool as soon as it is not needed anymore + pt.PutTaskInfoFeedInPool(taskInfoFeed) +} + // FeedFromFork feeds the process tree with a fork event. -func (pt *ProcessTree) FeedFromFork(feed ForkFeed) error { +func (pt *ProcessTree) FeedFromFork(feed *ForkFeed) error { if feed.ChildHash == 0 || feed.ParentHash == 0 { return errfmt.Errorf("invalid task hash") } @@ -51,27 +139,6 @@ func (pt *ProcessTree) FeedFromFork(feed ForkFeed) error { // Update the parent process (might already exist) - setParentFeed := func(parent *Process) { - parent.GetInfo().SetFeedAt( - TaskInfoFeed{ - Name: "", // do not change the parent name - Tid: int(feed.ParentTid), - Pid: int(feed.ParentPid), - NsTid: int(feed.ParentNsTid), - NsPid: int(feed.ParentNsPid), - StartTimeNS: feed.ParentStartTime, - PPid: -1, // do not change the parent ppid - NsPPid: -1, // do not change the parent nsppid - Uid: -1, // do not change the parent uid - Gid: -1, // do not change the parent gid - }, - feedTimeStamp, - ) - if pt.procfsQuery { - pt.FeedFromProcFSAsync(int(feed.ParentPid)) // try to enrich ppid and name from procfs - } - } - parent, found := pt.GetProcessByHash(feed.ParentHash) // always a real process if !found { parent = pt.GetOrCreateProcessByHash(feed.ParentHash) @@ -82,34 +149,13 @@ func (pt *ProcessTree) FeedFromFork(feed ForkFeed) error { // ppid, for example). if !found || parent.GetInfo().GetPid() != int(feed.ParentPid) { - setParentFeed(parent) + pt.setParentFeed(parent, feed, feedTimeStamp) } parent.AddChild(feed.LeaderHash) // add the leader as a child of the parent // Update the leader process (might exist, might be the same as child if child is a process) - setLeaderFeed := func(leader *Process) { - leader.GetInfo().SetFeedAt( - TaskInfoFeed{ - Name: parent.GetInfo().GetName(), - Tid: int(feed.LeaderTid), - Pid: int(feed.LeaderPid), - NsTid: int(feed.LeaderNsTid), - NsPid: int(feed.LeaderNsPid), - StartTimeNS: feed.LeaderStartTime, - PPid: int(feed.ParentPid), - NsPPid: int(feed.ParentNsPid), - Uid: -1, // do not change the parent ui - Gid: -1, // do not change the parent gid - }, - feedTimeStamp, - ) - if pt.procfsQuery { - pt.FeedFromProcFSAsync(int(feed.LeaderPid)) // try to enrich name from procfs if needed - } - } - leader, found := pt.GetProcessByHash(feed.LeaderHash) if !found { leader = pt.GetOrCreateProcessByHash(feed.LeaderHash) @@ -118,7 +164,7 @@ func (pt *ProcessTree) FeedFromFork(feed ForkFeed) error { // Same case here (for events out of order created by execve first) if !found || leader.GetInfo().GetPPid() != int(feed.ParentPid) { - setLeaderFeed(leader) + pt.setLeaderFeed(leader, parent, feed, feedTimeStamp) } leader.SetParentHash(feed.ParentHash) // add the parent as the parent of the leader @@ -127,32 +173,15 @@ func (pt *ProcessTree) FeedFromFork(feed ForkFeed) error { // leader" of a single threaded process). if feed.ChildHash == feed.LeaderHash { + fileInfoFeed := parent.GetExecutable().GetFeed() leader.GetExecutable().SetFeedAt( - parent.GetExecutable().GetFeed(), + &fileInfoFeed, feedTimeStamp, ) } // In all cases (task is a process, or a thread) there is a thread entry. - setThreadFeed := func(thread *Thread) { - thread.GetInfo().SetFeedAt( - TaskInfoFeed{ - Name: leader.GetInfo().GetName(), - Tid: int(feed.ChildTid), - Pid: int(feed.ChildPid), - NsTid: int(feed.ChildNsTid), - NsPid: int(feed.ChildNsPid), - StartTimeNS: feed.ChildStartTime, - PPid: int(feed.ParentPid), - NsPPid: int(feed.ParentNsPid), - Uid: -1, // do not change the thread uid - Gid: -1, // do not change the thread gid - }, - feedTimeStamp, - ) - } - thread, found := pt.GetThreadByHash(feed.ChildHash) if !found { thread = pt.GetOrCreateThreadByHash(feed.ChildHash) @@ -161,7 +190,7 @@ func (pt *ProcessTree) FeedFromFork(feed ForkFeed) error { // Same case here (for events out of order created by execve first) if !found || thread.GetInfo().GetPPid() != int(feed.ParentPid) { - setThreadFeed(thread) + pt.setThreadFeed(thread, leader, feed, feedTimeStamp) } thread.SetParentHash(feed.ParentHash) // all threads have the same parent as the thread group leader @@ -172,20 +201,20 @@ func (pt *ProcessTree) FeedFromFork(feed ForkFeed) error { } type ExecFeed struct { - TimeStamp uint64 - TaskHash uint32 - ParentHash uint32 - LeaderHash uint32 - CmdPath string - PathName string - Dev uint32 - Inode uint64 - Ctime uint64 - InodeMode uint16 - InterpreterPath string - InterpreterDev uint32 - InterpreterInode uint64 - InterpreterCtime uint64 + TimeStamp uint64 + TaskHash uint32 + ParentHash uint32 + LeaderHash uint32 + CmdPath string + PathName string + Dev uint32 + Inode uint64 + Ctime uint64 + InodeMode uint16 + // InterpreterPath string + // InterpreterDev uint32 + // InterpreterInode uint64 + // InterpreterCtime uint64 Interp string StdinType uint16 StdinPath string @@ -195,7 +224,7 @@ type ExecFeed struct { const COMM_LEN = 16 // FeedFromExec feeds the process tree with an exec event. -func (pt *ProcessTree) FeedFromExec(feed ExecFeed) error { +func (pt *ProcessTree) FeedFromExec(feed *ExecFeed) error { if feed.LeaderHash != 0 && feed.TaskHash != feed.LeaderHash { // Running execve() from a thread is discouraged and behavior can be unexpected: // @@ -230,16 +259,19 @@ func (pt *ProcessTree) FeedFromExec(feed ExecFeed) error { execTimestamp, ) - process.GetExecutable().SetFeedAt( - FileInfoFeed{ - Path: feed.PathName, - Dev: int(feed.Dev), - Ctime: int(feed.Ctime), - Inode: int(feed.Inode), - InodeMode: int(feed.InodeMode), - }, - execTimestamp, - ) + // NOTE: override all the fields of the fileInfoFeed, to avoid any previous data. + fileInfoFeed := pt.GetFileInfoFeedFromPool() + + fileInfoFeed.Path = feed.PathName + fileInfoFeed.Dev = int(feed.Dev) + fileInfoFeed.Ctime = int(feed.Ctime) + fileInfoFeed.Inode = int(feed.Inode) + fileInfoFeed.InodeMode = int(feed.InodeMode) + + process.GetExecutable().SetFeedAt(fileInfoFeed, execTimestamp) + + // Release the feed back to the pool as soon as it is not needed anymore + pt.PutFileInfoFeedInPool(fileInfoFeed) return nil } @@ -254,9 +286,14 @@ type ExitFeed struct { } // FeedFromExit feeds the process tree with an exit event. -func (pt *ProcessTree) FeedFromExit(feed ExitFeed) error { +func (pt *ProcessTree) FeedFromExit(feed *ExitFeed) error { // Always create a tree node because the events might be received out of order. + // NOTE: Currently FeedFromExit is only using TaskHash and TimeStamp from the ExitFeed. + // So the other fields are commented out for now. + // + // TODO: Analyze if the other fields will be needed in the future. + thread := pt.GetOrCreateThreadByHash(feed.TaskHash) thread.GetInfo().SetExitTime(feed.TimeStamp) diff --git a/pkg/proctree/proctree_procfs.go b/pkg/proctree/proctree_procfs.go index 863447fc010b..5a3d0c5491e7 100644 --- a/pkg/proctree/proctree_procfs.go +++ b/pkg/proctree/proctree_procfs.go @@ -178,22 +178,29 @@ func dealWithProc(pt *ProcessTree, givenPid int) error { procfsTimeStamp := traceetime.BootToEpochNS(startTimeNs) + // NOTE: override all the fields of the taskInfoFeed, to avoid any previous data. + taskInfoFeed := pt.GetTaskInfoFeedFromPool() + + taskInfoFeed.Name = name // command name (add "procfs+" to debug if needed) + taskInfoFeed.Tid = pid // status: pid == tid + taskInfoFeed.Pid = tgid // status: tgid == pid + taskInfoFeed.PPid = ppid // status: ppid == ppid + taskInfoFeed.NsTid = nspid // status: nspid == nspid + taskInfoFeed.NsPid = nstgid // status: nstgid == nspid + taskInfoFeed.NsPPid = nsppid // status: nsppid == nsppid + taskInfoFeed.Uid = -1 // do not change the parent uid + taskInfoFeed.Gid = -1 // do not change the parent gid + taskInfoFeed.StartTimeNS = procfsTimeStamp + taskInfoFeed.ExitTimeNS = 0 + procInfo.SetFeedAt( - TaskInfoFeed{ - Name: name, // command name (add "procfs+" to debug if needed) - Tid: pid, // status: pid == tid - Pid: tgid, // status: tgid == pid - PPid: ppid, // status: ppid == ppid - NsTid: nspid, // status: nspid == nspid - NsPid: nstgid, // status: nstgid == nspid - NsPPid: nsppid, // status: nsppid == nsppid - Uid: -1, // do not change the parent uid - Gid: -1, // do not change the parent gid - StartTimeNS: procfsTimeStamp, - }, + taskInfoFeed, traceetime.NsSinceEpochToTime(procfsTimeStamp), // try to be the first changelog entry ) + // Release the feed back to the pool as soon as it is not needed anymore + pt.PutTaskInfoFeedInPool(taskInfoFeed) + // TODO: Update executable with information from /proc//exe // update given process parent (if exists) @@ -254,22 +261,29 @@ func dealWithThread(pt *ProcessTree, givenPid int, givenTid int) error { procfsTimeStamp := traceetime.BootToEpochNS(startTimeNs) + // NOTE: override all the fields of the taskInfoFeed, to avoid any previous data. + taskInfoFeed := pt.GetTaskInfoFeedFromPool() + + taskInfoFeed.Name = name // command name (add "procfs+" to debug if needed) + taskInfoFeed.Tid = pid // status: pid == tid + taskInfoFeed.Pid = tgid // status: tgid == pid + taskInfoFeed.PPid = ppid // status: ppid == ppid + taskInfoFeed.NsTid = nspid // status: nspid == nspid + taskInfoFeed.NsPid = nstgid // status: nstgid == nspid + taskInfoFeed.NsPPid = nsppid // status: nsppid == nsppid + taskInfoFeed.Uid = -1 // do not change the parent uid + taskInfoFeed.Gid = -1 // do not change the parent gid + taskInfoFeed.StartTimeNS = procfsTimeStamp + taskInfoFeed.ExitTimeNS = 0 + threadInfo.SetFeedAt( - TaskInfoFeed{ - Name: name, // command name (add "procfs+" to debug if needed) - Tid: pid, // status: pid == tid - Pid: tgid, // status: tgid == pid - PPid: ppid, // status: ppid == ppid - NsTid: nspid, // status: nspid == nspid - NsPid: nstgid, // status: nstgid == nspid - NsPPid: nsppid, // status: nsppid == nsppid - Uid: -1, // do not change the parent uid - Gid: -1, // do not change the parent gid - StartTimeNS: procfsTimeStamp, - }, + taskInfoFeed, traceetime.NsSinceEpochToTime(procfsTimeStamp), // try to be the first changelog entry ) + // Release the feed back to the pool as soon as it is not needed anymore + pt.PutTaskInfoFeedInPool(taskInfoFeed) + // thread group leader (leader tid is the same as the thread's pid, so we can find it) leader, err := getProcessByPID(pt, tgid) diff --git a/pkg/proctree/proctree_test.go b/pkg/proctree/proctree_test.go index d11a5788e921..6c91aa4ce3cc 100644 --- a/pkg/proctree/proctree_test.go +++ b/pkg/proctree/proctree_test.go @@ -6,9 +6,10 @@ import ( "testing" ) +// TestProcessTreeConcurrency tests the ProcessTree for concurrent access. +// Enable data race detection with `go test -race`. func TestProcessTreeConcurrency(t *testing.T) { t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -41,13 +42,13 @@ func TestProcessTreeConcurrency(t *testing.T) { } // Run tests concurrently for different hashes - for i := 0; i < 1000; i++ { + for i := 0; i < 3000; i++ { wg.Add(1) go testFunc(uint32(i)) } // Run tests concurrently for the same hash - for i := 0; i < 1000; i++ { + for i := 0; i < 3000; i++ { wg.Add(1) go testFunc(42) } diff --git a/pkg/proctree/taskinfo.go b/pkg/proctree/taskinfo.go index 954ff1f3fa9c..8a9cd07c4c14 100644 --- a/pkg/proctree/taskinfo.go +++ b/pkg/proctree/taskinfo.go @@ -29,20 +29,20 @@ type TaskInfoFeed struct { // TaskInfo represents a task. type TaskInfo struct { - feed *changelog.Changelog[TaskInfoFeed] + feed *changelog.Changelog[*TaskInfoFeed] mutex *sync.RWMutex } // NewTaskInfo creates a new task. func NewTaskInfo() *TaskInfo { return &TaskInfo{ - feed: changelog.NewChangelog[TaskInfoFeed](3), + feed: changelog.NewChangelog[*TaskInfoFeed](3), mutex: &sync.RWMutex{}, } } // NewTaskInfoFromFeed creates a new task with values from the given feed. -func NewTaskInfoNewFromFeed(feed TaskInfoFeed) *TaskInfo { +func NewTaskInfoNewFromFeed(feed *TaskInfoFeed) *TaskInfo { new := NewTaskInfo() new.setFeed(feed) @@ -56,7 +56,7 @@ func NewTaskInfoNewFromFeed(feed TaskInfoFeed) *TaskInfo { // Multiple values at once (using a feed structure) // SetFeed sets the values of the task from the given feed at the current time. -func (ti *TaskInfo) SetFeed(feed TaskInfoFeed) { +func (ti *TaskInfo) SetFeed(feed *TaskInfoFeed) { ti.mutex.Lock() defer ti.mutex.Unlock() @@ -64,7 +64,7 @@ func (ti *TaskInfo) SetFeed(feed TaskInfoFeed) { } // SetFeedAt sets the values of the task from the given feed at the given time. -func (ti *TaskInfo) SetFeedAt(feed TaskInfoFeed, targetTime time.Time) { +func (ti *TaskInfo) SetFeedAt(feed *TaskInfoFeed, targetTime time.Time) { ti.mutex.Lock() defer ti.mutex.Unlock() @@ -135,11 +135,11 @@ func (ti *TaskInfo) SetExitTime(exitTime uint64) { // private setters -func (ti *TaskInfo) setFeed(feed TaskInfoFeed) { +func (ti *TaskInfo) setFeed(feed *TaskInfoFeed) { ti.setFeedAt(feed, time.Now()) } -func (ti *TaskInfo) setFeedAt(feed TaskInfoFeed, targetTime time.Time) { +func (ti *TaskInfo) setFeedAt(feed *TaskInfoFeed, targetTime time.Time) { ti.feed.Set(feed, targetTime) } @@ -154,7 +154,7 @@ func (ti *TaskInfo) GetFeed() TaskInfoFeed { ti.mutex.RLock() defer ti.mutex.RUnlock() - return ti.getFeed() + return *ti.getFeed() // return a copy } // GetFeedAt returns the values of the task as a feed at the given time. @@ -162,7 +162,7 @@ func (ti *TaskInfo) GetFeedAt(targetTime time.Time) TaskInfoFeed { ti.mutex.RLock() defer ti.mutex.RUnlock() - return ti.getFeedAt(targetTime) + return *ti.getFeedAt(targetTime) // return a copy } // Single values @@ -340,10 +340,20 @@ func (ti *TaskInfo) IsAliveAt(targetTime time.Time) bool { // private getters -func (ti *TaskInfo) getFeed() TaskInfoFeed { - return ti.feed.GetCurrent() +func (ti *TaskInfo) getFeed() *TaskInfoFeed { + feed := ti.feed.GetCurrent() + if feed == nil { + feed = &TaskInfoFeed{} + } + + return feed } -func (ti *TaskInfo) getFeedAt(targetTime time.Time) TaskInfoFeed { - return ti.feed.Get(targetTime) +func (ti *TaskInfo) getFeedAt(targetTime time.Time) *TaskInfoFeed { + feed := ti.feed.Get(targetTime) + if feed == nil { + feed = &TaskInfoFeed{} + } + + return feed } diff --git a/pkg/proctree/thread.go b/pkg/proctree/thread.go index cc8308ab3b9c..2132ad450190 100644 --- a/pkg/proctree/thread.go +++ b/pkg/proctree/thread.go @@ -1,17 +1,13 @@ package proctree -import ( - "sync" -) +import "sync/atomic" // Thread represents a thread. type Thread struct { - threadHash uint32 // hash of thread + threadHash uint32 // hash of thread (immutable, so no need of concurrency control) parentHash uint32 // hash of parent leaderHash uint32 // hash of thread group leader - info *TaskInfo // task info - // Control fields - mutex *sync.RWMutex // mutex to protect the thread + info *TaskInfo // task info (immutable pointer) } // NOTE: The importance of having the thread group leader hash to each thread is the following: the @@ -20,23 +16,13 @@ type Thread struct { // log, for a thread, is needed, the thread group leader hash will be used to find the process so it // can be logged as a process artifact. -// NewThread creates a new thread. -func NewThread(hash uint32) *Thread { - return &Thread{ - threadHash: hash, - parentHash: 0, - info: NewTaskInfo(), - mutex: &sync.RWMutex{}, - } -} - -// NewThreadWithInfo creates a new thread with an initialized task info. -func NewThreadWithInfo(hash uint32, info *TaskInfo) *Thread { +// NewThread creates a new thread with an initialized task info. +func NewThread(hash uint32, info *TaskInfo) *Thread { return &Thread{ threadHash: hash, parentHash: 0, + leaderHash: 0, info: info, - mutex: &sync.RWMutex{}, } } @@ -44,44 +30,32 @@ func NewThreadWithInfo(hash uint32, info *TaskInfo) *Thread { // GetHash returns the hash of the thread. func (t *Thread) GetHash() uint32 { - t.mutex.RLock() - defer t.mutex.RUnlock() - return t.threadHash + return t.threadHash // immutable } // GetParentHash returns the hash of the parent. func (t *Thread) GetParentHash() uint32 { - t.mutex.RLock() - defer t.mutex.RUnlock() - return t.parentHash + return atomic.LoadUint32(&t.parentHash) } // GEtLeaderHash returns the hash of the thread group leader. func (t *Thread) GetLeaderHash() uint32 { - t.mutex.RLock() - defer t.mutex.RUnlock() - return t.leaderHash + return atomic.LoadUint32(&t.leaderHash) } // GetInfo returns a instanced task info. func (t *Thread) GetInfo() *TaskInfo { - t.mutex.RLock() - defer t.mutex.RUnlock() - return t.info + return t.info // immutable pointer } // Setters // SetParentHash sets the hash of the parent. func (t *Thread) SetParentHash(parentHash uint32) { - t.mutex.Lock() - defer t.mutex.Unlock() - t.parentHash = parentHash + atomic.StoreUint32(&t.parentHash, parentHash) } // SetLeaderHash sets the hash of the thread group leader. func (t *Thread) SetLeaderHash(leaderHash uint32) { - t.mutex.Lock() - defer t.mutex.Unlock() - t.leaderHash = leaderHash + atomic.StoreUint32(&t.leaderHash, leaderHash) } diff --git a/pkg/server/grpc/tracee.go b/pkg/server/grpc/tracee.go index 84e974887d61..d629d92a2c2b 100644 --- a/pkg/server/grpc/tracee.go +++ b/pkg/server/grpc/tracee.go @@ -677,12 +677,11 @@ func getDefinitions(in *pb.GetEventDefinitionsRequest) ([]events.Definition, err definitions := make([]events.Definition, 0, len(in.EventNames)) for _, name := range in.EventNames { - id, ok := events.Core.GetDefinitionIDByName(name) - if !ok { + definition := events.Core.GetDefinitionByName(name) + if definition.NotValid() { return nil, fmt.Errorf("event %s not found", name) } - definition := events.Core.GetDefinitionByID(id) definitions = append(definitions, definition) } diff --git a/pkg/signatures/benchmark/signature/golang/anti_debugging.go b/pkg/signatures/benchmark/signature/golang/anti_debugging.go index 16b94d8c4f2d..f4f18575df51 100644 --- a/pkg/signatures/benchmark/signature/golang/anti_debugging.go +++ b/pkg/signatures/benchmark/signature/golang/anti_debugging.go @@ -3,6 +3,7 @@ package golang import ( "fmt" + "github.com/aquasecurity/tracee/pkg/events/parsers" "github.com/aquasecurity/tracee/signatures/helpers" "github.com/aquasecurity/tracee/types/detect" "github.com/aquasecurity/tracee/types/protocol" @@ -12,6 +13,7 @@ import ( type antiDebugging struct { cb detect.SignatureHandler metadata detect.SignatureMetadata + logger detect.Logger } func NewAntiDebuggingSignature() (detect.Signature, error) { @@ -30,6 +32,7 @@ func NewAntiDebuggingSignature() (detect.Signature, error) { func (sig *antiDebugging) Init(ctx detect.SignatureContext) error { sig.cb = ctx.Callback + sig.logger = ctx.Logger return nil } @@ -52,22 +55,30 @@ func (sig *antiDebugging) OnEvent(event protocol.Event) error { if ee.EventName != "ptrace" { return nil } - request, err := helpers.GetTraceeArgumentByName(ee, "request", helpers.GetArgOps{DefaultArgs: false}) + requestArg, err := helpers.GetTraceeIntArgumentByName(ee, "request") if err != nil { return err } - requestString, ok := request.Value.(string) - if !ok { - return fmt.Errorf("failed to cast request's value") - } - if requestString != "PTRACE_TRACEME" { + + if uint64(requestArg) != parsers.PTRACE_TRACEME.Value() { return nil } + + var ptraceRequestData string + requestString, err := parsers.ParsePtraceRequestArgument(uint64(requestArg)) + + if err != nil { + ptraceRequestData = fmt.Sprint(requestArg) + sig.logger.Debugw("anti_debugging sig: failed to parse ptrace request argument: %v", err) + } else { + ptraceRequestData = requestString + } + sig.cb(&detect.Finding{ SigMetadata: sig.metadata, Event: event, Data: map[string]interface{}{ - "ptrace request": requestString, + "ptrace request": ptraceRequestData, }, }) return nil diff --git a/pkg/signatures/benchmark/signature/golang/code_injection.go b/pkg/signatures/benchmark/signature/golang/code_injection.go index bdef40babea6..a3e99e644d8d 100644 --- a/pkg/signatures/benchmark/signature/golang/code_injection.go +++ b/pkg/signatures/benchmark/signature/golang/code_injection.go @@ -65,11 +65,11 @@ func (sig *codeInjection) OnEvent(event protocol.Event) error { } switch ee.EventName { case "open", "openat": - flags, err := helpers.GetTraceeArgumentByName(ee, "flags", helpers.GetArgOps{DefaultArgs: false}) + flags, err := helpers.GetTraceeIntArgumentByName(ee, "flags") if err != nil { return fmt.Errorf("%v %#v", err, ee) } - if helpers.IsFileWrite(flags.Value.(string)) { + if helpers.IsFileWrite(flags) { pathname, err := helpers.GetTraceeArgumentByName(ee, "pathname", helpers.GetArgOps{DefaultArgs: false}) if err != nil { return err diff --git a/pkg/utils/environment/kernel_symbols.go b/pkg/utils/environment/kernel_symbols.go index cb2b119c99f4..d6687df2215b 100644 --- a/pkg/utils/environment/kernel_symbols.go +++ b/pkg/utils/environment/kernel_symbols.go @@ -2,319 +2,283 @@ package environment import ( "bufio" - "fmt" + "io" "os" "strconv" "strings" - "sync" + + "github.com/aquasecurity/tracee/pkg/errfmt" + "github.com/aquasecurity/tracee/pkg/utils" ) const ( - kallsymsPath = "/proc/kallsyms" - chanBuffer = 112800 // TODO: check if we really need this buffer size + // Kernel symbols do not have an associated size, so we define a sensible size + // limit to prevent unrelated symbols from being returned for an address lookup + maxSymbolSize = 0x100000 + + ownerShift = 48 // Number of bits to shift the owner into the upper 16 bits + addressMask = (1 << ownerShift) - 1 // Mask to extract the address from the addressAndOwner field + kernelAddressPrefix = uint64(0xffff) << ownerShift // Precomputed prefix for kernel addresses ) +// KernelSymbol is a friendly representation of a kernel symbol. type KernelSymbol struct { Name string - Type string Address uint64 Owner string } -type nameAndOwner struct { - name string - owner string -} -type addrAndOwner struct { - addr uint64 - owner string -} - -// KernelSymbolTable manages kernel symbols with multiple maps for fast lookup. -type KernelSymbolTable struct { - symbols map[string][]*KernelSymbol - addrs map[uint64][]*KernelSymbol - symByName map[nameAndOwner][]*KernelSymbol - symByAddr map[addrAndOwner][]*KernelSymbol - requiredSyms map[string]struct{} - requiredAddrs map[uint64]struct{} - onlyRequired bool - updateLock sync.Mutex - updateWg sync.WaitGroup -} -func symNotFoundErr(v interface{}) error { - return fmt.Errorf("symbol not found: %v", v) +// kernelSymbolInternal is a memory efficient representation of +// a kernel symbol, used internally for storing all symbols. +type kernelSymbolInternal struct { + name string + // We save only the low 48 bits of the address, as all (non-percpu) symbols are at 0xffffXXXXXXXXXXXX + // Owner is a 16-bit index into a slice of seen owners for the symbol table this symbol belongs to. + // It can only be translated to the owner name if we have the symbol table. + // To conserve memory, we encode both of them as a single 64-bit integer where the lower 48-bits + // are the address and the hight 16-bits are the owner index. + addressAndOwner uint64 } -// NewKernelSymbolTable initializes a KernelSymbolTable with optional configuration functions. -func NewKernelSymbolTable(opts ...KSymbTableOption) (*KernelSymbolTable, error) { - k := &KernelSymbolTable{} - for _, opt := range opts { - if err := opt(k); err != nil { - return nil, err - } +func newKernelSymbolInternal(name string, address uint64, owner uint16) *kernelSymbolInternal { + return &kernelSymbolInternal{ + name: name, + addressAndOwner: (uint64(owner) << ownerShift) | (address & addressMask), } +} - // Set onlyRequired to true if there are required symbols or addresses - k.onlyRequired = k.requiredAddrs != nil || k.requiredSyms != nil - - // Initialize maps if they are nil - if k.requiredSyms == nil { - k.requiredSyms = make(map[string]struct{}) - } - if k.requiredAddrs == nil { - k.requiredAddrs = make(map[uint64]struct{}) - } +func (ks kernelSymbolInternal) Name() string { + return ks.name +} - return k, k.Refresh() +func (ks kernelSymbolInternal) Address() uint64 { + // Convert truncated address to the real kernel address + return kernelAddressPrefix | (ks.addressAndOwner & addressMask) } -// KSymbTableOption defines a function signature for configuration options. -type KSymbTableOption func(k *KernelSymbolTable) error +func (ks kernelSymbolInternal) owner() uint16 { + return uint16(ks.addressAndOwner >> ownerShift) +} -// WithRequiredSymbols sets the required symbols for the KernelSymbolTable. -func WithRequiredSymbols(reqSyms []string) KSymbTableOption { - return func(k *KernelSymbolTable) error { - k.requiredSyms = sliceToValidationMap(reqSyms) - return nil - } +func (ks kernelSymbolInternal) Contains(address uint64) bool { + symbolAddr := ks.Address() + return symbolAddr <= address && symbolAddr+maxSymbolSize > address } -// WithRequiredAddresses sets the required addresses for the KernelSymbolTable. -func WithRequiredAddresses(reqAddrs []uint64) KSymbTableOption { - return func(k *KernelSymbolTable) error { - k.requiredAddrs = sliceToValidationMap(reqAddrs) - return nil +func (ks kernelSymbolInternal) Clone() kernelSymbolInternal { + return kernelSymbolInternal{ + name: ks.name, + addressAndOwner: ks.addressAndOwner, } } -// TextSegmentContains returns true if the given address is in the kernel text segment. -func (k *KernelSymbolTable) TextSegmentContains(addr uint64) (bool, error) { - k.updateLock.Lock() - defer k.updateLock.Unlock() - - segStart, segEnd, err := k.getTextSegmentAddresses() - if err != nil { - return false, err - } +type KernelSymbolTable struct { + symbols *utils.SymbolTable[kernelSymbolInternal] - return addr >= segStart && addr < segEnd, nil + // Used for memory efficient representation of symbol owners + idxToSymbolOwner []string + symbolOwnerToIdx map[string]uint16 } -// GetSymbolByName returns all the symbols with the given name. -func (k *KernelSymbolTable) GetSymbolByName(name string) ([]KernelSymbol, error) { - k.updateLock.Lock() - defer k.updateLock.Unlock() - - if err := k.validateOrAddRequiredSym(name); err != nil { - return nil, err +// Creates a new KernelSymbolTable that will be populated from a reader. +// If lazyNameLookup is true, the mapping from name to symbol will be populated +// only when a failed lookup occurs. This reduces memory footprint at the cost +// of the time it takes to lookup a symbol name for the first time. +// If requiredDataSymbolsOnly is true, only the data symbols passed in the +// optional requiredDataSymbols argument will be added. +func NewKernelSymbolTableFromReader(reader io.Reader, lazyNameLookup bool, requiredDataSymbolsOnly bool, requiredDataSymbols ...string) (*KernelSymbolTable, error) { + kst := &KernelSymbolTable{ + symbols: utils.NewSymbolTable[kernelSymbolInternal](lazyNameLookup), + idxToSymbolOwner: []string{"system"}, + symbolOwnerToIdx: map[string]uint16{"system": 0}, } - symbols, exist := k.symbols[name] - if !exist { - return nil, symNotFoundErr(name) + if err := kst.update(reader, requiredDataSymbolsOnly, requiredDataSymbols); err != nil { + return nil, err } - return copySliceOfPointersToSliceOfStructs(symbols), nil + return kst, nil } -// GetSymbolByAddr returns all the symbols with the given address. -func (k *KernelSymbolTable) GetSymbolByAddr(addr uint64) ([]KernelSymbol, error) { - k.updateLock.Lock() - defer k.updateLock.Unlock() - - if err := k.validateOrAddRequiredAddr(addr); err != nil { - return nil, err +// Creates a new KernelSymbolTable that will be populated from /proc/kallsyms. +// If lazyNameLookup is true, the mapping from name to symbol will be populated +// only when a failed lookup occurs. This reduces memory footprint at the cost +// of the time it takes to lookup a symbol name for the first time. +// If requiredDataSymbolsOnly is true, only the data symbols passed in the +// optional requiredDataSymbols argument will be added. +func NewKernelSymbolTable(lazyNameLookup bool, requiredDataSymbolsOnly bool, requiredDataSymbols ...string) (*KernelSymbolTable, error) { + file, err := os.Open("/proc/kallsyms") + if err != nil { + return nil, errfmt.WrapError(err) } + defer func() { + _ = file.Close() + }() - symbols, exist := k.addrs[addr] - if !exist { - return nil, symNotFoundErr(addr) + return NewKernelSymbolTableFromReader(file, lazyNameLookup, requiredDataSymbolsOnly, requiredDataSymbols...) +} + +// Read the contents of the given buffer and update the symbol table +func (kst *KernelSymbolTable) update(reader io.Reader, requiredDataSymbolsOnly bool, requiredDataSymbols []string) error { + // Build set of required data symbols for efficient lookup + requiredDataSymbolsSet := make(map[string]struct{}) + for _, symbolName := range requiredDataSymbols { + requiredDataSymbolsSet[symbolName] = struct{}{} } - return copySliceOfPointersToSliceOfStructs(symbols), nil -} + symbols := []*kernelSymbolInternal{} -// GetSymbolByOwnerAndName returns all the symbols with the given owner and name. -func (k *KernelSymbolTable) GetSymbolByOwnerAndName(owner, name string) ([]KernelSymbol, error) { - k.updateLock.Lock() - defer k.updateLock.Unlock() + // Make sure we hold the required privileges by checking if we see actual addresses + seenRealAddress := false - if err := k.validateOrAddRequiredSym(name); err != nil { - return nil, err - } + scanner := bufio.NewScanner(reader) + for scanner.Scan() { + fields := strings.Fields(scanner.Text()) + if len(fields) < 3 { + continue + } - symbols, exist := k.symByName[nameAndOwner{name, owner}] - if !exist { - return nil, symNotFoundErr(nameAndOwner{name, owner}) - } + symbolAddr, err := strconv.ParseUint(fields[0], 16, 64) + if err != nil { + continue + } + if symbolAddr != 0 { + seenRealAddress = true + } - return copySliceOfPointersToSliceOfStructs(symbols), nil -} + // All kernel symbols are at 0xffffXXXXXXXXXXXX, except percpu symbols which we ignore + if !validKernelAddr(symbolAddr) { + continue + } -// GetSymbolByOwnerAndAddr returns all the symbols with the given owner and address. -func (k *KernelSymbolTable) GetSymbolByOwnerAndAddr(owner string, addr uint64) ([]KernelSymbol, error) { - k.updateLock.Lock() - defer k.updateLock.Unlock() + symbolType := fields[1] + symbolName := fields[2] - if err := k.validateOrAddRequiredAddr(addr); err != nil { - return nil, err - } + symbolOwner := "system" + if len(fields) > 3 { + symbolOwner = fields[3] + symbolOwner = strings.TrimPrefix(symbolOwner, "[") + symbolOwner = strings.TrimSuffix(symbolOwner, "]") + } - symbols, exist := k.symByAddr[addrAndOwner{addr, owner}] - if !exist { - return nil, symNotFoundErr(addrAndOwner{addr, owner}) - } + // This is a data symbol, requiredDataSymbolsOnly is true, and this symbol isn't required + if requiredDataSymbolsOnly && strings.ContainsAny(symbolType, "DdBbRr") { + if _, exists := requiredDataSymbolsSet[symbolName]; !exists { + continue + } + } - return copySliceOfPointersToSliceOfStructs(symbols), nil -} + // Get index of symbol owner, or add it if it doesn't exist + ownerIdx := kst.getOrAddSymbolOwner(symbolOwner) -// getTextSegmentAddresses gets the start and end addresses of the kernel text segment. -func (k *KernelSymbolTable) getTextSegmentAddresses() (uint64, uint64, error) { - stext, exist1 := k.symByName[nameAndOwner{"_stext", "system"}] - etext, exist2 := k.symByName[nameAndOwner{"_etext", "system"}] + symbols = append(symbols, newKernelSymbolInternal(symbolName, symbolAddr, ownerIdx)) + } - if !exist1 || !exist2 { - return 0, 0, fmt.Errorf("kernel text segment symbol(s) not found") + // We didn't hold the required privileges + if len(symbols) > 0 && !seenRealAddress { + return errfmt.Errorf("insufficient privileges when reading from /proc/kallsyms") } - textSegStart := stext[0].Address - textSegEnd := etext[0].Address + // Update the symbol table + kst.symbols.AddSymbols(symbols) - return textSegStart, textSegEnd, nil + return nil } -// validateOrAddRequiredSym checks if the given symbol is in the required list and adds it if not. -func (k *KernelSymbolTable) validateOrAddRequiredSym(sym string) error { - return k.validateOrAddRequired(func() bool { - _, ok := k.requiredSyms[sym] - return ok - }, func() { - k.requiredSyms[sym] = struct{}{} - }) -} +func (kst *KernelSymbolTable) getOrAddSymbolOwner(ownerStr string) uint16 { + ownerIdx, found := kst.symbolOwnerToIdx[ownerStr] + if !found { + kst.idxToSymbolOwner = append(kst.idxToSymbolOwner, ownerStr) + ownerIdx = uint16(len(kst.idxToSymbolOwner) - 1) + kst.symbolOwnerToIdx[ownerStr] = ownerIdx + } -// validateOrAddRequiredAddr checks if the given address is in the required list and adds it if not. -func (k *KernelSymbolTable) validateOrAddRequiredAddr(addr uint64) error { - return k.validateOrAddRequired(func() bool { - _, ok := k.requiredAddrs[addr] - return ok - }, func() { - k.requiredAddrs[addr] = struct{}{} - }) + return ownerIdx } -// validateOrAddRequired is a common function to check and add required symbols or addresses. -func (k *KernelSymbolTable) validateOrAddRequired(checkRequired func() bool, addRequired func()) error { - if !k.onlyRequired { - return nil +func (kst *KernelSymbolTable) symbolFromInternal(symbol *kernelSymbolInternal) *KernelSymbol { + return &KernelSymbol{ + Name: symbol.Name(), + Address: symbol.Address(), + Owner: kst.idxToSymbolOwner[symbol.owner()], } +} - if !checkRequired() { - addRequired() - return k.refresh() +// GetSymbolByName returns all the symbols with the given name. +func (kst *KernelSymbolTable) GetSymbolByName(name string) ([]*KernelSymbol, error) { + symbolsInternal, err := kst.symbols.LookupByName(name) + if err != nil { + return nil, errfmt.WrapError(err) } - return nil -} + symbols := make([]*KernelSymbol, 0, len(symbolsInternal)) + for _, symbolInternal := range symbolsInternal { + symbols = append(symbols, kst.symbolFromInternal(symbolInternal)) + } -// Refresh is the exported method that acquires the lock and calls the internal refresh method. -func (k *KernelSymbolTable) Refresh() error { - k.updateLock.Lock() - defer k.updateLock.Unlock() - return k.refresh() + return symbols, nil } -// refresh refreshes the KernelSymbolTable, reading the symbols from /proc/kallsyms. -func (k *KernelSymbolTable) refresh() error { - // Re-initialize the maps to include all new symbols. - k.symbols = make(map[string][]*KernelSymbol) - k.addrs = make(map[uint64][]*KernelSymbol) - k.symByName = make(map[nameAndOwner][]*KernelSymbol) - k.symByAddr = make(map[addrAndOwner][]*KernelSymbol) - - // Open the kallsyms file. - file, err := os.Open(kallsymsPath) +// GetSymbolByOwnerAndName returns all the symbols with the given owner and name. +func (kst *KernelSymbolTable) GetSymbolByOwnerAndName(owner, name string) ([]*KernelSymbol, error) { + symbolsInternal, err := kst.symbols.LookupByName(name) if err != nil { - return err + return nil, errfmt.WrapError(err) } - defer func() { - _ = file.Close() - }() - // Read the kallsyms file line by line and process each line. - scanner := bufio.NewScanner(file) - for scanner.Scan() { - fields := strings.Fields(scanner.Text()) - if len(fields) < 3 { - continue - } - sym := parseKallsymsLine(fields) - if sym == nil { - continue + symbols := make([]*KernelSymbol, 0, len(symbolsInternal)) + for _, symbolInternal := range symbolsInternal { + symbol := kst.symbolFromInternal(symbolInternal) + // Return only symbols that have the requested owner + if symbol.Owner == owner { + symbols = append(symbols, symbol) } - - if k.onlyRequired { - _, symRequired := k.requiredSyms[sym.Name] - _, addrRequired := k.requiredAddrs[sym.Address] - if !symRequired && !addrRequired { - continue - } - } - - k.symbols[sym.Name] = append(k.symbols[sym.Name], sym) - k.addrs[sym.Address] = append(k.addrs[sym.Address], sym) - k.symByName[nameAndOwner{sym.Name, sym.Owner}] = append(k.symByName[nameAndOwner{sym.Name, sym.Owner}], sym) - k.symByAddr[addrAndOwner{sym.Address, sym.Owner}] = append(k.symByAddr[addrAndOwner{sym.Address, sym.Owner}], sym) } - err = scanner.Err() - return err + return symbols, nil } -// parseKallsymsLine parses a line from /proc/kallsyms and returns a KernelSymbol. -func parseKallsymsLine(line []string) *KernelSymbol { - if len(line) < 3 { - return nil +// GetSymbolByAddr returns all the symbols with the given address. +func (kst *KernelSymbolTable) GetSymbolByAddr(addr uint64) ([]*KernelSymbol, error) { + symbolsInternal, err := kst.symbols.LookupByAddressExact(addr) + if err != nil { + return nil, errfmt.WrapError(err) } - symbolAddr, err := strconv.ParseUint(line[0], 16, 64) - if err != nil { - return nil + symbols := make([]*KernelSymbol, 0, len(symbolsInternal)) + for _, symbolInternal := range symbolsInternal { + symbols = append(symbols, kst.symbolFromInternal(symbolInternal)) } - symbolType := line[1] - symbolName := line[2] + return symbols, nil +} - symbolOwner := "system" - if len(line) > 3 { - line[3] = strings.TrimPrefix(line[3], "[") - line[3] = strings.TrimSuffix(line[3], "]") - symbolOwner = line[3] +// GetPotentiallyHiddenSymbolByAddr returns all the symbols with the given address, +// or if none are found, a fake symbol with the "hidden" owner. +func (kst *KernelSymbolTable) GetPotentiallyHiddenSymbolByAddr(addr uint64) []*KernelSymbol { + symbolsInternal, err := kst.symbols.LookupByAddressExact(addr) + if err != nil || !validKernelAddr(addr) { + // No symbol found or address not in kernel range, return a fake "hidden" symbol + return []*KernelSymbol{{ + Address: addr, + Owner: "hidden", + }} } - return &KernelSymbol{ - Name: symbolName, - Type: symbolType, - Address: symbolAddr, - Owner: symbolOwner, + symbols := make([]*KernelSymbol, 0, len(symbolsInternal)) + for _, symbolInternal := range symbolsInternal { + symbols = append(symbols, kst.symbolFromInternal(symbolInternal)) } + + return symbols } -// copySliceOfPointersToSliceOfStructs converts a slice of pointers to a slice of structs. -func copySliceOfPointersToSliceOfStructs(s []*KernelSymbol) []KernelSymbol { - ret := make([]KernelSymbol, len(s)) - for i, v := range s { - ret[i] = *v - } - return ret +func (kst *KernelSymbolTable) ForEachSymbol(callback func(*KernelSymbol)) { + kst.symbols.ForEachSymbol(func(symbol *kernelSymbolInternal) { + callback(kst.symbolFromInternal(symbol)) + }) } -// sliceToValidationMap converts a slice to a map for validation purposes. -func sliceToValidationMap[T comparable](items []T) map[T]struct{} { - res := make(map[T]struct{}) - for _, item := range items { - res[item] = struct{}{} - } - return res +func validKernelAddr(addr uint64) bool { + return addr&kernelAddressPrefix == kernelAddressPrefix } diff --git a/pkg/utils/environment/kernel_symbols_test.go b/pkg/utils/environment/kernel_symbols_test.go index 834b58026a1f..6bf9e9d81c2f 100644 --- a/pkg/utils/environment/kernel_symbols_test.go +++ b/pkg/utils/environment/kernel_symbols_test.go @@ -2,32 +2,30 @@ package environment import ( "reflect" + "strings" "testing" ) -// TestParseLine tests the parseKallsymsLine function. -func TestParseKallsymsLine(t *testing.T) { - testCases := []struct { - line []string - expected *KernelSymbol - }{ - {[]string{"00000000", "t", "my_symbol", "[my_owner]"}, &KernelSymbol{Name: "my_symbol", Type: "t", Address: 0, Owner: "my_owner"}}, - {[]string{"00000001", "T", "another_symbol"}, &KernelSymbol{Name: "another_symbol", Type: "T", Address: 1, Owner: "system"}}, - {[]string{"invalid_address", "T", "invalid_symbol"}, nil}, - {[]string{"00000002", "T"}, nil}, - } +type symbolInfo struct { + name string + address uint64 + owner string +} - for _, tc := range testCases { - result := parseKallsymsLine(tc.line) - if !reflect.DeepEqual(result, tc.expected) { - t.Errorf("parseKallsymsLine(%v) = %v; want %v", tc.line, result, tc.expected) - } +func symbolToSymbolInfo(symbol *KernelSymbol) *symbolInfo { + if symbol == nil { + return nil + } + return &symbolInfo{ + name: symbol.Name, + address: symbol.Address, + owner: symbol.Owner, } } // TestNewKernelSymbolTable tests the NewKernelSymbolTable function. func TestNewKernelSymbolTable(t *testing.T) { - kst, err := NewKernelSymbolTable() + kst, err := NewKernelSymbolTable(true, false) if err != nil { t.Fatalf("NewKernelSymbolTable() failed: %v", err) } @@ -36,26 +34,56 @@ func TestNewKernelSymbolTable(t *testing.T) { t.Fatalf("NewKernelSymbolTable() returned nil") } - // Check if the onlyRequired flag is set correctly - if kst.onlyRequired { - t.Errorf("onlyRequired flag should be false by default") + // Check if symbols is initialized + if kst.symbols == nil { + t.Errorf("KernelSymbolTable is not initialized correctly") + } +} + +func getTheOnlySymbol(t *testing.T, kst *KernelSymbolTable) *KernelSymbol { + i := 0 + var foundSymbol *KernelSymbol + kst.ForEachSymbol(func(symbol *KernelSymbol) { + i++ + foundSymbol = symbol + }) + if i > 1 { + t.Errorf("multiple symbols found") + } + return foundSymbol +} + +// TestUpdate tests the kallsyms parsing logic. +func TestUpdate(t *testing.T) { + testCases := []struct { + buf string + expected *symbolInfo + }{ + {"ffffffff00000001 t my_symbol [my_owner]", &symbolInfo{name: "my_symbol", address: 0xffffffff00000001, owner: "my_owner"}}, + {"ffffffff00000002 T another_symbol", &symbolInfo{name: "another_symbol", address: 0xffffffff00000002, owner: "system"}}, + {"invalid_address T invalid_symbol", nil}, + {"ffffffff00000003 T", nil}, } - // Check if maps are initialized - if kst.symbols == nil || kst.addrs == nil || kst.symByName == nil || kst.symByAddr == nil { - t.Errorf("KernelSymbolTable maps are not initialized correctly") + for _, tc := range testCases { + kst, err := NewKernelSymbolTableFromReader(strings.NewReader(tc.buf), false, false) + if err != nil { + t.Fatalf("NewKernelSymbolTableFromReader() failed: %v", err) + } + symbol := getTheOnlySymbol(t, kst) + result := symbolToSymbolInfo(symbol) + if !reflect.DeepEqual(result, tc.expected) { + t.Errorf("update(%v) = %v; want %v", tc.buf, result, tc.expected) + } } } // TestGetSymbolByName tests the GetSymbolByName function. func TestGetSymbolByName(t *testing.T) { - kst, err := NewKernelSymbolTable() + buf := "ffffffff00000001 t test_symbol test_owner" + kst, err := NewKernelSymbolTableFromReader(strings.NewReader(buf), false, false) if err != nil { - t.Fatalf("NewKernelSymbolTable() failed: %v", err) - } - - kst.symbols["test_symbol"] = []*KernelSymbol{ - {Name: "test_symbol", Type: "t", Address: 0, Owner: "test_owner"}, + t.Fatalf("NewKernelSymbolTableFromReader() failed: %v", err) } symbols, err := kst.GetSymbolByName("test_symbol") @@ -67,110 +95,79 @@ func TestGetSymbolByName(t *testing.T) { t.Errorf("Expected 1 symbol, got %d", len(symbols)) } - expectedSymbol := KernelSymbol{Name: "test_symbol", Type: "t", Address: 0, Owner: "test_owner"} - if !reflect.DeepEqual(symbols[0], expectedSymbol) { - t.Errorf("GetSymbolByName() = %v; want %v", symbols[0], expectedSymbol) + expected := &symbolInfo{name: "test_symbol", address: 0xffffffff00000001, owner: "test_owner"} + result := symbolToSymbolInfo(symbols[0]) + if !reflect.DeepEqual(result, expected) { + t.Errorf("GetSymbolByName() = %v; want %v", result, expected) } } -// TestGetSymbolByAddr tests the GetSymbolByAddr function. -func TestGetSymbolByAddr(t *testing.T) { - kst, err := NewKernelSymbolTable() +// TestGetSymbolByOwnerAndName tests the GetSymbolByOwnerAndName function. +func TestGetSymbolByOwnerAndName(t *testing.T) { + buf := `ffffffff00000001 t test_symbol test_owner1 +ffffffff00000002 t test_symbol test_owner2` + kst, err := NewKernelSymbolTableFromReader(strings.NewReader(buf), false, false) if err != nil { - t.Fatalf("NewKernelSymbolTable() failed: %v", err) + t.Fatalf("NewKernelSymbolTableFromReader() failed: %v", err) } - kst.addrs[0x1234] = []*KernelSymbol{ - {Name: "test_symbol", Type: "t", Address: 0x1234, Owner: "test_owner"}, - } - - symbols, err := kst.GetSymbolByAddr(0x1234) + symbols, err := kst.GetSymbolByOwnerAndName("test_owner1", "test_symbol") if err != nil { - t.Fatalf("GetSymbolByAddr() failed: %v", err) + t.Fatalf("GetSymbolByName() failed: %v", err) } if len(symbols) != 1 { t.Errorf("Expected 1 symbol, got %d", len(symbols)) } - expectedSymbol := KernelSymbol{Name: "test_symbol", Type: "t", Address: 0x1234, Owner: "test_owner"} - if !reflect.DeepEqual(symbols[0], expectedSymbol) { - t.Errorf("GetSymbolByAddr() = %v; want %v", symbols[0], expectedSymbol) + expected := &symbolInfo{name: "test_symbol", address: 0xffffffff00000001, owner: "test_owner1"} + result := symbolToSymbolInfo(symbols[0]) + if !reflect.DeepEqual(result, expected) { + t.Errorf("GetSymbolByOwnerAndName() = %v; want %v", result, expected) } } -// TestRefresh tests the Refresh function. -func TestRefresh(t *testing.T) { - // Creating a mock KernelSymbolTable with required symbols to test Refresh - kst, err := NewKernelSymbolTable(WithRequiredSymbols([]string{"_stext", "_etext"})) +// TestGetSymbolByAddr tests the GetSymbolByAddr function. +func TestGetSymbolByAddr(t *testing.T) { + buf := "ffffffff00001234 t test_symbol test_owner" + kst, err := NewKernelSymbolTableFromReader(strings.NewReader(buf), false, false) if err != nil { - t.Fatalf("NewKernelSymbolTable() failed: %v", err) - } - - // Simulate the presence of these symbols - kst.symbols["_stext"] = []*KernelSymbol{{Name: "_stext", Type: "T", Address: 0x1000, Owner: "system"}} - kst.symbols["_etext"] = []*KernelSymbol{{Name: "_etext", Type: "T", Address: 0x2000, Owner: "system"}} - - // Call Refresh to update the symbol table - if err := kst.Refresh(); err != nil { - t.Fatalf("Refresh() failed: %v", err) - } - - // Check if symbols were added correctly - symbolsToTest := []string{"_stext", "_etext"} - for _, symbol := range symbolsToTest { - if syms, err := kst.GetSymbolByName(symbol); err != nil || len(syms) == 0 { - t.Errorf("Expected to find symbol %s, but it was not found", symbol) - } + t.Fatalf("NewKernelSymbolTableFromReader() failed: %v", err) } -} -// TestTextSegmentContains tests the TextSegmentContains function. -func TestTextSegmentContains(t *testing.T) { - // Creating a mock KernelSymbolTable with text segment addresses - kst, err := NewKernelSymbolTable() + symbols, err := kst.GetSymbolByAddr(0xffffffff00001234) if err != nil { - t.Fatalf("NewKernelSymbolTable() failed: %v", err) + t.Fatalf("GetSymbolByAddr() failed: %v", err) } - kst.symByName[nameAndOwner{"_stext", "system"}] = []*KernelSymbol{{Name: "_stext", Type: "T", Address: 0x1000, Owner: "system"}} - kst.symByName[nameAndOwner{"_etext", "system"}] = []*KernelSymbol{{Name: "_etext", Type: "T", Address: 0x2000, Owner: "system"}} - - tests := []struct { - addr uint64 - expected bool - }{ - {0x1000, true}, - {0x1500, true}, - {0x2000, false}, - {0x0999, false}, + if len(symbols) != 1 { + t.Errorf("Expected 1 symbol, got %d", len(symbols)) } - for _, tt := range tests { - result, err := kst.TextSegmentContains(tt.addr) - if err != nil { - t.Errorf("TextSegmentContains(%v) failed: %v", tt.addr, err) - } - if result != tt.expected { - t.Errorf("TextSegmentContains(%v) = %v; want %v", tt.addr, result, tt.expected) - } + expected := &symbolInfo{name: "test_symbol", address: 0xffffffff00001234, owner: "test_owner"} + result := symbolToSymbolInfo(symbols[0]) + if !reflect.DeepEqual(result, expected) { + t.Errorf("GetSymbolByAddr() = %v; want %v", result, expected) } } -// Helper function to test required symbols or addresses. -func TestValidateOrAddRequired(t *testing.T) { - kst, err := NewKernelSymbolTable(WithRequiredSymbols([]string{"test_symbol"})) +// TestGetPotentiallyHiddenSymbolByAddr tests the GetPotentiallyHiddenSymbolByAddr function. +func TestGetPotentiallyHiddenSymbolByAddr(t *testing.T) { + buf := "ffffffff00000001 t test_symbol test_owner" + kst, err := NewKernelSymbolTableFromReader(strings.NewReader(buf), false, false) if err != nil { - t.Fatalf("NewKernelSymbolTable() failed: %v", err) + t.Fatalf("NewKernelSymbolTableFromReader() failed: %v", err) } - kst.requiredSyms["test_symbol"] = struct{}{} + symbols := kst.GetPotentiallyHiddenSymbolByAddr(0xffffffff00000002) - if err := kst.validateOrAddRequiredSym("test_symbol"); err != nil { - t.Errorf("validateOrAddRequiredSym() failed: %v", err) + if len(symbols) != 1 { + t.Errorf("Expected 1 symbol, got %d", len(symbols)) } - if err := kst.validateOrAddRequiredAddr(0x1234); err != nil { - t.Errorf("validateOrAddRequiredAddr() failed: %v", err) + expected := &symbolInfo{name: "", address: 0xffffffff00000002, owner: "hidden"} + result := symbolToSymbolInfo(symbols[0]) + if !reflect.DeepEqual(result, expected) { + t.Errorf("GetSymbolByAddr() = %v; want %v", result, expected) } } diff --git a/pkg/utils/symbol_table.go b/pkg/utils/symbol_table.go new file mode 100644 index 000000000000..bb10f6053068 --- /dev/null +++ b/pkg/utils/symbol_table.go @@ -0,0 +1,209 @@ +package utils + +import ( + "errors" + "sort" + "sync" +) + +// The Symbol interface defines what is needed from a symbol implementation in +// order to facilitate the lookup functionalities provided by SymbolTable. +// Implementations of Symbol can hold various types of information relevant to +// the type of symbol they represent. +type Symbol[T any] interface { + // Name returns the symbol's name + Name() string + // Address returns the base address of the symbol + Address() uint64 + // Contains returns whether a given address belongs to the symbol's + // address range, which is defined by the symbol's implementation + Contains(address uint64) bool + Cloner[T] +} + +// SymbolTable is used to hold information about symbols (mapping from symbolic +// names used in code to their address) in a certain executable. +// It can be used to hold symbols from an ELF binary, or symbols of the entire +// kernel and its modules. +// It provides functions to lookup symbols by address and name. +type SymbolTable[T Symbol[T]] struct { + mu sync.RWMutex + // All symbols sorted by their address in descending order, + // for quick binary searches by address. + sortedSymbols []*T + // If lazyNameLookup is true, the symbolsByName map + // will be populated only when a failed lookup occurs. + symbolsByName map[string][]*T + lazyNameLookup bool +} + +var ErrSymbolNotFound = errors.New("symbol not found") + +// Creates a new SymbolTable. If lazyNameLookup is true, the mapping from +// name to symbol will be populated only when a failed lookup occurs. +// This reduces memory footprint at the cost of the time it takes to lookup +// a symbol name for the first time. +func NewSymbolTable[T Symbol[T]](lazyNameLookup bool) *SymbolTable[T] { + return &SymbolTable[T]{ + sortedSymbols: make([]*T, 0), + symbolsByName: make(map[string][]*T), + lazyNameLookup: lazyNameLookup, + } +} + +// Adds a slice of symbols to the symbol table. +func (st *SymbolTable[T]) AddSymbols(symbols []*T) { + st.mu.Lock() + defer st.mu.Unlock() + + // Add the new symbols to the sorted slice (which now becomes unsorted). + // Allocate the slice with the needed capacity to avoid overallocation. + oldSymbols := st.sortedSymbols + newLen := len(oldSymbols) + len(symbols) + st.sortedSymbols = make([]*T, 0, newLen) + st.sortedSymbols = append(st.sortedSymbols, oldSymbols...) + st.sortedSymbols = append(st.sortedSymbols, symbols...) + + // If lazyNameLookup is false, we update the name to symbol mapping for + // each new symbol + if !st.lazyNameLookup { + for _, symbol := range symbols { + name := (*symbol).Name() + if symbols, found := st.symbolsByName[name]; found { + st.symbolsByName[name] = append(symbols, symbol) + } else { + st.symbolsByName[name] = []*T{symbol} + } + } + } + + // Sort the symbols slice by address in descending order + sort.Slice(st.sortedSymbols, + func(i, j int) bool { + return (*st.sortedSymbols[i]).Address() > (*st.sortedSymbols[j]).Address() + }) +} + +// Lookup a symbol in the table by its name. +// Because there may be multiple symbols with the same name, a slice of all +// matching symbols is returned. +func (st *SymbolTable[T]) LookupByName(name string) ([]*T, error) { + st.mu.RLock() + // We call RUnlock manually and not using defer because we may need to upgrade to a write lock later + + // Lookup the name in the name to symbol mapping + if symbols, found := st.symbolsByName[name]; found { + st.mu.RUnlock() + return symbols, nil + } + + // Lazy name lookup is disabled, the lookup failed + if !st.lazyNameLookup { + st.mu.RUnlock() + return nil, ErrSymbolNotFound + } + + // Lazy name lookup is enabled, perform a linear search to find the requested name + symbols := []*T{} + for _, symbol := range st.sortedSymbols { + if (*symbol).Name() == name { + symbols = append(symbols, symbol) + } + } + + if len(symbols) > 0 { + // We found symbols with this name, update the mapping + st.mu.RUnlock() + st.mu.Lock() + defer st.mu.Unlock() + st.symbolsByName[name] = symbols + return symbols, nil + } + + st.mu.RUnlock() + return nil, ErrSymbolNotFound +} + +// Lookup a symbol in the table by its exact address. +// Because there may be multiple symbols at the same address, a slice of all +// matching symbols is returned. +func (st *SymbolTable[T]) LookupByAddressExact(address uint64) ([]*T, error) { + st.mu.RLock() + defer st.mu.RUnlock() + + // Find the first symbol at an address smaller than or equal to the requested address + idx := sort.Search(len(st.sortedSymbols), + func(i int) bool { + return address >= (*st.sortedSymbols[i]).Address() + }) + + // Not found or not exact match + if idx == len(st.sortedSymbols) || (*st.sortedSymbols[idx]).Address() != address { + return nil, ErrSymbolNotFound + } + + // The search result is the first symbol with the requested address, + // find any additional symbols with the same address. + syms := []*T{st.sortedSymbols[idx]} + for i := idx + 1; i < len(st.sortedSymbols); i++ { + if (*st.sortedSymbols[i]).Address() != address { + break + } + syms = append(syms, st.sortedSymbols[i]) + } + + return syms, nil +} + +// Find the symbol which contains the given address. +// If multiple symbols at different addresses contain the requested address, +// the symbol with the highest address will be returned. +// If multiple symbols at the same address contain the requested address, +// one of them will be returned, but there is no guarantee which one. +// This function assumes that symbols don't overlap in a way that a symbol with +// a smaller address contains the requested address while a symbol with a larger +// address (but still smaller that requested) doesn't contain it. +// For example, the following situation is assumed to be impossible: +// +// Requested Address +// | +// | +// +---------------+--+ +// |Symbol 1 | | +// +---------------+--+ +// +--------+ | +// |Symbol 2| | +// +--------+ v +// <----------------------> +// +// Smaller Larger +// Address Address +// +// If the above situation happens, no symbol will be returned. +func (st *SymbolTable[T]) LookupByAddressContains(address uint64) (*T, error) { + st.mu.RLock() + defer st.mu.RUnlock() + + // Find the first symbol at an address smaller than or equal to the requested address + idx := sort.Search(len(st.sortedSymbols), + func(i int) bool { + return address >= (*st.sortedSymbols[i]).Address() + }) + + // Not found or the symbol doesn't contain this address + if idx == len(st.sortedSymbols) || !(*st.sortedSymbols[idx]).Contains(address) { + return nil, ErrSymbolNotFound + } + + return st.sortedSymbols[idx], nil +} + +func (st *SymbolTable[T]) ForEachSymbol(callback func(symbol *T)) { + st.mu.RLock() + defer st.mu.RUnlock() + + for i := range len(st.sortedSymbols) { + sym := (*st.sortedSymbols[i]).Clone() + callback(&sym) + } +} diff --git a/pkg/utils/symbol_table_test.go b/pkg/utils/symbol_table_test.go new file mode 100644 index 000000000000..a06077f5de1c --- /dev/null +++ b/pkg/utils/symbol_table_test.go @@ -0,0 +1,344 @@ +package utils + +import ( + "reflect" + "testing" +) + +type testSymbol struct { + name string + addr uint64 + size uint64 +} + +func (s testSymbol) Name() string { + return s.name +} + +func (s testSymbol) Address() uint64 { + return s.addr +} + +func (s testSymbol) Contains(address uint64) bool { + return s.addr <= address && s.addr+s.size > address +} + +func (s testSymbol) Clone() testSymbol { + return testSymbol{ + name: s.name, + addr: s.addr, + size: s.size, + } +} + +// TestNewSymbolTable tests the NewSymbolTable function. +func TestNewSymbolTable(t *testing.T) { + st := NewSymbolTable[testSymbol](true) + if st == nil { + t.Fatalf("NewSymbolTable() returned nil") + } + + if !st.lazyNameLookup { + t.Errorf("lazyNameLookup was not set to true") + } + + if st.sortedSymbols == nil || st.symbolsByName == nil { + t.Errorf("data structures are nil") + } +} + +// TestAddSymbols tests the AddSymbols function +func TestAddSymbols(t *testing.T) { + testCases := []struct { + symbols []*testSymbol + expectedOrder []int + }{ + {[]*testSymbol{ + {name: "symbol1", addr: 1, size: 1}, + {name: "symbol2", addr: 1, size: 1}, + }, []int{0, 1}}, + {[]*testSymbol{ + {name: "symbol1", addr: 2, size: 1}, + {name: "symbol2", addr: 1, size: 1}, + }, []int{0, 1}}, + {[]*testSymbol{ + {name: "symbol1", addr: 1, size: 1}, + {name: "symbol2", addr: 2, size: 1}, + }, []int{1, 0}}, + } + + for _, tc := range testCases { + st := NewSymbolTable[testSymbol](false) + st.AddSymbols(tc.symbols) + + if len(st.sortedSymbols) != len(tc.symbols) { + t.Errorf("len(st.sortedSymbol) = %d, want %d", len(st.sortedSymbols), len(tc.symbols)) + continue + } + + for i := range st.sortedSymbols { + if !reflect.DeepEqual(*st.sortedSymbols[i], *tc.symbols[tc.expectedOrder[i]]) { + t.Errorf("AddSymbols(%v) = symbol %d: %v; want %v", tc.symbols, i, st.sortedSymbols[i], tc.symbols[tc.expectedOrder[i]]) + } + } + } +} + +// TestLookupByName tests the LookupByName function +func TestLookupByName(t *testing.T) { + testCases := []struct { + symbols []*testSymbol + lookupName string + expectLookupError bool + expected []testSymbol + }{ + { + []*testSymbol{{name: "symbol1", addr: 1, size: 1}}, + "symbol1", + false, + []testSymbol{{name: "symbol1", addr: 1, size: 1}}, + }, + { + []*testSymbol{}, + "symbol2", + true, + []testSymbol{}, + }, + { + []*testSymbol{{name: "symbol3", addr: 1, size: 1}}, + "symbol4", + true, + []testSymbol{}, + }, + { + []*testSymbol{{name: "symbol5", addr: 1, size: 1}, {name: "symbol6", addr: 2, size: 2}}, + "symbol6", + false, + []testSymbol{{name: "symbol6", addr: 2, size: 2}}, + }, + { + []*testSymbol{{name: "symbol7", addr: 1, size: 1}, {name: "symbol7", addr: 2, size: 2}}, + "symbol7", + false, + []testSymbol{{name: "symbol7", addr: 1, size: 1}, {name: "symbol7", addr: 2, size: 2}}, + }, + } + + for _, tc := range testCases { + st := NewSymbolTable[testSymbol](false) + st.AddSymbols(tc.symbols) + result, err := st.LookupByName(tc.lookupName) + if !tc.expectLookupError && err != nil { + t.Errorf("LookupByName(%s) failed: %v", tc.lookupName, err) + continue + } else if tc.expectLookupError { + if err == nil { + t.Errorf("LookupByName(%s) expected to fail but didn't", tc.lookupName) + } + continue + } + if !reflect.DeepEqual(copySliceOfPointersToSliceOfStructs(result), tc.expected) { + t.Errorf("LookupByName(%s) = %v, expected %v", tc.lookupName, copySliceOfPointersToSliceOfStructs(result), tc.expected) + } + } +} + +// TestLazyNameLookup tests the lazy name lookup functionality +func TestLazyNameLookup(t *testing.T) { + testCases := []struct { + symbols []*testSymbol + lazyNameLookup bool + lookups []string + expectedMappings []int + }{ + { + []*testSymbol{{name: "symbol", addr: 1, size: 1}}, + false, + []string{}, + []int{0}, + }, + { + []*testSymbol{{name: "symbol", addr: 1, size: 1}}, + true, + []string{}, + []int{}, + }, + { + []*testSymbol{{name: "symbol1", addr: 1, size: 1}, {name: "symbol2", addr: 2, size: 1}}, + true, + []string{"symbol1", "symbol2"}, + []int{0, 1}, + }, + { + []*testSymbol{{name: "symbol1", addr: 1, size: 1}, {name: "symbol2", addr: 2, size: 1}}, + true, + []string{"symbol2"}, + []int{1}, + }, + } + +testLoop: + for _, tc := range testCases { + st := NewSymbolTable[testSymbol](tc.lazyNameLookup) + st.AddSymbols(tc.symbols) + if tc.lazyNameLookup { + if len(st.symbolsByName) != 0 { + t.Errorf("len(st.symbolsByName) = %d, expected 0", len(st.symbolsByName)) + continue + } + } else { + if len(st.symbolsByName) != len(tc.symbols) { + t.Errorf("len(st.symbolsByName) = %d, expected %d", len(st.symbolsByName), len(tc.symbols)) + continue + } + } + for _, lookupName := range tc.lookups { + _, err := st.LookupByName(lookupName) + if err != nil { + t.Errorf("LookupByName(%s) failed: %v", lookupName, err) + continue testLoop + } + } + for i := range tc.expectedMappings { + if !reflect.DeepEqual(*(st.symbolsByName[tc.symbols[tc.expectedMappings[i]].name][0]), *tc.symbols[tc.expectedMappings[i]]) { + t.Errorf("st.symbolsByName[\"%s\"] = %v, expected %v", tc.symbols[tc.expectedMappings[i]].name, *(st.symbolsByName[tc.symbols[tc.expectedMappings[i]].name][0]), *tc.symbols[tc.expectedMappings[i]]) + continue + } + } + } +} + +// TestLookupByAddressExact tests the LookupByAddressExact function +func TestLookupByAddressExact(t *testing.T) { + testCases := []struct { + symbols []*testSymbol + lookupAddr uint64 + expectLookupError bool + expected []testSymbol + }{ + { + []*testSymbol{{name: "symbol1", addr: 1, size: 1}}, + 1, + false, + []testSymbol{{name: "symbol1", addr: 1, size: 1}}, + }, + { + []*testSymbol{}, + 2, + true, + []testSymbol{}, + }, + { + []*testSymbol{{name: "symbol3", addr: 3, size: 1}}, + 4, + true, + []testSymbol{}, + }, + { + []*testSymbol{{name: "symbol5", addr: 5, size: 1}, {name: "symbol6", addr: 6, size: 2}}, + 6, + false, + []testSymbol{{name: "symbol6", addr: 6, size: 2}}, + }, + { + []*testSymbol{{name: "symbol7", addr: 7, size: 1}, {name: "symbol8", addr: 7, size: 2}}, + 7, + false, + []testSymbol{{name: "symbol7", addr: 7, size: 1}, {name: "symbol8", addr: 7, size: 2}}, + }, + } + + for _, tc := range testCases { + st := NewSymbolTable[testSymbol](false) + st.AddSymbols(tc.symbols) + result, err := st.LookupByAddressExact(tc.lookupAddr) + if !tc.expectLookupError && err != nil { + t.Errorf("LookupByAddressExact(%d) failed: %v", tc.lookupAddr, err) + continue + } else if tc.expectLookupError && err == nil { + t.Errorf("LookupByAddressExact(%d) expected to fail but didn't", tc.lookupAddr) + continue + } + if !reflect.DeepEqual(copySliceOfPointersToSliceOfStructs(result), tc.expected) { + t.Errorf("LookupByAddressExact(%d) = %v, expected %v", tc.lookupAddr, copySliceOfPointersToSliceOfStructs(result), tc.expected) + } + } +} + +// TestLookupByAddressContains tests the LookupByAddressContains function +func TestLookupByAddressContains(t *testing.T) { + testCases := []struct { + symbols []*testSymbol + lookupAddr uint64 + expected *testSymbol + }{ + { + []*testSymbol{}, + 1, + nil, + }, + { + []*testSymbol{{name: "symbol1", addr: 2, size: 2}}, + 2, + &testSymbol{name: "symbol1", addr: 2, size: 2}, + }, + { + []*testSymbol{{name: "symbol2", addr: 3, size: 2}}, + 4, + &testSymbol{name: "symbol2", addr: 3, size: 2}, + }, + { + []*testSymbol{{name: "symbol3", addr: 4, size: 2}}, + 6, + nil, + }, + { + []*testSymbol{{name: "symbol4", addr: 10, size: 2}}, + 8, + nil, + }, + { + []*testSymbol{{name: "symbol5", addr: 11, size: 2}}, + 14, + nil, + }, + { + []*testSymbol{{name: "symbol6", addr: 15, size: 5}, {name: "symbol7", addr: 17, size: 3}}, + 18, + &testSymbol{name: "symbol7", addr: 17, size: 3}, + }, + { // this is a special case assumed to be impossible in practice, see the docstring of LookupByAddressContains() + []*testSymbol{{name: "symbol8", addr: 20, size: 5}, {name: "symbol9", addr: 21, size: 2}}, + 23, + nil, + }, + } + + for _, tc := range testCases { + st := NewSymbolTable[testSymbol](false) + st.AddSymbols(tc.symbols) + result, err := st.LookupByAddressContains(tc.lookupAddr) + if tc.expected != nil && err != nil { + t.Errorf("LookupByAddressContains(%d) failed: %v", tc.lookupAddr, err) + continue + } + if tc.expected == nil { + if err == nil { + t.Errorf("LookupByAddressContains(%d) expected to fail, but returned %v", tc.lookupAddr, *result) + } + continue + } + if !reflect.DeepEqual(*result, *tc.expected) { + t.Errorf("LookupByAddressContains(%d) = %v, expected %v", tc.lookupAddr, *result, *tc.expected) + } + } +} + +// copySliceOfPointersToSliceOfStructs converts a slice of pointers to a slice of structs. +func copySliceOfPointersToSliceOfStructs(s []*testSymbol) []testSymbol { + ret := make([]testSymbol, len(s)) + for i, v := range s { + ret[i] = *v + } + return ret +} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 3a88eb74232b..e72529dcb34f 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -5,10 +5,7 @@ import ( "io" "math/rand" "reflect" - "strings" "time" - - "github.com/aquasecurity/tracee/pkg/utils/environment" ) // Cloner is a generic interface for objects that can clone themselves. @@ -25,23 +22,6 @@ type Iterator[T any] interface { Next() T } -func ParseSymbol(address uint64, table *environment.KernelSymbolTable) environment.KernelSymbol { - var hookingFunction environment.KernelSymbol - - symbols, err := table.GetSymbolByAddr(address) - if err != nil { - hookingFunction = environment.KernelSymbol{} - hookingFunction.Owner = "hidden" - } else { - hookingFunction = symbols[0] - } - - hookingFunction.Owner = strings.TrimPrefix(hookingFunction.Owner, "[") - hookingFunction.Owner = strings.TrimSuffix(hookingFunction.Owner, "]") - - return hookingFunction -} - func HasBit(n uint64, offset uint) bool { return (n & (1 << offset)) > 0 } diff --git a/signatures/golang/anti_debugging_ptraceme.go b/signatures/golang/anti_debugging_ptraceme.go index 9b2fb22afda8..95e80b2b117c 100644 --- a/signatures/golang/anti_debugging_ptraceme.go +++ b/signatures/golang/anti_debugging_ptraceme.go @@ -3,6 +3,7 @@ package main import ( "fmt" + "github.com/aquasecurity/tracee/pkg/events/parsers" "github.com/aquasecurity/tracee/signatures/helpers" "github.com/aquasecurity/tracee/types/detect" "github.com/aquasecurity/tracee/types/protocol" @@ -11,12 +12,12 @@ import ( type AntiDebuggingPtraceme struct { cb detect.SignatureHandler - ptraceTraceMe string + ptraceTraceMe int } func (sig *AntiDebuggingPtraceme) Init(ctx detect.SignatureContext) error { sig.cb = ctx.Callback - sig.ptraceTraceMe = "PTRACE_TRACEME" + sig.ptraceTraceMe = int(parsers.PTRACE_TRACEME.Value()) return nil } @@ -52,7 +53,7 @@ func (sig *AntiDebuggingPtraceme) OnEvent(event protocol.Event) error { switch eventObj.EventName { case "ptrace": - requestArg, err := helpers.GetTraceeStringArgumentByName(eventObj, "request") + requestArg, err := helpers.GetTraceeIntArgumentByName(eventObj, "request") if err != nil { return err } diff --git a/signatures/golang/anti_debugging_ptraceme_test.go b/signatures/golang/anti_debugging_ptraceme_test.go index 39139edb4e41..3d82108d65ce 100644 --- a/signatures/golang/anti_debugging_ptraceme_test.go +++ b/signatures/golang/anti_debugging_ptraceme_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/tracee/pkg/events/parsers" "github.com/aquasecurity/tracee/signatures/signaturestest" "github.com/aquasecurity/tracee/types/detect" "github.com/aquasecurity/tracee/types/trace" @@ -29,7 +30,7 @@ func TestAntiDebuggingPtraceme(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "request", }, - Value: interface{}("PTRACE_TRACEME"), + Value: interface{}(int64(parsers.PTRACE_TRACEME.Value())), }, }, }, @@ -44,7 +45,7 @@ func TestAntiDebuggingPtraceme(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "request", }, - Value: interface{}("PTRACE_TRACEME"), + Value: interface{}(int64(parsers.PTRACE_TRACEME.Value())), }, }, }.ToProtocol(), @@ -76,7 +77,7 @@ func TestAntiDebuggingPtraceme(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "request", }, - Value: interface{}("PTRACE_PEEKTEXT"), + Value: interface{}(int64(parsers.PTRACE_PEEKTEXT.Value())), }, }, }, diff --git a/signatures/golang/aslr_inspection.go b/signatures/golang/aslr_inspection.go index e16855f980a8..87f88989356f 100644 --- a/signatures/golang/aslr_inspection.go +++ b/signatures/golang/aslr_inspection.go @@ -57,7 +57,7 @@ func (sig *AslrInspection) OnEvent(event protocol.Event) error { return err } - flags, err := helpers.GetTraceeStringArgumentByName(eventObj, "flags") + flags, err := helpers.GetTraceeIntArgumentByName(eventObj, "flags") if err != nil { return err } diff --git a/signatures/golang/aslr_inspection_test.go b/signatures/golang/aslr_inspection_test.go index 39264499e6a3..e8e3b39ed98c 100644 --- a/signatures/golang/aslr_inspection_test.go +++ b/signatures/golang/aslr_inspection_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/tracee/pkg/events/parsers" "github.com/aquasecurity/tracee/signatures/signaturestest" "github.com/aquasecurity/tracee/types/detect" "github.com/aquasecurity/tracee/types/trace" @@ -29,7 +30,7 @@ func TestAslrInspection(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_RDONLY"), + Value: interface{}(buildFlagArgValue(parsers.O_RDONLY)), }, { ArgMeta: trace.ArgMeta{ @@ -50,7 +51,7 @@ func TestAslrInspection(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_RDONLY"), + Value: interface{}(buildFlagArgValue(parsers.O_RDONLY)), }, { ArgMeta: trace.ArgMeta{ @@ -94,7 +95,7 @@ func TestAslrInspection(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: interface{}(buildFlagArgValue(parsers.O_WRONLY)), }, }, }, @@ -111,7 +112,7 @@ func TestAslrInspection(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_RDONLY"), + Value: interface{}(buildFlagArgValue(parsers.O_RDONLY)), }, { ArgMeta: trace.ArgMeta{ diff --git a/signatures/golang/cgroup_notify_on_release_modification.go b/signatures/golang/cgroup_notify_on_release_modification.go index 1f018e0cdd28..ef5057976f04 100644 --- a/signatures/golang/cgroup_notify_on_release_modification.go +++ b/signatures/golang/cgroup_notify_on_release_modification.go @@ -59,7 +59,7 @@ func (sig *CgroupNotifyOnReleaseModification) OnEvent(event protocol.Event) erro } basename := path.Base(pathname) - flags, err := helpers.GetTraceeStringArgumentByName(eventObj, "flags") + flags, err := helpers.GetTraceeIntArgumentByName(eventObj, "flags") if err != nil { return err } diff --git a/signatures/golang/cgroup_notify_on_release_modification_test.go b/signatures/golang/cgroup_notify_on_release_modification_test.go index 661f77b149ad..d6507a3f65ff 100644 --- a/signatures/golang/cgroup_notify_on_release_modification_test.go +++ b/signatures/golang/cgroup_notify_on_release_modification_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/tracee/pkg/events/parsers" "github.com/aquasecurity/tracee/signatures/signaturestest" "github.com/aquasecurity/tracee/types/detect" "github.com/aquasecurity/tracee/types/trace" @@ -35,7 +36,7 @@ func TestCgroupNotifyOnReleaseModification(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: interface{}(buildFlagArgValue(parsers.O_WRONLY)), }, }, }, @@ -56,7 +57,7 @@ func TestCgroupNotifyOnReleaseModification(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: interface{}(buildFlagArgValue(parsers.O_WRONLY)), }, }, }.ToProtocol(), @@ -94,7 +95,7 @@ func TestCgroupNotifyOnReleaseModification(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_RDONLY"), + Value: interface{}(buildFlagArgValue(parsers.O_RDONLY)), }, }, }, @@ -117,7 +118,7 @@ func TestCgroupNotifyOnReleaseModification(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: interface{}(buildFlagArgValue(parsers.O_WRONLY)), }, }, }, diff --git a/signatures/golang/cgroup_release_agent_modification.go b/signatures/golang/cgroup_release_agent_modification.go index 87685fcf2cad..fd9fa537a93e 100644 --- a/signatures/golang/cgroup_release_agent_modification.go +++ b/signatures/golang/cgroup_release_agent_modification.go @@ -56,7 +56,7 @@ func (sig *CgroupReleaseAgentModification) OnEvent(event protocol.Event) error { switch eventObj.EventName { case "security_file_open": - flags, err := helpers.GetTraceeStringArgumentByName(eventObj, "flags") + flags, err := helpers.GetTraceeIntArgumentByName(eventObj, "flags") if err != nil { return err } diff --git a/signatures/golang/cgroup_release_agent_modification_test.go b/signatures/golang/cgroup_release_agent_modification_test.go index 262e33a31218..bce02349d280 100644 --- a/signatures/golang/cgroup_release_agent_modification_test.go +++ b/signatures/golang/cgroup_release_agent_modification_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/tracee/pkg/events/parsers" "github.com/aquasecurity/tracee/signatures/signaturestest" "github.com/aquasecurity/tracee/types/detect" "github.com/aquasecurity/tracee/types/trace" @@ -35,7 +36,7 @@ func TestCgroupReleaseAgentModification(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: interface{}(buildFlagArgValue(parsers.O_WRONLY)), }, }, }, @@ -56,7 +57,7 @@ func TestCgroupReleaseAgentModification(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: interface{}(buildFlagArgValue(parsers.O_WRONLY)), }, }, }.ToProtocol(), @@ -141,7 +142,7 @@ func TestCgroupReleaseAgentModification(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_RDONLY"), + Value: interface{}(buildFlagArgValue(parsers.O_RDONLY)), }, }, }, @@ -164,7 +165,7 @@ func TestCgroupReleaseAgentModification(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: interface{}(buildFlagArgValue(parsers.O_WRONLY)), }, }, }, diff --git a/signatures/golang/core_pattern_modification.go b/signatures/golang/core_pattern_modification.go index 5672a56c5629..4949ab7c7752 100644 --- a/signatures/golang/core_pattern_modification.go +++ b/signatures/golang/core_pattern_modification.go @@ -58,7 +58,7 @@ func (sig *CorePatternModification) OnEvent(event protocol.Event) error { return err } - flags, err := helpers.GetTraceeStringArgumentByName(eventObj, "flags") + flags, err := helpers.GetTraceeIntArgumentByName(eventObj, "flags") if err != nil { return err } diff --git a/signatures/golang/core_pattern_modification_test.go b/signatures/golang/core_pattern_modification_test.go index d2877db9b7e9..9c1d26b61808 100644 --- a/signatures/golang/core_pattern_modification_test.go +++ b/signatures/golang/core_pattern_modification_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/tracee/pkg/events/parsers" "github.com/aquasecurity/tracee/signatures/signaturestest" "github.com/aquasecurity/tracee/types/detect" "github.com/aquasecurity/tracee/types/trace" @@ -35,7 +36,7 @@ func TestCorePatternModification(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: buildFlagArgValue(parsers.O_WRONLY), }, }, }, @@ -56,7 +57,7 @@ func TestCorePatternModification(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: buildFlagArgValue(parsers.O_WRONLY), }, }, }.ToProtocol(), @@ -94,7 +95,7 @@ func TestCorePatternModification(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_RDONLY"), + Value: buildFlagArgValue(parsers.O_RDONLY), }, }, }, @@ -117,7 +118,7 @@ func TestCorePatternModification(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: buildFlagArgValue(parsers.O_WRONLY), }, }, }, diff --git a/signatures/golang/default_loader_modification.go b/signatures/golang/default_loader_modification.go index d2a2df58c1e0..738953d81fc0 100644 --- a/signatures/golang/default_loader_modification.go +++ b/signatures/golang/default_loader_modification.go @@ -59,7 +59,7 @@ func (sig *DefaultLoaderModification) OnEvent(event protocol.Event) error { switch eventObj.EventName { case "security_file_open": - flags, err := helpers.GetTraceeStringArgumentByName(eventObj, "flags") + flags, err := helpers.GetTraceeIntArgumentByName(eventObj, "flags") if err != nil { return err } diff --git a/signatures/golang/default_loader_modification_test.go b/signatures/golang/default_loader_modification_test.go index a33e2300896d..094b70f8fafb 100644 --- a/signatures/golang/default_loader_modification_test.go +++ b/signatures/golang/default_loader_modification_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/tracee/pkg/events/parsers" "github.com/aquasecurity/tracee/signatures/signaturestest" "github.com/aquasecurity/tracee/types/detect" "github.com/aquasecurity/tracee/types/trace" @@ -35,7 +36,7 @@ func TestDefaultLoaderModification(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: buildFlagArgValue(parsers.O_WRONLY), }, }, }, @@ -56,7 +57,7 @@ func TestDefaultLoaderModification(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: buildFlagArgValue(parsers.O_WRONLY), }, }, }.ToProtocol(), @@ -141,7 +142,7 @@ func TestDefaultLoaderModification(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_RDONLY"), + Value: buildFlagArgValue(parsers.O_RDONLY), }, }, }, @@ -164,7 +165,7 @@ func TestDefaultLoaderModification(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: buildFlagArgValue(parsers.O_WRONLY), }, }, }, diff --git a/signatures/golang/docker_abuse.go b/signatures/golang/docker_abuse.go index 9671f763c56c..7d2bfa3b11bd 100644 --- a/signatures/golang/docker_abuse.go +++ b/signatures/golang/docker_abuse.go @@ -61,7 +61,7 @@ func (sig *DockerAbuse) OnEvent(event protocol.Event) error { return err } - flags, err := helpers.GetTraceeStringArgumentByName(eventObj, "flags") + flags, err := helpers.GetTraceeIntArgumentByName(eventObj, "flags") if err != nil { return err } diff --git a/signatures/golang/docker_abuse_test.go b/signatures/golang/docker_abuse_test.go index 5ba66e94b766..24fde9746bba 100644 --- a/signatures/golang/docker_abuse_test.go +++ b/signatures/golang/docker_abuse_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/tracee/pkg/events/parsers" "github.com/aquasecurity/tracee/signatures/signaturestest" "github.com/aquasecurity/tracee/types/detect" "github.com/aquasecurity/tracee/types/trace" @@ -30,7 +31,7 @@ func TestDockerAbuse(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: buildFlagArgValue(parsers.O_WRONLY), }, { ArgMeta: trace.ArgMeta{ @@ -52,7 +53,7 @@ func TestDockerAbuse(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: buildFlagArgValue(parsers.O_WRONLY), }, { ArgMeta: trace.ArgMeta{ @@ -140,7 +141,7 @@ func TestDockerAbuse(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: buildFlagArgValue(parsers.O_WRONLY), }, { ArgMeta: trace.ArgMeta{ @@ -164,7 +165,7 @@ func TestDockerAbuse(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_RDONLY"), + Value: buildFlagArgValue(parsers.O_RDONLY), }, { ArgMeta: trace.ArgMeta{ diff --git a/signatures/golang/dynamic_code_loading.go b/signatures/golang/dynamic_code_loading.go index be148f9b5484..7e1245cd1513 100644 --- a/signatures/golang/dynamic_code_loading.go +++ b/signatures/golang/dynamic_code_loading.go @@ -11,12 +11,12 @@ import ( type DynamicCodeLoading struct { cb detect.SignatureHandler - alertText string + alertType trace.MemProtAlert } func (sig *DynamicCodeLoading) Init(ctx detect.SignatureContext) error { sig.cb = ctx.Callback - sig.alertText = "Protection changed from W to E!" + sig.alertType = trace.ProtAlertMprotectWXToX return nil } @@ -52,12 +52,13 @@ func (sig *DynamicCodeLoading) OnEvent(event protocol.Event) error { switch eventObj.EventName { case "mem_prot_alert": - alert, err := helpers.GetTraceeStringArgumentByName(eventObj, "alert") + alert, err := helpers.GetTraceeUintArgumentByName(eventObj, "alert") if err != nil { return err } + memProtAlert := trace.MemProtAlert(alert) - if alert == sig.alertText { + if memProtAlert == sig.alertType { metadata, err := sig.GetMetadata() if err != nil { return err diff --git a/signatures/golang/dynamic_code_loading_test.go b/signatures/golang/dynamic_code_loading_test.go index b7dec8a46208..51ab065f5056 100644 --- a/signatures/golang/dynamic_code_loading_test.go +++ b/signatures/golang/dynamic_code_loading_test.go @@ -29,7 +29,7 @@ func TestDynamicCodeLoading(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "alert", }, - Value: interface{}("Protection changed from W to E!"), + Value: uint32(trace.ProtAlertMprotectWXToX), }, }, }, @@ -44,7 +44,7 @@ func TestDynamicCodeLoading(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "alert", }, - Value: interface{}("Protection changed from W to E!"), + Value: uint32(trace.ProtAlertMprotectWXToX), }, }, }.ToProtocol(), @@ -76,7 +76,7 @@ func TestDynamicCodeLoading(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "alert", }, - Value: interface{}("Protection changed to Executable!"), + Value: uint32(trace.ProtAlertMmapWX), }, }, }, diff --git a/signatures/golang/k8s_service_account_token.go b/signatures/golang/k8s_service_account_token.go index d721b3ac83c7..4e861b41629b 100644 --- a/signatures/golang/k8s_service_account_token.go +++ b/signatures/golang/k8s_service_account_token.go @@ -70,7 +70,7 @@ func (sig *K8SServiceAccountToken) OnEvent(event protocol.Event) error { return err } - flags, err := helpers.GetTraceeStringArgumentByName(eventObj, "flags") + flags, err := helpers.GetTraceeIntArgumentByName(eventObj, "flags") if err != nil { return err } diff --git a/signatures/golang/k8s_service_account_token_test.go b/signatures/golang/k8s_service_account_token_test.go index 57e46a05eb4f..b06accf1c869 100644 --- a/signatures/golang/k8s_service_account_token_test.go +++ b/signatures/golang/k8s_service_account_token_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/tracee/pkg/events/parsers" "github.com/aquasecurity/tracee/signatures/signaturestest" "github.com/aquasecurity/tracee/types/detect" "github.com/aquasecurity/tracee/types/trace" @@ -30,7 +31,7 @@ func TestK8SServiceAccountToken(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_RDONLY"), + Value: buildFlagArgValue(parsers.O_RDONLY), }, { ArgMeta: trace.ArgMeta{ @@ -52,7 +53,7 @@ func TestK8SServiceAccountToken(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_RDONLY"), + Value: buildFlagArgValue(parsers.O_RDONLY), }, { ArgMeta: trace.ArgMeta{ @@ -91,7 +92,7 @@ func TestK8SServiceAccountToken(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: buildFlagArgValue(parsers.O_WRONLY), }, { ArgMeta: trace.ArgMeta{ @@ -115,7 +116,7 @@ func TestK8SServiceAccountToken(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_RDONLY"), + Value: buildFlagArgValue(parsers.O_RDONLY), }, { ArgMeta: trace.ArgMeta{ @@ -139,7 +140,7 @@ func TestK8SServiceAccountToken(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_RDONLY"), + Value: buildFlagArgValue(parsers.O_RDONLY), }, { ArgMeta: trace.ArgMeta{ diff --git a/signatures/golang/kubernetes_certificate_theft_attempt.go b/signatures/golang/kubernetes_certificate_theft_attempt.go index 847b5b388c0f..aeb1b7389721 100644 --- a/signatures/golang/kubernetes_certificate_theft_attempt.go +++ b/signatures/golang/kubernetes_certificate_theft_attempt.go @@ -65,7 +65,7 @@ func (sig *KubernetesCertificateTheftAttempt) OnEvent(event protocol.Event) erro } } - flags, err := helpers.GetTraceeStringArgumentByName(eventObj, "flags") + flags, err := helpers.GetTraceeIntArgumentByName(eventObj, "flags") if err != nil { return err } diff --git a/signatures/golang/kubernetes_certificate_theft_attempt_test.go b/signatures/golang/kubernetes_certificate_theft_attempt_test.go index 852642d768d8..b5f8b35a1121 100644 --- a/signatures/golang/kubernetes_certificate_theft_attempt_test.go +++ b/signatures/golang/kubernetes_certificate_theft_attempt_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/tracee/pkg/events/parsers" "github.com/aquasecurity/tracee/signatures/signaturestest" "github.com/aquasecurity/tracee/types/detect" "github.com/aquasecurity/tracee/types/trace" @@ -30,7 +31,7 @@ func TestKubernetesCertificateTheftAttempt(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_RDONLY"), + Value: buildFlagArgValue(parsers.O_RDONLY), }, { ArgMeta: trace.ArgMeta{ @@ -52,7 +53,7 @@ func TestKubernetesCertificateTheftAttempt(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_RDONLY"), + Value: buildFlagArgValue(parsers.O_RDONLY), }, { ArgMeta: trace.ArgMeta{ @@ -138,7 +139,7 @@ func TestKubernetesCertificateTheftAttempt(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: buildFlagArgValue(parsers.O_WRONLY), }, { ArgMeta: trace.ArgMeta{ @@ -162,7 +163,7 @@ func TestKubernetesCertificateTheftAttempt(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_RDONLY"), + Value: buildFlagArgValue(parsers.O_RDONLY), }, { ArgMeta: trace.ArgMeta{ @@ -186,7 +187,7 @@ func TestKubernetesCertificateTheftAttempt(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_RDONLY"), + Value: buildFlagArgValue(parsers.O_RDONLY), }, { ArgMeta: trace.ArgMeta{ diff --git a/signatures/golang/ld_preload.go b/signatures/golang/ld_preload.go index 181e6ab27dbe..6ff59e806eb2 100644 --- a/signatures/golang/ld_preload.go +++ b/signatures/golang/ld_preload.go @@ -85,7 +85,7 @@ func (sig *LdPreload) OnEvent(event protocol.Event) error { return err } - flags, err := helpers.GetTraceeStringArgumentByName(eventObj, "flags") + flags, err := helpers.GetTraceeIntArgumentByName(eventObj, "flags") if err != nil { return err } diff --git a/signatures/golang/ld_preload_test.go b/signatures/golang/ld_preload_test.go index 4d884cbf4c51..42ae921dd3bb 100644 --- a/signatures/golang/ld_preload_test.go +++ b/signatures/golang/ld_preload_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/tracee/pkg/events/parsers" "github.com/aquasecurity/tracee/signatures/signaturestest" "github.com/aquasecurity/tracee/types/detect" "github.com/aquasecurity/tracee/types/trace" @@ -29,7 +30,7 @@ func TestLdPreload(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: buildFlagArgValue(parsers.O_WRONLY), }, { ArgMeta: trace.ArgMeta{ @@ -50,7 +51,7 @@ func TestLdPreload(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: buildFlagArgValue(parsers.O_WRONLY), }, { ArgMeta: trace.ArgMeta{ @@ -194,7 +195,7 @@ func TestLdPreload(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: buildFlagArgValue(parsers.O_WRONLY), }, { ArgMeta: trace.ArgMeta{ @@ -217,7 +218,7 @@ func TestLdPreload(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_RDONLY"), + Value: buildFlagArgValue(parsers.O_RDONLY), }, { ArgMeta: trace.ArgMeta{ diff --git a/signatures/golang/proc_kcore_read.go b/signatures/golang/proc_kcore_read.go index 7a39d91f0573..29400b527f23 100644 --- a/signatures/golang/proc_kcore_read.go +++ b/signatures/golang/proc_kcore_read.go @@ -58,7 +58,7 @@ func (sig *ProcKcoreRead) OnEvent(event protocol.Event) error { return err } - flags, err := helpers.GetTraceeStringArgumentByName(eventObj, "flags") + flags, err := helpers.GetTraceeIntArgumentByName(eventObj, "flags") if err != nil { return err } diff --git a/signatures/golang/proc_kcore_read_test.go b/signatures/golang/proc_kcore_read_test.go index 6e05960bb7ec..c6ad61f1ae77 100644 --- a/signatures/golang/proc_kcore_read_test.go +++ b/signatures/golang/proc_kcore_read_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/tracee/pkg/events/parsers" "github.com/aquasecurity/tracee/signatures/signaturestest" "github.com/aquasecurity/tracee/types/detect" "github.com/aquasecurity/tracee/types/trace" @@ -29,7 +30,7 @@ func TestProcKcoreRead(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_RDONLY"), + Value: buildFlagArgValue(parsers.O_RDONLY), }, { ArgMeta: trace.ArgMeta{ @@ -50,7 +51,7 @@ func TestProcKcoreRead(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_RDONLY"), + Value: buildFlagArgValue(parsers.O_RDONLY), }, { ArgMeta: trace.ArgMeta{ @@ -94,7 +95,7 @@ func TestProcKcoreRead(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: buildFlagArgValue(parsers.O_WRONLY), }, }, }, @@ -117,7 +118,7 @@ func TestProcKcoreRead(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_RDONLY"), + Value: buildFlagArgValue(parsers.O_RDONLY), }, }, }, diff --git a/signatures/golang/proc_mem_access.go b/signatures/golang/proc_mem_access.go index e1f80b911067..a742ec9e3813 100644 --- a/signatures/golang/proc_mem_access.go +++ b/signatures/golang/proc_mem_access.go @@ -61,7 +61,7 @@ func (sig *ProcMemAccess) OnEvent(event protocol.Event) error { return err } - flags, err := helpers.GetTraceeStringArgumentByName(eventObj, "flags") + flags, err := helpers.GetTraceeIntArgumentByName(eventObj, "flags") if err != nil { return err } diff --git a/signatures/golang/proc_mem_access_test.go b/signatures/golang/proc_mem_access_test.go index 1fbd0ee93816..4c262cafd792 100644 --- a/signatures/golang/proc_mem_access_test.go +++ b/signatures/golang/proc_mem_access_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/tracee/pkg/events/parsers" "github.com/aquasecurity/tracee/signatures/signaturestest" "github.com/aquasecurity/tracee/types/detect" "github.com/aquasecurity/tracee/types/trace" @@ -29,7 +30,7 @@ func TestProcMemAccess(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_RDONLY"), + Value: buildFlagArgValue(parsers.O_RDONLY), }, { ArgMeta: trace.ArgMeta{ @@ -50,7 +51,7 @@ func TestProcMemAccess(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_RDONLY"), + Value: buildFlagArgValue(parsers.O_RDONLY), }, { ArgMeta: trace.ArgMeta{ @@ -88,7 +89,7 @@ func TestProcMemAccess(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: buildFlagArgValue(parsers.O_WRONLY), }, { ArgMeta: trace.ArgMeta{ @@ -111,7 +112,7 @@ func TestProcMemAccess(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_RDONLY"), + Value: buildFlagArgValue(parsers.O_RDONLY), }, { ArgMeta: trace.ArgMeta{ diff --git a/signatures/golang/proc_mem_code_injection.go b/signatures/golang/proc_mem_code_injection.go index 6dc7d84bfb17..4e935d36d578 100644 --- a/signatures/golang/proc_mem_code_injection.go +++ b/signatures/golang/proc_mem_code_injection.go @@ -61,7 +61,7 @@ func (sig *ProcMemCodeInjection) OnEvent(event protocol.Event) error { return err } - flags, err := helpers.GetTraceeStringArgumentByName(eventObj, "flags") + flags, err := helpers.GetTraceeIntArgumentByName(eventObj, "flags") if err != nil { return err } diff --git a/signatures/golang/proc_mem_code_injection_test.go b/signatures/golang/proc_mem_code_injection_test.go index 90a20c8b8e2f..92600803d1b8 100644 --- a/signatures/golang/proc_mem_code_injection_test.go +++ b/signatures/golang/proc_mem_code_injection_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/tracee/pkg/events/parsers" "github.com/aquasecurity/tracee/signatures/signaturestest" "github.com/aquasecurity/tracee/types/detect" "github.com/aquasecurity/tracee/types/trace" @@ -29,7 +30,7 @@ func TestProcMemCodeInjection(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: buildFlagArgValue(parsers.O_WRONLY), }, { ArgMeta: trace.ArgMeta{ @@ -50,7 +51,7 @@ func TestProcMemCodeInjection(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: buildFlagArgValue(parsers.O_WRONLY), }, { ArgMeta: trace.ArgMeta{ @@ -88,7 +89,7 @@ func TestProcMemCodeInjection(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_RDONLY"), + Value: buildFlagArgValue(parsers.O_RDONLY), }, { ArgMeta: trace.ArgMeta{ @@ -111,7 +112,7 @@ func TestProcMemCodeInjection(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: buildFlagArgValue(parsers.O_WRONLY), }, { ArgMeta: trace.ArgMeta{ diff --git a/signatures/golang/ptrace_code_injection.go b/signatures/golang/ptrace_code_injection.go index 593701d4903b..b6eb6ceeb1d5 100644 --- a/signatures/golang/ptrace_code_injection.go +++ b/signatures/golang/ptrace_code_injection.go @@ -3,6 +3,7 @@ package main import ( "fmt" + "github.com/aquasecurity/tracee/pkg/events/parsers" "github.com/aquasecurity/tracee/signatures/helpers" "github.com/aquasecurity/tracee/types/detect" "github.com/aquasecurity/tracee/types/protocol" @@ -11,14 +12,14 @@ import ( type PtraceCodeInjection struct { cb detect.SignatureHandler - ptracePokeText string - ptracePokeData string + ptracePokeText int + ptracePokeData int } func (sig *PtraceCodeInjection) Init(ctx detect.SignatureContext) error { sig.cb = ctx.Callback - sig.ptracePokeText = "PTRACE_POKETEXT" - sig.ptracePokeData = "PTRACE_POKEDATA" + sig.ptracePokeText = int(parsers.PTRACE_POKETEXT.Value()) + sig.ptracePokeData = int(parsers.PTRACE_POKEDATA.Value()) return nil } @@ -54,7 +55,7 @@ func (sig *PtraceCodeInjection) OnEvent(event protocol.Event) error { switch eventObj.EventName { case "ptrace": - requestArg, err := helpers.GetTraceeStringArgumentByName(eventObj, "request") + requestArg, err := helpers.GetTraceeIntArgumentByName(eventObj, "request") if err != nil { return err } diff --git a/signatures/golang/ptrace_code_injection_test.go b/signatures/golang/ptrace_code_injection_test.go index d882a4365b16..5f1424e25ebb 100644 --- a/signatures/golang/ptrace_code_injection_test.go +++ b/signatures/golang/ptrace_code_injection_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/tracee/pkg/events/parsers" "github.com/aquasecurity/tracee/signatures/signaturestest" "github.com/aquasecurity/tracee/types/detect" "github.com/aquasecurity/tracee/types/trace" @@ -29,7 +30,7 @@ func TestPtraceCodeInjection(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "request", }, - Value: interface{}("PTRACE_POKETEXT"), + Value: int32(parsers.PTRACE_POKETEXT.Value()), }, }, }, @@ -44,7 +45,7 @@ func TestPtraceCodeInjection(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "request", }, - Value: interface{}("PTRACE_POKETEXT"), + Value: int32(parsers.PTRACE_POKETEXT.Value()), }, }, }.ToProtocol(), @@ -76,7 +77,7 @@ func TestPtraceCodeInjection(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "request", }, - Value: interface{}("PTRACE_POKEDATA"), + Value: int32(parsers.PTRACE_POKEDATA.Value()), }, }, }, @@ -91,7 +92,7 @@ func TestPtraceCodeInjection(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "request", }, - Value: interface{}("PTRACE_POKEDATA"), + Value: int32(parsers.PTRACE_POKEDATA.Value()), }, }, }.ToProtocol(), @@ -123,7 +124,7 @@ func TestPtraceCodeInjection(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "request", }, - Value: interface{}("PTRACE_PEEKTEXT"), + Value: int32(parsers.PTRACE_PEEKTEXT.Value()), }, }, }, diff --git a/signatures/golang/rcd_modification.go b/signatures/golang/rcd_modification.go index fa4e0cdba68a..336338f96986 100644 --- a/signatures/golang/rcd_modification.go +++ b/signatures/golang/rcd_modification.go @@ -65,7 +65,7 @@ func (sig *RcdModification) OnEvent(event protocol.Event) error { return err } - flags, err := helpers.GetTraceeStringArgumentByName(eventObj, "flags") + flags, err := helpers.GetTraceeIntArgumentByName(eventObj, "flags") if err != nil { return err } diff --git a/signatures/golang/rcd_modification_test.go b/signatures/golang/rcd_modification_test.go index eae82c3fb26f..ba1f5131776f 100644 --- a/signatures/golang/rcd_modification_test.go +++ b/signatures/golang/rcd_modification_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/tracee/pkg/events/parsers" "github.com/aquasecurity/tracee/signatures/signaturestest" "github.com/aquasecurity/tracee/types/detect" "github.com/aquasecurity/tracee/types/trace" @@ -29,7 +30,7 @@ func TestRcdModification(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: buildFlagArgValue(parsers.O_WRONLY), }, { ArgMeta: trace.ArgMeta{ @@ -50,7 +51,7 @@ func TestRcdModification(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: buildFlagArgValue(parsers.O_WRONLY), }, { ArgMeta: trace.ArgMeta{ @@ -88,7 +89,7 @@ func TestRcdModification(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: buildFlagArgValue(parsers.O_WRONLY), }, { ArgMeta: trace.ArgMeta{ @@ -109,7 +110,7 @@ func TestRcdModification(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: buildFlagArgValue(parsers.O_WRONLY), }, { ArgMeta: trace.ArgMeta{ @@ -288,7 +289,7 @@ func TestRcdModification(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: buildFlagArgValue(parsers.O_WRONLY), }, { ArgMeta: trace.ArgMeta{ @@ -311,7 +312,7 @@ func TestRcdModification(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_RDONLY"), + Value: buildFlagArgValue(parsers.O_RDONLY), }, { ArgMeta: trace.ArgMeta{ diff --git a/signatures/golang/sched_debug_recon.go b/signatures/golang/sched_debug_recon.go index 6c10283fbb44..88335f820c50 100644 --- a/signatures/golang/sched_debug_recon.go +++ b/signatures/golang/sched_debug_recon.go @@ -57,7 +57,7 @@ func (sig *SchedDebugRecon) OnEvent(event protocol.Event) error { return err } - flags, err := helpers.GetTraceeStringArgumentByName(eventObj, "flags") + flags, err := helpers.GetTraceeIntArgumentByName(eventObj, "flags") if err != nil { return err } diff --git a/signatures/golang/sched_debug_recon_test.go b/signatures/golang/sched_debug_recon_test.go index 51db29b0d3eb..8337184f45d2 100644 --- a/signatures/golang/sched_debug_recon_test.go +++ b/signatures/golang/sched_debug_recon_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/tracee/pkg/events/parsers" "github.com/aquasecurity/tracee/signatures/signaturestest" "github.com/aquasecurity/tracee/types/detect" "github.com/aquasecurity/tracee/types/trace" @@ -29,7 +30,7 @@ func TestSchedDebugRecon(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_RDONLY"), + Value: buildFlagArgValue(parsers.O_RDONLY), }, { ArgMeta: trace.ArgMeta{ @@ -50,7 +51,7 @@ func TestSchedDebugRecon(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_RDONLY"), + Value: buildFlagArgValue(parsers.O_RDONLY), }, { ArgMeta: trace.ArgMeta{ @@ -88,7 +89,7 @@ func TestSchedDebugRecon(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_RDONLY"), + Value: buildFlagArgValue(parsers.O_RDONLY), }, { ArgMeta: trace.ArgMeta{ @@ -111,7 +112,7 @@ func TestSchedDebugRecon(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: buildFlagArgValue(parsers.O_WRONLY), }, { ArgMeta: trace.ArgMeta{ diff --git a/signatures/golang/scheduled_task_modification.go b/signatures/golang/scheduled_task_modification.go index a7564e78bd04..fe52daf8f43e 100644 --- a/signatures/golang/scheduled_task_modification.go +++ b/signatures/golang/scheduled_task_modification.go @@ -65,7 +65,7 @@ func (sig *ScheduledTaskModification) OnEvent(event protocol.Event) error { return err } - flags, err := helpers.GetTraceeStringArgumentByName(eventObj, "flags") + flags, err := helpers.GetTraceeIntArgumentByName(eventObj, "flags") if err != nil { return err } diff --git a/signatures/golang/scheduled_task_modification_test.go b/signatures/golang/scheduled_task_modification_test.go index c25abcb776ce..f3b1e8de1880 100644 --- a/signatures/golang/scheduled_task_modification_test.go +++ b/signatures/golang/scheduled_task_modification_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/tracee/pkg/events/parsers" "github.com/aquasecurity/tracee/signatures/signaturestest" "github.com/aquasecurity/tracee/types/detect" "github.com/aquasecurity/tracee/types/trace" @@ -29,7 +30,7 @@ func TestScheduledTaskModification(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: buildFlagArgValue(parsers.O_WRONLY), }, { ArgMeta: trace.ArgMeta{ @@ -50,7 +51,7 @@ func TestScheduledTaskModification(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: buildFlagArgValue(parsers.O_WRONLY), }, { ArgMeta: trace.ArgMeta{ @@ -88,7 +89,7 @@ func TestScheduledTaskModification(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: buildFlagArgValue(parsers.O_WRONLY), }, { ArgMeta: trace.ArgMeta{ @@ -109,7 +110,7 @@ func TestScheduledTaskModification(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: buildFlagArgValue(parsers.O_WRONLY), }, { ArgMeta: trace.ArgMeta{ @@ -288,7 +289,7 @@ func TestScheduledTaskModification(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: buildFlagArgValue(parsers.O_WRONLY), }, { ArgMeta: trace.ArgMeta{ @@ -311,7 +312,7 @@ func TestScheduledTaskModification(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_RDONLY"), + Value: buildFlagArgValue(parsers.O_RDONLY), }, { ArgMeta: trace.ArgMeta{ diff --git a/signatures/golang/sudoers_modification.go b/signatures/golang/sudoers_modification.go index 74973699e87b..90c4d90a1775 100644 --- a/signatures/golang/sudoers_modification.go +++ b/signatures/golang/sudoers_modification.go @@ -59,7 +59,7 @@ func (sig *SudoersModification) OnEvent(event protocol.Event) error { switch eventObj.EventName { case "security_file_open": - flags, err := helpers.GetTraceeStringArgumentByName(eventObj, "flags") + flags, err := helpers.GetTraceeIntArgumentByName(eventObj, "flags") if err != nil { return err } diff --git a/signatures/golang/sudoers_modification_test.go b/signatures/golang/sudoers_modification_test.go index 544392eb3d92..209749cb06ef 100644 --- a/signatures/golang/sudoers_modification_test.go +++ b/signatures/golang/sudoers_modification_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/tracee/pkg/events/parsers" "github.com/aquasecurity/tracee/signatures/signaturestest" "github.com/aquasecurity/tracee/types/detect" "github.com/aquasecurity/tracee/types/trace" @@ -35,7 +36,7 @@ func TestSudoersModification(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: buildFlagArgValue(parsers.O_WRONLY), }, }, }, @@ -56,7 +57,7 @@ func TestSudoersModification(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: buildFlagArgValue(parsers.O_WRONLY), }, }, }.ToProtocol(), @@ -94,7 +95,7 @@ func TestSudoersModification(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: buildFlagArgValue(parsers.O_WRONLY), }, }, }, @@ -115,7 +116,7 @@ func TestSudoersModification(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: buildFlagArgValue(parsers.O_WRONLY), }, }, }.ToProtocol(), @@ -247,7 +248,7 @@ func TestSudoersModification(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_RDONLY"), + Value: buildFlagArgValue(parsers.O_RDONLY), }, }, }, @@ -270,7 +271,7 @@ func TestSudoersModification(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: buildFlagArgValue(parsers.O_WRONLY), }, }, }, diff --git a/signatures/golang/system_request_key_config_modification.go b/signatures/golang/system_request_key_config_modification.go index 05d8c531fd03..e110c02e1305 100644 --- a/signatures/golang/system_request_key_config_modification.go +++ b/signatures/golang/system_request_key_config_modification.go @@ -52,7 +52,7 @@ func (sig *SystemRequestKeyConfigModification) OnEvent(event protocol.Event) err switch eventObj.EventName { case "security_file_open": - flags, err := helpers.GetTraceeStringArgumentByName(eventObj, "flags") + flags, err := helpers.GetTraceeIntArgumentByName(eventObj, "flags") if err != nil { return err } diff --git a/signatures/golang/system_request_key_config_modification_test.go b/signatures/golang/system_request_key_config_modification_test.go index f906621c3ed4..051cdd7d53d4 100644 --- a/signatures/golang/system_request_key_config_modification_test.go +++ b/signatures/golang/system_request_key_config_modification_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/tracee/pkg/events/parsers" "github.com/aquasecurity/tracee/signatures/signaturestest" "github.com/aquasecurity/tracee/types/detect" "github.com/aquasecurity/tracee/types/trace" @@ -35,7 +36,7 @@ func TestSystemRequestKeyConfigModification(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: buildFlagArgValue(parsers.O_WRONLY), }, }, }, @@ -56,7 +57,7 @@ func TestSystemRequestKeyConfigModification(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: buildFlagArgValue(parsers.O_WRONLY), }, }, }.ToProtocol(), @@ -94,7 +95,7 @@ func TestSystemRequestKeyConfigModification(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_RDONLY"), + Value: buildFlagArgValue(parsers.O_RDONLY), }, }, }, @@ -117,7 +118,7 @@ func TestSystemRequestKeyConfigModification(t *testing.T) { ArgMeta: trace.ArgMeta{ Name: "flags", }, - Value: interface{}("O_WRONLY"), + Value: buildFlagArgValue(parsers.O_WRONLY), }, }, }, diff --git a/signatures/golang/test_helpers.go b/signatures/golang/test_helpers.go new file mode 100644 index 000000000000..c395fefbedcc --- /dev/null +++ b/signatures/golang/test_helpers.go @@ -0,0 +1,11 @@ +package main + +import "github.com/aquasecurity/tracee/pkg/events/parsers" + +func buildFlagArgValue(flags ...parsers.SystemFunctionArgument) int32 { + var res int32 + for _, flagVal := range flags { + res = res | int32(flagVal.Value()) + } + return res +} diff --git a/signatures/helpers/go.mod b/signatures/helpers/go.mod index 80d479054c46..2a5699356f7e 100644 --- a/signatures/helpers/go.mod +++ b/signatures/helpers/go.mod @@ -5,3 +5,9 @@ go 1.22.0 toolchain go1.22.4 require github.com/aquasecurity/tracee/types v0.0.0-20241008181102-d40bc1f81863 + +require ( + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/stretchr/testify v1.9.0 // indirect +) diff --git a/signatures/helpers/go.sum b/signatures/helpers/go.sum index c75b3133860a..ea2e859217e2 100644 --- a/signatures/helpers/go.sum +++ b/signatures/helpers/go.sum @@ -1,10 +1,10 @@ github.com/aquasecurity/tracee/types v0.0.0-20241008181102-d40bc1f81863 h1:domVTTQICTuCvX+ZW5EjvdUBz8EH7FedBj5lRqwpgf4= github.com/aquasecurity/tracee/types v0.0.0-20241008181102-d40bc1f81863/go.mod h1:Jwh9OOuiMHXDoGQY12N9ls5YB+j1FlRcXvFMvh1CmIU= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/signatures/helpers/helpers.go b/signatures/helpers/helpers.go index ea53851d08af..d3cb0c23c3bc 100644 --- a/signatures/helpers/helpers.go +++ b/signatures/helpers/helpers.go @@ -3,15 +3,16 @@ package helpers import ( "fmt" "strings" + "syscall" "github.com/aquasecurity/tracee/types/trace" ) // IsFileWrite returns whether the passed file permissions string contains // o_wronly or o_rdwr -func IsFileWrite(flags string) bool { - flagsLow := strings.ToLower(flags) - if strings.Contains(flagsLow, "o_wronly") || strings.Contains(flagsLow, "o_rdwr") { +func IsFileWrite(flags int) bool { + accessMode := uint64(flags) & syscall.O_ACCMODE + if accessMode == syscall.O_WRONLY || accessMode == syscall.O_RDWR { return true } return false @@ -19,9 +20,9 @@ func IsFileWrite(flags string) bool { // IsFileRead returns whether the passed file permissions string contains // o_rdonly or o_rdwr -func IsFileRead(flags string) bool { - flagsLow := strings.ToLower(flags) - if strings.Contains(flagsLow, "o_rdonly") || strings.Contains(flagsLow, "o_rdwr") { +func IsFileRead(flags int) bool { + accessMode := uint64(flags) & syscall.O_ACCMODE + if accessMode == syscall.O_RDONLY || accessMode == syscall.O_RDWR { return true } return false diff --git a/tests/e2e-inst-signatures/e2e-bpf_attach.go b/tests/e2e-inst-signatures/e2e-bpf_attach.go index a803e78fba2d..4eeb87da2a71 100644 --- a/tests/e2e-inst-signatures/e2e-bpf_attach.go +++ b/tests/e2e-inst-signatures/e2e-bpf_attach.go @@ -3,6 +3,7 @@ package main import ( "fmt" + "github.com/aquasecurity/tracee/pkg/events/parsers" "github.com/aquasecurity/tracee/signatures/helpers" "github.com/aquasecurity/tracee/types/detect" "github.com/aquasecurity/tracee/types/protocol" @@ -48,14 +49,14 @@ func (sig *e2eBpfAttach) OnEvent(event protocol.Event) error { return err } - attachType, err := helpers.GetTraceeStringArgumentByName(eventObj, "attach_type") + attachType, err := helpers.GetTraceeIntArgumentByName(eventObj, "attach_type") if err != nil { return err } // check expected values from test for detection - if symbolName != "security_file_open" || attachType != "kprobe" { + if symbolName != "security_file_open" || attachType != int(parsers.BPFProgTypeKprobe) { return nil } diff --git a/tests/e2e-inst-signatures/e2e-set_fs_pwd.go b/tests/e2e-inst-signatures/e2e-set_fs_pwd.go index f9cda190b64b..99f7b6cac83b 100644 --- a/tests/e2e-inst-signatures/e2e-set_fs_pwd.go +++ b/tests/e2e-inst-signatures/e2e-set_fs_pwd.go @@ -4,6 +4,9 @@ import ( "fmt" "strings" + "kernel.org/pub/linux/libs/security/libcap/cap" + + "github.com/aquasecurity/tracee/pkg/capabilities" "github.com/aquasecurity/tracee/pkg/utils/environment" "github.com/aquasecurity/tracee/signatures/helpers" "github.com/aquasecurity/tracee/types/detect" @@ -21,7 +24,15 @@ func (sig *e2eSetFsPwd) Init(ctx detect.SignatureContext) error { // Find if this system has the bpf_probe_read_user_str helper. // If it doesn't we won't expect the unresolved path to contain anything - ksyms, err := environment.NewKernelSymbolTable() + var ksyms *environment.KernelSymbolTable + err := capabilities.GetInstance().Specific( + func() error { + var err error + ksyms, err = environment.NewKernelSymbolTable(false, false) + return err + }, + cap.SYSLOG, + ) if err != nil { return err } diff --git a/tests/e2e-inst-signatures/e2e-stack_pivot.go b/tests/e2e-inst-signatures/e2e-stack_pivot.go new file mode 100644 index 000000000000..b2ffd5f12303 --- /dev/null +++ b/tests/e2e-inst-signatures/e2e-stack_pivot.go @@ -0,0 +1,83 @@ +package main + +import ( + "fmt" + + "github.com/aquasecurity/tracee/pkg/events" + "github.com/aquasecurity/tracee/signatures/helpers" + "github.com/aquasecurity/tracee/types/detect" + "github.com/aquasecurity/tracee/types/protocol" + "github.com/aquasecurity/tracee/types/trace" +) + +type e2eStackPivot struct { + cb detect.SignatureHandler + falsePositive bool +} + +func (sig *e2eStackPivot) Init(ctx detect.SignatureContext) error { + sig.cb = ctx.Callback + + return nil +} + +func (sig *e2eStackPivot) GetMetadata() (detect.SignatureMetadata, error) { + return detect.SignatureMetadata{ + ID: "STACK_PIVOT", + EventName: "STACK_PIVOT", + Version: "0.1.0", + Name: "Stack Pivot Test", + Description: "Instrumentation events E2E Tests: Stack Pivot", + Tags: []string{"e2e", "instrumentation"}, + }, nil +} + +func (sig *e2eStackPivot) GetSelectedEvents() ([]detect.SignatureEventSelector, error) { + return []detect.SignatureEventSelector{ + {Source: "tracee", Name: "stack_pivot"}, + }, nil +} + +func (sig *e2eStackPivot) OnEvent(event protocol.Event) error { + eventObj, ok := event.Payload.(trace.Event) + if !ok { + return fmt.Errorf("failed to cast event's payload") + } + + switch eventObj.EventName { + case "stack_pivot": + syscall, err := helpers.ArgVal[int32](eventObj.Args, "syscall") + if err != nil { + return err + } + vmaType, err := helpers.ArgVal[string](eventObj.Args, "vma_type") + if err != nil { + return err + } + + // Make sure this is the exact event we're looking for + if eventObj.ProcessName == "stack_pivot" && syscall == int32(events.ExitGroup) && vmaType == "heap" { + // Make sure there was no false positive + if !sig.falsePositive { + m, _ := sig.GetMetadata() + + sig.cb(&detect.Finding{ + SigMetadata: m, + Event: event, + Data: map[string]interface{}{}, + }) + } + } else { + // False positive, mark it so that the test will fail + sig.falsePositive = true + } + } + + return nil +} + +func (sig *e2eStackPivot) OnSignal(s detect.Signal) error { + return nil +} + +func (sig *e2eStackPivot) Close() {} diff --git a/tests/e2e-inst-signatures/e2e-suspicious_syscall_source.go b/tests/e2e-inst-signatures/e2e-suspicious_syscall_source.go index 7d91adb878fd..5a67c3cf9f56 100644 --- a/tests/e2e-inst-signatures/e2e-suspicious_syscall_source.go +++ b/tests/e2e-inst-signatures/e2e-suspicious_syscall_source.go @@ -3,6 +3,7 @@ package main import ( "fmt" + "github.com/aquasecurity/tracee/pkg/events" "github.com/aquasecurity/tracee/signatures/helpers" "github.com/aquasecurity/tracee/types/detect" "github.com/aquasecurity/tracee/types/protocol" @@ -10,10 +11,11 @@ import ( ) type e2eSuspiciousSyscallSource struct { - cb detect.SignatureHandler - foundStack bool - foundHeap bool - foundAnonVma bool + cb detect.SignatureHandler + foundMainStack bool + foundHeap bool + foundAnonVma bool + foundThreadStack bool } func (sig *e2eSuspiciousSyscallSource) Init(ctx detect.SignatureContext) error { @@ -47,7 +49,7 @@ func (sig *e2eSuspiciousSyscallSource) OnEvent(event protocol.Event) error { switch eventObj.EventName { case "suspicious_syscall_source": - syscall, err := helpers.ArgVal[string](eventObj.Args, "syscall") + syscall, err := helpers.ArgVal[int32](eventObj.Args, "syscall") if err != nil { return err } @@ -58,21 +60,23 @@ func (sig *e2eSuspiciousSyscallSource) OnEvent(event protocol.Event) error { // check expected values from test for detection - if syscall != "exit" { + if syscall != int32(events.Exit) { return nil } - if vmaType == "stack" { - sig.foundStack = true + if vmaType == "main stack" { + sig.foundMainStack = true } else if vmaType == "heap" { sig.foundHeap = true } else if vmaType == "anonymous" { sig.foundAnonVma = true + } else if vmaType == "thread stack" { + sig.foundThreadStack = true } else { return nil } - if !sig.foundStack || !sig.foundHeap || !sig.foundAnonVma { + if !sig.foundMainStack || !sig.foundHeap || !sig.foundAnonVma || !sig.foundThreadStack { return nil } diff --git a/tests/e2e-inst-signatures/export.go b/tests/e2e-inst-signatures/export.go index 4cddd8abd6d9..5b480a39820d 100644 --- a/tests/e2e-inst-signatures/export.go +++ b/tests/e2e-inst-signatures/export.go @@ -23,6 +23,7 @@ var ExportedSignatures = []detect.Signature{ &e2eSetFsPwd{}, &e2eFtraceHook{}, &e2eSuspiciousSyscallSource{}, + &e2eStackPivot{}, } var ExportedDataSources = []detect.DataSource{ diff --git a/tests/e2e-inst-signatures/scripts/ds_writer/ds_writer.go b/tests/e2e-inst-signatures/scripts/ds_writer/ds_writer.go index 198af831807e..3e62d3d637e0 100644 --- a/tests/e2e-inst-signatures/scripts/ds_writer/ds_writer.go +++ b/tests/e2e-inst-signatures/scripts/ds_writer/ds_writer.go @@ -2,10 +2,13 @@ package main import ( "context" + "errors" "flag" "fmt" + "io" "math/rand" "os" + "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" @@ -23,8 +26,8 @@ func chooseWord(list []string) string { return list[rand.Intn(len(list))] } -func contaminate(client v1beta1.DataSourceServiceClient) error { - stream, err := client.WriteStream(context.Background()) +func contaminate(ctx context.Context, client v1beta1.DataSourceServiceClient) error { + stream, err := client.WriteStream(ctx) if err != nil { return fmt.Errorf("error establishing stream: %v", err) } @@ -38,6 +41,10 @@ func contaminate(client v1beta1.DataSourceServiceClient) error { Value: structpb.NewStringValue(randomValue), }) if err != nil { + if errors.Is(err, io.EOF) { + break + } + return err } } @@ -70,16 +77,26 @@ func main() { if err != nil { printAndExit("failed to dial tracee grpc server: %v\n", err) } + defer func() { + err := conn.Close() + if err != nil { + printAndExit("failed to close connection: %v\n", err) + } + }() + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) + defer cancel() + client := v1beta1.NewDataSourceServiceClient(conn) - err = contaminate(client) + err = contaminate(ctx, client) if err != nil { printAndExit("error contaminating data source: %v\n", err) } - _, err = client.Write(context.Background(), &v1beta1.WriteDataSourceRequest{ + _, err = client.Write(ctx, &v1beta1.WriteDataSourceRequest{ Id: "demo", Namespace: "e2e_inst", - Key: structpb.NewStringValue("bruh"), - Value: structpb.NewStringValue("moment"), + Key: structpb.NewStringValue(key), + Value: structpb.NewStringValue(value), }) if err != nil { diff --git a/tests/e2e-inst-signatures/scripts/stack_pivot.c b/tests/e2e-inst-signatures/scripts/stack_pivot.c new file mode 100644 index 000000000000..6689af03735d --- /dev/null +++ b/tests/e2e-inst-signatures/scripts/stack_pivot.c @@ -0,0 +1,51 @@ +#include +#include +#include +#include + +void* thread_func(void* arg) { + // Try triggering a false posivie + getpid(); + + return NULL; +} + +int main() { + // Start a thread that will call getpid(), in an attempt to trigger a false positive + pthread_t thread; + if (pthread_create(&thread, NULL, thread_func, NULL) != 0) { + perror("pthread_create failed"); + return 1; + } + + // Sleep a bit to have give potential false positives a chance to show themselves + sleep(15); + + // Allocate a block of memory on the heap + void *heap_memory = malloc(1024); + if (heap_memory == NULL) { + perror("malloc failed"); + return 1; + } + + // Set stack pointer to the allocated heap memory (top of the block) + void *new_sp = heap_memory + 1024; +#if defined(__x86_64__) + __asm__ volatile ( + "mov %0, %%rsp\n" + : + : "r"(new_sp) + ); +#elif defined(__aarch64__) + __asm__ volatile ( + "mov sp, %0\n" + : + : "r"(new_sp) + ); +#else + #error "Unsupported architecture" +#endif + + // Trigger the stack pivot event by invoking exit_group() while the stack pointer is pointing to the heap + exit(0); +} \ No newline at end of file diff --git a/tests/e2e-inst-signatures/scripts/stack_pivot.sh b/tests/e2e-inst-signatures/scripts/stack_pivot.sh new file mode 100755 index 000000000000..f114dbd4de5c --- /dev/null +++ b/tests/e2e-inst-signatures/scripts/stack_pivot.sh @@ -0,0 +1,12 @@ +#!/usr/bin/bash + +exit_err() { + echo -n "ERROR: " + echo "$@" + exit 1 +} + +prog=stack_pivot +dir=tests/e2e-inst-signatures/scripts +gcc $dir/$prog.c -pthread -o $dir/$prog || exit_err "could not compile $prog.c" +./$dir/$prog 2>&1 > /tmp/$prog.log || exit_err "could not run $prog" \ No newline at end of file diff --git a/tests/e2e-inst-signatures/scripts/suspicious_syscall_source.sh b/tests/e2e-inst-signatures/scripts/suspicious_syscall_source.sh index af27ae8b72f7..e54c3a2ee3b3 100755 --- a/tests/e2e-inst-signatures/scripts/suspicious_syscall_source.sh +++ b/tests/e2e-inst-signatures/scripts/suspicious_syscall_source.sh @@ -8,7 +8,8 @@ exit_err() { prog=sys_src_tester dir=tests/e2e-inst-signatures/scripts -gcc $dir/$prog.c -o $dir/$prog -z execstack || exit_err "could not compile $prog.c" +gcc $dir/$prog.c -pthread -o $dir/$prog -z execstack || exit_err "could not compile $prog.c" ./$dir/$prog stack 2>&1 > /tmp/$prog.log || exit_err "could not run $prog" ./$dir/$prog heap 2>&1 > /tmp/$prog.log || exit_err "could not run $prog" -./$dir/$prog mmap 2>&1 > /tmp/$prog.log || exit_err "could not run $prog" \ No newline at end of file +./$dir/$prog mmap 2>&1 > /tmp/$prog.log || exit_err "could not run $prog" +./$dir/$prog thread-stack 2>&1 > /tmp/$prog.log || exit_err "could not run $prog" \ No newline at end of file diff --git a/tests/e2e-inst-signatures/scripts/sys_src_tester.c b/tests/e2e-inst-signatures/scripts/sys_src_tester.c index 8de6a51c1c24..38325780680c 100644 --- a/tests/e2e-inst-signatures/scripts/sys_src_tester.c +++ b/tests/e2e-inst-signatures/scripts/sys_src_tester.c @@ -6,6 +6,7 @@ #include #include #include +#include // exit(0); #if defined(__x86_64__) @@ -24,6 +25,8 @@ char shellcode[] = SHELLCODE; +void *thread_func(void *); + int main(int argc, char *argv[]) { if (argc != 2) @@ -84,8 +87,46 @@ int main(int argc, char *argv[]) goto fail; } + if (strcmp(argv[1], "thread-stack") == 0) { + // spawn a new thread which will run the shellcode from its stack + pthread_t thread; + if (pthread_create(&thread, NULL, thread_func, NULL) != 0) { + perror("pthread_create failed"); + goto fail; + } + + // wait for the new thread to exit + if (pthread_join(thread, NULL) != 0) { + perror("pthread_join failed"); + goto fail; + } + + return 0; + } + usage: printf("usage: ./sys_src_tester [stack|heap|mmap]\n"); fail: exit(EXIT_FAILURE); } + +void *thread_func(void *arg) +{ + // place the shellcode on the stack + char shellcode_stack[] = SHELLCODE; + + // set the stack memory as executable + if (mprotect((void *)((unsigned long long)shellcode_stack & ~(sysconf(_SC_PAGE_SIZE) - 1)), 2 * sysconf(_SC_PAGE_SIZE), PROT_READ | PROT_WRITE | PROT_EXEC) == -1) { + perror("mprotect failed"); + return NULL; + } + + // jump to the shellcode +#if defined(__aarch64__) + __builtin___clear_cache (&shellcode_stack, &shellcode_stack + sizeof(shellcode)); +#endif + ((void (*)(void))shellcode_stack)(); + + // cannot be reached + return NULL; +} diff --git a/tests/e2e-inst-test.sh b/tests/e2e-inst-test.sh index 39a06395ee02..3dff54e438ee 100755 --- a/tests/e2e-inst-test.sh +++ b/tests/e2e-inst-test.sh @@ -79,6 +79,9 @@ if [[ ! -x ./dist/tracee ]]; then error_exit "could not find tracee executable" fi +logfile=$SCRIPT_TMP_DIR/tracee-log-$$ +outputfile=$SCRIPT_TMP_DIR/output-$$ + anyerror="" # Run tests, one by one @@ -128,9 +131,9 @@ for TEST in $TESTS; do continue fi ;; - SUSPICIOUS_SYSCALL_SOURCE) + SUSPICIOUS_SYSCALL_SOURCE|STACK_PIVOT) if cat /proc/kallsyms | grep -qP "trace.*vma_store"; then - info "skip suspicious_syscall_source test on kernel $(uname -r) (VMAs stored in maple tree)" + info "skip $TEST test on kernel $(uname -r) (VMAs stored in maple tree)" continue fi ;; @@ -138,8 +141,8 @@ for TEST in $TESTS; do # Run tracee - rm -f $SCRIPT_TMP_DIR/build-$$ - rm -f $SCRIPT_TMP_DIR/tracee-log-$$ + rm -f $outputfile + rm -f $logfile tracee_command="./dist/tracee \ --install-path $TRACEE_TMP_DIR \ @@ -147,19 +150,31 @@ for TEST in $TESTS; do --cache mem-cache-size=512 \ --proctree source=both \ --output option:sort-events \ - --output json:$SCRIPT_TMP_DIR/build-$$ \ --output option:parse-arguments \ - --log file:$SCRIPT_TMP_DIR/tracee-log-$$ \ + --output json:$outputfile \ + --log file:$logfile \ --signatures-dir "$SIG_DIR" \ - --scope comm=echo,mv,ls,tracee,proctreetester,ping,ds_writer,fsnotify_tester,process_execute,tracee-ebpf,writev,set_fs_pwd.sh,sys_src_tester \ --dnscache enable \ --grpc-listen-addr unix:/tmp/tracee.sock \ --events "$TEST"" + # Some tests might look for false positives and thus we shouldn't limit the scope for them + if [ "$TEST" != "STACK_PIVOT" ]; then + tracee_command="$tracee_command --scope comm=echo,mv,ls,tracee,proctreetester,ping,ds_writer,fsnotify_tester,process_execute,tracee-ebpf,writev,set_fs_pwd.sh,sys_src_tester" + fi + # Some tests might need event parameters - if [ "$TEST" = "SUSPICIOUS_SYSCALL_SOURCE" ]; then + case $TEST in + SUSPICIOUS_SYSCALL_SOURCE) tracee_command="$tracee_command --events suspicious_syscall_source.args.syscall=exit" - fi + ;; + STACK_PIVOT) + # The expected event is triggered using the exit_group syscall. + # Also add various high-frequency sycalls so that false positives have a chance to trigger. + # Also add getpid, which the tester program uses in an attempt to trigger a false positive + tracee_command="$tracee_command --events stack_pivot.args.syscall=exit_group,getpid,write,openat,mmap,execve,fork,clone,recvmsg,gettid,epoll_wait,poll,recvfrom" + ;; + esac $tracee_command & @@ -189,7 +204,7 @@ for TEST in $TESTS; do info info "$TEST: FAILED. ERRORS:" info - cat $SCRIPT_TMP_DIR/tracee-log-$$ + cat $logfile anyerror="${anyerror}$TEST," continue @@ -220,12 +235,18 @@ for TEST in $TESTS; do # The cleanup happens at EXIT - logfile=$SCRIPT_TMP_DIR/tracee-log-$$ + # Make sure we exit tracee before checking output and log files + + pid_tracee=$(pidof tracee | cut -d' ' -f1) + kill -SIGINT "$pid_tracee" + sleep $TRACEE_SHUTDOWN_TIMEOUT + kill -SIGKILL "$pid_tracee" >/dev/null 2>&1 + sleep 3 # Check if the test has failed or not found=0 - cat $SCRIPT_TMP_DIR/build-$$ | jq .eventName | grep -q "$TEST" && found=1 + cat $outputfile | jq .eventName | grep -q "$TEST" && found=1 errors=$(cat $logfile | wc -l 2>/dev/null) if [[ $TEST == "BPF_ATTACH" ]]; then @@ -237,25 +258,33 @@ for TEST in $TESTS; do info "$TEST: SUCCESS" else anyerror="${anyerror}$TEST," + info "$TEST: FAILED, stderr from tracee:" - cat $SCRIPT_TMP_DIR/tracee-log-$$ + cat $logfile + info "$TEST: FAILED, events from tracee:" - cat $SCRIPT_TMP_DIR/build-$$ + cat $outputfile + + info "Tracee command:" + echo "$tracee_command" | tr -s ' ' + + info "Tracee process is running?" + traceepids=$(pgrep tracee) + if [[ -n $traceepids ]]; then + info "YES, Tracee is still running (should not be, fix me!), pids: $traceepids" + info "Aborting tests" + break + else + info "NO, Tracee is not running" + fi info fi info - rm -f $SCRIPT_TMP_DIR/build-$$ - rm -f $SCRIPT_TMP_DIR/tracee-log-$$ - - # Make sure we exit tracee to start it again - - pid_tracee=$(pidof tracee | cut -d' ' -f1) - kill -SIGINT "$pid_tracee" - sleep $TRACEE_SHUTDOWN_TIMEOUT - kill -SIGKILL "$pid_tracee" >/dev/null 2>&1 - sleep 3 + # Cleanup + rm -f $outputfile + rm -f $logfile # Cleanup leftovers rm -rf $TRACEE_TMP_DIR done diff --git a/types/go.mod b/types/go.mod index f6f8a560f1a3..b887fd4c7429 100644 --- a/types/go.mod +++ b/types/go.mod @@ -2,10 +2,10 @@ module github.com/aquasecurity/tracee/types go 1.22.0 -require github.com/stretchr/testify v1.7.0 +require github.com/stretchr/testify v1.10.0 require ( - github.com/davecgh/go-spew v1.1.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/types/go.sum b/types/go.sum index fc3dd9e67e82..713a0b4f0a3a 100644 --- a/types/go.sum +++ b/types/go.sum @@ -1,12 +1,10 @@ -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=