CVE-2023-46738 (Go, published 2024-01-03T16:15Z)
Description: value: 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.
Severity: nan (CVSS v3 base score: 6.5)
Weakness: CWE-770 \u2014 Allocation of Resources Without Limits or Throttling
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.
Repository: https://github.com/cubefs/cubefs
Files changed:
objectnode/api_handler_bucket.go
objectnode/api_handler_multipart.go
objectnode/api_handler_object.go
objectnode/const.go
objectnode/lifecycle_handler.go
Commit message: fix(objectnode): fix read body entirely into memory and without an upper boundary

Signed-off-by: tangdeyi <tangdeyi@oppo.com>


Vulnerable code:
	"strconv"
	// 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
		}
		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 requestBytes []byte
	requestBytes, err = ioutil.ReadAll(r.Body)
	var bytes []byte
	bytes, err = ioutil.ReadAll(r.Body)

Fixed code:
	_, 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
	_, errorCode = VerifyContentLength(r, BodyLimit)
	if errorCode != nil {
		return
	}
	_, errorCode = VerifyContentLength(r, BodyLimit)
	if errorCode != nil {
		return
	}
	requestBytes, err := ioutil.ReadAll(r.Body)
	_, errorCode = VerifyContentLength(r, BodyLimit)
	if errorCode != nil {
		return
	}
	bytes, err := ioutil.ReadAll(r.Body)
	_, errorCode = VerifyContentLength(r, BodyLimit)
	if errorCode != nil {
		return
	}
	_, errorCode = VerifyContentLength(r, BodyLimit)
	if errorCode != nil {
		return
	}

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
}
	BodyLimit      = 1 << 20
	_, errorCode = VerifyContentLength(r, BodyLimit)
	if errorCode != nil {
		return
	}