fix: connection early termination resulting in internal error

When the connection terminates before all bytes read, we were
getting an io.ErrUnexpectedEOF that was not being handled as
a standard io.EOF resulting in an internal error being raised.
Translate io.ErrUnexpectedEOF to io.EOF so that we return the
normal errors for unexpected content. Add a log message so
that its clear the error is due to the connection being
terminated before all data sent and not the fault of the
gateway.
This commit is contained in:
Ben McClelland
2026-06-01 10:12:41 -07:00
parent cb49670cb7
commit e137e8d375
2 changed files with 16 additions and 0 deletions
+8
View File
@@ -30,6 +30,7 @@ import (
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/cespare/xxhash/v2"
"github.com/versity/versitygw/debuglogger"
"github.com/versity/versitygw/s3err"
"github.com/zeebo/xxh3"
)
@@ -127,6 +128,13 @@ func NewHashReader(r io.Reader, expectedSum string, ht HashType) (*HashReader, e
// Read allows *HashReader to be used as an io.Reader
func (hr *HashReader) Read(p []byte) (int, error) {
n, readerr := hr.r.Read(p)
// Treat ErrUnexpectedEOF as EOF so a truncated body triggers checksum
// validation (which will fail on partial data) rather than leaking a
// raw Go error as an internal server error.
if readerr == io.ErrUnexpectedEOF {
debuglogger.Logf("client connection terminated early")
readerr = io.EOF
}
_, err := hr.hash.Write(p[:n])
if err != nil {
return n, err
+8
View File
@@ -124,6 +124,14 @@ func NewSignedChunkReader(r io.Reader, authdata AuthData, canonicalString, secre
// Read satisfies the io.Reader for this type
func (cr *ChunkReader) Read(p []byte) (int, error) {
n, err := cr.r.Read(p)
// Treat ErrUnexpectedEOF as EOF so a connection that closes before
// all Content-Length bytes arrive follows the normal EOF path and
// returns a proper S3 error (e.g. ErrContentLengthMismatch or
// SignatureDoesNotMatch) instead of leaking an internal Go error.
if err == io.ErrUnexpectedEOF {
debuglogger.Logf("client connection terminated early")
err = io.EOF
}
if err != nil && err != io.EOF {
return 0, err
}