github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/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/nicocha30/gvisor-ligolo/pkg/abi/linux"
    19  	"github.com/nicocha30/gvisor-ligolo/pkg/context"
    20  	"github.com/nicocha30/gvisor-ligolo/pkg/errors/linuxerr"
    21  	"github.com/nicocha30/gvisor-ligolo/pkg/hostarch"
    22  	"github.com/nicocha30/gvisor-ligolo/pkg/marshal/primitive"
    23  	"github.com/nicocha30/gvisor-ligolo/pkg/sentry/arch"
    24  	"github.com/nicocha30/gvisor-ligolo/pkg/sentry/kernel"
    25  	"github.com/nicocha30/gvisor-ligolo/pkg/sentry/unimpl"
    26  	"github.com/nicocha30/gvisor-ligolo/pkg/sentry/vfs"
    27  	"github.com/nicocha30/gvisor-ligolo/pkg/sync"
    28  	"github.com/nicocha30/gvisor-ligolo/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, sysno uintptr, args arch.SyscallArguments) (uintptr, error) {
   149  	task := kernel.TaskFromContext(ctx)
   150  	if task == nil {
   151  		return 0, linuxerr.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.FIONREAD:
   159  		v, err := ioctlFionread(fd)
   160  		if err != nil {
   161  			return 0, err
   162  		}
   163  
   164  		var buf [4]byte
   165  		hostarch.ByteOrder.PutUint32(buf[:], v)
   166  		_, err = io.CopyOut(ctx, args[2].Pointer(), buf[:], usermem.IOOpts{})
   167  		return 0, err
   168  
   169  	case linux.TCGETS:
   170  		termios, err := ioctlGetTermios(fd)
   171  		if err != nil {
   172  			return 0, err
   173  		}
   174  		_, err = termios.CopyOut(task, args[2].Pointer())
   175  		return 0, err
   176  
   177  	case linux.TCSETS, linux.TCSETSW, linux.TCSETSF:
   178  		t.mu.Lock()
   179  		defer t.mu.Unlock()
   180  
   181  		if err := t.checkChange(ctx, linux.SIGTTOU); err != nil {
   182  			return 0, err
   183  		}
   184  
   185  		var termios linux.Termios
   186  		if _, err := termios.CopyIn(task, args[2].Pointer()); err != nil {
   187  			return 0, err
   188  		}
   189  		err := ioctlSetTermios(fd, ioctl, &termios)
   190  		if err == nil {
   191  			t.termios.FromTermios(termios)
   192  		}
   193  		return 0, err
   194  
   195  	case linux.TIOCGPGRP:
   196  		// Args: pid_t *argp
   197  		// When successful, equivalent to *argp = tcgetpgrp(fd).
   198  		// Get the process group ID of the foreground process group on this
   199  		// terminal.
   200  
   201  		pidns := kernel.PIDNamespaceFromContext(ctx)
   202  		if pidns == nil {
   203  			return 0, linuxerr.ENOTTY
   204  		}
   205  
   206  		t.mu.Lock()
   207  		defer t.mu.Unlock()
   208  
   209  		// Map the ProcessGroup into a ProcessGroupID in the task's PID namespace.
   210  		pgID := primitive.Int32(pidns.IDOfProcessGroup(t.fgProcessGroup))
   211  		_, err := pgID.CopyOut(task, args[2].Pointer())
   212  		return 0, err
   213  
   214  	case linux.TIOCSPGRP:
   215  		// Args: const pid_t *argp
   216  		// Equivalent to tcsetpgrp(fd, *argp).
   217  		// Set the foreground process group ID of this terminal.
   218  
   219  		t.mu.Lock()
   220  		defer t.mu.Unlock()
   221  
   222  		// Check that we are allowed to set the process group.
   223  		if err := t.checkChange(ctx, linux.SIGTTOU); err != nil {
   224  			// drivers/tty/tty_io.c:tiocspgrp() converts -EIO from tty_check_change()
   225  			// to -ENOTTY.
   226  			if linuxerr.Equals(linuxerr.EIO, err) {
   227  				return 0, linuxerr.ENOTTY
   228  			}
   229  			return 0, err
   230  		}
   231  
   232  		// Check that calling task's process group is in the TTY session.
   233  		if task.ThreadGroup().Session() != t.session {
   234  			return 0, linuxerr.ENOTTY
   235  		}
   236  
   237  		var pgIDP primitive.Int32
   238  		if _, err := pgIDP.CopyIn(task, args[2].Pointer()); err != nil {
   239  			return 0, err
   240  		}
   241  		pgID := kernel.ProcessGroupID(pgIDP)
   242  
   243  		// pgID must be non-negative.
   244  		if pgID < 0 {
   245  			return 0, linuxerr.EINVAL
   246  		}
   247  
   248  		// Process group with pgID must exist in this PID namespace.
   249  		pidns := task.PIDNamespace()
   250  		pg := pidns.ProcessGroupWithID(pgID)
   251  		if pg == nil {
   252  			return 0, linuxerr.ESRCH
   253  		}
   254  
   255  		// Check that new process group is in the TTY session.
   256  		if pg.Session() != t.session {
   257  			return 0, linuxerr.EPERM
   258  		}
   259  
   260  		t.fgProcessGroup = pg
   261  		return 0, nil
   262  
   263  	case linux.TIOCGWINSZ:
   264  		// Args: struct winsize *argp
   265  		// Get window size.
   266  		winsize, err := ioctlGetWinsize(fd)
   267  		if err != nil {
   268  			return 0, err
   269  		}
   270  		_, err = winsize.CopyOut(task, args[2].Pointer())
   271  		return 0, err
   272  
   273  	case linux.TIOCSWINSZ:
   274  		// Args: const struct winsize *argp
   275  		// Set window size.
   276  
   277  		// Unlike setting the termios, any process group (even background ones) can
   278  		// set the winsize.
   279  
   280  		var winsize linux.Winsize
   281  		if _, err := winsize.CopyIn(task, args[2].Pointer()); err != nil {
   282  			return 0, err
   283  		}
   284  		err := ioctlSetWinsize(fd, &winsize)
   285  		return 0, err
   286  
   287  	// Unimplemented commands.
   288  	case linux.TIOCSETD,
   289  		linux.TIOCSBRK,
   290  		linux.TIOCCBRK,
   291  		linux.TCSBRK,
   292  		linux.TCSBRKP,
   293  		linux.TIOCSTI,
   294  		linux.TIOCCONS,
   295  		linux.FIONBIO,
   296  		linux.TIOCEXCL,
   297  		linux.TIOCNXCL,
   298  		linux.TIOCGEXCL,
   299  		linux.TIOCNOTTY,
   300  		linux.TIOCSCTTY,
   301  		linux.TIOCGSID,
   302  		linux.TIOCGETD,
   303  		linux.TIOCVHANGUP,
   304  		linux.TIOCGDEV,
   305  		linux.TIOCMGET,
   306  		linux.TIOCMSET,
   307  		linux.TIOCMBIC,
   308  		linux.TIOCMBIS,
   309  		linux.TIOCGICOUNT,
   310  		linux.TCFLSH,
   311  		linux.TIOCSSERIAL,
   312  		linux.TIOCGPTPEER:
   313  
   314  		unimpl.EmitUnimplementedEvent(ctx, sysno)
   315  		fallthrough
   316  	default:
   317  		return 0, linuxerr.ENOTTY
   318  	}
   319  }
   320  
   321  // checkChange checks that the process group is allowed to read, write, or
   322  // change the state of the TTY.
   323  //
   324  // This corresponds to Linux drivers/tty/tty_io.c:tty_check_change(). The logic
   325  // is a bit convoluted, but documented inline.
   326  //
   327  // Preconditions: t.mu must be held.
   328  func (t *TTYFileDescription) checkChange(ctx context.Context, sig linux.Signal) error {
   329  	task := kernel.TaskFromContext(ctx)
   330  	if task == nil {
   331  		// No task? Linux does not have an analog for this case, but
   332  		// tty_check_change only blocks specific cases and is
   333  		// surprisingly permissive. Allowing the change seems
   334  		// appropriate.
   335  		return nil
   336  	}
   337  
   338  	tg := task.ThreadGroup()
   339  	pg := tg.ProcessGroup()
   340  
   341  	// If the session for the task is different than the session for the
   342  	// controlling TTY, then the change is allowed. Seems like a bad idea,
   343  	// but that's exactly what linux does.
   344  	if tg.Session() != t.fgProcessGroup.Session() {
   345  		return nil
   346  	}
   347  
   348  	// If we are the foreground process group, then the change is allowed.
   349  	if pg == t.fgProcessGroup {
   350  		return nil
   351  	}
   352  
   353  	// We are not the foreground process group.
   354  
   355  	// Is the provided signal blocked or ignored?
   356  	if (task.SignalMask()&linux.SignalSetOf(sig) != 0) || tg.SignalHandlers().IsIgnored(sig) {
   357  		// If the signal is SIGTTIN, then we are attempting to read
   358  		// from the TTY. Don't send the signal and return EIO.
   359  		if sig == linux.SIGTTIN {
   360  			return linuxerr.EIO
   361  		}
   362  
   363  		// Otherwise, we are writing or changing terminal state. This is allowed.
   364  		return nil
   365  	}
   366  
   367  	// If the process group is an orphan, return EIO.
   368  	if pg.IsOrphan() {
   369  		return linuxerr.EIO
   370  	}
   371  
   372  	// Otherwise, send the signal to the process group and return ERESTARTSYS.
   373  	//
   374  	// Note that Linux also unconditionally sets TIF_SIGPENDING on current,
   375  	// but this isn't necessary in gVisor because the rationale given in
   376  	// 040b6362d58f "tty: fix leakage of -ERESTARTSYS to userland" doesn't
   377  	// apply: the sentry will handle -ERESTARTSYS in
   378  	// kernel.runApp.execute() even if the kernel.Task isn't interrupted.
   379  	//
   380  	// Linux ignores the result of kill_pgrp().
   381  	_ = pg.SendSignal(kernel.SignalInfoPriv(sig))
   382  	return linuxerr.ERESTARTSYS
   383  }