From a9a586df5daf553b8274e954284b923e30cf3e7a Mon Sep 17 00:00:00 2001 From: Viktor Liu Date: Wed, 9 Sep 2026 10:04:06 +0200 Subject: [PATCH] Refuse a socket restriction on a Windows unix socket instead of ignoring it --- client/cmd/service_allow_group_windows.go | 25 +++++++++++++------ .../cmd/service_allow_group_windows_test.go | 12 +++++++++ 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/client/cmd/service_allow_group_windows.go b/client/cmd/service_allow_group_windows.go index 0eb12bbcb..608ae8a1e 100644 --- a/client/cmd/service_allow_group_windows.go +++ b/client/cmd/service_allow_group_windows.go @@ -43,15 +43,24 @@ func resolveAllowGroup(value string) (string, error) { // one ACE per principal. func checkAllowGroupSet([]string) error { return nil } -// applySocketAccess is a no-op on Windows, where access is decided by the -// security descriptor the pipe is created with rather than by a mode set on it -// afterwards. See allowedPipeSDDL. -func applySocketAccess(string, []string) error { return nil } +// applySocketAccess applies the restriction to a Unix socket, which on Windows +// it cannot: AF_UNIX sockets there carry no mode, and the daemon has no way to +// keep another local process off one. Serving it unrestricted would be the +// fail-open this flag exists to prevent, so a configured restriction is an +// error instead. +// +// The pipe transport is unaffected: its access lives in the security descriptor +// it is created with, and restrict never routes a named pipe here. +func applySocketAccess(path string, principals []string) error { + if len(principals) == 0 { + return nil + } + return fmt.Errorf("cannot restrict the unix socket %s on windows: it carries no access mode, serve the daemon on npipe:// instead", path) +} -// listenUnixPrivate binds a Unix socket. Windows has no umask, and a Unix -// socket there carries no mode the daemon could narrow, so there is nothing to -// do beyond binding it. A restriction on this transport is refused before it -// gets here: see listenOnAddress. +// listenUnixPrivate binds a Unix socket. Windows has no umask and no mode on +// these sockets, so binding is all there is to do; a configured restriction is +// refused by applySocketAccess before the daemon serves. func listenUnixPrivate(address string) (net.Listener, error) { return net.Listen("unix", address) } diff --git a/client/cmd/service_allow_group_windows_test.go b/client/cmd/service_allow_group_windows_test.go index a69111f1a..b147f6383 100644 --- a/client/cmd/service_allow_group_windows_test.go +++ b/client/cmd/service_allow_group_windows_test.go @@ -80,6 +80,18 @@ func TestCheckAllowGroupSet_AcceptsAny(t *testing.T) { assert.NoError(t, checkAllowGroupSet([]string{"sid:" + sidAdministrators, "sid:S-1-5-18"})) } +// A Unix socket on Windows carries no mode, so a restriction configured +// against one cannot be applied and must stop the daemon rather than leave the +// socket open to every local process. +func TestApplySocketAccess_UnixSocketCannotBeRestricted(t *testing.T) { + assert.NoError(t, applySocketAccess(`C:\ProgramData\Netbird\netbird.sock`, nil), + "an unrestricted unix socket is the historical behaviour and stays allowed") + + err := applySocketAccess(`C:\ProgramData\Netbird\netbird.sock`, []string{testAllowGroupPrincipal}) + require.Error(t, err) + assert.Contains(t, err.Error(), "npipe://", "the error should name the transport that can carry the restriction") +} + func TestAllowedPipeSDDL(t *testing.T) { t.Run("no principals leaves the pipe open", func(t *testing.T) { sddl, err := allowedPipeSDDL(nil)