From 1aca00721bb24d4be4978ec6f1a38c2de3b437c9 Mon Sep 17 00:00:00 2001 From: Edoardo Spadolini Date: Fri, 25 Mar 2022 17:08:32 +0100 Subject: [PATCH] Reexec with `/proc/self/exe` on Linux (#11283) (#11455) * Reexec with `/proc/self/exe` on Linux * Add a check for qemu-user * Add comment --- lib/srv/reexec_linux.go | 46 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/lib/srv/reexec_linux.go b/lib/srv/reexec_linux.go index 653f12a1abaf9..9dbb4cf9012f5 100644 --- a/lib/srv/reexec_linux.go +++ b/lib/srv/reexec_linux.go @@ -3,10 +3,49 @@ package srv import ( + "fmt" + "os" "os/exec" "syscall" + + "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 returned -1 or if we're in running in a + // version of qemu-user that's affected by this bug: + // https://gitlab.com/qemu-project/qemu/-/issues/927 + if fd1 == fd2 { + return + } + + // if one has failed but not the other we can't really trust what's + // happening anymore + if fd1 == -1 || fd2 == -1 { + syscall.Close(fd1) + syscall.Close(fd2) + return + } + + syscall.Close(fd2) + // we must specify the path with our pid number instead of self, because + // file descriptors are shuffled and overwritten during (*exec.Cmd).Start() + // after forking + reexecPath = fmt.Sprintf("/proc/%d/fd/%d", os.Getpid(), fd1) +} + +// reexecPath specifies a path to execute on reexec, overriding Path in the cmd +// passed to reexecCommandOSTweaks, if not empty. +var reexecPath string + func reexecCommandOSTweaks(cmd *exec.Cmd) { if cmd.SysProcAttr == nil { cmd.SysProcAttr = new(syscall.SysProcAttr) @@ -15,6 +54,13 @@ func reexecCommandOSTweaks(cmd *exec.Cmd) { // 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 + } } func userCommandOSTweaks(cmd *exec.Cmd) {