github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/fsimpl/host/tty.go (about)

     1  // Copyright 2020 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package host
    16  
    17  import (
    18  	"github.com/SagerNet/gvisor/pkg/abi/linux"
    19  	"github.com/SagerNet/gvisor/pkg/context"
    20  	"github.com/SagerNet/gvisor/pkg/errors/linuxerr"
    21  	"github.com/SagerNet/gvisor/pkg/marshal/primitive"
    22  	"github.com/SagerNet/gvisor/pkg/sentry/arch"
    23  	"github.com/SagerNet/gvisor/pkg/sentry/kernel"
    24  	"github.com/SagerNet/gvisor/pkg/sentry/unimpl"
    25  	"github.com/SagerNet/gvisor/pkg/sentry/vfs"
    26  	"github.com/SagerNet/gvisor/pkg/sync"
    27  	"github.com/SagerNet/gvisor/pkg/syserror"
    28  	"github.com/SagerNet/gvisor/pkg/usermem"
    29  )
    30  
    31  // TTYFileDescription implements vfs.FileDescriptionImpl for a host file
    32  // descriptor that wraps a TTY FD.
    33  //
    34  // +stateify savable
    35  type TTYFileDescription struct {
    36  	fileDescription
    37  
    38  	// mu protects the fields below.
    39  	mu sync.Mutex `state:"nosave"`
    40  
    41  	// session is the session attached to this TTYFileDescription.
    42  	session *kernel.Session
    43  
    44  	// fgProcessGroup is the foreground process group that is currently
    45  	// connected to this TTY.
    46  	fgProcessGroup *kernel.ProcessGroup
    47  
    48  	// termios contains the terminal attributes for this TTY.
    49  	termios linux.KernelTermios
    50  }
    51  
    52  // InitForegroundProcessGroup sets the foreground process group and session for
    53  // the TTY. This should only be called once, after the foreground process group
    54  // has been created, but before it has started running.
    55  func (t *TTYFileDescription) InitForegroundProcessGroup(pg *kernel.ProcessGroup) {
    56  	t.mu.Lock()
    57  	defer t.mu.Unlock()
    58  	if t.fgProcessGroup != nil {
    59  		panic("foreground process group is already set")
    60  	}
    61  	t.fgProcessGroup = pg
    62  	t.session = pg.Session()
    63  }
    64  
    65  // ForegroundProcessGroup returns the foreground process for the TTY.
    66  func (t *TTYFileDescription) ForegroundProcessGroup() *kernel.ProcessGroup {
    67  	t.mu.Lock()
    68  	defer t.mu.Unlock()
    69  	return t.fgProcessGroup
    70  }
    71  
    72  // Release implements fs.FileOperations.Release.
    73  func (t *TTYFileDescription) Release(ctx context.Context) {
    74  	t.mu.Lock()
    75  	t.fgProcessGroup = nil
    76  	t.mu.Unlock()
    77  
    78  	t.fileDescription.Release(ctx)
    79  }
    80  
    81  // PRead implements vfs.FileDescriptionImpl.PRead.
    82  //
    83  // Reading from a TTY is only allowed for foreground process groups. Background
    84  // process groups will either get EIO or a SIGTTIN.
    85  func (t *TTYFileDescription) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.ReadOptions) (int64, error) {
    86  	t.mu.Lock()
    87  	defer t.mu.Unlock()
    88  
    89  	// Are we allowed to do the read?
    90  	// drivers/tty/n_tty.c:n_tty_read()=>job_control()=>tty_check_change().
    91  	if err := t.checkChange(ctx, linux.SIGTTIN); err != nil {
    92  		return 0, err
    93  	}
    94  
    95  	// Do the read.
    96  	return t.fileDescription.PRead(ctx, dst, offset, opts)
    97  }
    98  
    99  // Read implements vfs.FileDescriptionImpl.Read.
   100  //
   101  // Reading from a TTY is only allowed for foreground process groups. Background
   102  // process groups will either get EIO or a SIGTTIN.
   103  func (t *TTYFileDescription) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) {
   104  	t.mu.Lock()
   105  	defer t.mu.Unlock()
   106  
   107  	// Are we allowed to do the read?
   108  	// drivers/tty/n_tty.c:n_tty_read()=>job_control()=>tty_check_change().
   109  	if err := t.checkChange(ctx, linux.SIGTTIN); err != nil {
   110  		return 0, err
   111  	}
   112  
   113  	// Do the read.
   114  	return t.fileDescription.Read(ctx, dst, opts)
   115  }
   116  
   117  // PWrite implements vfs.FileDescriptionImpl.PWrite.
   118  func (t *TTYFileDescription) PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts vfs.WriteOptions) (int64, error) {
   119  	t.mu.Lock()
   120  	defer t.mu.Unlock()
   121  
   122  	// Check whether TOSTOP is enabled. This corresponds to the check in
   123  	// drivers/tty/n_tty.c:n_tty_write().
   124  	if t.termios.LEnabled(linux.TOSTOP) {
   125  		if err := t.checkChange(ctx, linux.SIGTTOU); err != nil {
   126  			return 0, err
   127  		}
   128  	}
   129  	return t.fileDescription.PWrite(ctx, src, offset, opts)
   130  }
   131  
   132  // Write implements vfs.FileDescriptionImpl.Write.
   133  func (t *TTYFileDescription) Write(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) {
   134  	t.mu.Lock()
   135  	defer t.mu.Unlock()
   136  
   137  	// Check whether TOSTOP is enabled. This corresponds to the check in
   138  	// drivers/tty/n_tty.c:n_tty_write().
   139  	if t.termios.LEnabled(linux.TOSTOP) {
   140  		if err := t.checkChange(ctx, linux.SIGTTOU); err != nil {
   141  			return 0, err
   142  		}
   143  	}
   144  	return t.fileDescription.Write(ctx, src, opts)
   145  }
   146  
   147  // Ioctl implements vfs.FileDescriptionImpl.Ioctl.
   148  func (t *TTYFileDescription) Ioctl(ctx context.Context, io usermem.IO, args arch.SyscallArguments) (uintptr, error) {
   149  	task := kernel.TaskFromContext(ctx)
   150  	if task == nil {
   151  		return 0, syserror.ENOTTY
   152  	}
   153  
   154  	// Ignore arg[0]. This is the real FD:
   155  	fd := t.inode.hostFD
   156  	ioctl := args[1].Uint64()
   157  	switch ioctl {
   158  	case linux.TCGETS:
   159  		termios, err := ioctlGetTermios(fd)
   160  		if err != nil {
   161  			return 0, err
   162  		}
   163  		_, err = termios.CopyOut(task, args[2].Pointer())
   164  		return 0, err
   165  
   166  	case linux.TCSETS, linux.TCSETSW, linux.TCSETSF:
   167  		t.mu.Lock()
   168  		defer t.mu.Unlock()
   169  
   170  		if err := t.checkChange(ctx, linux.SIGTTOU); err != nil {
   171  			return 0, err
   172  		}
   173  
   174  		var termios linux.Termios
   175  		if _, err := termios.CopyIn(task, args[2].Pointer()); err != nil {
   176  			return 0, err
   177  		}
   178  		err := ioctlSetTermios(fd, ioctl, &termios)
   179  		if err == nil {
   180  			t.termios.FromTermios(termios)
   181  		}
   182  		return 0, err
   183  
   184  	case linux.TIOCGPGRP:
   185  		// Args: pid_t *argp
   186  		// When successful, equivalent to *argp = tcgetpgrp(fd).
   187  		// Get the process group ID of the foreground process group on this
   188  		// terminal.
   189  
   190  		pidns := kernel.PIDNamespaceFromContext(ctx)
   191  		if pidns == nil {
   192  			return 0, syserror.ENOTTY
   193  		}
   194  
   195  		t.mu.Lock()
   196  		defer t.mu.Unlock()
   197  
   198  		// Map the ProcessGroup into a ProcessGroupID in the task's PID namespace.
   199  		pgID := primitive.Int32(pidns.IDOfProcessGroup(t.fgProcessGroup))
   200  		_, err := pgID.CopyOut(task, args[2].Pointer())
   201  		return 0, err
   202  
   203  	case linux.TIOCSPGRP:
   204  		// Args: const pid_t *argp
   205  		// Equivalent to tcsetpgrp(fd, *argp).
   206  		// Set the foreground process group ID of this terminal.
   207  
   208  		t.mu.Lock()
   209  		defer t.mu.Unlock()
   210  
   211  		// Check that we are allowed to set the process group.
   212  		if err := t.checkChange(ctx, linux.SIGTTOU); err != nil {
   213  			// drivers/tty/tty_io.c:tiocspgrp() converts -EIO from tty_check_change()
   214  			// to -ENOTTY.
   215  			if linuxerr.Equals(linuxerr.EIO, err) {
   216  				return 0, syserror.ENOTTY
   217  			}
   218  			return 0, err
   219  		}
   220  
   221  		// Check that calling task's process group is in the TTY session.
   222  		if task.ThreadGroup().Session() != t.session {
   223  			return 0, syserror.ENOTTY
   224  		}
   225  
   226  		var pgIDP primitive.Int32
   227  		if _, err := pgIDP.CopyIn(task, args[2].Pointer()); err != nil {
   228  			return 0, err
   229  		}
   230  		pgID := kernel.ProcessGroupID(pgIDP)
   231  
   232  		// pgID must be non-negative.
   233  		if pgID < 0 {
   234  			return 0, linuxerr.EINVAL
   235  		}
   236  
   237  		// Process group with pgID must exist in this PID namespace.
   238  		pidns := task.PIDNamespace()
   239  		pg := pidns.ProcessGroupWithID(pgID)
   240  		if pg == nil {
   241  			return 0, syserror.ESRCH
   242  		}
   243  
   244  		// Check that new process group is in the TTY session.
   245  		if pg.Session() != t.session {
   246  			return 0, linuxerr.EPERM
   247  		}
   248  
   249  		t.fgProcessGroup = pg
   250  		return 0, nil
   251  
   252  	case linux.TIOCGWINSZ:
   253  		// Args: struct winsize *argp
   254  		// Get window size.
   255  		winsize, err := ioctlGetWinsize(fd)
   256  		if err != nil {
   257  			return 0, err
   258  		}
   259  		_, err = winsize.CopyOut(task, args[2].Pointer())
   260  		return 0, err
   261  
   262  	case linux.TIOCSWINSZ:
   263  		// Args: const struct winsize *argp
   264  		// Set window size.
   265  
   266  		// Unlike setting the termios, any process group (even background ones) can
   267  		// set the winsize.
   268  
   269  		var winsize linux.Winsize
   270  		if _, err := winsize.CopyIn(task, args[2].Pointer()); err != nil {
   271  			return 0, err
   272  		}
   273  		err := ioctlSetWinsize(fd, &winsize)
   274  		return 0, err
   275  
   276  	// Unimplemented commands.
   277  	case linux.TIOCSETD,
   278  		linux.TIOCSBRK,
   279  		linux.TIOCCBRK,
   280  		linux.TCSBRK,
   281  		linux.TCSBRKP,
   282  		linux.TIOCSTI,
   283  		linux.TIOCCONS,
   284  		linux.FIONBIO,
   285  		linux.TIOCEXCL,
   286  		linux.TIOCNXCL,
   287  		linux.TIOCGEXCL,
   288  		linux.TIOCNOTTY,
   289  		linux.TIOCSCTTY,
   290  		linux.TIOCGSID,
   291  		linux.TIOCGETD,
   292  		linux.TIOCVHANGUP,
   293  		linux.TIOCGDEV,
   294  		linux.TIOCMGET,
   295  		linux.TIOCMSET,
   296  		linux.TIOCMBIC,
   297  		linux.TIOCMBIS,
   298  		linux.TIOCGICOUNT,
   299  		linux.TCFLSH,
   300  		linux.TIOCSSERIAL,
   301  		linux.TIOCGPTPEER:
   302  
   303  		unimpl.EmitUnimplementedEvent(ctx)
   304  		fallthrough
   305  	default:
   306  		return 0, syserror.ENOTTY
   307  	}
   308  }
   309  
   310  // checkChange checks that the process group is allowed to read, write, or
   311  // change the state of the TTY.
   312  //
   313  // This corresponds to Linux drivers/tty/tty_io.c:tty_check_change(). The logic
   314  // is a bit convoluted, but documented inline.
   315  //
   316  // Preconditions: t.mu must be held.
   317  func (t *TTYFileDescription) checkChange(ctx context.Context, sig linux.Signal) error {
   318  	task := kernel.TaskFromContext(ctx)
   319  	if task == nil {
   320  		// No task? Linux does not have an analog for this case, but
   321  		// tty_check_change only blocks specific cases and is
   322  		// surprisingly permissive. Allowing the change seems
   323  		// appropriate.
   324  		return nil
   325  	}
   326  
   327  	tg := task.ThreadGroup()
   328  	pg := tg.ProcessGroup()
   329  
   330  	// If the session for the task is different than the session for the
   331  	// controlling TTY, then the change is allowed. Seems like a bad idea,
   332  	// but that's exactly what linux does.
   333  	if tg.Session() != t.fgProcessGroup.Session() {
   334  		return nil
   335  	}
   336  
   337  	// If we are the foreground process group, then the change is allowed.
   338  	if pg == t.fgProcessGroup {
   339  		return nil
   340  	}
   341  
   342  	// We are not the foreground process group.
   343  
   344  	// Is the provided signal blocked or ignored?
   345  	if (task.SignalMask()&linux.SignalSetOf(sig) != 0) || tg.SignalHandlers().IsIgnored(sig) {
   346  		// If the signal is SIGTTIN, then we are attempting to read
   347  		// from the TTY. Don't send the signal and return EIO.
   348  		if sig == linux.SIGTTIN {
   349  			return syserror.EIO
   350  		}
   351  
   352  		// Otherwise, we are writing or changing terminal state. This is allowed.
   353  		return nil
   354  	}
   355  
   356  	// If the process group is an orphan, return EIO.
   357  	if pg.IsOrphan() {
   358  		return syserror.EIO
   359  	}
   360  
   361  	// Otherwise, send the signal to the process group and return ERESTARTSYS.
   362  	//
   363  	// Note that Linux also unconditionally sets TIF_SIGPENDING on current,
   364  	// but this isn't necessary in gVisor because the rationale given in
   365  	// 040b6362d58f "tty: fix leakage of -ERESTARTSYS to userland" doesn't
   366  	// apply: the sentry will handle -ERESTARTSYS in
   367  	// kernel.runApp.execute() even if the kernel.Task isn't interrupted.
   368  	//
   369  	// Linux ignores the result of kill_pgrp().
   370  	_ = pg.SendSignal(kernel.SignalInfoPriv(sig))
   371  	return syserror.ERESTARTSYS
   372  }