Skip to content

Commit 7a5fc9b

Browse files
committed
os: add clone(CLONE_PIDFD) check to pidfd feature check
clone(CLONE_PIDFD) was added in Linux 5.2 and pidfd_open was added in Linux 5.3. Thus our feature check for pidfd_open should be sufficient to ensure that clone(CLONE_PIDFD) works. Unfortuantely, some alternative Linux implementations may not follow this strict ordering. For example, QEMU 7.2 (Dec 2022) added pidfd_open, but clone(CLONE_PIDFD) was only added in QEMU 8.0 (Apr 2023). Debian bookworm provides QEMU 7.2 by default. Fixes #69259. Change-Id: Ie3f3dc51f0cd76944871bf98690abf59f68fd7bf Reviewed-on: https://go-review.googlesource.com/c/go/+/592078 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Cherry Mui <[email protected]>
1 parent 0ee5d20 commit 7a5fc9b

File tree

2 files changed

+102
-3
lines changed

2 files changed

+102
-3
lines changed

src/os/pidfd_linux.go

+21-3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
// v5.3: pidfd_open syscall, clone3 syscall;
99
// v5.4: P_PIDFD idtype support for waitid syscall;
1010
// v5.6: pidfd_getfd syscall.
11+
//
12+
// N.B. Alternative Linux implementations may not follow this ordering. e.g.,
13+
// QEMU user mode 7.2 added pidfd_open, but CLONE_PIDFD was not added until
14+
// 8.0.
1115

1216
package os
1317

@@ -139,9 +143,9 @@ func pidfdWorks() bool {
139143

140144
var checkPidfdOnce = sync.OnceValue(checkPidfd)
141145

142-
// checkPidfd checks whether all required pidfd-related syscalls work.
143-
// This consists of pidfd_open and pidfd_send_signal syscalls, and waitid
144-
// syscall with idtype of P_PIDFD.
146+
// checkPidfd checks whether all required pidfd-related syscalls work. This
147+
// consists of pidfd_open and pidfd_send_signal syscalls, waitid syscall with
148+
// idtype of P_PIDFD, and clone(CLONE_PIDFD).
145149
//
146150
// Reasons for non-working pidfd syscalls include an older kernel and an
147151
// execution environment in which the above system calls are restricted by
@@ -172,5 +176,19 @@ func checkPidfd() error {
172176
return NewSyscallError("pidfd_send_signal", err)
173177
}
174178

179+
// Verify that clone(CLONE_PIDFD) works.
180+
//
181+
// This shouldn't be necessary since pidfd_open was added in Linux 5.3,
182+
// after CLONE_PIDFD in Linux 5.2, but some alternative Linux
183+
// implementations may not adhere to this ordering.
184+
if err := checkClonePidfd(); err != nil {
185+
return err
186+
}
187+
175188
return nil
176189
}
190+
191+
// Provided by syscall.
192+
//
193+
//go:linkname checkClonePidfd
194+
func checkClonePidfd() error

src/syscall/exec_linux.go

+81
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
package syscall
88

99
import (
10+
errpkg "errors"
1011
"internal/itoa"
1112
"runtime"
1213
"unsafe"
@@ -330,6 +331,7 @@ func forkAndExecInChild1(argv0 *byte, argv, envv []*byte, chroot, dir *byte, att
330331
if clone3 != nil {
331332
pid, err1 = rawVforkSyscall(_SYS_clone3, uintptr(unsafe.Pointer(clone3)), unsafe.Sizeof(*clone3), 0)
332333
} else {
334+
// N.B. Keep in sync with doCheckClonePidfd.
333335
flags |= uintptr(SIGCHLD)
334336
if runtime.GOARCH == "s390x" {
335337
// On Linux/s390, the first two arguments of clone(2) are swapped.
@@ -758,3 +760,82 @@ func forkAndExecFailureCleanup(attr *ProcAttr, sys *SysProcAttr) {
758760
*sys.PidFD = -1
759761
}
760762
}
763+
764+
// checkClonePidfd verifies that clone(CLONE_PIDFD) works by actually doing a
765+
// clone.
766+
//
767+
//go:linkname os_checkClonePidfd os.checkClonePidfd
768+
func os_checkClonePidfd() error {
769+
pidfd := int32(-1)
770+
pid, errno := doCheckClonePidfd(&pidfd)
771+
if errno != 0 {
772+
return errno
773+
}
774+
775+
if pidfd == -1 {
776+
// Bad: CLONE_PIDFD failed to provide a pidfd. Reap the process
777+
// before returning.
778+
779+
var err error
780+
for {
781+
var status WaitStatus
782+
_, err = Wait4(int(pid), &status, 0, nil)
783+
if err != EINTR {
784+
break
785+
}
786+
}
787+
if err != nil {
788+
return err
789+
}
790+
791+
return errpkg.New("clone(CLONE_PIDFD) failed to return pidfd")
792+
}
793+
794+
// Good: CLONE_PIDFD provided a pidfd. Reap the process and close the
795+
// pidfd.
796+
defer Close(int(pidfd))
797+
798+
for {
799+
const _P_PIDFD = 3
800+
_, _, errno = Syscall6(SYS_WAITID, _P_PIDFD, uintptr(pidfd), 0, WEXITED, 0, 0)
801+
if errno != EINTR {
802+
break
803+
}
804+
}
805+
if errno != 0 {
806+
return errno
807+
}
808+
809+
return nil
810+
}
811+
812+
// doCheckClonePidfd implements the actual clone call of os_checkClonePidfd and
813+
// child execution. This is a separate function so we can separate the child's
814+
// and parent's stack frames if we're using vfork.
815+
//
816+
// This is go:noinline because the point is to keep the stack frames of this
817+
// and os_checkClonePidfd separate.
818+
//
819+
//go:noinline
820+
func doCheckClonePidfd(pidfd *int32) (pid uintptr, errno Errno) {
821+
flags := uintptr(CLONE_VFORK|CLONE_VM|CLONE_PIDFD|SIGCHLD)
822+
if runtime.GOARCH == "s390x" {
823+
// On Linux/s390, the first two arguments of clone(2) are swapped.
824+
pid, errno = rawVforkSyscall(SYS_CLONE, 0, flags, uintptr(unsafe.Pointer(pidfd)))
825+
} else {
826+
pid, errno = rawVforkSyscall(SYS_CLONE, flags, 0, uintptr(unsafe.Pointer(pidfd)))
827+
}
828+
if errno != 0 || pid != 0 {
829+
// If we're in the parent, we must return immediately
830+
// so we're not in the same stack frame as the child.
831+
// This can at most use the return PC, which the child
832+
// will not modify, and the results of
833+
// rawVforkSyscall, which must have been written after
834+
// the child was replaced.
835+
return
836+
}
837+
838+
for {
839+
RawSyscall(SYS_EXIT, 0, 0, 0)
840+
}
841+
}

0 commit comments

Comments
 (0)