| // Copyright 2020 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. |
| |
| package host |
| |
| import ( |
| "gvisor.dev/gvisor/pkg/abi/linux" |
| "gvisor.dev/gvisor/pkg/context" |
| "gvisor.dev/gvisor/pkg/marshal/primitive" |
| "gvisor.dev/gvisor/pkg/sentry/arch" |
| fslock "gvisor.dev/gvisor/pkg/sentry/fs/lock" |
| "gvisor.dev/gvisor/pkg/sentry/kernel" |
| "gvisor.dev/gvisor/pkg/sentry/unimpl" |
| "gvisor.dev/gvisor/pkg/sentry/vfs" |
| "gvisor.dev/gvisor/pkg/sync" |
| "gvisor.dev/gvisor/pkg/syserror" |
| "gvisor.dev/gvisor/pkg/usermem" |
| ) |
| |
| // TTYFileDescription implements vfs.FileDescriptionImpl for a host file |
| // descriptor that wraps a TTY FD. |
| // |
| // +stateify savable |
| type TTYFileDescription struct { |
| fileDescription |
| |
| // mu protects the fields below. |
| mu sync.Mutex `state:"nosave"` |
| |
| // session is the session attached to this TTYFileDescription. |
| session *kernel.Session |
| |
| // fgProcessGroup is the foreground process group that is currently |
| // connected to this TTY. |
| fgProcessGroup *kernel.ProcessGroup |
| |
| // termios contains the terminal attributes for this TTY. |
| termios linux.KernelTermios |
| } |
| |
| // 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. |
| func (t *TTYFileDescription) InitForegroundProcessGroup(pg *kernel.ProcessGroup) { |
| t.mu.Lock() |
| defer t.mu.Unlock() |
| if t.fgProcessGroup != nil { |
| panic("foreground process group is already set") |
| } |
| t.fgProcessGroup = pg |
| t.session = pg.Session() |
| } |
| |
| // ForegroundProcessGroup returns the foreground process for the TTY. |
| func (t *TTYFileDescription) ForegroundProcessGroup() *kernel.ProcessGroup { |
| t.mu.Lock() |
| defer t.mu.Unlock() |
| return t.fgProcessGroup |
| } |
| |
| // Release implements fs.FileOperations.Release. |
| func (t *TTYFileDescription) Release(ctx context.Context) { |
| t.mu.Lock() |
| t.fgProcessGroup = nil |
| t.mu.Unlock() |
| |
| t.fileDescription.Release(ctx) |
| } |
| |
| // PRead implements vfs.FileDescriptionImpl.PRead. |
| // |
| // Reading from a TTY is only allowed for foreground process groups. Background |
| // process groups will either get EIO or a SIGTTIN. |
| func (t *TTYFileDescription) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.ReadOptions) (int64, error) { |
| t.mu.Lock() |
| defer t.mu.Unlock() |
| |
| // Are we allowed to do the read? |
| // drivers/tty/n_tty.c:n_tty_read()=>job_control()=>tty_check_change(). |
| if err := t.checkChange(ctx, linux.SIGTTIN); err != nil { |
| return 0, err |
| } |
| |
| // Do the read. |
| return t.fileDescription.PRead(ctx, dst, offset, opts) |
| } |
| |
| // Read implements vfs.FileDescriptionImpl.Read. |
| // |
| // Reading from a TTY is only allowed for foreground process groups. Background |
| // process groups will either get EIO or a SIGTTIN. |
| func (t *TTYFileDescription) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) { |
| t.mu.Lock() |
| defer t.mu.Unlock() |
| |
| // Are we allowed to do the read? |
| // drivers/tty/n_tty.c:n_tty_read()=>job_control()=>tty_check_change(). |
| if err := t.checkChange(ctx, linux.SIGTTIN); err != nil { |
| return 0, err |
| } |
| |
| // Do the read. |
| return t.fileDescription.Read(ctx, dst, opts) |
| } |
| |
| // PWrite implements vfs.FileDescriptionImpl.PWrite. |
| func (t *TTYFileDescription) PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts vfs.WriteOptions) (int64, error) { |
| t.mu.Lock() |
| defer t.mu.Unlock() |
| |
| // Check whether TOSTOP is enabled. This corresponds to the check in |
| // drivers/tty/n_tty.c:n_tty_write(). |
| if t.termios.LEnabled(linux.TOSTOP) { |
| if err := t.checkChange(ctx, linux.SIGTTOU); err != nil { |
| return 0, err |
| } |
| } |
| return t.fileDescription.PWrite(ctx, src, offset, opts) |
| } |
| |
| // Write implements vfs.FileDescriptionImpl.Write. |
| func (t *TTYFileDescription) Write(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) { |
| t.mu.Lock() |
| defer t.mu.Unlock() |
| |
| // Check whether TOSTOP is enabled. This corresponds to the check in |
| // drivers/tty/n_tty.c:n_tty_write(). |
| if t.termios.LEnabled(linux.TOSTOP) { |
| if err := t.checkChange(ctx, linux.SIGTTOU); err != nil { |
| return 0, err |
| } |
| } |
| return t.fileDescription.Write(ctx, src, opts) |
| } |
| |
| // Ioctl implements vfs.FileDescriptionImpl.Ioctl. |
| func (t *TTYFileDescription) Ioctl(ctx context.Context, io usermem.IO, args arch.SyscallArguments) (uintptr, error) { |
| task := kernel.TaskFromContext(ctx) |
| if task == nil { |
| return 0, syserror.ENOTTY |
| } |
| |
| // Ignore arg[0]. This is the real FD: |
| fd := t.inode.hostFD |
| ioctl := args[1].Uint64() |
| switch ioctl { |
| case linux.TCGETS: |
| termios, err := ioctlGetTermios(fd) |
| if err != nil { |
| return 0, err |
| } |
| _, err = termios.CopyOut(task, args[2].Pointer()) |
| return 0, err |
| |
| case linux.TCSETS, linux.TCSETSW, linux.TCSETSF: |
| t.mu.Lock() |
| defer t.mu.Unlock() |
| |
| if err := t.checkChange(ctx, linux.SIGTTOU); err != nil { |
| return 0, err |
| } |
| |
| var termios linux.Termios |
| if _, err := termios.CopyIn(task, args[2].Pointer()); err != nil { |
| return 0, err |
| } |
| err := ioctlSetTermios(fd, ioctl, &termios) |
| if err == nil { |
| t.termios.FromTermios(termios) |
| } |
| return 0, err |
| |
| case linux.TIOCGPGRP: |
| // Args: pid_t *argp |
| // When successful, equivalent to *argp = tcgetpgrp(fd). |
| // Get the process group ID of the foreground process group on this |
| // terminal. |
| |
| pidns := kernel.PIDNamespaceFromContext(ctx) |
| if pidns == nil { |
| return 0, syserror.ENOTTY |
| } |
| |
| t.mu.Lock() |
| defer t.mu.Unlock() |
| |
| // Map the ProcessGroup into a ProcessGroupID in the task's PID namespace. |
| pgID := primitive.Int32(pidns.IDOfProcessGroup(t.fgProcessGroup)) |
| _, err := pgID.CopyOut(task, args[2].Pointer()) |
| return 0, err |
| |
| case linux.TIOCSPGRP: |
| // Args: const pid_t *argp |
| // Equivalent to tcsetpgrp(fd, *argp). |
| // Set the foreground process group ID of this terminal. |
| |
| t.mu.Lock() |
| defer t.mu.Unlock() |
| |
| // Check that we are allowed to set the process group. |
| if err := t.checkChange(ctx, linux.SIGTTOU); err != nil { |
| // drivers/tty/tty_io.c:tiocspgrp() converts -EIO from tty_check_change() |
| // to -ENOTTY. |
| if err == syserror.EIO { |
| return 0, syserror.ENOTTY |
| } |
| return 0, err |
| } |
| |
| // Check that calling task's process group is in the TTY session. |
| if task.ThreadGroup().Session() != t.session { |
| return 0, syserror.ENOTTY |
| } |
| |
| var pgIDP primitive.Int32 |
| if _, err := pgIDP.CopyIn(task, args[2].Pointer()); err != nil { |
| return 0, err |
| } |
| pgID := kernel.ProcessGroupID(pgIDP) |
| |
| // pgID must be non-negative. |
| if pgID < 0 { |
| return 0, syserror.EINVAL |
| } |
| |
| // Process group with pgID must exist in this PID namespace. |
| pidns := task.PIDNamespace() |
| pg := pidns.ProcessGroupWithID(pgID) |
| if pg == nil { |
| return 0, syserror.ESRCH |
| } |
| |
| // Check that new process group is in the TTY session. |
| if pg.Session() != t.session { |
| return 0, syserror.EPERM |
| } |
| |
| t.fgProcessGroup = pg |
| return 0, nil |
| |
| case linux.TIOCGWINSZ: |
| // Args: struct winsize *argp |
| // Get window size. |
| winsize, err := ioctlGetWinsize(fd) |
| if err != nil { |
| return 0, err |
| } |
| _, err = winsize.CopyOut(task, args[2].Pointer()) |
| return 0, err |
| |
| case linux.TIOCSWINSZ: |
| // Args: const struct winsize *argp |
| // Set window size. |
| |
| // Unlike setting the termios, any process group (even background ones) can |
| // set the winsize. |
| |
| var winsize linux.Winsize |
| if _, err := winsize.CopyIn(task, args[2].Pointer()); err != nil { |
| return 0, err |
| } |
| err := ioctlSetWinsize(fd, &winsize) |
| return 0, err |
| |
| // Unimplemented commands. |
| case linux.TIOCSETD, |
| linux.TIOCSBRK, |
| linux.TIOCCBRK, |
| linux.TCSBRK, |
| linux.TCSBRKP, |
| linux.TIOCSTI, |
| linux.TIOCCONS, |
| linux.FIONBIO, |
| linux.TIOCEXCL, |
| linux.TIOCNXCL, |
| linux.TIOCGEXCL, |
| linux.TIOCNOTTY, |
| linux.TIOCSCTTY, |
| linux.TIOCGSID, |
| linux.TIOCGETD, |
| linux.TIOCVHANGUP, |
| linux.TIOCGDEV, |
| linux.TIOCMGET, |
| linux.TIOCMSET, |
| linux.TIOCMBIC, |
| linux.TIOCMBIS, |
| linux.TIOCGICOUNT, |
| linux.TCFLSH, |
| linux.TIOCSSERIAL, |
| linux.TIOCGPTPEER: |
| |
| unimpl.EmitUnimplementedEvent(ctx) |
| fallthrough |
| default: |
| return 0, syserror.ENOTTY |
| } |
| } |
| |
| // checkChange checks that the process group is allowed to read, write, or |
| // change the state of the TTY. |
| // |
| // This corresponds to Linux drivers/tty/tty_io.c:tty_check_change(). The logic |
| // is a bit convoluted, but documented inline. |
| // |
| // Preconditions: t.mu must be held. |
| func (t *TTYFileDescription) checkChange(ctx context.Context, sig linux.Signal) error { |
| task := kernel.TaskFromContext(ctx) |
| if task == nil { |
| // No task? Linux does not have an analog for this case, but |
| // tty_check_change only blocks specific cases and is |
| // surprisingly permissive. Allowing the change seems |
| // appropriate. |
| return nil |
| } |
| |
| tg := task.ThreadGroup() |
| pg := tg.ProcessGroup() |
| |
| // If the session for the task is different than the session for the |
| // controlling TTY, then the change is allowed. Seems like a bad idea, |
| // but that's exactly what linux does. |
| if tg.Session() != t.fgProcessGroup.Session() { |
| return nil |
| } |
| |
| // If we are the foreground process group, then the change is allowed. |
| if pg == t.fgProcessGroup { |
| return nil |
| } |
| |
| // We are not the foreground process group. |
| |
| // Is the provided signal blocked or ignored? |
| if (task.SignalMask()&linux.SignalSetOf(sig) != 0) || tg.SignalHandlers().IsIgnored(sig) { |
| // If the signal is SIGTTIN, then we are attempting to read |
| // from the TTY. Don't send the signal and return EIO. |
| if sig == linux.SIGTTIN { |
| return syserror.EIO |
| } |
| |
| // Otherwise, we are writing or changing terminal state. This is allowed. |
| return nil |
| } |
| |
| // If the process group is an orphan, return EIO. |
| if pg.IsOrphan() { |
| return syserror.EIO |
| } |
| |
| // Otherwise, send the signal to the process group and return ERESTARTSYS. |
| // |
| // Note that Linux also unconditionally sets TIF_SIGPENDING on current, |
| // but this isn't necessary in gVisor because the rationale given in |
| // 040b6362d58f "tty: fix leakage of -ERESTARTSYS to userland" doesn't |
| // apply: the sentry will handle -ERESTARTSYS in |
| // kernel.runApp.execute() even if the kernel.Task isn't interrupted. |
| // |
| // Linux ignores the result of kill_pgrp(). |
| _ = pg.SendSignal(kernel.SignalInfoPriv(sig)) |
| return syserror.ERESTARTSYS |
| } |
| |
| // LockPOSIX implements vfs.FileDescriptionImpl.LockPOSIX. |
| func (t *TTYFileDescription) LockPOSIX(ctx context.Context, uid fslock.UniqueID, typ fslock.LockType, start, length uint64, whence int16, block fslock.Blocker) error { |
| return t.Locks().LockPOSIX(ctx, &t.vfsfd, uid, typ, start, length, whence, block) |
| } |
| |
| // UnlockPOSIX implements vfs.FileDescriptionImpl.UnlockPOSIX. |
| func (t *TTYFileDescription) UnlockPOSIX(ctx context.Context, uid fslock.UniqueID, start, length uint64, whence int16) error { |
| return t.Locks().UnlockPOSIX(ctx, &t.vfsfd, uid, start, length, whence) |
| } |