Skip to content

Commit

Permalink
Fix parent process signal propagation
Browse files Browse the repository at this point in the history
  • Loading branch information
cclerget authored and GodloveD committed Jul 2, 2019
1 parent 12479ec commit f7b429f
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 46 deletions.
13 changes: 7 additions & 6 deletions internal/app/starter/master_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,14 @@ import (
func Master(rpcSocket, masterSocket int, isInstance bool, containerPid int, engine *engines.Engine) {
var fatal error
var status syscall.WaitStatus

fatalChan := make(chan error, 1)

// we could receive signal from child with CreateContainer call so we
// set the signal handler earlier to queue signals until MonitorContainer
// is called to handle them
signals := make(chan os.Signal, 1)
signal.Notify(signals)

ppid := os.Getppid()

go func() {
Expand Down Expand Up @@ -109,11 +115,6 @@ func Master(rpcSocket, masterSocket int, isInstance bool, containerPid int, engi

go func() {
var err error

// catch all signals
signals := make(chan os.Signal, 1)
signal.Notify(signals)

status, err = engine.MonitorContainer(containerPid, signals)
fatalChan <- err
}()
Expand Down
6 changes: 6 additions & 0 deletions internal/pkg/runtime/engines/singularity/monitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ func (engine *EngineOperations) MonitorContainer(pid int, signals chan os.Signal
continue
}
return status, nil
default:
if engine.EngineConfig.GetSignalPropagation() {
if err := syscall.Kill(pid, s.(syscall.Signal)); err != nil {
return status, fmt.Errorf("interrupted by signal %s", s.String())
}
}
}
}
}
28 changes: 28 additions & 0 deletions internal/pkg/runtime/engines/singularity/prepare_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"syscall"

"github.com/sylabs/singularity/pkg/util/fs/proc"
"golang.org/x/sys/unix"

specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/sylabs/singularity/internal/pkg/buildcfg"
Expand Down Expand Up @@ -748,9 +749,36 @@ func (e *EngineOperations) PrepareConfig(starterConfig *starter.Config) error {
starterConfig.SetCapabilities(capabilities.Ambient, e.EngineConfig.OciConfig.Process.Capabilities.Ambient)
}

// determine if engine need to propagate signals across processes
e.checkSignalPropagation()

return nil
}

func (e *EngineOperations) checkSignalPropagation() {
// obtain the process group ID of the associated controlling
// terminal (if there's one).
pgrp := 0
for i := 0; i <= 2; i++ {
// The two possible errors:
// - EBADF will return 0 as process group
// - ENOTTY will also return 0 as process group
pgrp, _ = unix.IoctlGetInt(i, unix.TIOCGPGRP)
// based on kernel source a 0 value for process group
// theorically be set but really not sure it can happen
// with linux tty behavior
if pgrp != 0 {
break
}
}
// cases we need to propagate signals to container process:
// - when pgrp == 0 because container won't run in a terminal
// - when process group is different from the process group controlling terminal
if pgrp == 0 || (pgrp > 0 && pgrp != syscall.Getpgrp()) {
e.EngineConfig.SetSignalPropagation(true)
}
}

