Skip to content

os.RemoveAll: openFdAt function without O_CLOEXEC and cause fd escape to child process #33405

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
fuweid opened this issue Aug 1, 2019 · 10 comments
Labels
FrozenDueToAge NeedsFix The path to resolution is known, but the work has not been done.

Comments

@fuweid
Copy link
Contributor

fuweid commented Aug 1, 2019

What version of Go are you using (go version)?

$ go version
go version go1.12.7 linux/amd64

Does this issue reproduce with the latest release?

This is concurrent issue. When parent process has goroutine to remove the folder, other goroutine try to exec child process. But the os.RemoveAll calles openFdAt function which open file without O_CLOEXEC. The opened file at parent process will escape to child process.

https://github.com/golang/go/blob/release-branch.go1.12/src/os/removeall_at.go#L156-L178

func openFdAt(dirfd int, name string) (*File, error) {
	var r int
	for {
		var e error
		r, e = unix.Openat(dirfd, name, O_RDONLY, 0)
		if e == nil {
			break
		}

		// See comment in openFileNolog.
		if runtime.GOOS == "darwin" && e == syscall.EINTR {
			continue
		}

		return nil, e
	}

	if !supportsCloseOnExec {
		syscall.CloseOnExec(r)
	}

	return newFile(uintptr(r), name, kindOpenFile), nil
}

unix.Openat works without O_CLOEXEC.

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GOARCH="amd64"
GOBIN=""
GOCACHE="/root/.cache/go-build"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/root/go"
GOPROXY=""
GORACE=""
GOROOT="/usr/local/go"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build585800205=/tmp/go-build -gno-record-gcc-switches"

What did you do?

I use the following script to reproduce issue instead of complex concurrent one.

package main

import (
        "fmt"
        "io/ioutil"
        "os"
        "os/exec"

        "golang.org/x/sys/unix"
)

func main() {
        dir, err := ioutil.TempDir("", "fd-escape")
        if err != nil {
                panic(err)
        }
        f, err := os.Open(dir)
        if err != nil {
                panic(err)
        }

        // copy from os.RemoveAll go 1.12 openFdAt without O_CLOEXEC
        if _, err := unix.Openat(int(f.Fd()), "/tmp", os.O_RDONLY, 0); err != nil {
                panic(err)
        }

        cmd := exec.Command("sleep", "10")
        if err := cmd.Start(); err != nil {
                panic(err)
        }

        fds, err := ioutil.ReadDir(fmt.Sprintf("/proc/%d/fd", cmd.Process.Pid))
        if err != nil {
                panic(err)
        }

        for _, fd := range fds {
                fname, err := os.Readlink(fmt.Sprintf("/proc/%d/fd/%s", cmd.Process.Pid, fd.Name()))
                if err != nil {
                        panic(err)
                }
                fmt.Println(fd.Name(), " --> ", fname)
        }
        cmd.Wait()
}
root@ubuntu-xenial ~/w/testing# go run parent.go
0  -->  /dev/null
1  -->  /dev/null
2  -->  /dev/null
5  -->  /tmp

When I add the O_CLOEXEC into unix.Openat, the /tmp will be gone.

// copy from os.RemoveAll go 1.12 openFdAt with O_CLOEXEC
if _, err := unix.Openat(int(f.Fd()), "/tmp", os.O_RDONLY|syscall.O_CLOEXEC, 0); err != nil {
                panic(err)
}

root@ubuntu-xenial ~/w/testing# go run parent.go
0  -->  /dev/null
1  -->  /dev/null
2  -->  /dev/null

What did you expect to see?

child process should not have any opened fd from parent.

I check go1.10, go.11 code base and found that the RemoveAll use os.Open with O_CLOEXEC. I think go1.12 might miss this part for openat.

What did you see instead?

fd escape to child - leaking

@yyb196 @Ace-Tang @rudyfly

@yyb196
Copy link

yyb196 commented Aug 1, 2019

I think it's a bug of go-1.12, child process will accidentally inherit fd opened by parent process doing os.RemoveAll("/tmp/xxx")

@oiooj oiooj added the NeedsFix The path to resolution is known, but the work has not been done. label Aug 1, 2019
@gopherbot
Copy link
Contributor

Change https://golang.org/cl/188537 mentions this issue: os: enable the close-on-exec flag for openFdAt

@fuweid
Copy link
Contributor Author

fuweid commented Aug 2, 2019

Hi @oiooj @ianlancetaylor Thanks for quick response! Could we please make patch to 1.12 release? I think it is like kind of security issue and it impact the behaviour of children process. WDYT?

@oiooj
Copy link
Member

oiooj commented Aug 2, 2019

Yes, I think it should be backport to 1.12. Hi, @gopherbot please open backport to 1.12

@gopherbot
Copy link
Contributor

Backport issue(s) opened: #33424 (for 1.12).

Remember to create the cherry-pick CL(s) as soon as the patch is submitted to master, according to https://golang.org/wiki/MinorReleases.

@fuweid
Copy link
Contributor Author

fuweid commented Aug 2, 2019

@oiooj Thanks!

@gopherbot
Copy link
Contributor

Change https://golang.org/cl/188538 mentions this issue: [release-branch.go1.12] os: enable the close-on-exec flag for openFdAt

gopherbot pushed a commit that referenced this issue Aug 2, 2019
There's a race here with fork/exec, enable the close-on-exec flag
for the new file descriptor.

Updates #33405
Fixes #33424

Change-Id: Ib1e405c3b48b11c867f183fd13eff8b73d95e3b4
Reviewed-on: https://go-review.googlesource.com/c/go/+/188537
Run-TryBot: Baokun Lee <[email protected]>
TryBot-Result: Gobot Gobot <[email protected]>
Reviewed-by: Ian Lance Taylor <[email protected]>
(cherry picked from commit 2d6ee6e)
Reviewed-on: https://go-review.googlesource.com/c/go/+/188538
Run-TryBot: Ian Lance Taylor <[email protected]>
@fuweid
Copy link
Contributor Author

fuweid commented Aug 6, 2019

Hi @oiooj Is there any schedule to release v1.12.8?

@oiooj
Copy link
Member

oiooj commented Aug 6, 2019

From Brad:

They're released approximately monthly:
https://golang.org/doc/devel/release.html#go1.12.minor
So the next one is due soon. It's been almost a month.

I think it's very soon, maybe in a week.

@fuweid
Copy link
Contributor Author

fuweid commented Aug 6, 2019

@oiooj Thanks!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge NeedsFix The path to resolution is known, but the work has not been done.
Projects
None yet
Development

No branches or pull requests

4 participants