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 }