CVE-2023-48713: Insufficient Information (NVD-CWE-noinfo)
value: Knative Serving builds on Kubernetes to support deploying and serving of applications and functions as serverless containers. An attacker who controls a pod to a degree where they can control the responses from the /metrics endpoint can cause Denial-of-Service of the autoscaler from an unbound memory allocation bug. This is a DoS vulnerability, where a non-privileged Knative user can cause a DoS for the cluster. This issue has been patched in version 0.39.0.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
CVSS v3 base score: 5.3
Published: 2023-11-28T04:15Z
Repository: https://github.com/knative/serving
Language: Go
Files changed: pkg/autoscaler/metrics/http_scrape_client.go, pkg/autoscaler/metrics/http_scrape_client_test.go
Commit date: 2023-10-16 16:16:48 +0000
Commit message: Bound buffer for reading stats (#14523)


Diff: diff --git a/pkg/autoscaler/metrics/http_scrape_client.go b/pkg/autoscaler/metrics/http_scrape_client.go
index e834684ad..40d614f77 100644
--- a/pkg/autoscaler/metrics/http_scrape_client.go
+++ b/pkg/autoscaler/metrics/http_scrape_client.go
@@ -80,11 +80,13 @@ func isPotentialMeshError(err error) bool {
 func statFromProto(body io.Reader) (Stat, error) {
 	var stat Stat
 	b := pool.Get().(*bytes.Buffer)
 	b.Reset()
 	defer pool.Put(b)
-	_, err := b.ReadFrom(body)
+	// 6 8-byte fields (+2 bytes marshalling), one hostname, 20 bytes extra space
+	r := io.LimitedReader{R: body, N: 6*10 + 256 + 20}
+	_, err := b.ReadFrom(&r)
 	if err != nil {
 		return emptyStat, fmt.Errorf("reading body failed: %w", err)
 	}
 	err = stat.Unmarshal(b.Bytes())
 	if err != nil {
diff --git a/pkg/autoscaler/metrics/http_scrape_client_test.go b/pkg/autoscaler/metrics/http_scrape_client_test.go
index ee1dd2af1..de5218190 100644
--- a/pkg/autoscaler/metrics/http_scrape_client_test.go
+++ b/pkg/autoscaler/metrics/http_scrape_client_test.go
@@ -93,10 +93,25 @@ func TestHTTPScrapeClientScrapeProtoErrorCases(t *testing.T) {
 	}, {
 		name:         "Wrong Content-Type",
 		responseCode: http.StatusOK,
 		responseType: "text/html",
 		expectedErr:  errUnsupportedMetricType.Error(),
+	}, {
+		name:         "LongStat",
+		responseCode: http.StatusOK,
+		responseType: "application/protobuf",
+		stat: Stat{
+			// We don't expect PodName to be 600 characters long
+			PodName:                          strings.Repeat("a123456789", 60),
+			AverageConcurrentRequests:        1.1,
+			AverageProxiedConcurrentRequests: 1.1,
+			RequestCount:                     33.2,
+			ProxiedRequestCount:              33.2,
+			ProcessUptime:                    12345.678,
+			Timestamp:                        1697431278,
+		},
+		expectedErr: "unmarshalling failed: unexpected EOF",
 	}}
 
 	for _, test := range testCases {
 		t.Run(test.name, func(t *testing.T) {
 			hClient := newTestHTTPClient(makeProtoResponse(test.responseCode, test.stat, test.responseType), test.responseErr)

Vulnerable code: 	_, err := b.ReadFrom(body)
Fixed code: 	// 6 8-byte fields (+2 bytes marshalling), one hostname, 20 bytes extra space
	r := io.LimitedReader{R: body, N: 6*10 + 256 + 20}
	_, err := b.ReadFrom(&r)
	}, {
		name:         "LongStat",
		responseCode: http.StatusOK,
		responseType: "application/protobuf",
		stat: Stat{
			// We don't expect PodName to be 600 characters long
			PodName:                          strings.Repeat("a123456789", 60),
			AverageConcurrentRequests:        1.1,
			AverageProxiedConcurrentRequests: 1.1,
			RequestCount:                     33.2,
			ProxiedRequestCount:              33.2,
			ProcessUptime:                    12345.678,
			Timestamp:                        1697431278,
		},
		expectedErr: "unmarshalling failed: unexpected EOF",