Skip to content

Rust implementation ignores --listen-socket and is TCP-only (no UnixListener, no fd:// socket activation) #25

Closed

Description

@abienkowski

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 matches

rs/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=builders

A 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

  1. Bind a tokio::net::UnixListener on --listen-socket in rs, serving it concurrently with the TCP listener (tokio::select! or a second tokio::spawn), matching Go's dual-listener behaviour.
  2. Support fd://3 via std::os::unix::io::FromRawFd + UnixListener::from_std for socket activation.
  3. Remove #![allow(dead_code)] from rs/src/main.rs:1, or narrow its scope, so regressions like this surface at compile time.
  4. Until (1) lands, either make rs fail fast when --listen-socket is set (rather than ignoring it), or correct README.md:184 to say Go-only.
  5. 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.

Activity

  1. added
    Priority: P1Added to issues and PRs relating to a high severity bugs.
    Type: BugAdded to issues and PRs if they are addressing a bug
    on Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Priority: P1Added to issues and PRs relating to a high severity bugs.Type: BugAdded to issues and PRs if they are addressing a bug

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions