go 操作系统,系统调用:如果调用者使用由os.StartProcess更新的pidfd关闭双重关闭pidfd,

brc7rcf0  于 22天前  发布在  Go
关注(0)|答案(5)|浏览(22)

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才会被更改。 |
| | PidFD
int |
但是我遇到了随机的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值。

相关问题