crypto/tls: split connErr to avoid read/write races.

Currently a write error will cause future reads to return that same error.
However, there may have been extra information from a peer pending on
the read direction that is now unavailable.

This change splits the single connErr into errors for the read, write and
handshake. (Splitting off the handshake error is needed because both read
and write paths check the handshake error.)

Fixes #7414.

LGTM=bradfitz, r
R=golang-codereviews, r, bradfitz
CC=golang-codereviews
https://golang.org/cl/69090044
This commit is contained in:
Adam Langley 2014-03-03 09:01:44 -05:00
parent 8d65af24c4
commit ef4934a9ed
3 changed files with 61 additions and 70 deletions

127
conn.go
View File

@ -28,6 +28,7 @@ type Conn struct {
// constant after handshake; protected by handshakeMutex // constant after handshake; protected by handshakeMutex
handshakeMutex sync.Mutex // handshakeMutex < in.Mutex, out.Mutex, errMutex handshakeMutex sync.Mutex // handshakeMutex < in.Mutex, out.Mutex, errMutex
handshakeErr error // error resulting from handshake
vers uint16 // TLS version vers uint16 // TLS version
haveVers bool // version has been negotiated haveVers bool // version has been negotiated
config *Config // configuration passed to constructor config *Config // configuration passed to constructor
@ -45,9 +46,6 @@ type Conn struct {
clientProtocol string clientProtocol string
clientProtocolFallback bool clientProtocolFallback bool
// first permanent error
connErr
// input/output // input/output
in, out halfConn // in.Mutex < out.Mutex in, out halfConn // in.Mutex < out.Mutex
rawInput *block // raw input, right off the wire rawInput *block // raw input, right off the wire
@ -57,27 +55,6 @@ type Conn struct {
tmp [16]byte tmp [16]byte
} }
type connErr struct {
mu sync.Mutex
value error
}
func (e *connErr) setError(err error) error {
e.mu.Lock()
defer e.mu.Unlock()
if e.value == nil {
e.value = err
}
return err
}
func (e *connErr) error() error {
e.mu.Lock()
defer e.mu.Unlock()
return e.value
}
// Access to net.Conn methods. // Access to net.Conn methods.
// Cannot just embed net.Conn because that would // Cannot just embed net.Conn because that would
// export the struct field too. // export the struct field too.
@ -116,6 +93,8 @@ func (c *Conn) SetWriteDeadline(t time.Time) error {
// connection, either sending or receiving. // connection, either sending or receiving.
type halfConn struct { type halfConn struct {
sync.Mutex sync.Mutex
err error // first permanent error
version uint16 // protocol version version uint16 // protocol version
cipher interface{} // cipher algorithm cipher interface{} // cipher algorithm
mac macFunction mac macFunction
@ -129,6 +108,18 @@ type halfConn struct {
inDigestBuf, outDigestBuf []byte inDigestBuf, outDigestBuf []byte
} }
func (hc *halfConn) setErrorLocked(err error) error {
hc.err = err
return err
}
func (hc *halfConn) error() error {
hc.Lock()
err := hc.err
hc.Unlock()
return err
}
// prepareCipherSpec sets the encryption and MAC states // prepareCipherSpec sets the encryption and MAC states
// that a subsequent changeCipherSpec will use. // that a subsequent changeCipherSpec will use.
func (hc *halfConn) prepareCipherSpec(version uint16, cipher interface{}, mac macFunction) { func (hc *halfConn) prepareCipherSpec(version uint16, cipher interface{}, mac macFunction) {
@ -520,16 +511,16 @@ func (c *Conn) readRecord(want recordType) error {
switch want { switch want {
default: default:
c.sendAlert(alertInternalError) c.sendAlert(alertInternalError)
return errors.New("tls: unknown record type requested") return c.in.setErrorLocked(errors.New("tls: unknown record type requested"))
case recordTypeHandshake, recordTypeChangeCipherSpec: case recordTypeHandshake, recordTypeChangeCipherSpec:
if c.handshakeComplete { if c.handshakeComplete {
c.sendAlert(alertInternalError) c.sendAlert(alertInternalError)
return errors.New("tls: handshake or ChangeCipherSpec requested after handshake complete") return c.in.setErrorLocked(errors.New("tls: handshake or ChangeCipherSpec requested after handshake complete"))
} }
case recordTypeApplicationData: case recordTypeApplicationData:
if !c.handshakeComplete { if !c.handshakeComplete {
c.sendAlert(alertInternalError) c.sendAlert(alertInternalError)
return errors.New("tls: application data record requested before handshake complete") return c.in.setErrorLocked(errors.New("tls: application data record requested before handshake complete"))
} }
} }
@ -548,7 +539,7 @@ Again:
// err = io.ErrUnexpectedEOF // err = io.ErrUnexpectedEOF
// } // }
if e, ok := err.(net.Error); !ok || !e.Temporary() { if e, ok := err.(net.Error); !ok || !e.Temporary() {
c.setError(err) c.in.setErrorLocked(err)
} }
return err return err
} }
@ -560,18 +551,18 @@ Again:
// an SSLv2 client. // an SSLv2 client.
if want == recordTypeHandshake && typ == 0x80 { if want == recordTypeHandshake && typ == 0x80 {
c.sendAlert(alertProtocolVersion) c.sendAlert(alertProtocolVersion)
return errors.New("tls: unsupported SSLv2 handshake received") return c.in.setErrorLocked(errors.New("tls: unsupported SSLv2 handshake received"))
} }
vers := uint16(b.data[1])<<8 | uint16(b.data[2]) vers := uint16(b.data[1])<<8 | uint16(b.data[2])
n := int(b.data[3])<<8 | int(b.data[4]) n := int(b.data[3])<<8 | int(b.data[4])
if c.haveVers && vers != c.vers { if c.haveVers && vers != c.vers {
c.sendAlert(alertProtocolVersion) c.sendAlert(alertProtocolVersion)
return fmt.Errorf("tls: received record with version %x when expecting version %x", vers, c.vers) return c.in.setErrorLocked(fmt.Errorf("tls: received record with version %x when expecting version %x", vers, c.vers))
} }
if n > maxCiphertext { if n > maxCiphertext {
c.sendAlert(alertRecordOverflow) c.sendAlert(alertRecordOverflow)
return fmt.Errorf("tls: oversized record received with length %d", n) return c.in.setErrorLocked(fmt.Errorf("tls: oversized record received with length %d", n))
} }
if !c.haveVers { if !c.haveVers {
// First message, be extra suspicious: // First message, be extra suspicious:
@ -584,7 +575,7 @@ Again:
// it's probably not real. // it's probably not real.
if (typ != recordTypeAlert && typ != want) || vers >= 0x1000 || n >= 0x3000 { if (typ != recordTypeAlert && typ != want) || vers >= 0x1000 || n >= 0x3000 {
c.sendAlert(alertUnexpectedMessage) c.sendAlert(alertUnexpectedMessage)
return fmt.Errorf("tls: first record does not look like a TLS handshake") return c.in.setErrorLocked(fmt.Errorf("tls: first record does not look like a TLS handshake"))
} }
} }
if err := b.readFromUntil(c.conn, recordHeaderLen+n); err != nil { if err := b.readFromUntil(c.conn, recordHeaderLen+n); err != nil {
@ -592,7 +583,7 @@ Again:
err = io.ErrUnexpectedEOF err = io.ErrUnexpectedEOF
} }
if e, ok := err.(net.Error); !ok || !e.Temporary() { if e, ok := err.(net.Error); !ok || !e.Temporary() {
c.setError(err) c.in.setErrorLocked(err)
} }
return err return err
} }
@ -601,27 +592,27 @@ Again:
b, c.rawInput = c.in.splitBlock(b, recordHeaderLen+n) b, c.rawInput = c.in.splitBlock(b, recordHeaderLen+n)
ok, off, err := c.in.decrypt(b) ok, off, err := c.in.decrypt(b)
if !ok { if !ok {
return c.sendAlert(err) c.in.setErrorLocked(c.sendAlert(err))
} }
b.off = off b.off = off
data := b.data[b.off:] data := b.data[b.off:]
if len(data) > maxPlaintext { if len(data) > maxPlaintext {
c.sendAlert(alertRecordOverflow) err := c.sendAlert(alertRecordOverflow)
c.in.freeBlock(b) c.in.freeBlock(b)
return c.error() return c.in.setErrorLocked(err)
} }
switch typ { switch typ {
default: default:
c.sendAlert(alertUnexpectedMessage) c.in.setErrorLocked(c.sendAlert(alertUnexpectedMessage))
case recordTypeAlert: case recordTypeAlert:
if len(data) != 2 { if len(data) != 2 {
c.sendAlert(alertUnexpectedMessage) c.in.setErrorLocked(c.sendAlert(alertUnexpectedMessage))
break break
} }
if alert(data[1]) == alertCloseNotify { if alert(data[1]) == alertCloseNotify {
c.setError(io.EOF) c.in.setErrorLocked(io.EOF)
break break
} }
switch data[0] { switch data[0] {
@ -630,24 +621,24 @@ Again:
c.in.freeBlock(b) c.in.freeBlock(b)
goto Again goto Again
case alertLevelError: case alertLevelError:
c.setError(&net.OpError{Op: "remote error", Err: alert(data[1])}) c.in.setErrorLocked(&net.OpError{Op: "remote error", Err: alert(data[1])})
default: default:
c.sendAlert(alertUnexpectedMessage) c.in.setErrorLocked(c.sendAlert(alertUnexpectedMessage))
} }
case recordTypeChangeCipherSpec: case recordTypeChangeCipherSpec:
if typ != want || len(data) != 1 || data[0] != 1 { if typ != want || len(data) != 1 || data[0] != 1 {
c.sendAlert(alertUnexpectedMessage) c.in.setErrorLocked(c.sendAlert(alertUnexpectedMessage))
break break
} }
err := c.in.changeCipherSpec() err := c.in.changeCipherSpec()
if err != nil { if err != nil {
c.sendAlert(err.(alert)) c.in.setErrorLocked(c.sendAlert(err.(alert)))
} }
case recordTypeApplicationData: case recordTypeApplicationData:
if typ != want { if typ != want {
c.sendAlert(alertUnexpectedMessage) c.in.setErrorLocked(c.sendAlert(alertUnexpectedMessage))
break break
} }
c.input = b c.input = b
@ -656,7 +647,7 @@ Again:
case recordTypeHandshake: case recordTypeHandshake:
// TODO(rsc): Should at least pick off connection close. // TODO(rsc): Should at least pick off connection close.
if typ != want { if typ != want {
return c.sendAlert(alertNoRenegotiation) return c.in.setErrorLocked(c.sendAlert(alertNoRenegotiation))
} }
c.hand.Write(data) c.hand.Write(data)
} }
@ -664,7 +655,7 @@ Again:
if b != nil { if b != nil {
c.in.freeBlock(b) c.in.freeBlock(b)
} }
return c.error() return c.in.err
} }
// sendAlert sends a TLS alert message. // sendAlert sends a TLS alert message.
@ -680,7 +671,7 @@ func (c *Conn) sendAlertLocked(err alert) error {
c.writeRecord(recordTypeAlert, c.tmp[0:2]) c.writeRecord(recordTypeAlert, c.tmp[0:2])
// closeNotify is a special case in that it isn't an error: // closeNotify is a special case in that it isn't an error:
if err != alertCloseNotify { if err != alertCloseNotify {
return c.setError(&net.OpError{Op: "local error", Err: err}) return c.out.setErrorLocked(&net.OpError{Op: "local error", Err: err})
} }
return nil return nil
} }
@ -766,7 +757,7 @@ func (c *Conn) writeRecord(typ recordType, data []byte) (n int, err error) {
c.tmp[0] = alertLevelError c.tmp[0] = alertLevelError
c.tmp[1] = byte(err.(alert)) c.tmp[1] = byte(err.(alert))
c.writeRecord(recordTypeAlert, c.tmp[0:2]) c.writeRecord(recordTypeAlert, c.tmp[0:2])
return n, c.setError(&net.OpError{Op: "local error", Err: err}) return n, c.out.setErrorLocked(&net.OpError{Op: "local error", Err: err})
} }
} }
return return
@ -777,7 +768,7 @@ func (c *Conn) writeRecord(typ recordType, data []byte) (n int, err error) {
// c.in.Mutex < L; c.out.Mutex < L. // c.in.Mutex < L; c.out.Mutex < L.
func (c *Conn) readHandshake() (interface{}, error) { func (c *Conn) readHandshake() (interface{}, error) {
for c.hand.Len() < 4 { for c.hand.Len() < 4 {
if err := c.error(); err != nil { if err := c.in.err; err != nil {
return nil, err return nil, err
} }
if err := c.readRecord(recordTypeHandshake); err != nil { if err := c.readRecord(recordTypeHandshake); err != nil {
@ -788,11 +779,10 @@ func (c *Conn) readHandshake() (interface{}, error) {
data := c.hand.Bytes() data := c.hand.Bytes()
n := int(data[1])<<16 | int(data[2])<<8 | int(data[3]) n := int(data[1])<<16 | int(data[2])<<8 | int(data[3])
if n > maxHandshake { if n > maxHandshake {
c.sendAlert(alertInternalError) return nil, c.in.setErrorLocked(c.sendAlert(alertInternalError))
return nil, c.error()
} }
for c.hand.Len() < 4+n { for c.hand.Len() < 4+n {
if err := c.error(); err != nil { if err := c.in.err; err != nil {
return nil, err return nil, err
} }
if err := c.readRecord(recordTypeHandshake); err != nil { if err := c.readRecord(recordTypeHandshake); err != nil {
@ -831,8 +821,7 @@ func (c *Conn) readHandshake() (interface{}, error) {
case typeFinished: case typeFinished:
m = new(finishedMsg) m = new(finishedMsg)
default: default:
c.sendAlert(alertUnexpectedMessage) return nil, c.in.setErrorLocked(c.sendAlert(alertUnexpectedMessage))
return nil, alertUnexpectedMessage
} }
// The handshake message unmarshallers // The handshake message unmarshallers
@ -841,25 +830,24 @@ func (c *Conn) readHandshake() (interface{}, error) {
data = append([]byte(nil), data...) data = append([]byte(nil), data...)
if !m.unmarshal(data) { if !m.unmarshal(data) {
c.sendAlert(alertUnexpectedMessage) return nil, c.in.setErrorLocked(c.sendAlert(alertUnexpectedMessage))
return nil, alertUnexpectedMessage
} }
return m, nil return m, nil
} }
// Write writes data to the connection. // Write writes data to the connection.
func (c *Conn) Write(b []byte) (int, error) { func (c *Conn) Write(b []byte) (int, error) {
if err := c.error(); err != nil {
return 0, err
}
if err := c.Handshake(); err != nil { if err := c.Handshake(); err != nil {
return 0, c.setError(err) return 0, err
} }
c.out.Lock() c.out.Lock()
defer c.out.Unlock() defer c.out.Unlock()
if err := c.out.err; err != nil {
return 0, err
}
if !c.handshakeComplete { if !c.handshakeComplete {
return 0, alertInternalError return 0, alertInternalError
} }
@ -878,14 +866,14 @@ func (c *Conn) Write(b []byte) (int, error) {
if _, ok := c.out.cipher.(cipher.BlockMode); ok { if _, ok := c.out.cipher.(cipher.BlockMode); ok {
n, err := c.writeRecord(recordTypeApplicationData, b[:1]) n, err := c.writeRecord(recordTypeApplicationData, b[:1])
if err != nil { if err != nil {
return n, c.setError(err) return n, c.out.setErrorLocked(err)
} }
m, b = 1, b[1:] m, b = 1, b[1:]
} }
} }
n, err := c.writeRecord(recordTypeApplicationData, b) n, err := c.writeRecord(recordTypeApplicationData, b)
return n + m, c.setError(err) return n + m, c.out.setErrorLocked(err)
} }
// Read can be made to time out and return a net.Error with Timeout() == true // Read can be made to time out and return a net.Error with Timeout() == true
@ -902,13 +890,13 @@ func (c *Conn) Read(b []byte) (n int, err error) {
// CBC IV. So this loop ignores a limited number of empty records. // CBC IV. So this loop ignores a limited number of empty records.
const maxConsecutiveEmptyRecords = 100 const maxConsecutiveEmptyRecords = 100
for emptyRecordCount := 0; emptyRecordCount <= maxConsecutiveEmptyRecords; emptyRecordCount++ { for emptyRecordCount := 0; emptyRecordCount <= maxConsecutiveEmptyRecords; emptyRecordCount++ {
for c.input == nil && c.error() == nil { for c.input == nil && c.in.err == nil {
if err := c.readRecord(recordTypeApplicationData); err != nil { if err := c.readRecord(recordTypeApplicationData); err != nil {
// Soft error, like EAGAIN // Soft error, like EAGAIN
return 0, err return 0, err
} }
} }
if err := c.error(); err != nil { if err := c.in.err; err != nil {
return 0, err return 0, err
} }
@ -949,16 +937,19 @@ func (c *Conn) Close() error {
func (c *Conn) Handshake() error { func (c *Conn) Handshake() error {
c.handshakeMutex.Lock() c.handshakeMutex.Lock()
defer c.handshakeMutex.Unlock() defer c.handshakeMutex.Unlock()
if err := c.error(); err != nil { if err := c.handshakeErr; err != nil {
return err return err
} }
if c.handshakeComplete { if c.handshakeComplete {
return nil return nil
} }
if c.isClient { if c.isClient {
return c.clientHandshake() c.handshakeErr = c.clientHandshake()
} else {
c.handshakeErr = c.serverHandshake()
} }
return c.serverHandshake() return c.handshakeErr
} }
// ConnectionState returns basic TLS details about the connection. // ConnectionState returns basic TLS details about the connection.

View File

@ -501,7 +501,7 @@ func (hs *clientHandshakeState) readFinished() error {
c := hs.c c := hs.c
c.readRecord(recordTypeChangeCipherSpec) c.readRecord(recordTypeChangeCipherSpec)
if err := c.error(); err != nil { if err := c.in.error(); err != nil {
return err return err
} }

View File

@ -470,7 +470,7 @@ func (hs *serverHandshakeState) readFinished() error {
c := hs.c c := hs.c
c.readRecord(recordTypeChangeCipherSpec) c.readRecord(recordTypeChangeCipherSpec)
if err := c.error(); err != nil { if err := c.in.error(); err != nil {
return err return err
} }