Reuse /var/run/netbird as VNC agent socket parent via configs.RuntimeDir

This commit is contained in:
Viktor Liu
2026-05-25 11:03:51 +02:00
parent bf2fb2fd44
commit 2f67841b1e
2 changed files with 35 additions and 27 deletions

View File

@@ -6,19 +6,30 @@ import (
"runtime"
)
var StateDir string
var (
// StateDir holds persistent state (config, profiles, install metadata).
StateDir string
// RuntimeDir holds ephemeral artifacts that should not survive reboot,
// such as Unix sockets for daemon and per-session IPC. Empty on
// platforms without a conventional /var/run-style location.
RuntimeDir string
)
func init() {
StateDir = os.Getenv("NB_STATE_DIR")
if StateDir != "" {
return
}
switch runtime.GOOS {
case "windows":
StateDir = filepath.Join(os.Getenv("PROGRAMDATA"), "Netbird")
case "darwin", "linux":
StateDir = "/var/lib/netbird"
RuntimeDir = "/var/run/netbird"
case "freebsd", "openbsd", "netbsd", "dragonfly":
StateDir = "/var/db/netbird"
RuntimeDir = "/var/run/netbird"
}
if v := os.Getenv("NB_STATE_DIR"); v != "" {
StateDir = v
}
if v := os.Getenv("NB_RUNTIME_DIR"); v != "" {
RuntimeDir = v
}
}

View File

@@ -17,6 +17,8 @@ import (
log "github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
"github.com/netbirdio/netbird/client/configs"
)
// darwinAgentManager spawns a per-user VNC agent on demand and keeps it
@@ -133,37 +135,32 @@ func (m *darwinAgentManager) Resolve(ctx context.Context) (string, string, uint3
return socketPath, token, consoleUID, nil
}
// agentSocketParentDir is the root the daemon creates (as root, mode 0755)
// to hold per-uid agent-socket subdirectories. Keeping it under
// /var/run/netbird-vnc (rather than /tmp) means a non-root local user
// cannot squat the socket path: only root can create the parent, and
// only the target user (plus root) can write inside the per-uid subdir.
const agentSocketParentDir = "/var/run/netbird-vnc"
// prepareAgentSocketDir creates (and tightens permissions on) a per-uid
// subdirectory the agent will bind its socket inside, returning the
// directory path. The subdirectory is owned by uid with mode 0700, so
// the only writers are the target user and root. The parent is created
// root-owned with mode 0755 if it doesn't already exist. Symlinks at
// the per-uid level are refused (replaced with a fresh directory) to
// avoid a low-priv user redirecting our chown.
// prepareAgentSocketDir creates a per-uid subdirectory under the netbird
// runtime directory where the agent will bind its Unix socket. The leaf is
// owned by uid with mode 0700, so only the target user and root can write
// there. The parent is created root-owned with mode 0755 if missing.
// Symlinks at the per-uid level are refused (replaced with a fresh
// directory) so a low-priv user cannot redirect the chown that follows.
func prepareAgentSocketDir(uid uint32) (string, error) {
if err := os.MkdirAll(agentSocketParentDir, 0o755); err != nil {
return "", fmt.Errorf("mkdir %s: %w", agentSocketParentDir, err)
parent := configs.RuntimeDir
if parent == "" {
return "", fmt.Errorf("no runtime directory configured for this platform")
}
// Refuse to use the parent if it's a symlink or not owned by root.
pInfo, err := os.Lstat(agentSocketParentDir)
if err := os.MkdirAll(parent, 0o755); err != nil {
return "", fmt.Errorf("mkdir %s: %w", parent, err)
}
pInfo, err := os.Lstat(parent)
if err != nil {
return "", fmt.Errorf("lstat %s: %w", agentSocketParentDir, err)
return "", fmt.Errorf("lstat %s: %w", parent, err)
}
if pInfo.Mode()&os.ModeSymlink != 0 {
return "", fmt.Errorf("%s is a symlink", agentSocketParentDir)
return "", fmt.Errorf("%s is a symlink", parent)
}
if st, ok := pInfo.Sys().(*syscall.Stat_t); ok && st.Uid != 0 {
return "", fmt.Errorf("%s not owned by root (uid=%d)", agentSocketParentDir, st.Uid)
return "", fmt.Errorf("%s not owned by root (uid=%d)", parent, st.Uid)
}
subdir := fmt.Sprintf("%s/%d", agentSocketParentDir, uid)
subdir := fmt.Sprintf("%s/vnc-%d", parent, uid)
// If a leftover entry exists, refuse it unless it's a real dir owned
// by the right uid with strict perms: otherwise remove and recreate
// from scratch under our control. Using os.Lstat (not Stat) so a