func (e *EngineOperations) loadImages(starterConfig *starter.Config) error {
images := make([]image.Image, 0)

Expand Down
10 changes: 10 additions & 0 deletions internal/pkg/runtime/engines/singularity/process_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -317,11 +317,21 @@ func (engine *EngineOperations) StartProcess(masterConn net.Conn) error {
}
default:
signal := s.(syscall.Signal)
// EPERM and EINVAL are deliberately ignored because they can't be
// returned in this context, this process is PID 1, so it has the
// permissions to send signals to its childs and EINVAL would
// mean to update the Go runtime or the kernel to something more
// stable :)
if isInstance {
if err := syscall.Kill(-cmd.Process.Pid, signal); err == syscall.ESRCH {
sylog.Debugf("No child process, exiting ...")
os.Exit(128 + int(signal))
}
} else if engine.EngineConfig.GetSignalPropagation() {
if err := syscall.Kill(cmd.Process.Pid, signal); err == syscall.ESRCH {
sylog.Debugf("No child process, exiting ...")
os.Exit(128 + int(signal))
}
}
}
case err := <-errChan:
Expand Down
95 changes: 55 additions & 40 deletions pkg/runtime/engines/singularity/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,46 +54,47 @@ type FileConfig struct {

// JSONConfig stores engine specific confguration that is allowed to be set by the user
type JSONConfig struct {
ScratchDir []string `json:"scratchdir,omitempty"`
OverlayImage []string `json:"overlayImage,omitempty"`
BindPath []string `json:"bindpath,omitempty"`
NetworkArgs []string `json:"networkArgs,omitempty"`
Security []string `json:"security,omitempty"`
LibrariesPath []string `json:"librariesPath,omitempty"`
ImageList []image.Image `json:"imageList,omitempty"`
OpenFd []int `json:"openFd,omitempty"`
TargetGID []int `json:"targetGID,omitempty"`
Image string `json:"image"`
Workdir string `json:"workdir,omitempty"`
CgroupsPath string `json:"cgroupsPath,omitempty"`
HomeSource string `json:"homedir,omitempty"`
HomeDest string `json:"homeDest,omitempty"`
Command string `json:"command,omitempty"`
Shell string `json:"shell,omitempty"`
TmpDir string `json:"tmpdir,omitempty"`
AddCaps string `json:"addCaps,omitempty"`
DropCaps string `json:"dropCaps,omitempty"`
Hostname string `json:"hostname,omitempty"`
Network string `json:"network,omitempty"`
DNS string `json:"dns,omitempty"`
Cwd string `json:"cwd,omitempty"`
TargetUID int `json:"targetUID,omitempty"`
WritableImage bool `json:"writableImage,omitempty"`
WritableTmpfs bool `json:"writableTmpfs,omitempty"`
Contain bool `json:"container,omitempty"`
Nv bool `json:"nv,omitempty"`
CustomHome bool `json:"customHome,omitempty"`
Instance bool `json:"instance,omitempty"`
InstanceJoin bool `json:"instanceJoin,omitempty"`
BootInstance bool `json:"bootInstance,omitempty"`
RunPrivileged bool `json:"runPrivileged,omitempty"`
AllowSUID bool `json:"allowSUID,omitempty"`
KeepPrivs bool `json:"keepPrivs,omitempty"`
NoPrivs bool `json:"noPrivs,omitempty"`
NoHome bool `json:"noHome,omitempty"`
NoInit bool `json:"noInit,omitempty"`
DeleteImage bool `json:"deleteImage,omitempty"`
Fakeroot bool `json:"fakeroot,omitempty"`
ScratchDir []string `json:"scratchdir,omitempty"`
OverlayImage []string `json:"overlayImage,omitempty"`
BindPath []string `json:"bindpath,omitempty"`
NetworkArgs []string `json:"networkArgs,omitempty"`
Security []string `json:"security,omitempty"`
LibrariesPath []string `json:"librariesPath,omitempty"`
ImageList []image.Image `json:"imageList,omitempty"`
OpenFd []int `json:"openFd,omitempty"`
TargetGID []int `json:"targetGID,omitempty"`
Image string `json:"image"`
Workdir string `json:"workdir,omitempty"`
CgroupsPath string `json:"cgroupsPath,omitempty"`
HomeSource string `json:"homedir,omitempty"`
HomeDest string `json:"homeDest,omitempty"`
Command string `json:"command,omitempty"`
Shell string `json:"shell,omitempty"`
TmpDir string `json:"tmpdir,omitempty"`
AddCaps string `json:"addCaps,omitempty"`
DropCaps string `json:"dropCaps,omitempty"`
Hostname string `json:"hostname,omitempty"`
Network string `json:"network,omitempty"`
DNS string `json:"dns,omitempty"`
Cwd string `json:"cwd,omitempty"`
TargetUID int `json:"targetUID,omitempty"`
WritableImage bool `json:"writableImage,omitempty"`
WritableTmpfs bool `json:"writableTmpfs,omitempty"`
Contain bool `json:"container,omitempty"`
Nv bool `json:"nv,omitempty"`
CustomHome bool `json:"customHome,omitempty"`
Instance bool `json:"instance,omitempty"`
InstanceJoin bool `json:"instanceJoin,omitempty"`
BootInstance bool `json:"bootInstance,omitempty"`
RunPrivileged bool `json:"runPrivileged,omitempty"`
AllowSUID bool `json:"allowSUID,omitempty"`
KeepPrivs bool `json:"keepPrivs,omitempty"`
NoPrivs bool `json:"noPrivs,omitempty"`
NoHome bool `json:"noHome,omitempty"`
NoInit bool `json:"noInit,omitempty"`
DeleteImage bool `json:"deleteImage,omitempty"`
Fakeroot bool `json:"fakeroot,omitempty"`
SignalPropagation bool `json:"signalPropagation,omitempty"`
}

// NewConfig returns singularity.EngineConfig with a parsed FileConfig
Expand Down Expand Up @@ -498,3 +499,17 @@ func (e *EngineConfig) GetDeleteImage() bool {
func (e *EngineConfig) SetDeleteImage(delete bool) {
e.JSON.DeleteImage = delete
}

// SetSignalPropagation sets if engine must propagate signals from
// master process -> container process when PID namespace is disabled
// or from master process -> sinit process -> container
// process when PID namespace is enabled
func (e *EngineConfig) SetSignalPropagation(propagation bool) {
e.JSON.SignalPropagation = propagation
}

// GetSignalPropagation returns if engine propagate signals across
// processes (see SetSignalPropagation)
func (e *EngineConfig) GetSignalPropagation() bool {
return e.JSON.SignalPropagation
}

0 comments on commit f7b429f

Please sign in to comment.