CVE-2018-1000538 (Go, published 2018-06-26T16:29Z)
Description: value: Minio Inc. Minio S3 server version prior to RELEASE.2018-05-16T23-35-33Z contains a Allocation of Memory Without Limits or Throttling (similar to CWE-774) vulnerability in write-to-RAM that can result in Denial of Service. This attack appear to be exploitable via Sending V4-(pre)signed requests with large bodies . This vulnerability appears to have been fixed in after commit 9c8b7306f55f2c8c0a5c7cea9a8db9d34be8faa7.
Severity: MEDIUM (CVSS v3 base score: 7.5)
Weakness: CWE-774 \u2014 Allocation of File Descriptors or Handles Without Limits or Throttling
The product allocates file descriptors or handles on behalf of an actor without imposing any restrictions on how many descriptors can be allocated, in violation of the intended security policy for that actor.
Repository: https://github.com/minio/minio
Files changed:
cmd/auth-handler.go
cmd/auth-handler_test.go
pkg/hash/reader.go
Commit message: security: fix write-to-RAM DoS vulnerability (#5957)

This commit fixes a DoS vulnerability for certain APIs using
signature V4 by verifying the content-md5 and/or content-sha56 of
the request body in a streaming mode.

The issue was caused by reading the entire body of the request into
memory to verify the content-md5 or content-sha56 checksum if present.

The vulnerability could be exploited by either replaying a V4 request
(in the 15 min time frame) or sending a V4 presigned request with a
large body.

Vulnerable code:
	if r == nil {
		return ErrInternalError
	}

	payload, err := ioutil.ReadAll(r.Body)
	if err != nil {
		logger.LogIf(context.Background(), err)
		return ErrInternalError
	}

	// Populate back the payload.
	r.Body = ioutil.NopCloser(bytes.NewReader(payload))

	// Verify Content-Md5, if payload is set.
	if clntMD5B64, ok := r.Header["Content-Md5"]; ok {
		if clntMD5B64[0] == "" {
		md5Sum, err := base64.StdEncoding.Strict().DecodeString(clntMD5B64[0])
		if err != nil {
			return ErrInvalidDigest
		}
		if !bytes.Equal(md5Sum, getMD5Sum(payload)) {
			return ErrBadDigest
		}
	if skipContentSha256Cksum(r) {
		return ErrNone
	}

	// Verify that X-Amz-Content-Sha256 Header == sha256(payload)
	// If X-Amz-Content-Sha256 header is not sent then we don't calculate/verify sha256(payload)
	sumHex, ok := r.Header["X-Amz-Content-Sha256"]
	if isRequestPresignedSignatureV4(r) {
		sumHex, ok = r.URL.Query()["X-Amz-Content-Sha256"]
	}
	if ok {
		if sumHex[0] == "" {
			return ErrContentSHA256Mismatch
		}
		sum, err := hex.DecodeString(sumHex[0])
		if err != nil {
			return ErrContentSHA256Mismatch
		if !bytes.Equal(sum, getSHA256Sum(payload)) {
		// When request is nil, internal error is returned.
		{nil, ErrInternalError},
	for _, testCase := range testCases {
			t.Fatalf("Unexpected s3error returned wanted %d, got %d", testCase.s3Error, s3Error)

		src:        io.LimitReader(src, size),

Fixed code:
	"github.com/minio/minio/pkg/hash"
	var (
		err                       error
		contentMD5, contentSHA256 []byte
	)
	// Extract 'Content-Md5' if present.
	if _, ok := r.Header["Content-Md5"]; ok {
		contentMD5, err = base64.StdEncoding.Strict().DecodeString(r.Header.Get("Content-Md5"))
		if err != nil || len(contentMD5) == 0 {
	// Extract either 'X-Amz-Content-Sha256' header or 'X-Amz-Content-Sha256' query parameter (if V4 presigned)
	// Do not verify 'X-Amz-Content-Sha256' if skipSHA256.
	if skipSHA256 := skipContentSha256Cksum(r); !skipSHA256 && isRequestPresignedSignatureV4(r) {
		if sha256Sum, ok := r.URL.Query()["X-Amz-Content-Sha256"]; ok && len(sha256Sum) > 0 {
			contentSHA256, err = hex.DecodeString(sha256Sum[0])
			if err != nil {
				return ErrContentSHA256Mismatch
			}
	} else if _, ok := r.Header["X-Amz-Content-Sha256"]; !skipSHA256 && ok {
		contentSHA256, err = hex.DecodeString(r.Header.Get("X-Amz-Content-Sha256"))
		if err != nil || len(contentSHA256) == 0 {

	// Verify 'Content-Md5' and/or 'X-Amz-Content-Sha256' if present.
	// The verification happens implicit during reading.
	reader, err := hash.NewReader(r.Body, -1, hex.EncodeToString(contentMD5), hex.EncodeToString(contentSHA256))
	if err != nil {
		return toAPIErrorCode(err)
	}
	r.Body = ioutil.NopCloser(reader)
	"io/ioutil"
	for i, testCase := range testCases {
			if _, err := ioutil.ReadAll(testCase.req.Body); toAPIErrorCode(err) != testCase.s3Error {
				t.Fatalf("Test %d: Unexpected S3 error: want %d - got %d (got after reading request %d)", i, testCase.s3Error, s3Error, toAPIErrorCode(err))
			}
	if size >= 0 {
		src = io.LimitReader(src, size)
	}
		src:        src,