CVE-2025-32777
Description
Volcano is a Kubernetes-native batch scheduling system. Prior to versions 1.11.2, 1.10.2, 1.9.1, 1.11.0-network-topology-preview.3, and 1.12.0-alpha.2, attacker compromise of either the Elastic service or the extender plugin can cause denial of service of the scheduler. This is a privilege escalation, because Volcano users may run their Elastic service and extender plugins in separate pods or nodes from the scheduler. In the Kubernetes security model, node isolation is a security boundary, and as such an attacker is able to cross that boundary in Volcano's case if they have compromised either the vulnerable services or the pod/node in which they are deployed. The scheduler will become unavailable to other users and workloads in the cluster. The scheduler will either crash with an unrecoverable OOM panic or freeze while consuming excessive amounts of memory. This issue has been patched in versions 1.11.2, 1.10.2, 1.9.1, 1.11.0-network-topology-preview.3, and 1.12.0-alpha.2.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
volcano.sh/volcanoGo | < 1.9.1 | 1.9.1 |
volcano.sh/volcanoGo | >= 1.10.0-alpha.0, < 1.10.2 | 1.10.2 |
volcano.sh/volcanoGo | >= 1.11.0-network-topology-preview.0, < 1.11.0-network-topology-preview.3 | 1.11.0-network-topology-preview.3 |
volcano.sh/volcanoGo | >= 1.11.0, < 1.11.2 | 1.11.2 |
volcano.sh/volcanoGo | >= 1.12.0-alpha.0, < 1.12.0-alpha.2 | 1.12.0-alpha.2 |
Patches
87103c18de198Merge commit from fork
4 files changed · +110 −1
pkg/scheduler/metrics/source/metrics_client_elasticsearch.go+4 −0 modified@@ -36,6 +36,9 @@ const ( esCPUUsageField = "host.cpu.usage" // esMemUsageField is the field name of mem usage in the document esMemUsageField = "system.memory.actual.used.pct" + + // 1MB + maxBodySize = 1 << 20 ) type ElasticsearchMetricsClient struct { @@ -156,6 +159,7 @@ func (e *ElasticsearchMetricsClient) NodeMetricsAvg(ctx context.Context, nodeNam } } `json:"aggregations"` } + res.Body = http.MaxBytesReader(nil, res.Body, maxBodySize) if err := json.NewDecoder(res.Body).Decode(&r); err != nil { return nil, err }
pkg/scheduler/metrics/source/metrics_client_elasticsearch_test.go+51 −1 modified@@ -16,7 +16,15 @@ package source -import "testing" +import ( + "context" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/elastic/go-elasticsearch/v7" +) func TestElasticsearchMetricsClientDefaultIndexName(t *testing.T) { client, err := NewElasticsearchMetricsClient(map[string]string{"address": "http://localhost:9200"}) @@ -37,3 +45,45 @@ func TestElasticsearchMetricsClientCustomIndexName(t *testing.T) { t.Errorf("Custom index name should be custom-index") } } + +func TestElasticsearchMetricsClientNodeMetricsAvg_MaxBodySizeExceeded(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + w.Write([]byte(`{"took": 1, "timed_out": false, "_shards": {"total": 1, "successful": 1, "skipped": 0, "failed": 0}, "hits": {"total": {"value": 1, "relation": "eq"}, "max_score": null, "hits": []}, "aggregations": {"cpu": {"value": 0.35}, "mem": {"value": 0.45}}, `)) + largeData := make([]byte, maxBodySize) + for i := range largeData { + largeData[i] = 'a' + } + w.Write([]byte(`"large_field": "`)) + w.Write(largeData) + w.Write([]byte(`"}"`)) + })) + defer server.Close() + + client, err := NewElasticsearchMetricsClient(map[string]string{ + "address": server.URL, + }) + if err != nil { + t.Fatalf("Failed to create client: %v", err) + } + + esClient, err := elasticsearch.NewClient(elasticsearch.Config{ + Addresses: []string{server.URL}, + }) + if err != nil { + t.Fatalf("Failed to create ES client: %v", err) + } + client.es = esClient + + _, err = client.NodeMetricsAvg(context.Background(), "test-node") + + if err == nil { + t.Error("Expected error due to response exceeding maxBodySize, but got nil") + } else { + if !strings.Contains(err.Error(), "body size limit") && !strings.Contains(err.Error(), "too large") { + t.Errorf("Expected error about body size limit, got: %v", err) + } + } +}
pkg/scheduler/plugins/extender/extender.go+4 −0 modified@@ -60,6 +60,9 @@ const ( ExtenderJobReadyVerb = "extender.jobReadyVerb" // ExtenderIgnorable indicates whether the extender can ignore unexpected errors ExtenderIgnorable = "extender.ignorable" + + // 10MB + maxBodySize = 10 << 20 ) type extenderConfig struct { @@ -322,6 +325,7 @@ func (ep *extenderPlugin) send(action string, args interface{}, result interface } if result != nil { + resp.Body = http.MaxBytesReader(nil, resp.Body, maxBodySize) return json.NewDecoder(resp.Body).Decode(result) } return nil
pkg/scheduler/plugins/extender/extender_test.go+51 −0 added@@ -0,0 +1,51 @@ +/* +Copyright 2025 The Volcano Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package extender + +import ( + "net/http" + "net/http/httptest" + "strings" + "testing" + + "volcano.sh/volcano/pkg/scheduler/api" +) + +func TestMaxBodySizeLimit2(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + response := strings.Repeat("a", maxBodySize+1) + w.Write([]byte(`{"padding":"` + response + `"}`)) + })) + defer server.Close() + + plugin := &extenderPlugin{ + client: http.Client{}, + config: &extenderConfig{ + urlPrefix: server.URL, + }, + } + + var result map[string]interface{} + err := plugin.send("test", &PredicateRequest{Task: &api.TaskInfo{}, Node: &api.NodeInfo{}}, &result) + + if err == nil { + t.Error("Expected error due to request body size limit, but got nil") + } else if !strings.Contains(err.Error(), "http: request body too large") { + t.Errorf("Expected 'http: request body too large' error, got: %v", err) + } +}
735842af59b9Add http response body size limit
4 files changed · +110 −1
pkg/scheduler/metrics/source/metrics_client_elasticsearch.go+4 −0 modified@@ -36,6 +36,9 @@ const ( esCPUUsageField = "host.cpu.usage" // esMemUsageField is the field name of mem usage in the document esMemUsageField = "system.memory.actual.used.pct" + + // 1MB + maxBodySize = 1 << 20 ) type ElasticsearchMetricsClient struct { @@ -156,6 +159,7 @@ func (e *ElasticsearchMetricsClient) NodeMetricsAvg(ctx context.Context, nodeNam } } `json:"aggregations"` } + res.Body = http.MaxBytesReader(nil, res.Body, maxBodySize) if err := json.NewDecoder(res.Body).Decode(&r); err != nil { return nil, err }
pkg/scheduler/metrics/source/metrics_client_elasticsearch_test.go+51 −1 modified@@ -16,7 +16,15 @@ package source -import "testing" +import ( + "context" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/elastic/go-elasticsearch/v7" +) func TestElasticsearchMetricsClientDefaultIndexName(t *testing.T) { client, err := NewElasticsearchMetricsClient(map[string]string{"address": "http://localhost:9200"}) @@ -37,3 +45,45 @@ func TestElasticsearchMetricsClientCustomIndexName(t *testing.T) { t.Errorf("Custom index name should be custom-index") } } + +func TestElasticsearchMetricsClientNodeMetricsAvg_MaxBodySizeExceeded(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + w.Write([]byte(`{"took": 1, "timed_out": false, "_shards": {"total": 1, "successful": 1, "skipped": 0, "failed": 0}, "hits": {"total": {"value": 1, "relation": "eq"}, "max_score": null, "hits": []}, "aggregations": {"cpu": {"value": 0.35}, "mem": {"value": 0.45}}, `)) + largeData := make([]byte, maxBodySize) + for i := range largeData { + largeData[i] = 'a' + } + w.Write([]byte(`"large_field": "`)) + w.Write(largeData) + w.Write([]byte(`"}"`)) + })) + defer server.Close() + + client, err := NewElasticsearchMetricsClient(map[string]string{ + "address": server.URL, + }) + if err != nil { + t.Fatalf("Failed to create client: %v", err) + } + + esClient, err := elasticsearch.NewClient(elasticsearch.Config{ + Addresses: []string{server.URL}, + }) + if err != nil { + t.Fatalf("Failed to create ES client: %v", err) + } + client.es = esClient + + _, err = client.NodeMetricsAvg(context.Background(), "test-node") + + if err == nil { + t.Error("Expected error due to response exceeding maxBodySize, but got nil") + } else { + if !strings.Contains(err.Error(), "body size limit") && !strings.Contains(err.Error(), "too large") { + t.Errorf("Expected error about body size limit, got: %v", err) + } + } +}
pkg/scheduler/plugins/extender/extender.go+4 −0 modified@@ -60,6 +60,9 @@ const ( ExtenderJobReadyVerb = "extender.jobReadyVerb" // ExtenderIgnorable indicates whether the extender can ignore unexpected errors ExtenderIgnorable = "extender.ignorable" + + // 10MB + maxBodySize = 10 << 20 ) type extenderConfig struct { @@ -322,6 +325,7 @@ func (ep *extenderPlugin) send(action string, args interface{}, result interface } if result != nil { + resp.Body = http.MaxBytesReader(nil, resp.Body, maxBodySize) return json.NewDecoder(resp.Body).Decode(result) } return nil
pkg/scheduler/plugins/extender/extender_test.go+51 −0 added@@ -0,0 +1,51 @@ +/* +Copyright 2025 The Volcano Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package extender + +import ( + "net/http" + "net/http/httptest" + "strings" + "testing" + + "volcano.sh/volcano/pkg/scheduler/api" +) + +func TestMaxBodySizeLimit2(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + response := strings.Repeat("a", maxBodySize+1) + w.Write([]byte(`{"padding":"` + response + `"}`)) + })) + defer server.Close() + + plugin := &extenderPlugin{ + client: http.Client{}, + config: &extenderConfig{ + urlPrefix: server.URL, + }, + } + + var result map[string]interface{} + err := plugin.send("test", &PredicateRequest{Task: &api.TaskInfo{}, Node: &api.NodeInfo{}}, &result) + + if err == nil { + t.Error("Expected error due to request body size limit, but got nil") + } else if !strings.Contains(err.Error(), "http: request body too large") { + t.Errorf("Expected 'http: request body too large' error, got: %v", err) + } +}
6b7b5df16414ca2dbb36bb41e9690aa65a5945a4347471a5Add http response body size limit
4 files changed · +110 −1
pkg/scheduler/metrics/source/metrics_client_elasticsearch.go+4 −0 modified@@ -36,6 +36,9 @@ const ( esCPUUsageField = "host.cpu.usage" // esMemUsageField is the field name of mem usage in the document esMemUsageField = "system.memory.actual.used.pct" + + // 1MB + maxBodySize = 1 << 20 ) type ElasticsearchMetricsClient struct { @@ -156,6 +159,7 @@ func (e *ElasticsearchMetricsClient) NodeMetricsAvg(ctx context.Context, nodeNam } } `json:"aggregations"` } + res.Body = http.MaxBytesReader(nil, res.Body, maxBodySize) if err := json.NewDecoder(res.Body).Decode(&r); err != nil { return nil, err }
pkg/scheduler/metrics/source/metrics_client_elasticsearch_test.go+51 −1 modified@@ -16,7 +16,15 @@ package source -import "testing" +import ( + "context" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/elastic/go-elasticsearch/v7" +) func TestElasticsearchMetricsClientDefaultIndexName(t *testing.T) { client, err := NewElasticsearchMetricsClient(map[string]string{"address": "http://localhost:9200"}) @@ -37,3 +45,45 @@ func TestElasticsearchMetricsClientCustomIndexName(t *testing.T) { t.Errorf("Custom index name should be custom-index") } } + +func TestElasticsearchMetricsClientNodeMetricsAvg_MaxBodySizeExceeded(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + w.Write([]byte(`{"took": 1, "timed_out": false, "_shards": {"total": 1, "successful": 1, "skipped": 0, "failed": 0}, "hits": {"total": {"value": 1, "relation": "eq"}, "max_score": null, "hits": []}, "aggregations": {"cpu": {"value": 0.35}, "mem": {"value": 0.45}}, `)) + largeData := make([]byte, maxBodySize) + for i := range largeData { + largeData[i] = 'a' + } + w.Write([]byte(`"large_field": "`)) + w.Write(largeData) + w.Write([]byte(`"}"`)) + })) + defer server.Close() + + client, err := NewElasticsearchMetricsClient(map[string]string{ + "address": server.URL, + }) + if err != nil { + t.Fatalf("Failed to create client: %v", err) + } + + esClient, err := elasticsearch.NewClient(elasticsearch.Config{ + Addresses: []string{server.URL}, + }) + if err != nil { + t.Fatalf("Failed to create ES client: %v", err) + } + client.es = esClient + + _, err = client.NodeMetricsAvg(context.Background(), "test-node") + + if err == nil { + t.Error("Expected error due to response exceeding maxBodySize, but got nil") + } else { + if !strings.Contains(err.Error(), "body size limit") && !strings.Contains(err.Error(), "too large") { + t.Errorf("Expected error about body size limit, got: %v", err) + } + } +}
pkg/scheduler/plugins/extender/extender.go+4 −0 modified@@ -60,6 +60,9 @@ const ( ExtenderJobReadyVerb = "extender.jobReadyVerb" // ExtenderIgnorable indicates whether the extender can ignore unexpected errors ExtenderIgnorable = "extender.ignorable" + + // 10MB + maxBodySize = 10 << 20 ) type extenderConfig struct { @@ -322,6 +325,7 @@ func (ep *extenderPlugin) send(action string, args interface{}, result interface } if result != nil { + resp.Body = http.MaxBytesReader(nil, resp.Body, maxBodySize) return json.NewDecoder(resp.Body).Decode(result) } return nil
pkg/scheduler/plugins/extender/extender_test.go+51 −0 added@@ -0,0 +1,51 @@ +/* +Copyright 2025 The Volcano Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package extender + +import ( + "net/http" + "net/http/httptest" + "strings" + "testing" + + "volcano.sh/volcano/pkg/scheduler/api" +) + +func TestMaxBodySizeLimit2(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + response := strings.Repeat("a", maxBodySize+1) + w.Write([]byte(`{"padding":"` + response + `"}`)) + })) + defer server.Close() + + plugin := &extenderPlugin{ + client: http.Client{}, + config: &extenderConfig{ + urlPrefix: server.URL, + }, + } + + var result map[string]interface{} + err := plugin.send("test", &PredicateRequest{Task: &api.TaskInfo{}, Node: &api.NodeInfo{}}, &result) + + if err == nil { + t.Error("Expected error due to request body size limit, but got nil") + } else if !strings.Contains(err.Error(), "http: request body too large") { + t.Errorf("Expected 'http: request body too large' error, got: %v", err) + } +}
d687f75a11faAdd http response body size limit
4 files changed · +110 −1
pkg/scheduler/metrics/source/metrics_client_elasticsearch.go+4 −0 modified@@ -36,6 +36,9 @@ const ( esCPUUsageField = "host.cpu.usage" // esMemUsageField is the field name of mem usage in the document esMemUsageField = "system.memory.actual.used.pct" + + // 1MB + maxBodySize = 1 << 20 ) type ElasticsearchMetricsClient struct { @@ -156,6 +159,7 @@ func (e *ElasticsearchMetricsClient) NodeMetricsAvg(ctx context.Context, nodeNam } } `json:"aggregations"` } + res.Body = http.MaxBytesReader(nil, res.Body, maxBodySize) if err := json.NewDecoder(res.Body).Decode(&r); err != nil { return nil, err }
pkg/scheduler/metrics/source/metrics_client_elasticsearch_test.go+51 −1 modified@@ -16,7 +16,15 @@ package source -import "testing" +import ( + "context" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/elastic/go-elasticsearch/v7" +) func TestElasticsearchMetricsClientDefaultIndexName(t *testing.T) { client, err := NewElasticsearchMetricsClient(map[string]string{"address": "http://localhost:9200"}) @@ -37,3 +45,45 @@ func TestElasticsearchMetricsClientCustomIndexName(t *testing.T) { t.Errorf("Custom index name should be custom-index") } } + +func TestElasticsearchMetricsClientNodeMetricsAvg_MaxBodySizeExceeded(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + w.Write([]byte(`{"took": 1, "timed_out": false, "_shards": {"total": 1, "successful": 1, "skipped": 0, "failed": 0}, "hits": {"total": {"value": 1, "relation": "eq"}, "max_score": null, "hits": []}, "aggregations": {"cpu": {"value": 0.35}, "mem": {"value": 0.45}}, `)) + largeData := make([]byte, maxBodySize) + for i := range largeData { + largeData[i] = 'a' + } + w.Write([]byte(`"large_field": "`)) + w.Write(largeData) + w.Write([]byte(`"}"`)) + })) + defer server.Close() + + client, err := NewElasticsearchMetricsClient(map[string]string{ + "address": server.URL, + }) + if err != nil { + t.Fatalf("Failed to create client: %v", err) + } + + esClient, err := elasticsearch.NewClient(elasticsearch.Config{ + Addresses: []string{server.URL}, + }) + if err != nil { + t.Fatalf("Failed to create ES client: %v", err) + } + client.es = esClient + + _, err = client.NodeMetricsAvg(context.Background(), "test-node") + + if err == nil { + t.Error("Expected error due to response exceeding maxBodySize, but got nil") + } else { + if !strings.Contains(err.Error(), "body size limit") && !strings.Contains(err.Error(), "too large") { + t.Errorf("Expected error about body size limit, got: %v", err) + } + } +}
pkg/scheduler/plugins/extender/extender.go+4 −0 modified@@ -60,6 +60,9 @@ const ( ExtenderJobReadyVerb = "extender.jobReadyVerb" // ExtenderIgnorable indicates whether the extender can ignore unexpected errors ExtenderIgnorable = "extender.ignorable" + + // 10MB + maxBodySize = 10 << 20 ) type extenderConfig struct { @@ -315,6 +318,7 @@ func (ep *extenderPlugin) send(action string, args interface{}, result interface } if result != nil { + resp.Body = http.MaxBytesReader(nil, resp.Body, maxBodySize) return json.NewDecoder(resp.Body).Decode(result) } return nil
pkg/scheduler/plugins/extender/extender_test.go+51 −0 added@@ -0,0 +1,51 @@ +/* +Copyright 2025 The Volcano Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package extender + +import ( + "net/http" + "net/http/httptest" + "strings" + "testing" + + "volcano.sh/volcano/pkg/scheduler/api" +) + +func TestMaxBodySizeLimit2(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + response := strings.Repeat("a", maxBodySize+1) + w.Write([]byte(`{"padding":"` + response + `"}`)) + })) + defer server.Close() + + plugin := &extenderPlugin{ + client: http.Client{}, + config: &extenderConfig{ + urlPrefix: server.URL, + }, + } + + var result map[string]interface{} + err := plugin.send("test", &PredicateRequest{Task: &api.TaskInfo{}, Node: &api.NodeInfo{}}, &result) + + if err == nil { + t.Error("Expected error due to request body size limit, but got nil") + } else if !strings.Contains(err.Error(), "http: request body too large") { + t.Errorf("Expected 'http: request body too large' error, got: %v", err) + } +}
7c0ea53fa3cfAdd http response body size limit
4 files changed · +110 −1
pkg/scheduler/metrics/source/metrics_client_elasticsearch.go+4 −0 modified@@ -36,6 +36,9 @@ const ( esCPUUsageField = "host.cpu.usage" // esMemUsageField is the field name of mem usage in the document esMemUsageField = "system.memory.actual.used.pct" + + // 1MB + maxBodySize = 1 << 20 ) type ElasticsearchMetricsClient struct { @@ -156,6 +159,7 @@ func (e *ElasticsearchMetricsClient) NodeMetricsAvg(ctx context.Context, nodeNam } } `json:"aggregations"` } + res.Body = http.MaxBytesReader(nil, res.Body, maxBodySize) if err := json.NewDecoder(res.Body).Decode(&r); err != nil { return nil, err }
pkg/scheduler/metrics/source/metrics_client_elasticsearch_test.go+51 −1 modified@@ -16,7 +16,15 @@ package source -import "testing" +import ( + "context" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/elastic/go-elasticsearch/v7" +) func TestElasticsearchMetricsClientDefaultIndexName(t *testing.T) { client, err := NewElasticsearchMetricsClient(map[string]string{"address": "http://localhost:9200"}) @@ -37,3 +45,45 @@ func TestElasticsearchMetricsClientCustomIndexName(t *testing.T) { t.Errorf("Custom index name should be custom-index") } } + +func TestElasticsearchMetricsClientNodeMetricsAvg_MaxBodySizeExceeded(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + w.Write([]byte(`{"took": 1, "timed_out": false, "_shards": {"total": 1, "successful": 1, "skipped": 0, "failed": 0}, "hits": {"total": {"value": 1, "relation": "eq"}, "max_score": null, "hits": []}, "aggregations": {"cpu": {"value": 0.35}, "mem": {"value": 0.45}}, `)) + largeData := make([]byte, maxBodySize) + for i := range largeData { + largeData[i] = 'a' + } + w.Write([]byte(`"large_field": "`)) + w.Write(largeData) + w.Write([]byte(`"}"`)) + })) + defer server.Close() + + client, err := NewElasticsearchMetricsClient(map[string]string{ + "address": server.URL, + }) + if err != nil { + t.Fatalf("Failed to create client: %v", err) + } + + esClient, err := elasticsearch.NewClient(elasticsearch.Config{ + Addresses: []string{server.URL}, + }) + if err != nil { + t.Fatalf("Failed to create ES client: %v", err) + } + client.es = esClient + + _, err = client.NodeMetricsAvg(context.Background(), "test-node") + + if err == nil { + t.Error("Expected error due to response exceeding maxBodySize, but got nil") + } else { + if !strings.Contains(err.Error(), "body size limit") && !strings.Contains(err.Error(), "too large") { + t.Errorf("Expected error about body size limit, got: %v", err) + } + } +}
pkg/scheduler/plugins/extender/extender.go+4 −0 modified@@ -60,6 +60,9 @@ const ( ExtenderJobReadyVerb = "extender.jobReadyVerb" // ExtenderIgnorable indicates whether the extender can ignore unexpected errors ExtenderIgnorable = "extender.ignorable" + + // 10MB + maxBodySize = 10 << 20 ) type extenderConfig struct { @@ -322,6 +325,7 @@ func (ep *extenderPlugin) send(action string, args interface{}, result interface } if result != nil { + resp.Body = http.MaxBytesReader(nil, resp.Body, maxBodySize) return json.NewDecoder(resp.Body).Decode(result) } return nil
pkg/scheduler/plugins/extender/extender_test.go+51 −0 added@@ -0,0 +1,51 @@ +/* +Copyright 2025 The Volcano Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package extender + +import ( + "net/http" + "net/http/httptest" + "strings" + "testing" + + "volcano.sh/volcano/pkg/scheduler/api" +) + +func TestMaxBodySizeLimit2(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + response := strings.Repeat("a", maxBodySize+1) + w.Write([]byte(`{"padding":"` + response + `"}`)) + })) + defer server.Close() + + plugin := &extenderPlugin{ + client: http.Client{}, + config: &extenderConfig{ + urlPrefix: server.URL, + }, + } + + var result map[string]interface{} + err := plugin.send("test", &PredicateRequest{Task: &api.TaskInfo{}, Node: &api.NodeInfo{}}, &result) + + if err == nil { + t.Error("Expected error due to request body size limit, but got nil") + } else if !strings.Contains(err.Error(), "http: request body too large") { + t.Errorf("Expected 'http: request body too large' error, got: %v", err) + } +}
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
14- github.com/advisories/GHSA-hg79-fw4p-25p8ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-32777ghsaADVISORY
- github.com/volcano-sh/volcano/commit/45a4347471a5254121d10afef04c6732095fa398ghsaWEB
- github.com/volcano-sh/volcano/commit/7103c18de19821cd278f949fa24c13da350a8c5dghsaWEB
- github.com/volcano-sh/volcano/commit/735842af59b9be0da5090677db7693c98a798b2aghsaWEB
- github.com/volcano-sh/volcano/commit/7c0ea53fa3cfa7a05b5fba7a8af7bfe88adc41c3ghsaWEB
- github.com/volcano-sh/volcano/commit/d687f75a11fa36f37b54e4b6ff8e49bc0a3ca6b4ghsaWEB
- github.com/volcano-sh/volcano/releases/tag/v1.10.2nvdWEB
- github.com/volcano-sh/volcano/releases/tag/v1.11.0-network-topology-preview.3nvdWEB
- github.com/volcano-sh/volcano/releases/tag/v1.11.2nvdWEB
- github.com/volcano-sh/volcano/releases/tag/v1.12.0-alpha.2nvdWEB
- github.com/volcano-sh/volcano/releases/tag/v1.9.1nvdWEB
- github.com/volcano-sh/volcano/security/advisories/GHSA-hg79-fw4p-25p8nvdWEB
- pkg.go.dev/vuln/GO-2025-3656ghsaWEB
News mentions
0No linked articles in our index yet.