Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion pkg/config/pathmanager_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@ func (d *darwinPathManager) StateDir() (string, error) {
return ensureDir(filepath.Join(home, "."+RepoName, "state"))
}

// RuntimeDir returns a directory for runtime state, such as sockets and PID files.
func (d *darwinPathManager) RuntimeDir() (string, error) {
return ensureDir(filepath.Join(os.TempDir(), fmt.Sprintf("%s-%d", RepoName, os.Getuid())))
home, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("runtime dir: %w", err)
}

dir, err := ensureDir(filepath.Join(home, "."+RepoName, "run"))
if err != nil {
return "", err
}
return dir, nil
}
21 changes: 5 additions & 16 deletions pkg/ssh/server/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,8 @@ import (
const (
agentSocketDirPrefix = "devsy-ssh-agent-"

// maxAgentSocketPath is the most restrictive Unix-domain socket path
// length across supported platforms (macOS/BSD: sun_path[104];
// Linux: sun_path[108]). bind(2) returns EINVAL when exceeded, which
// surfaces as "invalid argument" — a confusing error far from the cause.
// We check eagerly and fail with a clear message instead.
// maxAgentSocketPath is the size of the OS sun_path array. bind(2) returns
// EINVAL when the path length reaches this limit.
maxAgentSocketPath = 104

// agentListenFile mirrors the socket filename ssh.NewAgentListener
Expand Down Expand Up @@ -149,20 +146,12 @@ func SweepStaleAgentSockets() {
}

// checkAgentSocketPathLen verifies the eventual unix socket path fits within
// the OS sun_path limit. bind(2) returns the unhelpful EINVAL when it does
// not, so we validate up front and emit a clear, actionable error that
// names the offending path and the limit.
// the OS sun_path limit.
func checkAgentSocketPathLen(dir string) error {
sockPath := filepath.Join(dir, agentListenFile)
if len(sockPath) > maxAgentSocketPath {
log.Warnf(
"agent socket path exceeds max (%d > %d): %s",
len(sockPath),
maxAgentSocketPath,
sockPath,
)
if len(sockPath) >= maxAgentSocketPath {
return fmt.Errorf(
"agent socket path exceeds max (%d > %d): %s",
"agent socket path exceeds max (%d >= %d): %s",
len(sockPath),
maxAgentSocketPath,
sockPath,
Expand Down
14 changes: 14 additions & 0 deletions pkg/ssh/server/agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,20 @@ func TestCleanupAgentSocketDir(t *testing.T) {
})
}

func TestCheckAgentSocketPathLen_Boundary(t *testing.T) {
dirFor := func(pathLen int) string {
suffixLen := len(string(os.PathSeparator)) + len(agentListenFile)
return strings.Repeat("a", pathLen-suffixLen)
}

assert.NoError(t, checkAgentSocketPathLen(dirFor(maxAgentSocketPath-1)),
"path of maxAgentSocketPath-1 must be accepted")
assert.Error(t, checkAgentSocketPathLen(dirFor(maxAgentSocketPath)),
"path of exactly maxAgentSocketPath must be rejected (kernel returns EINVAL)")
assert.Error(t, checkAgentSocketPathLen(dirFor(maxAgentSocketPath+1)),
"path over maxAgentSocketPath must be rejected")
}

func TestSetupConnectionAgentListener_BadRuntimeDir(t *testing.T) {
if runtime.GOOS == osWindows {
t.Skip("unix socket based test")
Expand Down
Loading