Repository navigation
Rust implementation ignores --listen-socket and is TCP-only (no UnixListener, no fd:// socket activation) #25
Description
Summary
The Rust implementation accepts --listen-socket but never binds a Unix socket. The flag is parsed and then never read again, so rs is TCP-only in practice while advertising Unix-socket support. This is a fail-open security issue for deployments that rely on Unix-socket file ownership as their access-control boundary.
Evidence
listen_socket appears exactly once in the whole Rust tree — its own declaration:
$ grep -rn "listen_socket" rs/
rs/src/main.rs:21: listen_socket: String,
$ grep -rn "UnixListener" rs/
$ # no matchesrs/src/main.rs:20-21 declares it:
#[arg(long, default_value = "/var/run/docker-socket-policy.sock")]
listen_socket: String,…but main() only ever binds TCP (rs/src/main.rs:62-64):
let addr: std::net::SocketAddr = cli.listen_tcp.parse()?;
let listener = tokio::net::TcpListener::bind(addr).await?;
tracing::info!("listening on TCP {}", addr);There is no second listener and no UnixListener import. The dead field would normally produce a dead_code warning, but #![allow(dead_code)] at rs/src/main.rs:1 suppresses it.
Go implements this correctly for comparison (go/main.go:76-84), including systemd socket activation:
if network == "unix" {
if addr == "fd://3" {
listener, err = net.FileListener(os.NewFile(3, "socket"))
} else {
_ = os.Remove(addr)
listener, err = net.Listen("unix", addr)
}
}Impact
1. Silent fail-open. An operator passing --listen-socket=/var/run/docker-socket-policy.sock gets a clean startup, no warning, and no socket file — while an unauthenticated TCP listener stays open on 127.0.0.1:2375. Nothing in the logs indicates the requested listener was ignored.
2. Loses the access-control boundary. The documented deployment model gates access via socket ownership:
[Socket]
ListenStream=/var/run/docker-socket-policy.sock
SocketMode=0660
SocketGroup=buildersA TCP socket has no file ownership, so any local user or process on the host can reach policy-mediated Docker. This is the same argument the README already makes for the daemon side:
Connecting to the daemon over TCP would bypass the socket's user/group ownership, which is the security boundary.
That reasoning applies symmetrically to the listen side.
3. systemd socket activation is unimplementable on rs. --listen-socket=fd://3 (README line 213) cannot work without FromRawFd/LISTEN_FDS handling.
Docs bug
README.md:184 currently claims the flag works on both:
| `--listen-socket` | `/var/run/docker-socket-policy.sock` | Unix socket (or `fd://3` for systemd). **Go/Rust only** |
It is Go-only. The note at README.md:190-192 calls out the TypeScript limitation but not the Rust one.
Suggested fix
- Bind a
tokio::net::UnixListeneron--listen-socketinrs, serving it concurrently with the TCP listener (tokio::select!or a secondtokio::spawn), matching Go's dual-listener behaviour. - Support
fd://3viastd::os::unix::io::FromRawFd+UnixListener::from_stdfor socket activation. - Remove
#![allow(dead_code)]fromrs/src/main.rs:1, or narrow its scope, so regressions like this surface at compile time. - Until (1) lands, either make
rsfail fast when--listen-socketis set (rather than ignoring it), or correctREADME.md:184to say Go-only. - Consider an integration test asserting the socket file exists and accepts a request, run against all implementations that claim support — this would have caught it.
Context
Found while specifying the Ansible role that deploys this proxy in ChainSafe/lodestar-infrastructure (PR #9). That deployment depends on 0660 root:builders socket ownership to gate external-contributor access, so it currently cannot use the Rust build. Related parity issue: #24.
Metadata
Metadata
Assignees
Labels
Type
Fields
Priority
Activity