CVE-2023-46738 (published 2024-01-03T16:15Z)

CubeFS is an open-source cloud-native file storage system. A security vulnerability was found in CubeFS HandlerNode in versions prior to 3.3.1 that could allow authenticated users to send maliciously-crafted requests that would crash the ObjectNode and deny other users from using it. The root cause was improper handling of incoming HTTP requests that could allow an attacker to control the ammount of memory that the ObjectNode would allocate. A malicious request could make the ObjectNode allocate more memory that the machine had available, and the attacker could exhaust memory by way of a single malicious request. An attacker would need to be authenticated in order to invoke the vulnerable code with their malicious request and have permissions to delete objects. In addition, the attacker would need to know the names of existing buckets of the CubeFS deployment - otherwise the request would be rejected before it reached the vulnerable code. As such, the most likely attacker is an inside user or an attacker that has breached the account of an existing user in the cluster. The issue has been patched in v3.3.1. There is no other mitigation besides upgrading.

CVSS v3 base score: 6.5, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H)

Weakness information:
cwe_id: CWE-770
cwe_name: Allocation of Resources Without Limits or Throttling
description: The product allocates a reusable resource or group of resources on behalf of an actor without imposing any restrictions on the size or number of resources that can be allocated, in violation of the intended security policy for that actor.
extended_description: Code frequently has to work with limited resources, so programmers must be careful to ensure that resources are not consumed too quickly, or too easily.  Without use of quotas, resource limits, or other protection mechanisms, it can be easy for an attacker to consume many resources by rapidly making many requests, or causing larger resources to be used than is needed. When too many resources are allocated, or if a single resource is too large, then it can prevent the code from working correctly, possibly leading to a denial of service.
         

Fix information:
repo_url: https://github.com/cubefs/cubefs
commit_details: repo_url: https://github.com/cubefs/cubefs
author: tangdeyi
author_date: 2023-10-20 15:14:24+08:00
msg: fix(objectnode): fix read body entirely into memory and without an upper boundary

Signed-off-by: tangdeyi <tangdeyi@oppo.com>
repository: repo_url: https://github.com/cubefs/cubefs
repo_name: cubefs/cubefs
description: cloud-native distributed storage
repo_language: Go
owner: cubefs
file_changes: filename: api_handler_bucket.go
change_type: ModificationType.MODIFY
diff: @@ -23,7 +23,6 @@ import (
 	"io/ioutil"
 	"net/http"
 	"regexp"
-	"strconv"
 	"strings"
 
 	"github.com/cubefs/cubefs/proto"
@@ -86,30 +85,29 @@ func (o *ObjectNode) createBucketHandler(w http.ResponseWriter, r *http.Request)
 	}
 	defer rateLimit.ReleaseLimitResource(userInfo.UserID, param.apiName)
 
