diff --git a/pkg/sentry/fsimpl/devpts/line_discipline.go b/pkg/sentry/fsimpl/devpts/line_discipline.go index 6f9f2f27ae5..9d7806f1e0c 100644 --- a/pkg/sentry/fsimpl/devpts/line_discipline.go +++ b/pkg/sentry/fsimpl/devpts/line_discipline.go @@ -85,10 +85,11 @@ const ( // // +stateify savable type lineDiscipline struct { - // sizeMu protects size. sizeMu sync.Mutex `state:"nosave"` // size is the terminal size (width and height). + // + // +checklocks:sizeMu size linux.Winsize // inQueue is the input queue of the terminal. @@ -101,13 +102,19 @@ type lineDiscipline struct { termiosMu sync.RWMutex `state:"nosave"` // termios is the terminal configuration used by the lineDiscipline. + // + // +checklocks:termiosMu termios linux.KernelTermios // column is the location in a row of the cursor. This is important for // handling certain special characters like backspace. + // + // +checklocks:outQueue.mu column int // numReplicas is the number of replica file descriptors. + // + // +checklocks:termiosMu numReplicas int // masterWaiter is used to wait on the master end of the TTY. @@ -120,6 +127,8 @@ type lineDiscipline struct { terminal *Terminal // packet indicates the master is in packet mode. + // + // +checklocks:termiosMu packet bool // packetStatus contains pending TIOCPKT_* status bits for the next master @@ -127,6 +136,8 @@ type lineDiscipline struct { // // Currently only TIOCPKT_FLUSHREAD and TIOCPKT_FLUSHWRITE are emitted // through packetStatus. + // + // +checklocks:termiosMu packetStatus uint8 } @@ -375,8 +386,12 @@ func (l *lineDiscipline) replicaClose() { // transformer is a helper interface to make it easier to stateify queue. type transformer interface { - // transform functions require queue's mutex to be held. - // The boolean indicates whether there was any echoed bytes. + // transform requires the line discipline's termiosMu to be held for reading + // and the queue's mu to be held. checklocks does not propagate these lock + // contracts through this interface. + // + // The boolean indicates whether to notify master readers of possible echo + // output. transform(*lineDiscipline, *queue, []byte) (int, bool) } @@ -389,9 +404,12 @@ type outputQueueTransformer struct{} // transform does output processing for one end of the pty. See // drivers/tty/n_tty.c:do_output_char for an analogous kernel function. // -// Preconditions: -// - l.termiosMu must be held for reading. -// - q.mu must be held. +// q must be &l.outQueue. checklocks does not infer that identity from the +// queue's transformer, so both mutex paths are annotated below. +// +// +checklocksread:l.termiosMu +// +checklocks:q.mu +// +checklocks:l.outQueue.mu func (*outputQueueTransformer) transform(l *lineDiscipline, q *queue, buf []byte) (int, bool) { // transformOutput is effectively always in noncanonical mode, as the // master termios never has ICANON set. @@ -486,12 +504,12 @@ type inputQueueTransformer struct{} // transformed according to flags set in the termios struct. See // drivers/tty/n_tty.c:n_tty_receive_char_special for an analogous kernel // function. -// It returns an extra boolean indicating whether any characters need to be -// echoed, in which case we need to notify readers. // -// Preconditions: -// - l.termiosMu must be held for reading. -// - q.mu must be held. +// It returns an extra boolean indicating whether to notify master readers of +// possible echo output. +// +// +checklocksread:l.termiosMu +// +checklocks:q.mu func (*inputQueueTransformer) transform(l *lineDiscipline, q *queue, buf []byte) (int, bool) { // If there's a line waiting to be read in canonical mode, don't write // anything else to the read buffer. @@ -685,15 +703,16 @@ func (*inputQueueTransformer) transform(l *lineDiscipline, q *queue, buf []byte) // too many bytes are enqueued, we keep reading input and discarding it until // we find a terminating character. Signal/echo processing still occurs. // -// Precondition: -// - l.termiosMu must be held for reading. -// - q.mu must be held. +// +checklocksread:l.termiosMu +// +checklocks:q.mu func (l *lineDiscipline) shouldDiscard(q *queue, cBytes []byte) bool { return l.termios.LEnabled(linux.ICANON) && len(q.readBuf)+len(cBytes) >= canonMaxBytes && !l.termios.IsTerminating(cBytes) } // peek returns the size in bytes of the next character to process. As long as // b isn't empty, peek returns a value of at least 1. +// +// +checklocksread:l.termiosMu func (l *lineDiscipline) peek(b []byte) int { size := 1 // If UTF-8 support is enabled, runes might be multiple bytes. diff --git a/pkg/sentry/fsimpl/devpts/queue.go b/pkg/sentry/fsimpl/devpts/queue.go index 53e33682930..3a261ba26be 100644 --- a/pkg/sentry/fsimpl/devpts/queue.go +++ b/pkg/sentry/fsimpl/devpts/queue.go @@ -40,27 +40,37 @@ const waitBufMaxBytes = 131072 // // +stateify savable type queue struct { - // mu protects everything in queue. mu sync.Mutex `state:"nosave"` // readBuf is buffer of data ready to be read when readable is true. // This data has been processed. + // + // +checklocks:mu readBuf []byte // waitBuf contains data that can't fit into readBuf. It is put here // until it can be loaded into the read buffer. waitBuf contains data // that hasn't been processed. - waitBuf [][]byte + // + // +checklocks:mu + waitBuf [][]byte + + // waitBufLen is the number of bytes in waitBuf. + // + // +checklocks:mu waitBufLen uint64 // readable indicates whether the read buffer can be read from. In // canonical mode, there can be an unterminated line in the read buffer, // so readable must be checked. + // + // +checklocks:mu readable bool - // transform is the queue's function for transforming bytes - // entering the queue. For example, transform might convert all '\r's - // entering the queue to '\n's. + // transformer processes bytes entering the queue. For example, its + // transform method might convert '\r' to '\n'. + // + // It is immutable after initialization. transformer } @@ -100,11 +110,10 @@ func (q *queue) readableSize(t *kernel.Task, io usermem.IO, args arch.SyscallArg // read reads from q to userspace. It returns: // - The number of bytes read -// - Whether the read caused more readable data to become available (whether -// data was pushed from the wait buffer to the read buffer). -// - Whether any data was echoed back (need to notify readers). +// - Whether any bytes were processed from the wait buffer. +// - Whether to notify master readers of possible echo output. // -// Preconditions: l.termiosMu must be held for reading. +// +checklocksread:l.termiosMu func (q *queue) read(ctx context.Context, dst usermem.IOSequence, l *lineDiscipline, packet bool) (int64, bool, bool, error) { q.mu.Lock() defer q.mu.Unlock() @@ -133,16 +142,18 @@ func (q *queue) read(ctx context.Context, dst usermem.IOSequence, l *lineDiscipl } n, err := dst.CopyOutFrom(ctx, safemem.ReaderFunc(func(dst safemem.BlockSeq) (uint64, error) { - src := safemem.BlockSeqOf(safemem.BlockFromSafeSlice(q.readBuf)) + // CopyOutFrom invokes this callback synchronously with q.mu held. + // checklocks does not propagate that lock state into a passed callback. + src := safemem.BlockSeqOf(safemem.BlockFromSafeSlice(q.readBuf)) // +checklocksignore n, err := safemem.CopySeq(dst, src) if err != nil { return 0, err } - q.readBuf = q.readBuf[n:] + q.readBuf = q.readBuf[n:] // +checklocksignore // If we read everything, this queue is no longer readable. - if len(q.readBuf) == 0 { - q.readable = false + if len(q.readBuf) == 0 { // +checklocksignore + q.readable = false // +checklocksignore } return n, nil @@ -158,17 +169,21 @@ func (q *queue) read(ctx context.Context, dst usermem.IOSequence, l *lineDiscipl } // write writes to q from userspace. -// The returned boolean indicates whether any data was echoed back. // -// Preconditions: l.termiosMu must be held for reading. +// The returned boolean indicates whether to notify master readers of possible +// echo output. +// +// +checklocksread:l.termiosMu func (q *queue) write(ctx context.Context, src usermem.IOSequence, l *lineDiscipline) (int64, bool, error) { q.mu.Lock() defer q.mu.Unlock() // Copy data into the wait buffer. n, err := src.CopyInTo(ctx, safemem.WriterFunc(func(src safemem.BlockSeq) (uint64, error) { + // CopyInTo invokes this callback synchronously with q.mu held. + // checklocks does not propagate that lock state into a passed callback. copyLen := src.NumBytes() - room := waitBufMaxBytes - q.waitBufLen + room := waitBufMaxBytes - q.waitBufLen // +checklocksignore // If out of room, return EAGAIN. if room == 0 && copyLen > 0 { return 0, linuxerr.ErrWouldBlock @@ -186,7 +201,7 @@ func (q *queue) write(ctx context.Context, src usermem.IOSequence, l *lineDiscip if err != nil { return 0, err } - q.waitBufAppend(buf) + q.waitBufAppend(buf) // +checklocksignore return n, nil })) @@ -201,9 +216,11 @@ func (q *queue) write(ctx context.Context, src usermem.IOSequence, l *lineDiscip } // writeBytes writes to q from b. -// The returned boolean indicates whether any data was echoed back. // -// Preconditions: l.termiosMu must be held for reading. +// The returned boolean indicates whether to notify master readers of possible +// echo output. +// +// +checklocksread:l.termiosMu func (q *queue) writeBytes(b []byte, l *lineDiscipline) bool { q.mu.Lock() defer q.mu.Unlock() @@ -216,11 +233,12 @@ func (q *queue) writeBytes(b []byte, l *lineDiscipline) bool { // pushWaitBufLocked fills the queue's read buffer with data from the wait // buffer. -// The returned boolean indicates whether any data was echoed back. // -// Preconditions: -// - l.termiosMu must be held for reading. -// - q.mu must be locked. +// The returned boolean indicates whether to notify master readers of possible +// echo output. +// +// +checklocksread:l.termiosMu +// +checklocks:q.mu func (q *queue) pushWaitBufLocked(l *lineDiscipline) (int, bool) { if q.waitBufLen == 0 { return 0, false @@ -249,7 +267,9 @@ func (q *queue) pushWaitBufLocked(l *lineDiscipline) (int, bool) { return total, notifyEcho } -// Precondition: q.mu must be locked. +// waitBufAppend appends unprocessed data to the wait buffer. +// +// +checklocks:q.mu func (q *queue) waitBufAppend(b []byte) { q.waitBuf = append(q.waitBuf, b) q.waitBufLen += uint64(len(b))