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)