-	// get LocationConstraint if any
-	contentLenStr := r.Header.Get(ContentLength)
-	if contentLen, errConv := strconv.Atoi(contentLenStr); errConv == nil && contentLen > 0 {
-		var requestBytes []byte
-		requestBytes, err = ioutil.ReadAll(r.Body)
-		if err != nil && err != io.EOF {
-			log.LogErrorf("createBucketHandler: read request body fail: requestID(%v) err(%v)", GetRequestID(r), err)
-			return
-		}
+	_, errorCode = VerifyContentLength(r, BodyLimit)
+	if errorCode != nil {
+		return
+	}
+	requestBytes, err := ioutil.ReadAll(r.Body)
+	if err != nil && err != io.EOF {
+		log.LogErrorf("createBucketHandler: read request body fail: requestID(%v) err(%v)", GetRequestID(r), err)
+		return
+	}
 
-		createBucketRequest := &CreateBucketRequest{}
-		err = UnmarshalXMLEntity(requestBytes, createBucketRequest)
-		if err != nil {
-			log.LogErrorf("createBucketHandler: unmarshal xml fail: requestID(%v) err(%v)",
-				GetRequestID(r), err)
-			errorCode = InvalidArgument
-			return
-		}
-		if createBucketRequest.LocationConstraint != o.region {
-			log.LogErrorf("createBucketHandler: location constraint not match the service: requestID(%v) LocationConstraint(%v) region(%v)",
-				GetRequestID(r), createBucketRequest.LocationConstraint, o.region)
-			errorCode = InvalidLocationConstraint
-			return
-		}
+	createBucketRequest := &CreateBucketRequest{}
+	err = UnmarshalXMLEntity(requestBytes, createBucketRequest)
+	if err != nil {
+		log.LogErrorf("createBucketHandler: unmarshal xml fail: requestID(%v) err(%v)",
+			GetRequestID(r), err)
+		errorCode = InvalidArgument
+		return
+	}
+	if createBucketRequest.LocationConstraint != o.region {
+		log.LogErrorf("createBucketHandler: location constraint not match the service: requestID(%v) LocationConstraint(%v) region(%v)",
+			GetRequestID(r), createBucketRequest.LocationConstraint, o.region)
+		errorCode = InvalidLocationConstraint
+		return
 	}
 
 	var acl *AccessControlPolicy
@@ -397,6 +395,10 @@ func (o *ObjectNode) putBucketTaggingHandler(w http.ResponseWriter, r *http.Requ
 	}
 	defer rateLimit.ReleaseLimitResource(vol.owner, param.apiName)
 
+	_, errorCode = VerifyContentLength(r, BodyLimit)
+	if errorCode != nil {
+		return
+	}
 	var body []byte
 	if body, err = ioutil.ReadAll(r.Body); err != nil {
 		log.LogErrorf("putBucketTaggingHandler: read request body data fail: requestID(%v) err(%v)",

programming_language: Go
filename: api_handler_multipart.go
change_type: ModificationType.MODIFY
diff: @@ -651,8 +651,11 @@ func (o *ObjectNode) completeMultipartUploadHandler(w http.ResponseWriter, r *ht
 	defer rateLimit.ReleaseLimitResource(vol.owner, param.apiName)
 
 	// get uploaded part info in request
-	var requestBytes []byte
-	requestBytes, err = ioutil.ReadAll(r.Body)
+	_, errorCode = VerifyContentLength(r, BodyLimit)
+	if errorCode != nil {
+		return
+	}
+	requestBytes, err := ioutil.ReadAll(r.Body)
 	if err != nil && err != io.EOF {
 		log.LogErrorf("completeMultipartUploadHandler: read request body fail: requestID(%v) err(%v)",
 			GetRequestID(r), err)

programming_language: Go
filename: api_handler_object.go
change_type: ModificationType.MODIFY
diff: @@ -557,8 +557,11 @@ func (o *ObjectNode) deleteObjectsHandler(w http.ResponseWriter, r *http.Request
 		return
 	}
 
-	var bytes []byte
-	bytes, err = ioutil.ReadAll(r.Body)
+	_, errorCode = VerifyContentLength(r, BodyLimit)
+	if errorCode != nil {
+		return
+	}
+	bytes, err := ioutil.ReadAll(r.Body)
 	if err != nil {
 		log.LogErrorf("deleteObjectsHandler: read request body fail: requestID(%v) volume(%v) err(%v)",
 			GetRequestID(r), param.Bucket(), err)
@@ -1552,6 +1555,10 @@ func (o *ObjectNode) putObjectTaggingHandler(w http.ResponseWriter, r *http.Requ
 	}
 	defer rateLimit.ReleaseLimitResource(vol.owner, param.apiName)
 
+	_, errorCode = VerifyContentLength(r, BodyLimit)
+	if errorCode != nil {
+		return
+	}
 	var requestBody []byte
 	if requestBody, err = ioutil.ReadAll(r.Body); err != nil {
 		log.LogErrorf("putObjectTaggingHandler: read request body data fail: requestID(%v) err(%v)",
@@ -1660,6 +1667,10 @@ func (o *ObjectNode) putObjectXAttrHandler(w http.ResponseWriter, r *http.Reques
 	}
 	defer rateLimit.ReleaseLimitResource(vol.owner, param.apiName)
 
+	_, errorCode = VerifyContentLength(r, BodyLimit)
+	if errorCode != nil {
+		return
+	}
 	var requestBody []byte
 	if requestBody, err = ioutil.ReadAll(r.Body); err != nil {
 		errorCode = &ErrorCode{
@@ -1962,3 +1973,21 @@ func GetContentLength(r *http.Request) int64 {
 	}
 	return r.ContentLength
 }
+
+func VerifyContentLength(r *http.Request, bodyLimit int64) (int64, *ErrorCode) {
+	dcl := r.Header.Get(HeaderNameXAmzDecodedContentLength)
+	var length = r.ContentLength
+	if dcl != "" {
+		l, err := strconv.ParseInt(dcl, 10, 64)
+		if err == nil {
+			length = l
+		}
+	}
+	if length > bodyLimit {
+		return 0, EntityTooLarge
+	}
+	if length <= 0 {
+		return 0, MissingContentLength
+	}
+	return length, nil
+}

programming_language: Go
filename: const.go
change_type: ModificationType.MODIFY
diff: @@ -133,6 +133,7 @@ const (
 	MaxParts       = 1000
 	MaxUploads     = 1000
 	SinglePutLimit = 5 * 1 << 30 // 5G
+	BodyLimit      = 1 << 20
 )
 
 const (

programming_language: Go
filename: lifecycle_handler.go
change_type: ModificationType.MODIFY
diff: @@ -106,6 +106,10 @@ func (o *ObjectNode) putBucketLifecycleConfigurationHandler(w http.ResponseWrite
 		return
 	}
 
+	_, errorCode = VerifyContentLength(r, BodyLimit)
+	if errorCode != nil {
+		return
+	}
 	var requestBody []byte
 	if requestBody, err = ioutil.ReadAll(r.Body); err != nil && err != io.EOF {
 		log.LogErrorf("putBucketLifecycle failed: read request body data err: requestID(%v) err(%v)", GetRequestID(r), err)

programming_language: Go