Panic in netstack during cleanup where a FIN becomes a RST.

There is a subtle bug where during cleanup with unread data a FIN can
be converted to a RST, at that point the entire connection should be
aborted as we're not expecting any ACKs to the RST.

PiperOrigin-RevId: 202691271
Change-Id: Idae70800208ca26e07a379bc6b2b8090805d0a22
This commit is contained in:
Brian Geffon
2018-06-29 12:40:26 -07:00
committed by Shentubot
parent 1b5e09f968
commit 23f49097c7
4 changed files with 104 additions and 20 deletions
+4
View File
@@ -938,6 +938,10 @@ func (e *endpoint) protocolMainLoop(passive bool) *tcpip.Error {
e.snd.updateMaxPayloadSize(mtu, count)
}
if n&notifyReset != 0 {
e.resetConnectionLocked(tcpip.ErrConnectionAborted)
}
if n&notifyClose != 0 && closeTimer == nil {
// Reset the connection 3 seconds after the
// endpoint has been closed.
+15 -1
View File
@@ -40,6 +40,7 @@ const (
notifyClose
notifyMTUChanged
notifyDrain
notifyReset
)
// SACKInfo holds TCP SACK related information for a given endpoint.
@@ -919,7 +920,20 @@ func (e *endpoint) Shutdown(flags tcpip.ShutdownFlags) *tcpip.Error {
switch e.state {
case stateConnected:
// Close for write.
if (flags & tcpip.ShutdownWrite) != 0 {
if (e.shutdownFlags & tcpip.ShutdownWrite) != 0 {
if (e.shutdownFlags & tcpip.ShutdownRead) != 0 {
// We're fully closed, if we have unread data we need to abort
// the connection with a RST.
e.rcvListMu.Lock()
rcvBufUsed := e.rcvBufUsed
e.rcvListMu.Unlock()
if rcvBufUsed > 0 {
e.notifyProtocolGoroutine(notifyReset)
return nil
}
}
e.sndBufMu.Lock()
if e.sndClosed {
+8 -19
View File
@@ -333,28 +333,17 @@ func (s *sender) sendData() {
var segEnd seqnum.Value
if seg.data.Size() == 0 {
seg.flags = flagAck
s.ep.rcvListMu.Lock()
rcvBufUsed := s.ep.rcvBufUsed
s.ep.rcvListMu.Unlock()
s.ep.mu.Lock()
// We're sending a FIN by default
fl := flagFin
segEnd = seg.sequenceNumber
if (s.ep.shutdownFlags&tcpip.ShutdownRead) != 0 && rcvBufUsed > 0 {
// If there is unread data we must send a RST.
// For more information see RFC 2525 section 2.17.
fl = flagRst
} else {
segEnd = seg.sequenceNumber.Add(1)
if s.writeList.Back() != seg {
panic("FIN segments must be the final segment in the write list.")
}
s.ep.mu.Unlock()
seg.flags |= uint8(fl)
seg.flags = flagAck | flagFin
segEnd = seg.sequenceNumber.Add(1)
} else {
// We're sending a non-FIN segment.
if seg.flags&flagFin != 0 {
panic("Netstack queues FIN segments without data.")
}
if !seg.sequenceNumber.LessThan(end) {
break
}
+77
View File
@@ -416,6 +416,83 @@ func TestRstOnCloseWithUnreadData(t *testing.T) {
})
}
func TestRstOnCloseWithUnreadDataFinConvertRst(t *testing.T) {
c := context.New(t, defaultMTU)
defer c.Cleanup()
c.CreateConnected(789, 30000, nil)
we, ch := waiter.NewChannelEntry(nil)
c.WQ.EventRegister(&we, waiter.EventIn)
defer c.WQ.EventUnregister(&we)
if _, _, err := c.EP.Read(nil); err != tcpip.ErrWouldBlock {
t.Fatalf("Unexpected error from Read: %v", err)
}
data := []byte{1, 2, 3}
c.SendPacket(data, &context.Headers{
SrcPort: context.TestPort,
DstPort: c.Port,
Flags: header.TCPFlagAck,
SeqNum: 790,
AckNum: c.IRS.Add(1),
RcvWnd: 30000,
})
// Wait for receive to be notified.
select {
case <-ch:
case <-time.After(3 * time.Second):
t.Fatalf("Timed out waiting for data to arrive")
}
// Check that ACK is received, this happens regardless of the read.
checker.IPv4(t, c.GetPacket(),
checker.TCP(
checker.DstPort(context.TestPort),
checker.SeqNum(uint32(c.IRS)+1),
checker.AckNum(uint32(790+len(data))),
checker.TCPFlags(header.TCPFlagAck),
),
)
// Cause a FIN to be generated.
c.EP.Shutdown(tcpip.ShutdownWrite)
// Make sure we get the FIN but DON't ACK IT.
checker.IPv4(t, c.GetPacket(),
checker.TCP(
checker.DstPort(context.TestPort),
checker.TCPFlags(header.TCPFlagAck|header.TCPFlagFin),
checker.SeqNum(uint32(c.IRS)+1),
))
// Cause a RST to be generated by closing the read end now since we have
// unread data.
c.EP.Shutdown(tcpip.ShutdownRead)
// Make sure we get the RST
checker.IPv4(t, c.GetPacket(),
checker.TCP(
checker.DstPort(context.TestPort),
checker.TCPFlags(header.TCPFlagAck|header.TCPFlagRst),
// We shouldn't consume a sequence number on RST.
checker.SeqNum(uint32(c.IRS)+1),
))
// The ACK to the FIN should now be rejected since the connection has been
// closed by a RST.
c.SendPacket(nil, &context.Headers{
SrcPort: context.TestPort,
DstPort: c.Port,
Flags: header.TCPFlagAck,
SeqNum: seqnum.Value(790 + len(data)),
AckNum: c.IRS.Add(seqnum.Size(2)),
RcvWnd: 30000,
})
}
func TestFullWindowReceive(t *testing.T) {
c := context.New(t, defaultMTU)
defer c.Cleanup()