Go版本:go1.23.0
在你的模块/工作区中,go env
的输出为:
GO111MODULE=''
GOARCH='amd64'
GOBIN=''
GOCACHE='/home/fuweid/.cache/go-build'
GOENV='/home/fuweid/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMODCACHE='/home/fuweid/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/home/fuweid/go'
GOPRIVATE=''
GOPROXY='https://goproxy.cn,direct'
GOROOT='/usr/local/go'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/local/go/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.23.0'
GODEBUG=''
GOTELEMETRY='local'
GOTELEMETRYDIR='/home/fuweid/.config/go/telemetry'
GCCGO='gccgo'
GOAMD64='v1'
AR='ar'
CC='gcc'
CXX='g++'
CGO_ENABLED='1'
GOMOD='/home/fuweid/workspace/demo/go.mod'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build655447324=/tmp/go-build -gno-record-gcc-switches
你做了什么?
注意:它需要内核支持pidfd。
我尝试使用os.StartProcess与ptrace来移除containerd项目containerd/containerd#10611中所有对linkname的使用。对于os.StartProcess选项,我设置了PidFD,使用os.NewFile
为其创建了一个句柄并关闭了它。
go/src/syscall/exec_linux.go
第108行到第111行
| | // 如果内核支持该功能,PidFD将用于存储子进程的pidfd,否则为-1。注意:只有在进程成功启动时,PidFD才会被更改。 |
| | PidFDint |
但是我遇到了随机的accept4: bad file descriptor
问题,因为Process
具有在GC期间关闭pidfd的终结器。
go/src/os/exec_posix.go
第64行到第72行
| | ifruntime.GOOS!="windows" { |
| | varokbool |
| | h, ok=getPidfd(sysattr.Sys) |
| | if!ok { |
| | returnnewPIDProcess(pid), nil |
| | } |
| | } |
| | |
| | returnnewHandleProcess(pid, h), nil |
go/src/os/exec.go
第114行到第122行
| | funcnewHandleProcess(pidint, handleuintptr) *Process { |
| | p:=&Process{ |
| | Pid: pid, |
| | mode: modeHandle, |
| | handle: handle, |
| | } |
| | p.state.Store(1) // 1 persistent reference |
| | runtime.SetFinalizer(p, (*Process).Release) |
| | returnp |
这里有一个用于重现问题的POC代码。
package main
import (
"fmt"
"os"
"runtime"
"syscall"
"time"
"golang.org/x/sys/unix"
)
func main() {
firstFd := createNoopFd()
defer firstFd.Close()
var pidfd int
proc, err := os.StartProcess("/proc/self/exe", []string{"echo"}, &os.ProcAttr{
Sys: &syscall.SysProcAttr{
Ptrace: true,
PidFD: &pidfd,
Pdeathsig: syscall.SIGKILL,
},
})
if err != nil {
panic(err)
}
if pidfd <= 0 {
proc.Kill()
proc.Wait()
panic("empty pidfd")
}
fmt.Println("pidfd = ", pidfd)
pidFD := os.NewFile(uintptr(pidfd), "pidfd")
unix.PidfdSendSignal(int(pidFD.Fd()), unix.SIGKILL, nil, 0)
unix.Waitid(unix.P_PIDFD, int(pidFD.Fd()), nil, unix.WEXITED, nil)
pidFD.Close()
fmt.Println("Closed pidfd")
secondFd := createNoopFd()
defer secondFd.Close()
thirdFd := createNoopFd()
defer thirdFd.Close()
failedFd := createNoopFd()
defer failedFd.Close()
if fd := failedFd.Fd(); fd != uintptr(pidfd) {
panic("please align fd manually")
}
fmt.Println("target fd ", failedFd.Fd())
for {
_, err := failedFd.Write([]byte("hello"))
if err != nil {
panic(err)
}
time.Sleep(1 * time.Second)
runtime.GC() // target finalizer for `proc`
}
}
func createNoopFd() *os.File {
f, err := os.CreateTemp("/tmp", "PIDFD")
if err != nil {
panic(err)
}
return f
}
我认为文档应该提到不应该直接使用用户的pidfd值,或者在使用之前应该克隆它。
当你使用go1.23.0运行POC代码时,你会看到类似这样的错误:
➜ demo go version
go version go1.23.0 linux/amd64
➜ demo go run main.go
pidfd = 8
Closed pidfd
target fd 8
panic: write /tmp/PIDFD3716412711: bad file descriptor
goroutine 1 [running]:
main.main()
/home/fuweid/workspace/demo/main.go:59 +0x50b
exit status 2
你期望看到什么?
不应该直接使用用户的pidfd值。
5条答案
按热度按时间kh212irz1#
CC @kolyshkin
ac1kyiln2#
相关问题和文档
(如果觉得有帮助,请给表情投票;欢迎在 this discussion 中提供更详细的反馈。)
bwleehnv3#
我认为这是一个错误。如果你在SysProcAttr中手动设置了PidFD,那么os.Process不应该关闭它。这应该是你的责任。
ezykj2lf4#
cc @golang/runtime
sg2wtvxw5#
https://go.dev/cl/607695提到了这个问题:
os: dup pidfd if caller sets PidFD manually