Skip to content

Commit

Permalink
Properly handle io.EOF error conditions when reading
Browse files Browse the repository at this point in the history
Previously, the Server.Serve method would never return nil,
because the infinite for-loop handling request packets would
only break if reading a packet reported an error.
A common termination condition is when the underlying connection
is closed and recvPacket returns io.EOF.
In which case Serve should ignore io.EOF and
treat it as a normal shutdown.

However, this means that recvPacket must correctly handle io.EOF
such that it never reports io.EOF if a packet is partially read.
There are two calls to io.ReadFull in recvPacket.
The first call correctly forwards an io.EOF error
if no additional bytes of the next packet are read.
However, the second call incorrectly forwards io.EOF
when no bytes of the payload could be read.
This is incorrect since we already read the length and
should convert the io.EOF into an io.ErrUnexpectedEOF.
  • Loading branch information
dsnet committed Jul 21, 2023
1 parent ec1c8ca commit f707262
Show file tree
Hide file tree
Showing 3 changed files with 11 additions and 2 deletions.
4 changes: 3 additions & 1 deletion conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ type conn struct {
}

// the orderID is used in server mode if the allocator is enabled.
// For the client mode just pass 0
// For the client mode just pass 0.
// It returns io.EOF if the connection is closed and
// there are no more packets to read.
func (c *conn) recvPacket(orderID uint32) (uint8, []byte, error) {
return recvPacket(c, c.alloc, orderID)
}
Expand Down
3 changes: 3 additions & 0 deletions packet.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,9 @@ func recvPacket(r io.Reader, alloc *allocator, orderID uint32) (uint8, []byte, e
b = make([]byte, length)
}
if _, err := io.ReadFull(r, b[:length]); err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
debug("recv packet %d bytes: err %v", length, err)
return 0, nil, err
}
Expand Down
6 changes: 5 additions & 1 deletion server.go
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ func handlePacket(s *Server, p orderedRequest) error {
}

// Serve serves SFTP connections until the streams stop or the SFTP subsystem
// is stopped.
// is stopped. It returns nil if the server exits cleanly.
func (svr *Server) Serve() error {
defer func() {
if svr.pktMgr.alloc != nil {
Expand All @@ -353,6 +353,10 @@ func (svr *Server) Serve() error {
for {
pktType, pktBytes, err = svr.serverConn.recvPacket(svr.pktMgr.getNextOrderID())
if err != nil {
// Check whether the connection terminated cleanly in-between packets.
if err == io.EOF {
err = nil
}
// we don't care about releasing allocated pages here, the server will quit and the allocator freed
break
}
Expand Down

0 comments on commit f707262

Please sign in to comment.