diff --git a/pkg/sighandling/sighandling.go b/pkg/sighandling/sighandling.go index ba26d63fc..c70d41572 100644 --- a/pkg/sighandling/sighandling.go +++ b/pkg/sighandling/sighandling.go @@ -81,6 +81,9 @@ func StartSignalForwarding(handler func(linux.Signal)) func() { // // External real-time signals are not supported. We rely on the go-runtime // for their handling. + // + // We do not forward some signals that are likely induced by the behavior + // of the forwarding process. var sigchans []chan os.Signal for sig := 1; sig <= numSignals+1; sig++ { sigchan := make(chan os.Signal, 1) @@ -94,6 +97,10 @@ func StartSignalForwarding(handler func(linux.Signal)) func() { if sig == int(linux.SIGPIPE) { continue } + // SIGCHLD is received when a child of the forwarding process exits. + if sig == int(linux.SIGCHLD) { + continue + } signal.Notify(sigchan, unix.Signal(sig)) } // Start up our listener. diff --git a/runsc/cmd/do.go b/runsc/cmd/do.go index 8b10f6f25..f6ef57456 100644 --- a/runsc/cmd/do.go +++ b/runsc/cmd/do.go @@ -33,6 +33,7 @@ import ( "gvisor.dev/gvisor/pkg/log" "gvisor.dev/gvisor/runsc/cmd/util" "gvisor.dev/gvisor/runsc/config" + "gvisor.dev/gvisor/runsc/console" "gvisor.dev/gvisor/runsc/container" "gvisor.dev/gvisor/runsc/flag" "gvisor.dev/gvisor/runsc/specutils" @@ -167,6 +168,7 @@ func (c *Do) Execute(_ context.Context, f *flag.FlagSet, args ...any) subcommand Args: f.Args(), Env: os.Environ(), Capabilities: specutils.AllCapabilities(), + Terminal: console.IsPty(os.Stdin.Fd()), }, Hostname: hostname, } @@ -445,7 +447,7 @@ func startContainerAndWait(spec *specs.Spec, conf *config.Config, cid string, wa // // N.B. There is a still a window before this where a signal may kill // this process, skipping cleanup. - stopForwarding := ct.ForwardSignals(0 /* pid */, false /* fgProcess */) + stopForwarding := ct.ForwardSignals(0 /* pid */, spec.Process.Terminal /* fgProcess */) defer stopForwarding() ws, err := ct.Wait() diff --git a/runsc/cmd/exec.go b/runsc/cmd/exec.go index f995ce467..bd871d29c 100644 --- a/runsc/cmd/exec.go +++ b/runsc/cmd/exec.go @@ -329,7 +329,7 @@ func (ex *Exec) argsFromCLI(argv []string, enableRaw bool) (*control.ExecArgs, e KGID: ex.user.kgid, ExtraKGIDs: extraKGIDs, Capabilities: caps, - StdioIsPty: ex.consoleSocket != "", + StdioIsPty: ex.consoleSocket != "" || console.IsPty(os.Stdin.Fd()), FilePayload: urpc.FilePayload{[]*os.File{os.Stdin, os.Stdout, os.Stderr}}, }, nil } diff --git a/runsc/console/BUILD b/runsc/console/BUILD index 06924bccd..e58a1e2d6 100644 --- a/runsc/console/BUILD +++ b/runsc/console/BUILD @@ -6,6 +6,7 @@ go_library( name = "console", srcs = [ "console.go", + "pty_linux.go" ], visibility = [ "//runsc:__subpackages__", diff --git a/runsc/console/pty_linux.go b/runsc/console/pty_linux.go new file mode 100644 index 000000000..589e915d4 --- /dev/null +++ b/runsc/console/pty_linux.go @@ -0,0 +1,26 @@ +// Copyright 2023 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build linux +// +build linux + +package console + +import "golang.org/x/sys/unix" + +// IsPty returns true if FD is a PTY. +func IsPty(fd uintptr) bool { + _, err := unix.IoctlGetTermios(int(fd), unix.TCGETS) + return err == nil +} diff --git a/runsc/container/container.go b/runsc/container/container.go index e68fdc2b3..9d4352935 100644 --- a/runsc/container/container.go +++ b/runsc/container/container.go @@ -521,6 +521,15 @@ func Run(conf *config.Config, args Args) (unix.WaitStatus, error) { return 0, fmt.Errorf("starting container: %v", err) } } + + // If we allocate a terminal, forward signals to the sandbox process. + // Otherwise, Ctrl+C will terminate this process and its children, + // including the terminal. + if c.Spec.Process.Terminal { + stopForwarding := c.ForwardSignals(0, true /* fgProcess */) + defer stopForwarding() + } + if args.Attached { return c.Wait() } @@ -1059,7 +1068,11 @@ func (c *Container) createGoferProcess(spec *specs.Spec, conf *config.Config, bu // Start with the general config flags. cmd := exec.Command(specutils.ExePath, conf.ToFlags()...) - cmd.SysProcAttr = &unix.SysProcAttr{} + cmd.SysProcAttr = &unix.SysProcAttr{ + // Detach from session. Otherwise, signals sent to the foreground process + // will also be forwarded by this process, resulting in duplicate signals. + Setsid: true, + } // Set Args[0] to make easier to spot the gofer process. Otherwise it's // shown as `exe`. diff --git a/runsc/specutils/namespace.go b/runsc/specutils/namespace.go index 09c723f82..b17505399 100644 --- a/runsc/specutils/namespace.go +++ b/runsc/specutils/namespace.go @@ -282,6 +282,10 @@ func MaybeRunAsRoot() error { // Make sure child is killed when the parent terminates. Pdeathsig: unix.SIGKILL, + + // Detach from session. Otherwise, signals sent to the foreground process + // will also be forwarded by this process, resulting in duplicate signals. + Setsid: true, } cmd.Env = os.Environ()