1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

Merge pull request #44215 from corhere/fix-unlockosthread-pdeathsig

Stop subprocesses from getting unexpectedly killed
This commit is contained in:
Sebastiaan van Stijn 2022-10-06 20:08:53 +02:00 committed by GitHub
commit 1515e02c8a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 91 additions and 12 deletions

View file

@ -50,6 +50,13 @@ func mountFrom(dir, device, target, mType string, flags uintptr, label string) e
output := bytes.NewBuffer(nil) output := bytes.NewBuffer(nil)
cmd.Stdout = output cmd.Stdout = output
cmd.Stderr = output cmd.Stderr = output
// reexec.Command() sets cmd.SysProcAttr.Pdeathsig on Linux, which
// causes the started process to be signaled when the creating OS thread
// dies. Ensure that the reexec is not prematurely signaled. See
// https://go.dev/issue/27505 for more information.
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if err := cmd.Start(); err != nil { if err := cmd.Start(); err != nil {
w.Close() w.Close()
return fmt.Errorf("mountfrom error on re-exec cmd: %v", err) return fmt.Errorf("mountfrom error on re-exec cmd: %v", err)

View file

@ -404,6 +404,7 @@ func waitUntilFlushedImpl(s *journald) error {
go func() { go func() {
defer close(flushed) defer close(flushed)
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread()
var ( var (
j *sdjournal.Journal j *sdjournal.Journal

View file

@ -6,6 +6,7 @@ import (
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"runtime"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -200,18 +201,34 @@ func (r *remote) startContainerd() error {
cmd.Env = append(cmd.Env, e) cmd.Env = append(cmd.Env, e)
} }
} }
if err := cmd.Start(); err != nil {
return err startedCh := make(chan error)
go func() {
// On Linux, when cmd.SysProcAttr.Pdeathsig is set,
// the signal is sent to the subprocess when the creating thread
// terminates. The runtime terminates a thread if a goroutine
// exits while locked to it. Prevent the containerd process
// from getting killed prematurely by ensuring that the thread
// used to start it remains alive until it or the daemon process
// exits. See https://go.dev/issue/27505 for more details.
runtime.LockOSThread()
defer runtime.UnlockOSThread()
err := cmd.Start()
startedCh <- err
if err != nil {
return
} }
r.daemonWaitCh = make(chan struct{}) r.daemonWaitCh = make(chan struct{})
go func() {
// Reap our child when needed // Reap our child when needed
if err := cmd.Wait(); err != nil { if err := cmd.Wait(); err != nil {
r.logger.WithError(err).Errorf("containerd did not exit successfully") r.logger.WithError(err).Errorf("containerd did not exit successfully")
} }
close(r.daemonWaitCh) close(r.daemonWaitCh)
}() }()
if err := <-startedCh; err != nil {
return err
}
r.daemonPid = cmd.Process.Pid r.daemonPid = cmd.Process.Pid

View file

@ -6,6 +6,7 @@ import (
"net" "net"
"os" "os"
"os/exec" "os/exec"
"runtime"
"strconv" "strconv"
"syscall" "syscall"
"time" "time"
@ -37,9 +38,10 @@ func newProxyCommand(proto string, hostIP net.IP, hostPort int, containerIP net.
Path: path, Path: path,
Args: args, Args: args,
SysProcAttr: &syscall.SysProcAttr{ SysProcAttr: &syscall.SysProcAttr{
Pdeathsig: syscall.SIGTERM, // send a sigterm to the proxy if the daemon process dies Pdeathsig: syscall.SIGTERM, // send a sigterm to the proxy if the creating thread in the daemon process dies (https://go.dev/issue/27505)
}, },
}, },
wait: make(chan error, 1),
}, nil }, nil
} }
@ -47,6 +49,7 @@ func newProxyCommand(proto string, hostIP net.IP, hostPort int, containerIP net.
// proxies as separate processes. // proxies as separate processes.
type proxyCommand struct { type proxyCommand struct {
cmd *exec.Cmd cmd *exec.Cmd
wait chan error
} }
func (p *proxyCommand) Start() error { func (p *proxyCommand) Start() error {
@ -56,7 +59,29 @@ func (p *proxyCommand) Start() error {
} }
defer r.Close() defer r.Close()
p.cmd.ExtraFiles = []*os.File{w} p.cmd.ExtraFiles = []*os.File{w}
if err := p.cmd.Start(); err != nil {
// As p.cmd.SysProcAttr.Pdeathsig is set, the signal will be sent to the
// process when the OS thread on which p.cmd.Start() was executed dies.
// If the thread is allowed to be released back into the goroutine
// thread pool, the thread could get terminated at any time if a
// goroutine gets scheduled onto it which calls runtime.LockOSThread()
// and exits without a matching number of runtime.UnlockOSThread()
// calls. Ensure that the thread from which Start() is called stays
// alive until the proxy or the daemon process exits to prevent the
// proxy from getting terminated early. See https://go.dev/issue/27505
// for more details.
started := make(chan error)
go func() {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
err := p.cmd.Start()
started <- err
if err != nil {
return
}
p.wait <- p.cmd.Wait()
}()
if err := <-started; err != nil {
return err return err
} }
w.Close() w.Close()
@ -92,7 +117,7 @@ func (p *proxyCommand) Stop() error {
if err := p.cmd.Process.Signal(os.Interrupt); err != nil { if err := p.cmd.Process.Signal(os.Interrupt); err != nil {
return err return err
} }
return p.cmd.Wait() return <-p.wait
} }
return nil return nil
} }

