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

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