mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-18 16:26:38 +00:00
[client] Use setsid to avoid the parent process from being killed via HUP by login (#4900)
This commit is contained in:
@@ -42,6 +42,11 @@ func (s *Server) detectSuPtySupport(context.Context) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// detectUtilLinuxLogin always returns false on JS/WASM
|
||||||
|
func (s *Server) detectUtilLinuxLogin(context.Context) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// executeCommandWithPty is not supported on JS/WASM
|
// executeCommandWithPty is not supported on JS/WASM
|
||||||
func (s *Server) executeCommandWithPty(logger *log.Entry, session ssh.Session, execCmd *exec.Cmd, privilegeResult PrivilegeCheckResult, ptyReq ssh.Pty, winCh <-chan ssh.Window) bool {
|
func (s *Server) executeCommandWithPty(logger *log.Entry, session ssh.Session, execCmd *exec.Cmd, privilegeResult PrivilegeCheckResult, ptyReq ssh.Pty, winCh <-chan ssh.Window) bool {
|
||||||
logger.Errorf("PTY command execution not supported on JS/WASM")
|
logger.Errorf("PTY command execution not supported on JS/WASM")
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/user"
|
"os/user"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
@@ -75,6 +76,29 @@ func (s *Server) detectSuPtySupport(ctx context.Context) bool {
|
|||||||
return supported
|
return supported
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// detectUtilLinuxLogin checks if login is from util-linux (vs shadow-utils).
|
||||||
|
// util-linux login uses vhangup() which requires setsid wrapper to avoid killing parent.
|
||||||
|
// See https://bugs.debian.org/1078023 for details.
|
||||||
|
func (s *Server) detectUtilLinuxLogin(ctx context.Context) bool {
|
||||||
|
if runtime.GOOS != "linux" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, 500*time.Millisecond)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
cmd := exec.CommandContext(ctx, "login", "--version")
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("login --version failed (likely shadow-utils): %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
isUtilLinux := strings.Contains(string(output), "util-linux")
|
||||||
|
log.Debugf("util-linux login detected: %v", isUtilLinux)
|
||||||
|
return isUtilLinux
|
||||||
|
}
|
||||||
|
|
||||||
// createSuCommand creates a command using su -l -c for privilege switching
|
// createSuCommand creates a command using su -l -c for privilege switching
|
||||||
func (s *Server) createSuCommand(session ssh.Session, localUser *user.User, hasPty bool) (*exec.Cmd, error) {
|
func (s *Server) createSuCommand(session ssh.Session, localUser *user.User, hasPty bool) (*exec.Cmd, error) {
|
||||||
suPath, err := exec.LookPath("su")
|
suPath, err := exec.LookPath("su")
|
||||||
@@ -144,7 +168,7 @@ func (s *Server) handlePty(logger *log.Entry, session ssh.Session, privilegeResu
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Infof("starting interactive shell: %s", execCmd.Path)
|
logger.Infof("starting interactive shell: %s", strings.Join(execCmd.Args, " "))
|
||||||
return s.runPtyCommand(logger, session, execCmd, ptyReq, winCh)
|
return s.runPtyCommand(logger, session, execCmd, ptyReq, winCh)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -383,6 +383,11 @@ func (s *Server) detectSuPtySupport(context.Context) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// detectUtilLinuxLogin always returns false on Windows
|
||||||
|
func (s *Server) detectUtilLinuxLogin(context.Context) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// executeCommandWithPty executes a command with PTY allocation on Windows using ConPty
|
// executeCommandWithPty executes a command with PTY allocation on Windows using ConPty
|
||||||
func (s *Server) executeCommandWithPty(logger *log.Entry, session ssh.Session, execCmd *exec.Cmd, privilegeResult PrivilegeCheckResult, ptyReq ssh.Pty, winCh <-chan ssh.Window) bool {
|
func (s *Server) executeCommandWithPty(logger *log.Entry, session ssh.Session, execCmd *exec.Cmd, privilegeResult PrivilegeCheckResult, ptyReq ssh.Pty, winCh <-chan ssh.Window) bool {
|
||||||
command := session.RawCommand()
|
command := session.RawCommand()
|
||||||
|
|||||||
@@ -138,7 +138,8 @@ type Server struct {
|
|||||||
jwtExtractor *jwt.ClaimsExtractor
|
jwtExtractor *jwt.ClaimsExtractor
|
||||||
jwtConfig *JWTConfig
|
jwtConfig *JWTConfig
|
||||||
|
|
||||||
suSupportsPty bool
|
suSupportsPty bool
|
||||||
|
loginIsUtilLinux bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type JWTConfig struct {
|
type JWTConfig struct {
|
||||||
@@ -193,6 +194,7 @@ func (s *Server) Start(ctx context.Context, addr netip.AddrPort) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s.suSupportsPty = s.detectSuPtySupport(ctx)
|
s.suSupportsPty = s.detectSuPtySupport(ctx)
|
||||||
|
s.loginIsUtilLinux = s.detectUtilLinuxLogin(ctx)
|
||||||
|
|
||||||
ln, addrDesc, err := s.createListener(ctx, addr)
|
ln, addrDesc, err := s.createListener(ctx, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -87,11 +87,8 @@ func (s *Server) getLoginCmd(username string, remoteAddr net.Addr) (string, []st
|
|||||||
|
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
case "linux":
|
case "linux":
|
||||||
// Special handling for Arch Linux without /etc/pam.d/remote
|
p, a := s.getLinuxLoginCmd(loginPath, username, addrPort.Addr().String())
|
||||||
if s.fileExists("/etc/arch-release") && !s.fileExists("/etc/pam.d/remote") {
|
return p, a, nil
|
||||||
return loginPath, []string{"-f", username, "-p"}, nil
|
|
||||||
}
|
|
||||||
return loginPath, []string{"-f", username, "-h", addrPort.Addr().String(), "-p"}, nil
|
|
||||||
case "darwin", "freebsd", "openbsd", "netbsd", "dragonfly":
|
case "darwin", "freebsd", "openbsd", "netbsd", "dragonfly":
|
||||||
return loginPath, []string{"-fp", "-h", addrPort.Addr().String(), username}, nil
|
return loginPath, []string{"-fp", "-h", addrPort.Addr().String(), username}, nil
|
||||||
default:
|
default:
|
||||||
@@ -99,7 +96,37 @@ func (s *Server) getLoginCmd(username string, remoteAddr net.Addr) (string, []st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fileExists checks if a file exists (helper for login command logic)
|
// getLinuxLoginCmd returns the login command for Linux systems.
|
||||||
|
// Handles differences between util-linux and shadow-utils login implementations.
|
||||||
|
func (s *Server) getLinuxLoginCmd(loginPath, username, remoteIP string) (string, []string) {
|
||||||
|
// Special handling for Arch Linux without /etc/pam.d/remote
|
||||||
|
var loginArgs []string
|
||||||
|
if s.fileExists("/etc/arch-release") && !s.fileExists("/etc/pam.d/remote") {
|
||||||
|
loginArgs = []string{"-f", username, "-p"}
|
||||||
|
} else {
|
||||||
|
loginArgs = []string{"-f", username, "-h", remoteIP, "-p"}
|
||||||
|
}
|
||||||
|
|
||||||
|
// util-linux login requires setsid -c to create a new session and set the
|
||||||
|
// controlling terminal. Without this, vhangup() kills the parent process.
|
||||||
|
// See https://bugs.debian.org/1078023 for details.
|
||||||
|
// TODO: handle this via the executor using syscall.Setsid() + TIOCSCTTY + syscall.Exec()
|
||||||
|
// to avoid external setsid dependency.
|
||||||
|
if !s.loginIsUtilLinux {
|
||||||
|
return loginPath, loginArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
setsidPath, err := exec.LookPath("setsid")
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("setsid not available but util-linux login detected, login may fail: %v", err)
|
||||||
|
return loginPath, loginArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
args := append([]string{"-w", "-c", loginPath}, loginArgs...)
|
||||||
|
return setsidPath, args
|
||||||
|
}
|
||||||
|
|
||||||
|
// fileExists checks if a file exists
|
||||||
func (s *Server) fileExists(path string) bool {
|
func (s *Server) fileExists(path string) bool {
|
||||||
_, err := os.Stat(path)
|
_, err := os.Stat(path)
|
||||||
return err == nil
|
return err == nil
|
||||||
|
|||||||
Reference in New Issue
Block a user