session-helper: embeddable binary and embedding logic (#65784)

* Clean up unneeded forwarder for reexec.IsReexec

* Disallow reexec through common.Run

* Remove the checkhomedir SSH subsystem

* Add true reexec subcommand

* Assume /proc/self/exe is always usable

* Add sessionhelper embed logic
This commit is contained in:
Edoardo Spadolini
2026-04-17 14:16:53 +00:00
committed by GitHub
parent 6105aebd81
commit 3da0f44c80
23 changed files with 517 additions and 159 deletions
-4
View File
@@ -962,10 +962,6 @@ const (
)
const (
// GetHomeDirSubsystem is an SSH subsystem request that Teleport
// uses to get the home directory of a remote user.
GetHomeDirSubsystem = "gethomedir"
// SFTPSubsystem is the SFTP SSH subsystem.
SFTPSubsystem = "sftp"
)
+1 -1
View File
@@ -176,6 +176,7 @@ require (
github.com/json-iterator/go v1.1.12
github.com/julienschmidt/httprouter v1.3.0
github.com/keys-pub/go-libfido2 v1.5.3-0.20220306005615-8ab03fb1ec27
github.com/klauspost/compress v1.18.5
github.com/mailgun/mailgun-go/v4 v4.23.0
github.com/mark3labs/mcp-go v0.43.0
github.com/mattn/go-shellwords v1.0.12
@@ -468,7 +469,6 @@ require (
github.com/joshlf/testutil v0.0.0-20170608050642-b5d8aa79d93d // indirect
github.com/kelseyhightower/envconfig v1.4.0 // indirect
github.com/klauspost/asmfmt v1.3.2 // indirect
github.com/klauspost/compress v1.18.5 // indirect
github.com/klauspost/cpuid/v2 v2.2.11 // indirect
github.com/kr/fs v0.1.0 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
+27
View File
@@ -195,6 +195,7 @@ import (
webapp "github.com/gravitational/teleport/lib/web/app"
"github.com/gravitational/teleport/session/auditd"
"github.com/gravitational/teleport/session/pam"
"github.com/gravitational/teleport/session/reexec"
"github.com/gravitational/teleport/session/selinux"
)
@@ -3536,6 +3537,15 @@ func (process *TeleportProcess) initSSH() error {
logger.WarnContext(process.ExitContext(), warn)
}
// TODO(espadolini): relax this once the selinux module is updated to support the potentially embedded reexec helper
if !cfg.SSH.EnableSELinux {
checkEmbeddedReexecAndLog(process.ExitContext(), logger)
} else {
logger.DebugContext(process.ExitContext(),
"The embedded session helper is not supported when SELinux support is enabled.",
)
}
useLocalListener := cfg.SSH.ForceListen || !conn.UseTunnel()
// Provide helpful log message if listen_addr or public_addr are not being
@@ -3828,6 +3838,23 @@ func (process *TeleportProcess) initSSH() error {
return nil
}
func checkEmbeddedReexecAndLog(ctx context.Context, logger *slog.Logger) {
if ok, err := reexec.InitEmbeddedReexec(); err != nil {
logger.WarnContext(ctx,
"This Teleport build supports the embedded session helper but it is not available in this environment, performance of user sessions might be impacted.",
"error", err,
)
} else if ok {
logger.DebugContext(ctx,
"The embedded session helper is available and will be used for user sessions.",
)
} else {
logger.DebugContext(ctx,
"This Teleport build does not support the embedded session helper for user sessions.",
)
}
}
// RegisterWithAuthServer uses one time provisioning token obtained earlier
// from the server to get a pair of SSH keys signed by Auth server host
// certificate authority
-70
View File
@@ -1,70 +0,0 @@
/*
* Teleport
* Copyright (C) 2023 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package regular
import (
"context"
"io"
"os"
"os/user"
"strings"
"github.com/gravitational/trace"
"golang.org/x/crypto/ssh"
"github.com/gravitational/teleport/lib/srv"
"github.com/gravitational/teleport/session/reexec"
)
type homeDirSubsys struct {
done chan struct{}
}
func newHomeDirSubsys() *homeDirSubsys {
return &homeDirSubsys{
done: make(chan struct{}),
}
}
func (h *homeDirSubsys) Start(_ context.Context, serverConn *ssh.ServerConn, ch ssh.Channel, _ *ssh.Request, _ *srv.ServerContext) error {
defer close(h.done)
connUser := serverConn.User()
localUser, err := user.Lookup(connUser)
if err != nil {
return trace.Wrap(err)
}
exists, err := reexec.CheckHomeDir(localUser)
if err != nil {
return trace.Wrap(err)
}
homeDir := localUser.HomeDir
if !exists {
homeDir = string(os.PathSeparator)
}
_, err = io.Copy(ch, strings.NewReader(homeDir))
return trace.Wrap(err)
}
func (h *homeDirSubsys) Wait() error {
<-h.done
return nil
}
-3
View File
@@ -2545,9 +2545,6 @@ func (s *Server) parseSubsystemRequest(ctx context.Context, req *ssh.Request, se
}
switch r.Name {
// DELETE IN 15.0.0 (deprecated, tsh will not be using this anymore)
case teleport.GetHomeDirSubsystem:
return newHomeDirSubsys(), nil
case teleport.SFTPSubsystem:
err := serverContext.CheckSFTPAllowed(s.reg)
if err != nil {
+2 -2
View File
@@ -2757,9 +2757,9 @@ func TestParseSubsystemRequest(t *testing.T) {
wantErrInRegularMode: false,
},
{
name: teleport.GetHomeDirSubsystem,
name: "gethomedir",
wantErrInProxyMode: true,
wantErrInRegularMode: false,
wantErrInRegularMode: true,
},
{
name: "proxysites",
@@ -0,0 +1,36 @@
//go:build !sessionhelper_embed
// Teleport
// Copyright (C) 2026 Gravitational, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"fmt"
"os"
"github.com/gravitational/teleport/session/reexec"
"github.com/gravitational/teleport/session/reexec/reexecconstants"
)
func main() {
if len(os.Args) < 2 {
fmt.Fprintf(os.Stderr, "Failed to launch: missing subcommand.\n")
os.Exit(reexecconstants.RemoteCommandFailure)
}
reexec.RunAndExit(os.Args[1])
}
+1
View File
@@ -0,0 +1 @@
/sessionhelper_*.gz
+28
View File
@@ -0,0 +1,28 @@
// Teleport
// Copyright (C) 2026 Gravitational, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package reexec
// InitEmbeddedReexec tries to set up the embedded session helper for execution,
// returning true if the helper is available and was tested successfully, false
// if there is no embedded session helper in the build, or an error if the
// helper is present in the build but the setup failed. This function can be
// called multiple times with no adverse effects, and, if successful, any
// subsequent call to [CommandOSTweaks] will result in a reexecution using the
// embedded helper rather than the current process.
func InitEmbeddedReexec() (bool, error) {
return initEmbeddedReexec()
}
+78
View File
@@ -0,0 +1,78 @@
//go:build sessionhelper_embed
// Teleport
// Copyright (C) 2026 Gravitational, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package reexec
import (
_ "embed"
"os"
"os/exec"
"sync"
"sync/atomic"
"github.com/gravitational/trace"
)
var embeddedReexecMemfd atomic.Pointer[os.File]
var embeddedReexecOnce sync.Once
var embeddedReexecError error
func initEmbeddedReexec() (bool, error) {
embeddedReexecOnce.Do(func() {
if os.Getenv("TELEPORT_UNSTABLE_DISABLE_EMBEDDED_REEXEC") == "yes" {
embeddedReexecError = trace.Errorf("embedded binary disabled by TELEPORT_UNSTABLE_DISABLE_EMBEDDED_REEXEC")
return
}
// this name is only displayed as a link in /proc/<pid>/exe or /proc/<pid>/fd/<n>
mf, err := loadEmbeddedReexec("teleport-sessionhelper", sessionHelperGZ)
if err != nil {
embeddedReexecError = trace.Wrap(err)
return
}
embeddedReexecMemfd.Store(mf)
})
return true, embeddedReexecError
}
func setLinuxReexecPath(cmd *exec.Cmd) {
if cmd.SysProcAttr != nil && cmd.SysProcAttr.Credential != nil {
// this should never happen because the main process always launches the
// "exec" or "networking" subcommands as itself and there's no other
// direct launches with unprivileged credentials anymore, but if we
// accidentally launched the session helper directly as an unprivileged
// child it just wouldn't work due to permissions, because the child
// wouldn't be able to launch "/proc/<pid of parent>/fd/<n>" and there's
// no other way to pass a file descriptor such that it's available in
// the post-fork pre-exec environment but not inherited by the child in
// go
cmd.Path = "/proc/self/exe"
return
}
if mf := embeddedReexecMemfd.Load(); mf != nil {
cmd.Path = mf.Name()
return
}
// we failed to initialize the reexec memfd or we haven't bothered to call
// [initEmbeddedReexec] in this process (maybe because we're reexecuting a
// subcommand from a subcommand in the main binary)
cmd.Path = "/proc/self/exe"
}
+24
View File
@@ -0,0 +1,24 @@
//go:build sessionhelper_embed
// Teleport
// Copyright (C) 2026 Gravitational, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package reexec
import _ "embed"
//go:embed sessionhelper_linux_386.gz
var sessionHelperGZ string
+24
View File
@@ -0,0 +1,24 @@
//go:build sessionhelper_embed
// Teleport
// Copyright (C) 2026 Gravitational, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package reexec
import _ "embed"
//go:embed sessionhelper_linux_amd64.gz
var sessionHelperGZ string
+24
View File
@@ -0,0 +1,24 @@
//go:build sessionhelper_embed
// Teleport
// Copyright (C) 2026 Gravitational, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package reexec
import _ "embed"
//go:embed sessionhelper_linux_arm.gz
var sessionHelperGZ string
+24
View File
@@ -0,0 +1,24 @@
//go:build sessionhelper_embed
// Teleport
// Copyright (C) 2026 Gravitational, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package reexec
import _ "embed"
//go:embed sessionhelper_linux_arm64.gz
var sessionHelperGZ string
+112
View File
@@ -0,0 +1,112 @@
// Teleport
// Copyright (C) 2026 Gravitational, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package reexec
import (
"errors"
"fmt"
"os"
"os/exec"
"strings"
"github.com/gravitational/trace"
"github.com/klauspost/compress/gzip"
"golang.org/x/sys/unix"
"github.com/gravitational/teleport/session/reexec/reexecconstants"
)
// loadEmbeddedReexec loads the given gzipped session helper binary into a
// sealed, executable memfd and tests it to confirm that it can be executed,
// then returns the memfd.
func loadEmbeddedReexec(name string, dataGZ string) (*os.File, error) {
mf, err := createExecutableMemfd(name, dataGZ)
if err != nil {
return nil, trace.Wrap(err)
}
// TODO(espadolini): set the selinux file label on mf, figure out if it
// should be the same as the current executable or a fixed
// teleport_ssh_exec_t
// run a self test with a noop command, just to be sure
c := &exec.Cmd{
Path: mf.Name(),
Args: []string{"teleport", reexecconstants.TrueSubCommand},
}
if err := c.Run(); err != nil {
_ = mf.Close()
return nil, trace.Wrap(trace.ConvertSystemError(err), "failed to launch embedded binary")
}
return mf, nil
}
// createExecutableMemfd returns a sealed memfd with the given name, containing
// the uncompressed gzipped binary.
func createExecutableMemfd(name string, dataGZ string) (_ *os.File, err error) {
// we follow the logic from runc here
// https://github.com/opencontainers/runc/blob/edbed618bff99edaaa9d622f9f8687004fe5e50a/libcontainer/system/linux.go#L89
mfd, err := unix.MemfdCreate(name, unix.MFD_ALLOW_SEALING|unix.MFD_CLOEXEC|unix.MFD_EXEC)
if errors.Is(err, unix.EINVAL) {
mfd, err = unix.MemfdCreate(name, unix.MFD_ALLOW_SEALING|unix.MFD_CLOEXEC)
}
if err != nil {
if errors.Is(err, unix.EACCES) {
return nil, trace.AccessDenied("failed to create executable memfd for embedded binary (possibly due to vm.memfd_noexec=2)")
}
return nil, trace.Wrap(trace.ConvertSystemError(err), "failed to create executable memfd for embedded binary")
}
// This must not be "/proc/self/fd/<n>" because in the post-fork, pre-exec
// environment of the child the file descriptors inherited from the parent
// are not guaranteed to be available - the fd will likely end up being
// small if this runs early, so it will likely get overridden as part of the
// exec machinery with one of the files in [exec.Cmd.ExtraFiles]. This does
// however mean that an unprivileged child will not be able to launch the
// helper, so this is only usable when launching it as the same user that
// the Teleport process is running as. [setLinuxReexecPath] will ensure that
// that's the case before attempting to use the memfd, and it will fall back
// to a regular reexecution of "/proc/self/exe" if need be.
mf := os.NewFile(uintptr(mfd), fmt.Sprintf("/proc/%d/fd/%d", os.Getpid(), mfd))
defer func() {
if err != nil {
_ = mf.Close()
}
}()
gzreader, err := gzip.NewReader(strings.NewReader(dataGZ))
if err != nil {
return nil, trace.Wrap(err, "failed to decompress embedded binary (this is a bug)")
}
if _, err := gzreader.WriteTo(mf); err != nil {
return nil, trace.Wrap(err, "failed to write embedded binary in memfd")
}
if err := mf.Chmod(0o555); err != nil {
return nil, trace.Wrap(trace.ConvertSystemError(err), "failed to chmod memfd for embedded binary")
}
// ignore errors here since F_SEAL_EXEC is newer than the rest of the memfd interface
_, _ = unix.FcntlInt(uintptr(mfd), unix.F_ADD_SEALS, unix.F_SEAL_EXEC)
if _, err := unix.FcntlInt(uintptr(mfd), unix.F_ADD_SEALS, unix.F_SEAL_SEAL|unix.F_SEAL_SHRINK|unix.F_SEAL_GROW|unix.F_SEAL_WRITE); err != nil {
return nil, trace.Wrap(trace.ConvertSystemError(err), "failed to seal memfd for embedded binary")
}
return mf, nil
}
+73
View File
@@ -0,0 +1,73 @@
// Teleport
// Copyright (C) 2026 Gravitational, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package reexec
import (
"io/fs"
"os"
"os/exec"
"strings"
"testing"
"github.com/klauspost/compress/gzip"
"github.com/stretchr/testify/require"
"golang.org/x/sys/unix"
)
func TestLoadEmbeddedReexec(t *testing.T) {
t.Parallel()
truePath, err := exec.LookPath("true")
require.NoError(t, err)
trueBin, err := os.ReadFile(truePath)
require.NoError(t, err)
b := new(strings.Builder)
gzwriter := gzip.NewWriter(b)
_, err = gzwriter.Write(trueBin)
require.NoError(t, err)
require.NoError(t, gzwriter.Close())
trueGZ := b.String()
mf, err := loadEmbeddedReexec("true", trueGZ)
require.NoError(t, err)
defer mf.Close()
r, err := unix.FcntlInt(mf.Fd(), unix.F_GET_SEALS, 0)
require.NoError(t, err)
require.NotZero(t, r&unix.F_SEAL_SEAL)
require.NotZero(t, r&unix.F_SEAL_SHRINK)
require.NotZero(t, r&unix.F_SEAL_GROW)
require.NotZero(t, r&unix.F_SEAL_WRITE)
if r&unix.F_SEAL_EXEC == 0 {
t.Log("didn't get F_SEAL_EXEC on memfd, ignoring since the kernel might be old")
}
fi, err := mf.Stat()
require.NoError(t, err)
require.Equal(t, fs.FileMode(0o555), fi.Mode())
// loadEmbeddedReexec should've already tested it, but let's be thorough
err = (&exec.Cmd{
Path: mf.Name(),
Args: []string{"true"},
}).Run()
require.NoError(t, err)
}
@@ -1,3 +1,5 @@
//go:build !sessionhelper_embed
// Teleport
// Copyright (C) 2026 Gravitational, Inc.
//
@@ -14,13 +16,8 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package srv
package reexec
import (
"github.com/gravitational/teleport/session/reexec"
)
//go:fix inline
func IsReexec() bool {
return reexec.IsReexec()
func initEmbeddedReexec() (bool, error) {
return false, nil
}
+25
View File
@@ -0,0 +1,25 @@
//go:build !sessionhelper_embed
// Teleport
// Copyright (C) 2026 Gravitational, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package reexec
import "os/exec"
func setLinuxReexecPath(cmd *exec.Cmd) {
cmd.Path = "/proc/self/exe"
}
+7 -4
View File
@@ -826,7 +826,7 @@ func RunNetworking() (code int, err error) {
// Create a minimal default environment for the user.
workingDir := rootDirectory
hasAccess, err := CheckHomeDir(localUser)
hasAccess, err := checkHomeDir(localUser)
if hasAccess && err == nil {
workingDir = localUser.HomeDir
}
@@ -1123,6 +1123,8 @@ func RunAndExit(commandType string) {
code = runCheckHomeDir()
case reexecconstants.ParkSubCommand:
code = runPark()
case reexecconstants.TrueSubCommand:
// nothing to do
case reexecconstants.SFTPSubCommand:
initLogger("sftp", os.Stderr, ExecLogConfig{})
err = reexecsftp.RunSFTP(slog.Default())
@@ -1176,6 +1178,7 @@ func IsReexec() bool {
reexecconstants.NetworkingSubCommand,
reexecconstants.CheckHomeDirSubCommand,
reexecconstants.ParkSubCommand,
reexecconstants.TrueSubCommand,
reexecconstants.SFTPSubCommand:
return true
default:
@@ -1356,7 +1359,7 @@ func BuildCommand(c *ExecCommand, localUser *user.User, pamEnvironment []string)
// Set the command's cwd to the user's $HOME, or "/" if
// they don't have an existing home dir.
// TODO (atburke): Generalize this to support Windows.
hasAccess, err := CheckHomeDir(localUser)
hasAccess, err := checkHomeDir(localUser)
if err != nil {
return nil, trace.Wrap(err)
}
@@ -1457,10 +1460,10 @@ func hasAccessibleHomeDir() error {
return nil
}
// CheckHomeDir checks if the user's home directory exists and is accessible to the user. Only catastrophic
// checkHomeDir checks if the user's home directory exists and is accessible to the user. Only catastrophic
// errors will be returned, which means a missing, inaccessible, or otherwise invalid home directory will result
// in a return of (false, nil)
func CheckHomeDir(localUser *user.User) (bool, error) {
func checkHomeDir(localUser *user.User) (bool, error) {
currentUser, err := user.Current()
if err != nil {
return false, trace.Wrap(err)
+10 -47
View File
@@ -1,5 +1,3 @@
//go:build linux
/*
* Teleport
* Copyright (C) 2023 Gravitational, Inc.
@@ -26,69 +24,33 @@ import (
"syscall"
"github.com/gravitational/trace"
"golang.org/x/sys/unix"
)
func init() {
// errors in open/openat are signaled by returning -1, we don't really care
// about the specifics anyway, so we can just ignore the error value
//
// we're opening with O_PATH rather than O_RDONLY because the binary might
// not actually be readable (but only executable)
fd1, _ := syscall.Open("/proc/self/exe", unix.O_PATH|syscall.O_NOCTTY|syscall.O_CLOEXEC, 0)
fd2, _ := syscall.Open("/proc/self/exe", unix.O_PATH|syscall.O_NOCTTY|syscall.O_CLOEXEC, 0)
// this can happen if both calls failed (returning -1) or if we're
// running in a version of qemu-user that's affected by this bug:
// https://gitlab.com/qemu-project/qemu/-/issues/927
// (hopefully they'll also add special handling for execve on /proc/self/exe
// if they ever fix that bug)
if fd1 == fd2 {
return
}
// closing -1 is harmless, no need to check here
syscall.Close(fd1)
syscall.Close(fd2)
// if one Open has failed but not the other we can't really
// trust the availability of "/proc/self/exe"
if fd1 == -1 || fd2 == -1 {
return
}
reexecPath = "/proc/self/exe"
}
// reexecPath specifies a path to execute on reexec, overriding Path in the cmd
// passed to reexecCommandOSTweaks, if not empty.
var reexecPath string
func CommandOSTweaks(cmd *exec.Cmd) {
setLinuxReexecPath(cmd)
if cmd.SysProcAttr == nil {
cmd.SysProcAttr = new(syscall.SysProcAttr)
}
// Linux only: when parent process (node) dies unexpectedly without
// cleaning up child processes, send a signal for graceful shutdown
// to children.
cmd.SysProcAttr.Pdeathsig = syscall.SIGQUIT
// replace the path on disk (which might not exist, or refer to an
// upgraded version of teleport) with reexecPath, which contains
// some path that refers to the specific binary we're running
if reexecPath != "" {
cmd.Path = reexecPath
}
}
// if we ever need to run parkers on macOS or other platforms with no PDEATHSIG
// we should rework the parker to block on a pipe so it can exit when its parent
// is terminated
func parkerCommandOSTweaks(cmd *exec.Cmd) {
CommandOSTweaks(cmd)
setLinuxReexecPath(cmd)
if cmd.SysProcAttr == nil {
cmd.SysProcAttr = new(syscall.SysProcAttr)
}
// parker processes can leak if their PDEATHSIG is SIGQUIT, otherwise we
// could just use reexecCommandOSTweaks
// could just use [commandOSTweaks]
cmd.SysProcAttr.Pdeathsig = syscall.SIGKILL
}
@@ -96,6 +58,7 @@ func userCommandOSTweaks(cmd *exec.Cmd) {
if cmd.SysProcAttr == nil {
cmd.SysProcAttr = new(syscall.SysProcAttr)
}
// Linux only: when parent process (this process) dies unexpectedly, kill
// the child process instead of orphaning it.
// SIGKILL because we don't control the child process, and it could choose
+4 -4
View File
@@ -222,22 +222,22 @@ func TestRootCheckHomeDir(t *testing.T) {
require.NoError(t, os.Chown(home, uid, gid))
require.NoError(t, os.Chown(file, uid, gid))
hasAccess, err := CheckHomeDir(testUser)
hasAccess, err := checkHomeDir(testUser)
require.NoError(t, err)
require.True(t, hasAccess)
changeHomeDir(t, login, file)
hasAccess, err = CheckHomeDir(testUser)
hasAccess, err = checkHomeDir(testUser)
require.NoError(t, err)
require.False(t, hasAccess)
changeHomeDir(t, login, notFound)
hasAccess, err = CheckHomeDir(testUser)
hasAccess, err = checkHomeDir(testUser)
require.NoError(t, err)
require.False(t, hasAccess)
changeHomeDir(t, login, noAccess)
hasAccess, err = CheckHomeDir(testUser)
hasAccess, err = checkHomeDir(testUser)
require.NoError(t, err)
require.False(t, hasAccess)
}
@@ -35,6 +35,9 @@ const (
// spawning the intended child process.
ParkSubCommand = "park"
// TrueSubCommand is a sub-command that exits with a success exit code.
TrueSubCommand = "true"
// SFTPSubCommand is the sub-command Teleport uses to re-exec itself to
// handle SFTP connections.
SFTPSubCommand = "sftp"
+9 -16
View File
@@ -57,7 +57,6 @@ import (
"github.com/gravitational/teleport/lib/utils"
logutils "github.com/gravitational/teleport/lib/utils/log"
"github.com/gravitational/teleport/lib/versioncontrol"
"github.com/gravitational/teleport/session/reexec"
"github.com/gravitational/teleport/session/reexec/reexecconstants"
"github.com/gravitational/teleport/session/selinux"
)
@@ -115,7 +114,8 @@ func Run(options Options) (app *kingpin.Application, executedCommand string, con
exec := app.Command(reexecconstants.ExecSubCommand, "Used internally by Teleport to re-exec itself to run a command.").Hidden()
networking := app.Command(reexecconstants.NetworkingSubCommand, "Used internally by Teleport to re-exec itself to handle networking requests.").Hidden()
checkHomeDir := app.Command(reexecconstants.CheckHomeDirSubCommand, "Used internally by Teleport to re-exec itself to check access to a directory.").Hidden()
park := app.Command(reexecconstants.ParkSubCommand, "Used internally by Teleport to re-exec itself to do nothing.").Hidden()
park := app.Command(reexecconstants.ParkSubCommand, "Used internally by Teleport to re-exec itself to do nothing, forever.").Hidden()
trueCmd := app.Command(reexecconstants.TrueSubCommand, "Used internally by Teleport to re-exec itself to do nothing, successfully.").Hidden()
app.HelpFlag.Short('h')
// define start flags:
@@ -746,20 +746,13 @@ Examples:
dumpFlags.Roles = defaults.RoleNode
err = onConfigDump(dumpFlags)
// TODO(espadolini): replace these after enterprise calls reexec.MaybeReexec
// in main with an error message ("invalid format for reexec subcommand")
// because if we got here it's because MaybeReexec didn't find the correct
// first argument
case exec.FullCommand():
reexec.RunAndExit(reexecconstants.ExecSubCommand)
case networking.FullCommand():
reexec.RunAndExit(reexecconstants.NetworkingSubCommand)
case checkHomeDir.FullCommand():
reexec.RunAndExit(reexecconstants.CheckHomeDirSubCommand)
case park.FullCommand():
reexec.RunAndExit(reexecconstants.ParkSubCommand)
case sftp.FullCommand():
reexec.RunAndExit(reexecconstants.SFTPSubCommand)
case exec.FullCommand(),
networking.FullCommand(),
checkHomeDir.FullCommand(),
park.FullCommand(),
trueCmd.FullCommand(),
sftp.FullCommand():
err = trace.BadParameter("invalid command line format for internal reexecution command (this is a bug)")
case waitNoResolveCmd.FullCommand():
err = onWaitNoResolve(waitFlags)