github.com/ttpreport/gvisor-ligolo@v0.0.0-20240123134145-a858404967ba/pkg/sentry/fsimpl/devpts/line_discipline.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 devpts 16 17 import ( 18 "bytes" 19 "unicode/utf8" 20 21 "github.com/ttpreport/gvisor-ligolo/pkg/abi/linux" 22 "github.com/ttpreport/gvisor-ligolo/pkg/context" 23 "github.com/ttpreport/gvisor-ligolo/pkg/errors/linuxerr" 24 "github.com/ttpreport/gvisor-ligolo/pkg/sentry/arch" 25 "github.com/ttpreport/gvisor-ligolo/pkg/sentry/kernel" 26 "github.com/ttpreport/gvisor-ligolo/pkg/sync" 27 "github.com/ttpreport/gvisor-ligolo/pkg/usermem" 28 "github.com/ttpreport/gvisor-ligolo/pkg/waiter" 29 ) 30 31 const ( 32 // canonMaxBytes is the number of bytes that fit into a single line of 33 // terminal input in canonical mode. This corresponds to N_TTY_BUF_SIZE 34 // in include/linux/tty.h. 35 canonMaxBytes = 4096 36 37 // nonCanonMaxBytes is the maximum number of bytes that can be read at 38 // a time in noncanonical mode. 39 nonCanonMaxBytes = canonMaxBytes - 1 40 41 spacesPerTab = 8 42 ) 43 44 // lineDiscipline dictates how input and output are handled between the 45 // pseudoterminal (pty) master and replica. It can be configured to alter I/O, 46 // modify control characters (e.g. Ctrl-C for SIGINT), etc. The following man 47 // pages are good resources for how to affect the line discipline: 48 // 49 // - termios(3) 50 // - tty_ioctl(4) 51 // 52 // This file corresponds most closely to drivers/tty/n_tty.c. 53 // 54 // lineDiscipline has a simple structure but supports a multitude of options 55 // (see the above man pages). It consists of two queues of bytes: one from the 56 // terminal master to replica (the input queue) and one from replica to master 57 // (the output queue). When bytes are written to one end of the pty, the line 58 // discipline reads the bytes, modifies them or takes special action if 59 // required, and enqueues them to be read by the other end of the pty: 60 // 61 // input from terminal +-------------+ input to process (e.g. bash) 62 // +------------------------>| input queue |---------------------------+ 63 // | (inputQueueWrite) +-------------+ (inputQueueRead) | 64 // | | 65 // | v 66 // 67 // masterFD replicaFD 68 // 69 // ^ | 70 // | | 71 // | output to terminal +--------------+ output from process | 72 // +------------------------| output queue |<--------------------------+ 73 // (outputQueueRead) +--------------+ (outputQueueWrite) 74 // 75 // There is special handling for the ECHO option, where bytes written to the 76 // input queue are also output back to the terminal by being written to 77 // l.outQueue by the input queue transformer. 78 // 79 // Lock order: 80 // 81 // termiosMu 82 // inQueue.mu 83 // outQueue.mu 84 // 85 // +stateify savable 86 type lineDiscipline struct { 87 // sizeMu protects size. 88 sizeMu sync.Mutex `state:"nosave"` 89 90 // size is the terminal size (width and height). 91 size linux.WindowSize 92 93 // inQueue is the input queue of the terminal. 94 inQueue queue 95 96 // outQueue is the output queue of the terminal. 97 outQueue queue 98 99 // termiosMu protects termios. 100 termiosMu sync.RWMutex `state:"nosave"` 101 102 // termios is the terminal configuration used by the lineDiscipline. 103 termios linux.KernelTermios 104 105 // column is the location in a row of the cursor. This is important for 106 // handling certain special characters like backspace. 107 column int 108 109 // numReplicas is the number of replica file descriptors. 110 numReplicas int 111 112 // masterWaiter is used to wait on the master end of the TTY. 113 masterWaiter waiter.Queue 114 115 // replicaWaiter is used to wait on the replica end of the TTY. 116 replicaWaiter waiter.Queue 117 118 // terminal is the terminal linked to this lineDiscipline. 119 terminal *Terminal 120 } 121 122 func newLineDiscipline(termios linux.KernelTermios, terminal *Terminal) *lineDiscipline { 123 ld := lineDiscipline{ 124 termios: termios, 125 terminal: terminal, 126 } 127 ld.inQueue.transformer = &inputQueueTransformer{} 128 ld.outQueue.transformer = &outputQueueTransformer{} 129 return &ld 130 } 131 132 // getTermios gets the linux.Termios for the tty. 133 func (l *lineDiscipline) getTermios(task *kernel.Task, args arch.SyscallArguments) (uintptr, error) { 134 l.termiosMu.RLock() 135 defer l.termiosMu.RUnlock() 136 // We must copy a Termios struct, not KernelTermios. 137 t := l.termios.ToTermios() 138 _, err := t.CopyOut(task, args[2].Pointer()) 139 return 0, err 140 } 141 142 // setTermios sets a linux.Termios for the tty. 143 func (l *lineDiscipline) setTermios(task *kernel.Task, args arch.SyscallArguments) (uintptr, error) { 144 l.termiosMu.Lock() 145 oldCanonEnabled := l.termios.LEnabled(linux.ICANON) 146 // We must copy a Termios struct, not KernelTermios. 147 var t linux.Termios 148 _, err := t.CopyIn(task, args[2].Pointer()) 149 l.termios.FromTermios(t) 150 151 // If canonical mode is turned off, move bytes from inQueue's wait 152 // buffer to its read buffer. Anything already in the read buffer is 153 // now readable. 154 if oldCanonEnabled && !l.termios.LEnabled(linux.ICANON) { 155 l.inQueue.mu.Lock() 156 l.inQueue.pushWaitBufLocked(l) 157 l.inQueue.readable = true 158 l.inQueue.mu.Unlock() 159 l.termiosMu.Unlock() 160 l.replicaWaiter.Notify(waiter.ReadableEvents) 161 } else { 162 l.termiosMu.Unlock() 163 } 164 165 return 0, err 166 } 167 168 func (l *lineDiscipline) windowSize(t *kernel.Task, args arch.SyscallArguments) error { 169 l.sizeMu.Lock() 170 defer l.sizeMu.Unlock() 171 _, err := l.size.CopyOut(t, args[2].Pointer()) 172 return err 173 } 174 175 func (l *lineDiscipline) setWindowSize(t *kernel.Task, args arch.SyscallArguments) error { 176 l.sizeMu.Lock() 177 defer l.sizeMu.Unlock() 178 _, err := l.size.CopyIn(t, args[2].Pointer()) 179 return err 180 } 181 182 func (l *lineDiscipline) masterReadiness() waiter.EventMask { 183 // We don't have to lock a termios because the default master termios 184 // is immutable. 185 return l.inQueue.writeReadiness(&linux.MasterTermios) | l.outQueue.readReadiness(&linux.MasterTermios) 186 } 187 188 func (l *lineDiscipline) replicaReadiness() waiter.EventMask { 189 l.termiosMu.RLock() 190 defer l.termiosMu.RUnlock() 191 return l.outQueue.writeReadiness(&l.termios) | l.inQueue.readReadiness(&l.termios) 192 } 193 194 func (l *lineDiscipline) inputQueueReadSize(t *kernel.Task, io usermem.IO, args arch.SyscallArguments) error { 195 return l.inQueue.readableSize(t, io, args) 196 } 197 198 func (l *lineDiscipline) inputQueueRead(ctx context.Context, dst usermem.IOSequence) (int64, error) { 199 l.termiosMu.RLock() 200 n, pushed, notifyEcho, err := l.inQueue.read(ctx, dst, l) 201 l.termiosMu.RUnlock() 202 if err != nil { 203 return 0, err 204 } 205 if n > 0 { 206 if notifyEcho { 207 l.masterWaiter.Notify(waiter.ReadableEvents | waiter.WritableEvents) 208 } else { 209 l.masterWaiter.Notify(waiter.WritableEvents) 210 } 211 if pushed { 212 l.replicaWaiter.Notify(waiter.ReadableEvents) 213 } 214 return n, nil 215 } else if notifyEcho { 216 l.masterWaiter.Notify(waiter.ReadableEvents) 217 } 218 return 0, linuxerr.ErrWouldBlock 219 } 220 221 func (l *lineDiscipline) inputQueueWrite(ctx context.Context, src usermem.IOSequence) (int64, error) { 222 l.termiosMu.RLock() 223 n, notifyEcho, err := l.inQueue.write(ctx, src, l) 224 l.termiosMu.RUnlock() 225 if err != nil { 226 return 0, err 227 } 228 if notifyEcho { 229 l.masterWaiter.Notify(waiter.ReadableEvents) 230 } 231 if n > 0 { 232 l.replicaWaiter.Notify(waiter.ReadableEvents) 233 return n, nil 234 } 235 return 0, linuxerr.ErrWouldBlock 236 } 237 238 func (l *lineDiscipline) outputQueueReadSize(t *kernel.Task, io usermem.IO, args arch.SyscallArguments) error { 239 return l.outQueue.readableSize(t, io, args) 240 } 241 242 func (l *lineDiscipline) outputQueueRead(ctx context.Context, dst usermem.IOSequence) (int64, error) { 243 l.termiosMu.RLock() 244 // Ignore notifyEcho, as it cannot happen when reading from the output queue. 245 n, pushed, _, err := l.outQueue.read(ctx, dst, l) 246 l.termiosMu.RUnlock() 247 if err != nil { 248 return 0, err 249 } 250 if n > 0 { 251 l.replicaWaiter.Notify(waiter.WritableEvents) 252 if pushed { 253 l.masterWaiter.Notify(waiter.ReadableEvents) 254 } 255 return n, nil 256 } 257 return 0, linuxerr.ErrWouldBlock 258 } 259 260 func (l *lineDiscipline) outputQueueWrite(ctx context.Context, src usermem.IOSequence) (int64, error) { 261 l.termiosMu.RLock() 262 // Ignore notifyEcho, as it cannot happen when writing to the output queue. 263 n, _, err := l.outQueue.write(ctx, src, l) 264 l.termiosMu.RUnlock() 265 if err != nil { 266 return 0, err 267 } 268 if n > 0 { 269 l.masterWaiter.Notify(waiter.ReadableEvents) 270 return n, nil 271 } 272 return 0, linuxerr.ErrWouldBlock 273 } 274 275 // replicaOpen is called when a replica file descriptor is opened. 276 func (l *lineDiscipline) replicaOpen() { 277 l.termiosMu.Lock() 278 defer l.termiosMu.Unlock() 279 l.numReplicas++ 280 } 281 282 // replicaClose is called when a replica file descriptor is closed. 283 func (l *lineDiscipline) replicaClose() { 284 l.termiosMu.Lock() 285 defer l.termiosMu.Unlock() 286 l.numReplicas-- 287 } 288 289 // transformer is a helper interface to make it easier to stateify queue. 290 type transformer interface { 291 // transform functions require queue's mutex to be held. 292 // The boolean indicates whether there was any echoed bytes. 293 transform(*lineDiscipline, *queue, []byte) (int, bool) 294 } 295 296 // outputQueueTransformer implements transformer. It performs line discipline 297 // transformations on the output queue. 298 // 299 // +stateify savable 300 type outputQueueTransformer struct{} 301 302 // transform does output processing for one end of the pty. See 303 // drivers/tty/n_tty.c:do_output_char for an analogous kernel function. 304 // 305 // Preconditions: 306 // - l.termiosMu must be held for reading. 307 // - q.mu must be held. 308 func (*outputQueueTransformer) transform(l *lineDiscipline, q *queue, buf []byte) (int, bool) { 309 // transformOutput is effectively always in noncanonical mode, as the 310 // master termios never has ICANON set. 311 312 if !l.termios.OEnabled(linux.OPOST) { 313 q.readBuf = append(q.readBuf, buf...) 314 if len(q.readBuf) > 0 { 315 q.readable = true 316 } 317 return len(buf), false 318 } 319 320 var ret int 321 for len(buf) > 0 { 322 size := l.peek(buf) 323 cBytes := append([]byte{}, buf[:size]...) 324 ret += size 325 buf = buf[size:] 326 // We're guaranteed that cBytes has at least one element. 327 switch cBytes[0] { 328 case '\n': 329 if l.termios.OEnabled(linux.ONLRET) { 330 l.column = 0 331 } 332 if l.termios.OEnabled(linux.ONLCR) { 333 q.readBuf = append(q.readBuf, '\r', '\n') 334 continue 335 } 336 case '\r': 337 if l.termios.OEnabled(linux.ONOCR) && l.column == 0 { 338 continue 339 } 340 if l.termios.OEnabled(linux.OCRNL) { 341 cBytes[0] = '\n' 342 if l.termios.OEnabled(linux.ONLRET) { 343 l.column = 0 344 } 345 break 346 } 347 l.column = 0 348 case '\t': 349 spaces := spacesPerTab - l.column%spacesPerTab 350 if l.termios.OutputFlags&linux.TABDLY == linux.XTABS { 351 l.column += spaces 352 q.readBuf = append(q.readBuf, bytes.Repeat([]byte{' '}, spacesPerTab)...) 353 continue 354 } 355 l.column += spaces 356 case '\b': 357 if l.column > 0 { 358 l.column-- 359 } 360 default: 361 l.column++ 362 } 363 q.readBuf = append(q.readBuf, cBytes...) 364 } 365 if len(q.readBuf) > 0 { 366 q.readable = true 367 } 368 return ret, false 369 } 370 371 // inputQueueTransformer implements transformer. It performs line discipline 372 // transformations on the input queue. 373 // 374 // +stateify savable 375 type inputQueueTransformer struct{} 376 377 // transform does input processing for one end of the pty. Characters read are 378 // transformed according to flags set in the termios struct. See 379 // drivers/tty/n_tty.c:n_tty_receive_char_special for an analogous kernel 380 // function. 381 // It returns an extra boolean indicating whether any characters need to be 382 // echoed, in which case we need to notify readers. 383 // 384 // Preconditions: 385 // - l.termiosMu must be held for reading. 386 // - q.mu must be held. 387 func (*inputQueueTransformer) transform(l *lineDiscipline, q *queue, buf []byte) (int, bool) { 388 // If there's a line waiting to be read in canonical mode, don't write 389 // anything else to the read buffer. 390 if l.termios.LEnabled(linux.ICANON) && q.readable { 391 return 0, false 392 } 393 394 maxBytes := nonCanonMaxBytes 395 if l.termios.LEnabled(linux.ICANON) { 396 maxBytes = canonMaxBytes 397 } 398 399 var ret int 400 var notifyEcho bool 401 for len(buf) > 0 && len(q.readBuf) < canonMaxBytes { 402 size := l.peek(buf) 403 cBytes := append([]byte{}, buf[:size]...) 404 // We're guaranteed that cBytes has at least one element. 405 switch cBytes[0] { 406 case '\r': 407 if l.termios.IEnabled(linux.IGNCR) { 408 buf = buf[size:] 409 ret += size 410 continue 411 } 412 if l.termios.IEnabled(linux.ICRNL) { 413 cBytes[0] = '\n' 414 } 415 case '\n': 416 if l.termios.IEnabled(linux.INLCR) { 417 cBytes[0] = '\r' 418 } 419 case l.termios.ControlCharacters[linux.VINTR]: // ctrl-c 420 // The input queue is reading from the master TTY and 421 // writing to the replica TTY which is connected to the 422 // interactive program (like bash). We want to send the 423 // signal the process connected to the replica TTY. 424 l.terminal.replicaKTTY.SignalForegroundProcessGroup(kernel.SignalInfoPriv(linux.SIGINT)) 425 case l.termios.ControlCharacters[linux.VSUSP]: // ctrl-z 426 l.terminal.replicaKTTY.SignalForegroundProcessGroup(kernel.SignalInfoPriv(linux.SIGTSTP)) 427 case l.termios.ControlCharacters[linux.VQUIT]: // ctrl-\ 428 l.terminal.replicaKTTY.SignalForegroundProcessGroup(kernel.SignalInfoPriv(linux.SIGQUIT)) 429 } 430 431 // In canonical mode, we discard non-terminating characters 432 // after the first 4095. 433 if l.shouldDiscard(q, cBytes) { 434 buf = buf[size:] 435 ret += size 436 continue 437 } 438 439 // Stop if the buffer would be overfilled. 440 if len(q.readBuf)+size > maxBytes { 441 break 442 } 443 buf = buf[size:] 444 ret += size 445 446 // If we get EOF, make the buffer available for reading. 447 if l.termios.LEnabled(linux.ICANON) && l.termios.IsEOF(cBytes[0]) { 448 q.readable = true 449 break 450 } 451 452 q.readBuf = append(q.readBuf, cBytes...) 453 454 // Anything written to the readBuf will have to be echoed. 455 if l.termios.LEnabled(linux.ECHO) { 456 l.outQueue.writeBytes(cBytes, l) 457 notifyEcho = true 458 } 459 460 // If we finish a line, make it available for reading. 461 if l.termios.LEnabled(linux.ICANON) && l.termios.IsTerminating(cBytes) { 462 q.readable = true 463 break 464 } 465 } 466 467 // In noncanonical mode, everything is readable. 468 if !l.termios.LEnabled(linux.ICANON) && len(q.readBuf) > 0 { 469 q.readable = true 470 } 471 472 return ret, notifyEcho 473 } 474 475 // shouldDiscard returns whether c should be discarded. In canonical mode, if 476 // too many bytes are enqueued, we keep reading input and discarding it until 477 // we find a terminating character. Signal/echo processing still occurs. 478 // 479 // Precondition: 480 // - l.termiosMu must be held for reading. 481 // - q.mu must be held. 482 func (l *lineDiscipline) shouldDiscard(q *queue, cBytes []byte) bool { 483 return l.termios.LEnabled(linux.ICANON) && len(q.readBuf)+len(cBytes) >= canonMaxBytes && !l.termios.IsTerminating(cBytes) 484 } 485 486 // peek returns the size in bytes of the next character to process. As long as 487 // b isn't empty, peek returns a value of at least 1. 488 func (l *lineDiscipline) peek(b []byte) int { 489 size := 1 490 // If UTF-8 support is enabled, runes might be multiple bytes. 491 if l.termios.IEnabled(linux.IUTF8) { 492 _, size = utf8.DecodeRune(b) 493 } 494 return size 495 }