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 }