Add writev, shutdown, and chown to system-probe seccomp profile#2634
Merged
Conversation
Required by system-probe-lite (Rust/hyper-based) which uses vectored writes (`writev`) for HTTP responses and `shutdown(2)` for graceful connection close. The Go-based system-probe didn't surface these as a problem because net/http buffers responses through bufio.Writer and writes them via plain `write(2)`, and the HTTP/1 keep-alive happy path used by the agent's checks never triggers a `shutdown(2)` (Go only calls `CloseWrite` in edge cases like 431 oversized headers or partially-consumed request bodies). Without this fix, the seccomp profile (defaultAction SCMP_ACT_ERRNO) returns EPERM for `writev`/`shutdown`, surfacing as "error writing a body to connection" / "error shutting down connection" in system-probe-lite logs and "EOF" on the agent's `GET /debug/stats` startup probe, preventing the discovery check from ever starting.
Run helm-docs to refresh the README version badge to match the bumped chart version.
Contributor
|
vitkyrka
marked this pull request as ready for review
May 7, 2026 10:13
3 tasks
vitkyrka
marked this pull request as draft
May 7, 2026 10:53
Required by system-probe-lite on x86_64. After socket creation, system-probe-lite calls `std::os::unix::fs::chown(path, uid, gid)` to hand the socket off to `dd-agent`; on x86_64 that resolves to the `chown(2)` syscall, which the existing profile rejects with EPERM (SCMP_ACT_ERRNO): SYS-PROBE-LITE | WARN | could not set socket ownership: Operation not permitted (os error 1) The Go-based system-probe didn't surface this because Go's `syscall.Chown` on Linux dispatches through `fchownat(AT_FDCWD, ...)` (`src/syscall/syscall_linux.go`), which is already in the allowlist. arm64 didn't surface it either, because glibc's `chown()` on aarch64 also wraps `fchownat(2)` (the kernel has no `chown` syscall there). Failure is non-fatal in default helm deployments where every agent container runs as root (`datadog.securityContext.runAsUser: 0`) and can talk to the resulting `root:root` mode 0720 socket regardless. But for setups that override the agent to run as `dd-agent`, the socket would be unreachable. Allow `chown` unconditionally to match the existing unconditional `fchown`/`fchownat` entries.
vitkyrka
marked this pull request as ready for review
May 7, 2026 11:06
swang392
approved these changes
May 7, 2026
brycekahle
approved these changes
May 7, 2026
This was referenced May 8, 2026
gh-worker-dd-mergequeue-cf854d Bot
pushed a commit
to DataDog/datadog-agent
that referenced
this pull request
May 8, 2026
…er causes (#50538) ### What does this PR do? In \`pkg/discovery/module/rust/src/main.rs\`, three \`error!\`/\`warn!\` call sites now use the \`{:#}\` alternate format (via \`anyhow\`) so the full error cause chain is included in the log line. | Call site | Before | After | |---|---|---| | \`serve_connection\` failure | \`Error serving connection: error writing a body to connection\` | \`Error serving connection: error writing a body to connection: Operation not permitted (os error 1)\` | | \`handle_request\` failure | \`Request handling failed: <top-level>\` | \`Request handling failed: <top-level>: <cause>\` | | \`drop_capabilities\` failure | \`Failed to restrict capabilities: <top-level>; …\` | \`Failed to restrict capabilities: <top-level>: <cause>; …\` | For \`hyper::Error\` (the connection error), wrapping in \`anyhow::Error::new(err)\` is required because \`hyper::Error\`'s own \`Display\` impl does not traverse \`.source()\`. For the two \`anyhow::Error\` sites the change is simply adding \`#\` to the format specifier. ### Motivation While debugging [helm-charts#2634](DataDog/helm-charts#2634), which added \`writev\`, \`shutdown\`, and \`chown\` to the system-probe seccomp allowlist for SPL, the only log produced was: ``` SYS-PROBE-LITE | ERROR | Error serving connection: error writing a body to connection ``` The underlying cause (\`Operation not permitted (os error 1)\`) was invisible, making \`strace\` necessary to identify the blocked syscall. With this fix the EPERM — or any other root cause — appears directly in the log. ### Describe how you validated your changes - \`bazel build --config=lint //pkg/discovery/module/rust/...\` passes - \`bazel test //pkg/discovery/module/rust/...\` — all 7 tests pass - Manual test for the \`serve_connection\` path: ran SPL with an \`LD_PRELOAD\` shim that makes every \`writev(2)\` call return \`EPERM\`, then sent a GET request to the socket: - **Before:** \`Error serving connection: error writing a body to connection\` - **After:** \`Error serving connection: error writing a body to connection: Operation not permitted (os error 1)\` - Manual test for the \`handle_request\` path: ran SPL with a temporary chained-error injection (\`anyhow!("inner").context("outer")\`) at the top of \`handle_request\`: - **Before:** \`Request handling failed: outer\` - **After:** \`Request handling failed: outer: inner\` Co-authored-by: vincent.whitchurch <[email protected]>
gh-worker-dd-mergequeue-cf854d Bot
pushed a commit
to DataDog/datadog-agent
that referenced
this pull request
May 12, 2026
### What does this PR do?
Adds a Kubernetes E2E sanity test for the Datadog Agent's discovery feature, mirroring (in spirit) the existing Linux VM suite in `test/new-e2e/tests/discovery/linux_test.go`. The new suite at `test/new-e2e/tests/discovery/k8s_test.go`:
- Provisions a Kind-on-EC2 cluster (`provkindvm`) with the agent installed via Helm and an `nginx` workload.
- Runs `TestNginxDiscovered` in two sub-tests covering both discovery modes:
- `system-probe-lite` — discovery enabled alone; the agent execs into `system-probe-lite` per `cmd/system-probe/subcommands/run/splite.go`.
- `system-probe` — adds `datadog.systemProbe.enableOOMKill: true` so the agent's `shouldExecSPLite` keeps the full binary running.
- Asserts on the matched nginx process: TCP port 80 listener, `ServiceDiscovery.GeneratedServiceName == {nginx, SOURCE_COMMAND_LINE}`, `ApmInstrumentation == false`, `InjectionState == NOT_INJECTED`, non-empty `ContainerId`.
- Pins the Helm chart version to `3.213.0`; the framework default (`3.155.1`) predates both the discovery template (chart 3.205.0, DataDog/helm-charts#2598) and the `latest`/`7` → `7.78.x` fallback fix (chart 3.213.0, DataDog/helm-charts#2643).
### Motivation
https://datadoghq.atlassian.net/browse/DSCVR-385
There is no k8s e2e for discovery today. DataDog/helm-charts#2634 fixed a bug in discovery on k8s related to the seccomp profile; the change went undetected before merge because nothing exercises the discovery flow on a Helm-installed agent. This suite closes that gap with a smoke-level check that the discovery payload reaches fakeintake in both modes.
The k8s suite is narrower than `linux_test.go` (single workload, smoke-level assertions), at least for now. This is to avoid having to add new app definitions at this point for a basic test. The VM suite continues to be the exhaustive per-language source of truth.
### Describe how you validated your changes
Ran locally against AWS twice with `E2E_DEV_MODE=true dda inv -- -e new-e2e-tests.run --targets ./tests/discovery --run TestK8sTestSuite --stack-name-suffix v2`:
```
--- PASS: TestK8sTestSuite (364.64s)
--- PASS: TestK8sTestSuite/TestNginxDiscovered (...)
--- PASS: TestK8sTestSuite/TestNginxDiscovered/system-probe-lite
--- PASS: TestK8sTestSuite/TestNginxDiscovered/system-probe
```
Lint: `golangci-lint run ./test/new-e2e/tests/discovery/...` clean (0 issues).
### Additional Notes
A few quirks worth flagging for reviewers:
- **Forced DaemonSet rollout via pod annotation.** Toggling `datadog.systemProbe.enableOOMKill` between sub-tests only mutates the chart-rendered configmap; the chart's `daemonset.yaml` has no `checksum/system-probe-yaml` annotation tying the pod template to that configmap, so the existing pod stays alive across the change. The suite writes a `agents.podAnnotations.discovery-e2e-mode: "spl"/"sp"` that intentionally differs per sub-test to bump the pod-template hash and force a rollout.
- **Tracer metadata / language detection out of scope.** Nginx is C/native so `language` and `serviceDiscovery.tracerMetadata` aren't populated. The two natural ways to extend this — switch to (or add) an OTel-instrumented Go server (`apps-calendar-go` — no `K8sAppDefinition` yet) or a `dd-trace-go`-instrumented app (`apps-tracegen` predates the memfd-tracer-metadata feature in `dd-trace-go.v1`) — both require cross-repo work in `test-infra-definitions`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: vincent.whitchurch <[email protected]>
chouetz
pushed a commit
to DataDog/datadog-agent
that referenced
this pull request
May 13, 2026
…er causes (#50538) ### What does this PR do? In \`pkg/discovery/module/rust/src/main.rs\`, three \`error!\`/\`warn!\` call sites now use the \`{:#}\` alternate format (via \`anyhow\`) so the full error cause chain is included in the log line. | Call site | Before | After | |---|---|---| | \`serve_connection\` failure | \`Error serving connection: error writing a body to connection\` | \`Error serving connection: error writing a body to connection: Operation not permitted (os error 1)\` | | \`handle_request\` failure | \`Request handling failed: <top-level>\` | \`Request handling failed: <top-level>: <cause>\` | | \`drop_capabilities\` failure | \`Failed to restrict capabilities: <top-level>; …\` | \`Failed to restrict capabilities: <top-level>: <cause>; …\` | For \`hyper::Error\` (the connection error), wrapping in \`anyhow::Error::new(err)\` is required because \`hyper::Error\`'s own \`Display\` impl does not traverse \`.source()\`. For the two \`anyhow::Error\` sites the change is simply adding \`#\` to the format specifier. ### Motivation While debugging [helm-charts#2634](DataDog/helm-charts#2634), which added \`writev\`, \`shutdown\`, and \`chown\` to the system-probe seccomp allowlist for SPL, the only log produced was: ``` SYS-PROBE-LITE | ERROR | Error serving connection: error writing a body to connection ``` The underlying cause (\`Operation not permitted (os error 1)\`) was invisible, making \`strace\` necessary to identify the blocked syscall. With this fix the EPERM — or any other root cause — appears directly in the log. ### Describe how you validated your changes - \`bazel build --config=lint //pkg/discovery/module/rust/...\` passes - \`bazel test //pkg/discovery/module/rust/...\` — all 7 tests pass - Manual test for the \`serve_connection\` path: ran SPL with an \`LD_PRELOAD\` shim that makes every \`writev(2)\` call return \`EPERM\`, then sent a GET request to the socket: - **Before:** \`Error serving connection: error writing a body to connection\` - **After:** \`Error serving connection: error writing a body to connection: Operation not permitted (os error 1)\` - Manual test for the \`handle_request\` path: ran SPL with a temporary chained-error injection (\`anyhow!("inner").context("outer")\`) at the top of \`handle_request\`: - **Before:** \`Request handling failed: outer\` - **After:** \`Request handling failed: outer: inner\` Co-authored-by: vincent.whitchurch <[email protected]>
chouetz
pushed a commit
to DataDog/datadog-agent
that referenced
this pull request
May 13, 2026
### What does this PR do?
Adds a Kubernetes E2E sanity test for the Datadog Agent's discovery feature, mirroring (in spirit) the existing Linux VM suite in `test/new-e2e/tests/discovery/linux_test.go`. The new suite at `test/new-e2e/tests/discovery/k8s_test.go`:
- Provisions a Kind-on-EC2 cluster (`provkindvm`) with the agent installed via Helm and an `nginx` workload.
- Runs `TestNginxDiscovered` in two sub-tests covering both discovery modes:
- `system-probe-lite` — discovery enabled alone; the agent execs into `system-probe-lite` per `cmd/system-probe/subcommands/run/splite.go`.
- `system-probe` — adds `datadog.systemProbe.enableOOMKill: true` so the agent's `shouldExecSPLite` keeps the full binary running.
- Asserts on the matched nginx process: TCP port 80 listener, `ServiceDiscovery.GeneratedServiceName == {nginx, SOURCE_COMMAND_LINE}`, `ApmInstrumentation == false`, `InjectionState == NOT_INJECTED`, non-empty `ContainerId`.
- Pins the Helm chart version to `3.213.0`; the framework default (`3.155.1`) predates both the discovery template (chart 3.205.0, DataDog/helm-charts#2598) and the `latest`/`7` → `7.78.x` fallback fix (chart 3.213.0, DataDog/helm-charts#2643).
### Motivation
https://datadoghq.atlassian.net/browse/DSCVR-385
There is no k8s e2e for discovery today. DataDog/helm-charts#2634 fixed a bug in discovery on k8s related to the seccomp profile; the change went undetected before merge because nothing exercises the discovery flow on a Helm-installed agent. This suite closes that gap with a smoke-level check that the discovery payload reaches fakeintake in both modes.
The k8s suite is narrower than `linux_test.go` (single workload, smoke-level assertions), at least for now. This is to avoid having to add new app definitions at this point for a basic test. The VM suite continues to be the exhaustive per-language source of truth.
### Describe how you validated your changes
Ran locally against AWS twice with `E2E_DEV_MODE=true dda inv -- -e new-e2e-tests.run --targets ./tests/discovery --run TestK8sTestSuite --stack-name-suffix v2`:
```
--- PASS: TestK8sTestSuite (364.64s)
--- PASS: TestK8sTestSuite/TestNginxDiscovered (...)
--- PASS: TestK8sTestSuite/TestNginxDiscovered/system-probe-lite
--- PASS: TestK8sTestSuite/TestNginxDiscovered/system-probe
```
Lint: `golangci-lint run ./test/new-e2e/tests/discovery/...` clean (0 issues).
### Additional Notes
A few quirks worth flagging for reviewers:
- **Forced DaemonSet rollout via pod annotation.** Toggling `datadog.systemProbe.enableOOMKill` between sub-tests only mutates the chart-rendered configmap; the chart's `daemonset.yaml` has no `checksum/system-probe-yaml` annotation tying the pod template to that configmap, so the existing pod stays alive across the change. The suite writes a `agents.podAnnotations.discovery-e2e-mode: "spl"/"sp"` that intentionally differs per sub-test to bump the pod-template hash and force a rollout.
- **Tracer metadata / language detection out of scope.** Nginx is C/native so `language` and `serviceDiscovery.tracerMetadata` aren't populated. The two natural ways to extend this — switch to (or add) an OTel-instrumented Go server (`apps-calendar-go` — no `K8sAppDefinition` yet) or a `dd-trace-go`-instrumented app (`apps-tracegen` predates the memfd-tracer-metadata feature in `dd-trace-go.v1`) — both require cross-repo work in `test-infra-definitions`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: vincent.whitchurch <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this PR does / why we need it:
Adds
writev,shutdown, andchownto thesystem-probeseccomp profile (default actionSCMP_ACT_ERRNO). All three are required bysystem-probe-lite(Rust/hyper-based) but were tolerated or never invoked by the Gosystem-probe, so the existing profile rejected them withEPERM.writevandshutdownHyper calls
shutdown(SHUT_WR)at the end of every served HTTP/1 connection (proto/h1/dispatch.rs—Future::pollpassesshould_shutdown=trueand the dispatcher invokespoll_shutdownonceis_done()), and usestokio'sAsyncWrite::poll_write_vectored, which translates towritev(2). The Go-basedsystem-probedidn't surface either as a problem becausenet/httpbuffers responses throughbufio.Writerand writes them via plainwrite(2), and the HTTP/1 keep-alive happy path used by the agent's checks never triggers ashutdown(2)(Go only callsCloseWritein edge cases like 431 oversized headers or partially-consumed request bodies).Symptom: hyper's first
writevreturnsEPERM, so the response body never reaches the agent. The agent'spkg/system-probe/api/client/check.go:ensureStarted()retriesGET /debug/statsevery 5s and only ever seesEOF, blocking the discovery check from starting at all:chownAfter binding the socket,
system-probe-litecallsstd::os::unix::fs::chown(socket_path, uid, gid)to hand it off todd-agent. On x86_64 this dispatches to thechown(2)syscall, which the existing profile rejected withEPERM:Two reasons this didn't show up before:
syscall.Chownon Linux always dispatches throughfchownat(AT_FDCWD, ...)(src/syscall/syscall_linux.go), andfchownatwas already in the allowlist — so the Gosystem-probewas unaffected.chown(2)syscall in the kernel; glibc'schown()wrapper falls through tofchownat(2). Original verification was done on aarch64, where the profile happened to allow what was actually called.Failure is non-fatal in default helm deployments where every agent container runs as root (
datadog.securityContext.runAsUser: 0) and can talk to the resultingroot:rootmode 0720 socket. But for setups overriding the agent user todd-agent, the socket would be unreachable.Which issue this PR fixes
N/A.
Special notes for your reviewer:
chownis removed —fchown/fchown32/fchownatwere already unconditional, so thechowngate was inconsistent.system-probe-lite— noError serving connectionlogs, no agent-sideEOFwarnings, no chown WARN, socket correctly owneddd-agent:root,check:discoveryrunning cleanly.Checklist
datadog/patch-version)datadogchart changes, update the test baselines (regenerated viamake update-test-baselines-datadog-agent)datadogchart changes, received ✅ from a member of your teamCHANGELOG.mdhas been updated