fix: reject unsupported checksum assertions

Reject unimplemented x-amz-checksum value and trailer names instead of accepting uploads without verification. Apply the same contract to PutObject, multipart initiation and parts, CopyObject, and UploadPartCopy while preserving the five supported algorithms.

Signed-off-by: Feng Ruohang <rh@vonng.com>
This commit is contained in:
Feng Ruohang
2026-08-29 17:23:26 +08:00
parent 04d3d316d2
commit 7c103389f5
5 changed files with 227 additions and 9 deletions
+42 -9
View File
@@ -657,22 +657,55 @@ func AddChecksumHeader(w http.ResponseWriter, c map[string]string) {
}
}
func isSupportedChecksumHeader(name string) bool {
switch {
case strings.EqualFold(name, xhttp.AmzChecksumAlgo),
strings.EqualFold(name, xhttp.AmzChecksumType),
strings.EqualFold(name, xhttp.AmzChecksumMode):
return true
}
for _, checksumType := range BaseChecksumTypes {
if strings.EqualFold(name, checksumType.Key()) {
return true
}
}
return false
}
func hasUnsupportedChecksumHeader(h http.Header) bool {
for name := range h {
if strings.HasPrefix(strings.ToLower(name), "x-amz-checksum-") && !isSupportedChecksumHeader(name) {
return true
}
}
return false
}
// GetContentChecksum returns content checksum.
// Returns ErrInvalidChecksum if so.
// Returns nil, nil if no checksum.
func GetContentChecksum(h http.Header) (*Checksum, error) {
if hasUnsupportedChecksumHeader(h) {
return nil, ErrInvalidChecksum
}
if trailing := h.Values(xhttp.AmzTrailer); len(trailing) > 0 {
var res *Checksum
for _, header := range trailing {
var duplicates bool
for _, t := range BaseChecksumTypes {
if strings.EqualFold(t.Key(), header) {
duplicates = res != nil
res = NewChecksumWithType(t|ChecksumTrailing, "")
for _, headers := range trailing {
for header := range strings.SplitSeq(headers, ",") {
header = strings.TrimSpace(header)
var duplicates bool
for _, t := range BaseChecksumTypes {
if strings.EqualFold(t.Key(), header) {
duplicates = res != nil
res = NewChecksumWithType(t|ChecksumTrailing, "")
}
}
if strings.HasPrefix(strings.ToLower(header), "x-amz-checksum-") && !isSupportedChecksumHeader(header) {
return nil, ErrInvalidChecksum
}
if duplicates {
return nil, ErrInvalidChecksum
}
}
if duplicates {
return nil, ErrInvalidChecksum
}
}
if res != nil {
+41
View File
@@ -18,12 +18,53 @@
package hash
import (
"errors"
"net/http"
"net/http/httptest"
"testing"
xhttp "github.com/minio/minio/internal/http"
)
func TestGetContentChecksumRejectsUnsupportedHeaders(t *testing.T) {
unsupported := []string{
"x-amz-checksum-md5",
"x-amz-checksum-sha512",
"x-amz-checksum-xxhash64",
"x-amz-checksum-xxhash3",
"x-amz-checksum-xxhash128",
"x-amz-checksum-future",
}
for _, header := range unsupported {
t.Run("header/"+header, func(t *testing.T) {
h := http.Header{header: {"AA=="}}
if _, err := GetContentChecksum(h); !errors.Is(err, ErrInvalidChecksum) {
t.Fatalf("GetContentChecksum(%s) error = %v, want ErrInvalidChecksum", header, err)
}
})
t.Run("trailer/"+header, func(t *testing.T) {
h := http.Header{xhttp.AmzTrailer: {header}}
if _, err := GetContentChecksum(h); !errors.Is(err, ErrInvalidChecksum) {
t.Fatalf("GetContentChecksum(trailer %s) error = %v, want ErrInvalidChecksum", header, err)
}
})
}
for header, value := range map[string]string{
xhttp.AmzChecksumAlgo: "CRC32",
xhttp.AmzChecksumType: xhttp.AmzChecksumTypeComposite,
xhttp.AmzChecksumMode: "ENABLED",
"x-amz-sdk-checksum-algorithm": "SHA512",
} {
t.Run("control/"+header, func(t *testing.T) {
h := http.Header{header: {value}}
if _, err := GetContentChecksum(h); errors.Is(err, ErrInvalidChecksum) {
t.Fatalf("control header %s was rejected", header)
}
})
}
}
// TestChecksumAddToHeader tests that adding and retrieving a checksum on a header works
func TestChecksumAddToHeader(t *testing.T) {
tests := []struct {