View file

@ -95,6 +95,12 @@ func invokeUnpack(decompressedArchive io.Reader, dest string, options *archive.T
cmd.Stdout = output cmd.Stdout = output
cmd.Stderr = output cmd.Stderr = output
// reexec.Command() sets cmd.SysProcAttr.Pdeathsig on Linux, which
// causes the started process to be signaled when the creating OS thread
// dies. Ensure that the reexec is not prematurely signaled. See
// https://go.dev/issue/27505 for more information.
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if err := cmd.Start(); err != nil { if err := cmd.Start(); err != nil {
w.Close() w.Close()
return fmt.Errorf("Untar error on re-exec cmd: %v", err) return fmt.Errorf("Untar error on re-exec cmd: %v", err)
@ -188,15 +194,27 @@ func invokePack(srcPath string, options *archive.TarOptions, root string) (io.Re
return nil, errors.Wrap(err, "error getting options pipe for tar process") return nil, errors.Wrap(err, "error getting options pipe for tar process")
} }
if err := cmd.Start(); err != nil { started := make(chan error)
return nil, errors.Wrap(err, "tar error on re-exec cmd")
}
go func() { go func() {
// reexec.Command() sets cmd.SysProcAttr.Pdeathsig on Linux,
// which causes the started process to be signaled when the
// creating OS thread dies. Ensure that the subprocess is not
// prematurely signaled. See https://go.dev/issue/27505 for more
// information.
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if err := cmd.Start(); err != nil {
started <- err
return
}
close(started)
err := cmd.Wait() err := cmd.Wait()
err = errors.Wrapf(err, "error processing tar file: %s", errBuff) err = errors.Wrapf(err, "error processing tar file: %s", errBuff)
tarW.CloseWithError(err) tarW.CloseWithError(err)
}() }()
if err := <-started; err != nil {
return nil, errors.Wrap(err, "tar error on re-exec cmd")
}
if err := json.NewEncoder(stdin).Encode(options); err != nil { if err := json.NewEncoder(stdin).Encode(options); err != nil {
stdin.Close() stdin.Close()

View file

@ -115,6 +115,12 @@ func applyLayerHandler(dest string, layer io.Reader, options *archive.TarOptions
outBuf, errBuf := new(bytes.Buffer), new(bytes.Buffer) outBuf, errBuf := new(bytes.Buffer), new(bytes.Buffer)
cmd.Stdout, cmd.Stderr = outBuf, errBuf cmd.Stdout, cmd.Stderr = outBuf, errBuf
// reexec.Command() sets cmd.SysProcAttr.Pdeathsig on Linux, which
// causes the started process to be signaled when the creating OS thread
// dies. Ensure that the reexec is not prematurely signaled. See
// https://go.dev/issue/27505 for more information.
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if err = cmd.Run(); err != nil { if err = cmd.Run(); err != nil {
return 0, fmt.Errorf("ApplyLayer %s stdout: %s stderr: %s", err, outBuf, errBuf) return 0, fmt.Errorf("ApplyLayer %s stdout: %s stderr: %s", err, outBuf, errBuf)
} }

View file

@ -17,6 +17,11 @@ func Self() string {
// SysProcAttr.Pdeathsig to SIGTERM. // SysProcAttr.Pdeathsig to SIGTERM.
// This will use the in-memory version (/proc/self/exe) of the current binary, // This will use the in-memory version (/proc/self/exe) of the current binary,
// it is thus safe to delete or replace the on-disk binary (os.Args[0]). // it is thus safe to delete or replace the on-disk binary (os.Args[0]).
//
// As SysProcAttr.Pdeathsig is set, the signal will be sent to the process when
// the OS thread which created the process dies. It is the caller's
// responsibility to ensure that the creating thread is not terminated
// prematurely. See https://go.dev/issue/27505 for more details.
func Command(args ...string) *exec.Cmd { func Command(args ...string) *exec.Cmd {
return &exec.Cmd{ return &exec.Cmd{
Path: Self(), Path: Self(),