mirror of
https://github.com/pgsty/minio.git
synced 2026-09-05 18:16:16 +03:00
fix: validate explicit multipart checksum type
Signed-off-by: Feng Ruohang <rh@vonng.com>
This commit is contained in:
@@ -420,8 +420,74 @@ func testAPICompleteMultipartChecksumTypeMismatch(obj ObjectLayer, instanceType,
|
||||
t.Fatalf("%s: object was created despite a rejected checksum type", instanceType)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run(test.name+"-type-only", func(t *testing.T) {
|
||||
objectName := "type-mismatch/type-only-" + test.name
|
||||
uploadID := newMultipartUploadHTTP(t, apiRouter, credentials, bucketName, objectName,
|
||||
typ.String(), test.createdType)
|
||||
etags := uploadPartsHTTP(t, apiRouter, credentials, bucketName, objectName, uploadID, typ, partData)
|
||||
partCS := []string{mustChecksum(t, typ, partData[0]), mustChecksum(t, typ, partData[1])}
|
||||
rec := completeMultipartUploadHTTP(t, apiRouter, credentials, bucketName, objectName, uploadID, etags, partCS,
|
||||
map[string]string{xhttp.AmzChecksumType: test.providedType})
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Fatalf("%s: checksum type-only mismatch returned %d, want 400", instanceType, rec.Code)
|
||||
}
|
||||
apiErr := apiError(t, rec)
|
||||
if apiErr.Code != "BadDigest" {
|
||||
t.Fatalf("%s: expected BadDigest, got %q", instanceType, apiErr.Code)
|
||||
}
|
||||
wantMessage := fmt.Sprintf("The checksum type %s does not match the multipart upload checksum type %s.", test.providedType, test.createdType)
|
||||
if apiErr.Message != wantMessage {
|
||||
t.Fatalf("%s: expected message %q, got %q", instanceType, wantMessage, apiErr.Message)
|
||||
}
|
||||
if _, err := obj.GetObjectInfo(t.Context(), bucketName, objectName, ObjectOptions{}); err == nil {
|
||||
t.Fatalf("%s: object was created despite a rejected checksum type-only assertion", instanceType)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
providedType string
|
||||
withChecksum bool
|
||||
}{
|
||||
{name: "unknown-type-only", providedType: "NOT_A_TYPE"},
|
||||
{name: "lowercase-type-only", providedType: "full_object"},
|
||||
{name: "unknown-with-checksum", providedType: "NOT_A_TYPE", withChecksum: true},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
objectName := "type-mismatch/invalid-" + test.name
|
||||
uploadID := newMultipartUploadHTTP(t, apiRouter, credentials, bucketName, objectName,
|
||||
typ.String(), xhttp.AmzChecksumTypeComposite)
|
||||
etags := uploadPartsHTTP(t, apiRouter, credentials, bucketName, objectName, uploadID, typ, partData)
|
||||
partCS := []string{mustChecksum(t, typ, partData[0]), mustChecksum(t, typ, partData[1])}
|
||||
headers := map[string]string{xhttp.AmzChecksumType: test.providedType}
|
||||
if test.withChecksum {
|
||||
headers[typ.Key()] = mustChecksum(t, typ, full)
|
||||
}
|
||||
rec := completeMultipartUploadHTTP(t, apiRouter, credentials, bucketName, objectName, uploadID, etags, partCS, headers)
|
||||
if rec.Code != http.StatusBadRequest || apiErrorCode(t, rec) != "InvalidArgument" {
|
||||
t.Fatalf("%s: invalid checksum type returned %d %s", instanceType, rec.Code, rec.Body.String())
|
||||
}
|
||||
if _, err := obj.GetObjectInfo(t.Context(), bucketName, objectName, ObjectOptions{}); err == nil {
|
||||
t.Fatalf("%s: object was created despite an invalid checksum type", instanceType)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("matching-type-only", func(t *testing.T) {
|
||||
objectName := "type-mismatch/matching-type-only"
|
||||
uploadID := newMultipartUploadHTTP(t, apiRouter, credentials, bucketName, objectName,
|
||||
typ.String(), xhttp.AmzChecksumTypeComposite)
|
||||
etags := uploadPartsHTTP(t, apiRouter, credentials, bucketName, objectName, uploadID, typ, partData)
|
||||
partCS := []string{mustChecksum(t, typ, partData[0]), mustChecksum(t, typ, partData[1])}
|
||||
rec := completeMultipartUploadHTTP(t, apiRouter, credentials, bucketName, objectName, uploadID, etags, partCS,
|
||||
map[string]string{xhttp.AmzChecksumType: xhttp.AmzChecksumTypeComposite})
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("%s: matching checksum type-only assertion returned %d %s", instanceType, rec.Code, rec.Body.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("omitted-type-is-not-composite", func(t *testing.T) {
|
||||
objectName := "type-mismatch/omitted-type"
|
||||
uploadID := newMultipartUploadHTTP(t, apiRouter, credentials, bucketName, objectName,
|
||||
|
||||
@@ -1173,9 +1173,9 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
|
||||
var checksumType hash.ChecksumType
|
||||
if cs := fi.Metadata[hash.MinIOMultipartChecksum]; cs != "" {
|
||||
checksumType = hash.NewChecksumType(cs, fi.Metadata[hash.MinIOMultipartChecksumType])
|
||||
expectedType := checksumType | hash.ChecksumMultipart | hash.ChecksumIncludesMultipart
|
||||
if opts.WantChecksum != nil {
|
||||
providedType := opts.WantChecksum.Type | hash.ChecksumMultipart | hash.ChecksumIncludesMultipart
|
||||
expectedType := checksumType | hash.ChecksumMultipart | hash.ChecksumIncludesMultipart
|
||||
if providedType.Base() != expectedType.Base() {
|
||||
return oi, InvalidArgument{
|
||||
Bucket: bucket,
|
||||
@@ -1183,8 +1183,16 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
|
||||
Err: fmt.Errorf("checksum algorithm mismatch. got %q expected %q", providedType.String(), expectedType.String()),
|
||||
}
|
||||
}
|
||||
if opts.wantChecksumTypeSet && providedType.ObjType() != expectedType.ObjType() {
|
||||
return oi, completeMultipartChecksumTypeMismatch(providedType.ObjType(), expectedType.ObjType())
|
||||
}
|
||||
if opts.wantChecksumType != "" {
|
||||
providedObjectType := opts.wantChecksumType
|
||||
// CRC64NVME is always canonicalized to FULL_OBJECT. Preserve this
|
||||
// behavior until its exact AWS wire semantics have been probed.
|
||||
if checksumType.Base().Is(hash.ChecksumCRC64NVME) {
|
||||
providedObjectType = xhttp.AmzChecksumTypeFullObject
|
||||
}
|
||||
if providedObjectType != expectedType.ObjType() {
|
||||
return oi, completeMultipartChecksumTypeMismatch(opts.wantChecksumType, expectedType.ObjType())
|
||||
}
|
||||
}
|
||||
checksumType |= hash.ChecksumMultipart | hash.ChecksumIncludesMultipart
|
||||
|
||||
@@ -84,8 +84,8 @@ type ObjectOptions struct {
|
||||
Expiration ExpirationOptions
|
||||
LifecycleAuditEvent lcAuditEvent
|
||||
|
||||
WantChecksum *hash.Checksum // x-amz-checksum-XXX checksum sent to PutObject/ CompleteMultipartUpload.
|
||||
wantChecksumTypeSet bool // x-amz-checksum-type was explicitly set on CompleteMultipartUpload.
|
||||
WantChecksum *hash.Checksum // x-amz-checksum-XXX checksum sent to PutObject/ CompleteMultipartUpload.
|
||||
wantChecksumType string // explicit x-amz-checksum-type value on CompleteMultipartUpload.
|
||||
|
||||
WantServerSideChecksumType hash.ChecksumType // if set, we compute a server-side checksum of this type
|
||||
|
||||
|
||||
@@ -469,11 +469,17 @@ func completeMultipartOpts(ctx context.Context, r *http.Request, bucket, object
|
||||
}
|
||||
}
|
||||
|
||||
opts.wantChecksumType = r.Header.Get(xhttp.AmzChecksumType)
|
||||
switch opts.wantChecksumType {
|
||||
case "", xhttp.AmzChecksumTypeComposite, xhttp.AmzChecksumTypeFullObject:
|
||||
default:
|
||||
return opts, hash.ErrInvalidChecksum
|
||||
}
|
||||
|
||||
opts.WantChecksum, err = hash.GetContentChecksum(r.Header)
|
||||
if err != nil {
|
||||
return opts, err
|
||||
}
|
||||
opts.wantChecksumTypeSet = r.Header.Get(xhttp.AmzChecksumType) != ""
|
||||
opts.MTime = mtime
|
||||
opts.UserDefined = make(map[string]string)
|
||||
// Transfer SSEC key in opts.EncryptFn
|
||||
|
||||
Reference in New Issue
Block a user