Skip to content

Commit b10b1d4

Browse files
authored
chore(profiling): Use SECURITY_ANONYMOUS when connecting to named pipe server (#2134)
# What does this PR do? Disables named-pipe client impersonation in libdatadog's Windows named-pipe transport by opening the pipe with `SECURITY_ANONYMOUS` (the Rust/tokio equivalent of .NET's `TokenImpersonationLevel.Anonymous`). Concretely, `libdd-common`'s hyper-based connector now sets `ClientOptions::security_qos_flags(SECURITY_ANONYMOUS)` when opening a named pipe in `ConnStream::from_named_pipe_uri`, instead of relying on tokio's default of `SECURITY_IDENTIFICATION`. # Motivation This mirrors the dd-trace-dotnet change [Set `TokenImpersonationLevel.Anonymous` in `NamedPipeClient` (#8676)] DataDog/dd-trace-dotnet#8676). A Windows named pipe exposes the connecting client's security token to the server end, which can use it to identify or even impersonate the client. In our usage the pipe is just a dumb byte transport to the Datadog Agent, so the server end never needs any information about, or the ability to act as, the connecting (potentially privileged) process. Opening with `Anonymous` enforces least privilege and removes that capability, hardening against a malicious or pipe-squatting server reading the client's identity/privileges. # Additional Notes - libdatadog opens named-pipe clients through two transport stacks: - **hyper connector** (`libdd-common`) — fixed in this PR. Used by the `libdd-http-client` hyper backend. - **reqwest** — the path used by the profiling exporter, `libdd-agent-client`, and the default `libdd-http-client` backend. reqwest currently hardcodes `ClientOptions::new().open(pipe)` and exposes no API to set the QoS flags, so that path is **not** addressed here. A follow-up will upstream a security-flags option to reqwest and then consume it at those call sites. - Severity note: tokio's default is already `Identification` (not `Impersonation`), so the server cannot *act* as the client today; it can only read the client's identity/privileges. This change is therefore a defense-in-depth / least-privilege hardening that aligns the Rust posture with the .NET fix, not a fix for an exploitable privilege escalation on the Rust side. - A shared `ANONYMOUS_IMPERSONATION_QOS` constant was added in `libdd-common/src/connector/named_pipe.rs` so the same value can be reused by the future reqwest call sites. # How to test the change? The change is Windows-only (`#[cfg(windows)]`). - Automated: a round-trip test `from_named_pipe_uri_connects_with_anonymous_qos` was added in `libdd-common/src/connector/conn_stream.rs`. It stands up a tokio `ServerOptions` named-pipe server and asserts the client connects successfully with the Anonymous QoS flags. On a Windows host: Co-authored-by: gregory.leocadie <gregory.leocadie@datadoghq.com>
1 parent 54bd386 commit b10b1d4

2 files changed

Lines changed: 57 additions & 1 deletion

File tree

libdd-common/src/connector/conn_stream.rs

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ impl ConnStream {
6363
let path = super::named_pipe::named_pipe_path_from_uri(&uri)?;
6464
Ok(ConnStream::NamedPipe {
6565
transport: TokioIo::new(
66-
tokio::net::windows::named_pipe::ClientOptions::new().open(path)?,
66+
tokio::net::windows::named_pipe::ClientOptions::new()
67+
.security_qos_flags(super::named_pipe::ANONYMOUS_IMPERSONATION_QOS)
68+
.open(path)?,
6769
),
6870
})
6971
}
@@ -190,3 +192,43 @@ impl hyper::rt::Write for ConnStream {
190192
}
191193
}
192194
}
195+
196+
#[cfg(all(test, windows))]
197+
mod windows_named_pipe_tests {
198+
use super::ConnStream;
199+
use crate::connector::named_pipe::named_pipe_path_to_uri;
200+
use std::path::Path;
201+
use tokio::net::windows::named_pipe::ServerOptions;
202+
203+
/// Verifies that `from_named_pipe_uri` opens a client connection successfully
204+
/// when impersonation is disabled (Anonymous QoS). The server accepting the
205+
/// connection confirms the `SECURITY_SQOS_PRESENT | SECURITY_ANONYMOUS` flags
206+
/// produce a usable transport.
207+
#[tokio::test]
208+
async fn from_named_pipe_uri_connects_with_anonymous_qos() {
209+
let pipe_name = format!(
210+
r"\\.\pipe\libdd_common_conn_stream_test_{}_{}",
211+
std::process::id(),
212+
rand::random::<u64>()
213+
);
214+
215+
let server = ServerOptions::new()
216+
.first_pipe_instance(true)
217+
.create(&pipe_name)
218+
.expect("failed to create named pipe server");
219+
220+
let server_task = tokio::spawn(async move {
221+
server.connect().await.expect("server failed to accept");
222+
});
223+
224+
let uri = named_pipe_path_to_uri(Path::new(&pipe_name)).expect("failed to build uri");
225+
let conn = ConnStream::from_named_pipe_uri(uri).await;
226+
assert!(
227+
conn.is_ok(),
228+
"expected named pipe client to connect with Anonymous QoS: {:?}",
229+
conn.err()
230+
);
231+
232+
server_task.await.expect("server task panicked");
233+
}
234+
}

libdd-common/src/connector/named_pipe.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,20 @@
33

44
use std::path::{Path, PathBuf};
55

6+
/// Security Quality of Service flags used when opening a Windows named pipe client.
7+
///
8+
/// We disable impersonation by requesting `SECURITY_ANONYMOUS`, mirroring the
9+
/// dd-trace-dotnet change that sets `TokenImpersonationLevel.Anonymous`. The pipe is
10+
/// only used as a byte transport to the agent, so the server end never needs to
11+
/// identify or impersonate the (potentially privileged) connecting client. Denying
12+
/// impersonation enforces least privilege and prevents a malicious/squatting pipe
13+
/// server from reading the client's identity or acting on its behalf.
14+
///
15+
/// `SECURITY_ANONYMOUS` has the value `0`; tokio's `ClientOptions::security_qos_flags`
16+
/// automatically ORs in `SECURITY_SQOS_PRESENT`.
17+
#[cfg(windows)]
18+
pub const ANONYMOUS_IMPERSONATION_QOS: u32 = 0; // SECURITY_ANONYMOUS
19+
620
/// Windows Named Pipe
721
/// https://docs.microsoft.com/en-us/windows/win32/ipc/named-pipes
822
///

0 commit comments

Comments
 (0)