Skip to content

Commit 35c14c6

Browse files
committed
sys/mount_linux: use pipe for communicating mount result
forkAndMountat forks a process to chdir then mount layers. Signals are blocked (using runtime_beforeFork) during fork. There is a race condition that the child process finishes before the parent process is scheduled and can unblock signal handling. The SIGCHLD signal sent from the finished process may have been delivered to the shim process's reaper thread and caused the parent process fail with ECHLD error. This patch sets up a pipe for communication between child and parent instead of waiting for child exit status. Fixes #4009. Signed-off-by: Haitao Li <[email protected]>
1 parent 173cbc1 commit 35c14c6

1 file changed

Lines changed: 39 additions & 13 deletions

File tree

sys/mount_linux.go

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"syscall"
2222
"unsafe"
2323

24+
"github.com/containerd/containerd/log"
2425
"github.com/pkg/errors"
2526
"golang.org/x/sys/unix"
2627
)
@@ -30,9 +31,8 @@ func FMountat(dirfd uintptr, source, target, fstype string, flags uintptr, data
3031
var (
3132
sourceP, targetP, fstypeP, dataP *byte
3233
pid uintptr
33-
ws unix.WaitStatus
3434
err error
35-
errno syscall.Errno
35+
errno, status syscall.Errno
3636
)
3737

3838
sourceP, err = syscall.BytePtrFromString(source)
@@ -60,37 +60,62 @@ func FMountat(dirfd uintptr, source, target, fstype string, flags uintptr, data
6060
runtime.LockOSThread()
6161
defer runtime.UnlockOSThread()
6262

63+
var pipefds [2]int
64+
if err := syscall.Pipe2(pipefds[:], syscall.O_CLOEXEC); err != nil {
65+
return errors.Wrap(err, "failed to open pipe")
66+
}
67+
68+
defer func() {
69+
// close both ends of the pipe in a deferred function, since open file
70+
// descriptor table is shared with child
71+
syscall.Close(pipefds[0])
72+
syscall.Close(pipefds[1])
73+
}()
74+
6375
pid, errno = forkAndMountat(dirfd,
6476
uintptr(unsafe.Pointer(sourceP)),
6577
uintptr(unsafe.Pointer(targetP)),
6678
uintptr(unsafe.Pointer(fstypeP)),
6779
flags,
68-
uintptr(unsafe.Pointer(dataP)))
80+
uintptr(unsafe.Pointer(dataP)),
81+
pipefds[1],
82+
)
6983

7084
if errno != 0 {
7185
return errors.Wrap(errno, "failed to fork thread")
7286
}
7387

74-
_, err = unix.Wait4(int(pid), &ws, 0, nil)
75-
for err == syscall.EINTR {
76-
_, err = unix.Wait4(int(pid), &ws, 0, nil)
77-
}
88+
defer func() {
89+
_, err := unix.Wait4(int(pid), nil, 0, nil)
90+
for err == syscall.EINTR {
91+
_, err = unix.Wait4(int(pid), nil, 0, nil)
92+
}
7893

79-
if err != nil {
80-
return errors.Wrapf(err, "failed to find pid=%d process", pid)
81-
}
94+
if err != nil {
95+
log.L.WithError(err).Debugf("failed to find pid=%d process", pid)
96+
}
97+
}()
8298

83-
errno = syscall.Errno(ws.ExitStatus())
99+
_, _, errno = syscall.RawSyscall(syscall.SYS_READ,
100+
uintptr(pipefds[0]),
101+
uintptr(unsafe.Pointer(&status)),
102+
unsafe.Sizeof(status))
84103
if errno != 0 {
85-
return errors.Wrap(errno, "failed to mount")
104+
return errors.Wrap(errno, "failed to read pipe")
86105
}
106+
107+
if status != 0 {
108+
return errors.Wrap(status, "failed to mount")
109+
}
110+
87111
return nil
88112
}
89113

90114
// forkAndMountat will fork thread, change working dir and mount.
91115
//
92116
// precondition: the runtime OS thread must be locked.
93-
func forkAndMountat(dirfd uintptr, source, target, fstype, flags, data uintptr) (pid uintptr, errno syscall.Errno) {
117+
func forkAndMountat(dirfd uintptr, source, target, fstype, flags, data uintptr, pipefd int) (pid uintptr, errno syscall.Errno) {
118+
94119
// block signal during clone
95120
beforeFork()
96121

@@ -114,6 +139,7 @@ func forkAndMountat(dirfd uintptr, source, target, fstype, flags, data uintptr)
114139
_, _, errno = syscall.RawSyscall6(syscall.SYS_MOUNT, source, target, fstype, flags, data, 0)
115140

116141
childerr:
142+
_, _, errno = syscall.RawSyscall(syscall.SYS_WRITE, uintptr(pipefd), uintptr(unsafe.Pointer(&errno)), unsafe.Sizeof(errno))
117143
syscall.RawSyscall(syscall.SYS_EXIT, uintptr(errno), 0, 0)
118144
panic("unreachable")
119145
}

0 commit comments

Comments
 (0)