//go:build darwin && !ios package cmd import ( "fmt" "os" "syscall" ) // dropAgentPrivileges drops the vnc-agent process from root (its // launchctl-asuser-inherited starting uid) to the target console user // before any other initialisation runs. Without this the agent runs as // root for the lifetime of the session; any post-auth memory-safety // issue in the capture/input/encode paths would then be a root-level // RCE on the host instead of a user-level one. Also makes the daemon's // LOCAL_PEERCRED check correctly identify the agent as the console user, // not as root. // // Returns an error when the agent is running as a non-root uid that // differs from targetUID: non-root can only setuid to itself, so a // mismatch here means the spawn went to the wrong session. func dropAgentPrivileges(targetUID uint32) error { if targetUID == 0 { return fmt.Errorf("refusing to keep agent running as root (target uid 0)") } cur := uint32(os.Getuid()) if cur == targetUID { return nil } if cur != 0 { return fmt.Errorf("agent uid %d does not match expected %d and we lack root to fix it", cur, targetUID) } // Drop supplementary groups first: setgid alone doesn't touch the // auxiliary group list, leaving root's groups attached would let the // dropped process write to root-only group-writable files. if err := syscall.Setgroups([]int{}); err != nil { return fmt.Errorf("setgroups([]): %w", err) } if err := syscall.Setgid(int(targetUID)); err != nil { return fmt.Errorf("setgid(%d): %w", targetUID, err) } if err := syscall.Setuid(int(targetUID)); err != nil { return fmt.Errorf("setuid(%d): %w", targetUID, err) } if uint32(os.Getuid()) != targetUID || uint32(os.Geteuid()) != targetUID { return fmt.Errorf("setuid verification: uid=%d euid=%d, expected %d", os.Getuid(), os.Geteuid(), targetUID) } return nil }