From 8ac1321075ea4f215da4c80e0ffdbb9af0f73d8c Mon Sep 17 00:00:00 2001 From: Cam Hutchison Date: Fri, 20 Jun 2025 12:22:28 +1000 Subject: [PATCH 1/3] tsh: Add wrapper for syscall.Dup2 for linux/arm64 Add a wrapper for `syscall.Dup2()` as linux ARM64 does not have that syscall. On that platform, `syscall.Dup3()` needs to be used instead. Fixes: 57c909dab1b8f7f0a8ba7e3199a902775f82f42a --- tool/tsh/common/dup2_linux_arm64.go | 34 +++++++++++++++++++++++++++++ tool/tsh/common/dup2_unix.go | 27 +++++++++++++++++++++++ tool/tsh/common/reexec_unix.go | 4 ++-- 3 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 tool/tsh/common/dup2_linux_arm64.go create mode 100644 tool/tsh/common/dup2_unix.go diff --git a/tool/tsh/common/dup2_linux_arm64.go b/tool/tsh/common/dup2_linux_arm64.go new file mode 100644 index 0000000000000..48d5397a15849 --- /dev/null +++ b/tool/tsh/common/dup2_linux_arm64.go @@ -0,0 +1,34 @@ +// Teleport +// Copyright (C) 2025 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 . + +//go:build linux && arm64 + +package common + +import "syscall" + +// dup2 implements syscall.Dup2(oldfd, newfd) on Linux ARM64, as that platform +// does not have syscall.Dup2(). Instead syscall.Dup3() must be used. +// syscall.Dup3() is not available on all unix platforms so it cannot be used +// unconditionally. +func dup2(oldfd, newfd int) error { + if oldfd == newfd { + // dup2 would do nothing in this case, but dup3 returns an error. + // Emulate dup2 behavior. + return nil + } + return syscall.Dup3(oldfd, newfd, 0) +} diff --git a/tool/tsh/common/dup2_unix.go b/tool/tsh/common/dup2_unix.go new file mode 100644 index 0000000000000..b5515d200d2d1 --- /dev/null +++ b/tool/tsh/common/dup2_unix.go @@ -0,0 +1,27 @@ +// Teleport +// Copyright (C) 2025 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 . + +//go:build !(linux && arm64) + +package common + +import "syscall" + +// dup2 wraps syscall.Dup2(oldfd, newfd) on platforms that have it, allowing +// platforms that do not to implement dup2() with syscall.Dup3() instead. +func dup2(oldfd, newfd int) error { + return syscall.Dup2(oldfd, newfd) +} diff --git a/tool/tsh/common/reexec_unix.go b/tool/tsh/common/reexec_unix.go index bbcf9a0d5a6d6..34f775418ebdf 100644 --- a/tool/tsh/common/reexec_unix.go +++ b/tool/tsh/common/reexec_unix.go @@ -51,7 +51,7 @@ func replaceStdin() (*os.File, error) { } var dupErr error if ctrlErr := rc.Control(func(fd uintptr) { - dupErr = syscall.Dup2(int(fd), syscall.Stdin) + dupErr = dup2(int(fd), syscall.Stdin) // stdin is not O_CLOEXEC after dup2 but thankfully the three stdio // file descriptors must be not O_CLOEXEC anyway, so we can avoid // a linux-specific implementation or syscall.ForkLock shenanigans @@ -60,7 +60,7 @@ func replaceStdin() (*os.File, error) { return nil, trace.Wrap(ctrlErr) } if dupErr != nil { - // this is the error from Dup2 + // this is the error from dup2 _ = devNull.Close() return nil, trace.Wrap(err) } From b43a6c8c02ae275b92666e17e968b5a03d545ef5 Mon Sep 17 00:00:00 2001 From: Cam Hutchison Date: Mon, 23 Jun 2025 18:18:15 +1000 Subject: [PATCH 2/3] Implement dup2 with syscall.Dup3 on all linux platforms --- tool/tsh/common/{dup2_linux_arm64.go => dup2_linux.go} | 10 +++++----- tool/tsh/common/dup2_unix.go | 7 ++++--- tool/tsh/common/reexec_unix.go | 7 ++++--- 3 files changed, 13 insertions(+), 11 deletions(-) rename tool/tsh/common/{dup2_linux_arm64.go => dup2_linux.go} (76%) diff --git a/tool/tsh/common/dup2_linux_arm64.go b/tool/tsh/common/dup2_linux.go similarity index 76% rename from tool/tsh/common/dup2_linux_arm64.go rename to tool/tsh/common/dup2_linux.go index 48d5397a15849..52ca8b0585e2c 100644 --- a/tool/tsh/common/dup2_linux_arm64.go +++ b/tool/tsh/common/dup2_linux.go @@ -14,16 +14,16 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -//go:build linux && arm64 +//go:build linux package common import "syscall" -// dup2 implements syscall.Dup2(oldfd, newfd) on Linux ARM64, as that platform -// does not have syscall.Dup2(). Instead syscall.Dup3() must be used. -// syscall.Dup3() is not available on all unix platforms so it cannot be used -// unconditionally. +// dup2 implements syscall.Dup2(oldfd, newfd) in a way that works on all +// current Linux platforms, and likely on any new platforms. New platforms +// such as ARM64 do not implement syscall.Dup2() instead implementing +// syscall.Dup3() which is largely a superset, with one special case. func dup2(oldfd, newfd int) error { if oldfd == newfd { // dup2 would do nothing in this case, but dup3 returns an error. diff --git a/tool/tsh/common/dup2_unix.go b/tool/tsh/common/dup2_unix.go index b5515d200d2d1..2bec3bb4e64ac 100644 --- a/tool/tsh/common/dup2_unix.go +++ b/tool/tsh/common/dup2_unix.go @@ -14,14 +14,15 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -//go:build !(linux && arm64) +//go:build !linux package common import "syscall" -// dup2 wraps syscall.Dup2(oldfd, newfd) on platforms that have it, allowing -// platforms that do not to implement dup2() with syscall.Dup3() instead. +// dup2 wraps syscall.Dup2(oldfd, newfd) on non-linux unix platforms. The +// linux implementation uses syscall.Dup3() as Dup2() is not available +// on all linux platforms. func dup2(oldfd, newfd int) error { return syscall.Dup2(oldfd, newfd) } diff --git a/tool/tsh/common/reexec_unix.go b/tool/tsh/common/reexec_unix.go index 34f775418ebdf..4a707ab8a62ca 100644 --- a/tool/tsh/common/reexec_unix.go +++ b/tool/tsh/common/reexec_unix.go @@ -52,9 +52,10 @@ func replaceStdin() (*os.File, error) { var dupErr error if ctrlErr := rc.Control(func(fd uintptr) { dupErr = dup2(int(fd), syscall.Stdin) - // stdin is not O_CLOEXEC after dup2 but thankfully the three stdio - // file descriptors must be not O_CLOEXEC anyway, so we can avoid - // a linux-specific implementation or syscall.ForkLock shenanigans + // dup2() is sufficient here as the three stdio file + // descriptors must not be O_CLOEXEC. Darwin does not have + // dup3(), so would need to resort to syscall.ForkLock + // shenanigans if we did need to set O_CLOEXEC. }); ctrlErr != nil { _ = devNull.Close() return nil, trace.Wrap(ctrlErr) From afc7ee15095a54699b9aef6a0d582207862819ce Mon Sep 17 00:00:00 2001 From: Cam Hutchison Date: Mon, 23 Jun 2025 20:23:24 +1000 Subject: [PATCH 3/3] Add explicit "unix" constraint to dup2_unix.go to ensure Windows is excluded --- tool/tsh/common/dup2_unix.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/tsh/common/dup2_unix.go b/tool/tsh/common/dup2_unix.go index 2bec3bb4e64ac..47aef96884851 100644 --- a/tool/tsh/common/dup2_unix.go +++ b/tool/tsh/common/dup2_unix.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -//go:build !linux +//go:build unix && !linux package common