s3: accept FULL_OBJECT checksums without per-part checksums at complete

COMPOSITE uploads must still carry every part checksum in the complete
request, but FULL_OBJECT uploads may instead supply the whole-object
checksum in an x-amz-checksum-* request header. Compare that header
against the computed object checksum and return BadDigest on mismatch,
matching AWS.
This commit is contained in:
Chris Lu committed 2026-09-19 23:28:16 -07:00
1 parent cb7f12de04
commit 09124cac66
2 files changed
+68 -3

No files matched your search

+13 -3
View File
@@ -591,15 +591,22 @@ func (s3a *S3ApiServer) prepareMultipartCompletionState(r *http.Request, input *
provided[part.PartNumber] = part.GetChecksum(checksumHeaderName)
}
for _, partNumber := range completedPartNumbers {
if provided[partNumber] == "" {
return nil, nil, s3err.ErrInvalidRequest
providedChecksum := provided[partNumber]
if providedChecksum == "" {
// COMPOSITE uploads must carry every part checksum in the
// request; FULL_OBJECT uploads instead carry the whole-object
// checksum in a request header.
if checksumType == s3_constants.ChecksumTypeComposite {
return nil, nil, s3err.ErrInvalidRequest
}
continue
}
raw, _, decodeErr := decodePartChecksum(partNumber, partEntries[partNumber], checksumHeaderName)
if decodeErr != nil {
glog.Errorf("completeMultipartUpload: %v", decodeErr)
return nil, nil, s3err.ErrInvalidPart
}
if provided[partNumber] != base64.StdEncoding.EncodeToString(raw) {
if providedChecksum != base64.StdEncoding.EncodeToString(raw) {
return nil, nil, s3err.ErrBadDigest
}
}
@@ -614,6 +621,9 @@ func (s3a *S3ApiServer) prepareMultipartCompletionState(r *http.Request, input *
glog.Errorf("completeMultipartUpload: %s checksum computation failed: %v", checksumType, checksumErr)
return nil, nil, s3err.ErrInvalidPart
}
if objectChecksum := r.Header.Get(checksumHeaderName); objectChecksum != "" && objectChecksum != checksumValue {
return nil, nil, s3err.ErrBadDigest
}
}
// Fold after the boundaries captured byte offsets; finalParts is flat