From d5a9d523bb9f7e1b7962049491bbf419121e3e15 Mon Sep 17 00:00:00 2001 From: Nicolas Lacasse Date: Wed, 2 Oct 2024 19:37:04 -0700 Subject: [PATCH] Implement /dev/tty for donated host TTYs Fixes #10925 PiperOrigin-RevId: 681684673 --- pkg/sentry/control/proc.go | 7 +++++++ pkg/sentry/fsimpl/host/tty.go | 9 +++++++++ pkg/sentry/kernel/kernel.go | 9 +++++++++ runsc/boot/loader.go | 7 +++++++ runsc/container/console_test.go | 10 ++++++++++ 5 files changed, 42 insertions(+) diff --git a/pkg/sentry/control/proc.go b/pkg/sentry/control/proc.go index 6e643038f..5cfdb53b6 100644 --- a/pkg/sentry/control/proc.go +++ b/pkg/sentry/control/proc.go @@ -272,6 +272,13 @@ func (proc *Proc) execAsync(args *ExecArgs) (*kernel.ThreadGroup, kernel.ThreadI return nil, 0, nil, err } + if ttyFile != nil { + // Index does not matter here. This tty is not coming from a + // devpts mount, so it won't collide with any of the ptys + // created there. + initArgs.TTY = kernel.NewTTY(0, ttyFile) + } + // Set cgroups to the new exec task if cgroups are mounted. cgroupRegistry := proc.Kernel.CgroupRegistry() initialCgrps := map[kernel.Cgroup]struct{}{} diff --git a/pkg/sentry/fsimpl/host/tty.go b/pkg/sentry/fsimpl/host/tty.go index 5f490f467..7de57787f 100644 --- a/pkg/sentry/fsimpl/host/tty.go +++ b/pkg/sentry/fsimpl/host/tty.go @@ -31,6 +31,8 @@ import ( // TTYFileDescription implements vfs.FileDescriptionImpl for a host file // descriptor that wraps a TTY FD. // +// It implements kernel.TTYOperations. +// // +stateify savable type TTYFileDescription struct { fileDescription @@ -49,6 +51,13 @@ type TTYFileDescription struct { termios linux.KernelTermios } +// Open re-opens the tty fd, for example via open(/dev/tty). See Linux's +// tty_repoen(). +func (t *TTYFileDescription) Open(_ context.Context, _ *vfs.Mount, _ *vfs.Dentry, _ vfs.OpenOptions) (*vfs.FileDescription, error) { + t.vfsfd.IncRef() + return &t.vfsfd, nil +} + // InitForegroundProcessGroup sets the foreground process group and session for // the TTY. This should only be called once, after the foreground process group // has been created, but before it has started running. diff --git a/pkg/sentry/kernel/kernel.go b/pkg/sentry/kernel/kernel.go index 41b32eeb9..7b722882b 100644 --- a/pkg/sentry/kernel/kernel.go +++ b/pkg/sentry/kernel/kernel.go @@ -991,6 +991,9 @@ type CreateProcessArgs struct { // Origin indicates how the task was first created. Origin TaskOrigin + + // TTY is the optional TTY to associate with this process. + TTY *TTY } // NewContext returns a context.Context that represents the task that will be @@ -1222,6 +1225,12 @@ func (k *Kernel) CreateProcess(args CreateProcessArgs) (*ThreadGroup, ThreadID, } t.traceExecEvent(image) // Simulate exec for tracing. + // Set TTY if configured. + if args.TTY != nil { + t.tg.tty = args.TTY + args.TTY.tg = t.tg + } + // Success. cu.Release() tgid := k.tasks.Root.IDOfThreadGroup(tg) diff --git a/runsc/boot/loader.go b/runsc/boot/loader.go index cd15621c0..9ad780ea3 100644 --- a/runsc/boot/loader.go +++ b/runsc/boot/loader.go @@ -1110,6 +1110,13 @@ func (l *Loader) createContainerProcess(info *containerInfo) (*kernel.ThreadGrou // ours either way. info.procArgs.FDTable = fdTable + if ttyFile != nil { + // Index does not matter here. This tty is not coming from a + // devpts mount, so it won't collide with any of the ptys + // created there. + info.procArgs.TTY = kernel.NewTTY(0, ttyFile) + } + if info.execFD != nil { if info.procArgs.Filename != "" { return nil, nil, fmt.Errorf("process must either be started from a file or a filename, not both") diff --git a/runsc/container/console_test.go b/runsc/container/console_test.go index c668bed38..60d0e51fe 100644 --- a/runsc/container/console_test.go +++ b/runsc/container/console_test.go @@ -594,6 +594,16 @@ func TestMultiContainerTerminal(t *testing.T) { if err := testutil.WaitUntilRead(ptyBuf, "foo-/-123", 5*time.Second); err != nil { t.Fatalf("echo didn't execute: %v", err) } + + // Make sure we can open /dev/tty. We do this + // by asking `head` to to read 0 bytes, which + // causes it to simply open & close the file. + if _, err := tc.master.Write([]byte("head -n 0 /dev/tty; echo $?\n")); err != nil { + t.Fatalf("master.Write(): %v", err) + } + if err := testutil.WaitUntilRead(ptyBuf, "0", 5*time.Second); err != nil { + t.Fatalf("head didn't execute: %v", err) + } } }) }