Skip to content

fix: set the listening socket mode instead of inheriting the umask - #42

Merged
abienkowski merged 1 commit into
mainfrom
fix/listen-socket-mode
Sep 28, 2026
Merged

abienkowski merged 1 commit into
mainfrom
fix/listen-socket-mode

Conversation

@abienkowski

Copy link
Copy Markdown
Collaborator

Closes #40.

Branched off main (not stacked this time).

The bug

bind(2) on an AF_UNIX socket creates the inode with 0777 & ~umask, and none of the three implementations touched it afterwards. unix(7): connecting to a filesystem-visible socket requires write permission on it.

ambient umask socket mode consequence
022 (default) 0755 group has no w, so the grant the README documents silently does not work — only the owning uid can connect
000 0777 any local uid can drive the full Docker API through the proxy

With the TCP listener removed in #35, this mode is the entire access-control boundary.

Changes

--listen-socket-mode (default 0660) and --listen-socket-group (group name or gid) in all three.

  • umask narrowed to 0177 around the bind, so the socket is created at 0600, then chown'd and chmod'd before the first accept. Setting the mode after bind would leave a window in which the socket is already listening at the ambient mode. Chown precedes chmod so it is never briefly reachable by the wrong group.
  • World-writable modes refused at startup, exit 2. No opt-out, per the decision on the issue — it removes the boundary entirely.
  • Both flags ignored for fd://3, where systemd owns the socket and SocketMode/SocketGroup are the right controls.

Verified natively, both umasks

=== umask 022 (was 0755) ===      === umask 000 (was 0777) ===
  go  -> mode 660  ping=200         go  -> mode 660  ping=200
  rs  -> mode 660  ping=200         rs  -> mode 660  ping=200
  ts  -> mode 660  ping=200         ts  -> mode 660  ping=200

Rejection paths, all three exit 2:

--listen-socket-mode=0666 -> rc=2 : world-writable
--listen-socket-mode=668  -> rc=2 : not an octal mode
--listen-socket-group=no-such-group-xyz -> rc=2 : no such group

One deliberate asymmetry

Group name lookup differs by runtime, and I could not make it uniform:

  • Go — os/user.LookupGroup (NSS)
  • Rust — getgrnam_r (NSS)
  • TypeScript — parses /etc/group, because Node exposes no getgrnam equivalent at all

So an NSS-backed group (LDAP, SSSD) resolves in Go and Rust but not in TypeScript. The TS error message says so and points at passing a numeric gid, which works everywhere. Flagging it explicitly rather than burying it — it is the one place the three genuinely differ.

libc added to Rust for umask(2) and getgrnam_r(3), neither of which std exposes. It was already in Cargo.lock transitively via tokio, so it costs no new compile units.

Tests

Every new test was checked to fail without its fix — they report 755 under umask 022 and 777 under umask 0, reproducing the issue exactly.

before after new
Go 84 93 mode applied, umask ignored, mode parsing incl. o+w, group resolution
Rust 124 128 same
TS 125 138 same, plus listen.ts umask restore and bind-failure paths
sock integration 8 12 ×3 mode is 660, owned by --listen-socket-group, not world-writable

ts/src/listen.ts is extracted from index.ts so the mode is testable at all — it was top-level module code with no export, the same problem shutdown had in #41.

Verification

  • make test-all, make lint-all — pass
  • make test-integration{,-rs,-ts} — 27/27 each
  • make test-integration-sock{,-rs,-ts} — 12/12 each
  • Native probes above

Docs

README.md corrected — it documented a grant mechanism that did not work. Now states the mode is set regardless of umask, explains why connect(2) needs write, notes that the group is what makes the mode useful, and points fd://3 users at the unit file.

bind(2) on an AF_UNIX socket creates the inode with 0777 & ~umask, and none of
the three implementations touched it afterwards. connect(2) requires write
permission on the socket, so at the usual umask 022 the socket came out 0755
and the grant the README documents — put the caller in the socket's group —
silently did not work; only the owning uid could connect. Under umask 0 it came
out 0777 and any local uid could drive the full Docker API through it. With the
TCP listener gone this mode is the entire access-control boundary.

Adds --listen-socket-mode (default 0660) and --listen-socket-group, taking
either a group name or a gid. The umask is narrowed to 0177 around the bind so
the socket is created at 0600, then chown'd and chmod'd before the first
accept. Setting the mode after bind instead would leave a window in which the
socket is already listening at the ambient mode.

A world-writable mode is refused at startup with exit 2. There is deliberately
no opt-out: it removes the boundary entirely.

Both flags are ignored for fd://3, where systemd owns the socket and
SocketMode/SocketGroup in the .socket unit are the right controls.

Group name lookup differs by runtime: Go uses os/user.LookupGroup and Rust uses
getgrnam_r, both of which consult NSS. Node has no equivalent, so TypeScript
reads /etc/group — enough for the container case, and the error points at
passing a numeric gid otherwise.

TypeScript's bind is extracted to ts/src/listen.ts so the mode can be tested at
all; it was top-level module code with no export.

Verified natively under both umasks, all three: mode 660 either way, and
serving. Each new test was checked to fail without the fix (it reports 755
under umask 022 and 777 under umask 0, exactly as reported).

Closes #40
@abienkowski abienkowski added Type: Bug Added to issues and PRs if they are addressing a bug Priority: P1 Added to issues and PRs relating to a high severity bugs. Status: Break Change Added to a PR or issue that would cause a breaking change labels Sep 28, 2026
@abienkowski abienkowski self-assigned this Sep 28, 2026
@abienkowski
abienkowski merged commit 15b8630 into main Sep 28, 2026
6 checks passed
@abienkowski
abienkowski deleted the fix/listen-socket-mode branch September 28, 2026 22:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Priority: P1 Added to issues and PRs relating to a high severity bugs. Status: Break Change Added to a PR or issue that would cause a breaking change Type: Bug Added to issues and PRs if they are addressing a bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Listening socket mode is left to the umask: group-grant does not work, and umask 0 makes it world-connectable

1 participant