VYPR
Moderate severityNVD Advisory· Published Dec 15, 2022· Updated Apr 18, 2025

Helm vulnerable to Denial of service through string value parsing

CVE-2022-23524

Description

Helm is a tool for managing Charts, pre-configured Kubernetes resources. Versions prior to 3.10.3 are subject to Uncontrolled Resource Consumption, resulting in Denial of Service. Input to functions in the _strvals_ package can cause a stack overflow. In Go, a stack overflow cannot be recovered from. Applications that use functions from the _strvals_ package in the Helm SDK can have a Denial of Service attack when they use this package and it panics. This issue has been patched in 3.10.3. SDK users can validate strings supplied by users won't create large arrays causing significant memory usage before passing them to the _strvals_ functions.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
helm.sh/helm/v3Go
< 3.10.33.10.3

Affected products

1

Patches

1
3636f6824757

Merge pull request from GHSA-6rx9-889q-vv2r

https://github.com/helm/helmMartin HickeyDec 14, 2022via ghsa
2 files changed · +83 9
  • pkg/strvals/parser.go+21 9 modified
    @@ -36,6 +36,10 @@ var ErrNotList = errors.New("not a list")
     // The default value 65536 = 1024 * 64
     var MaxIndex = 65536
     
    +// MaxNestedNameLevel is the maximum level of nesting for a value name that
    +// will be allowed.
    +var MaxNestedNameLevel = 30
    +
     // ToYAML takes a string of arguments and converts to a YAML document.
     func ToYAML(s string) (string, error) {
     	m, err := Parse(s)
    @@ -155,7 +159,7 @@ func newFileParser(sc *bytes.Buffer, data map[string]interface{}, reader RunesVa
     
     func (t *parser) parse() error {
     	for {
    -		err := t.key(t.data)
    +		err := t.key(t.data, 0)
     		if err == nil {
     			continue
     		}
    @@ -174,7 +178,7 @@ func runeSet(r []rune) map[rune]bool {
     	return s
     }
     
    -func (t *parser) key(data map[string]interface{}) (reterr error) {
    +func (t *parser) key(data map[string]interface{}, nestedNameLevel int) (reterr error) {
     	defer func() {
     		if r := recover(); r != nil {
     			reterr = fmt.Errorf("unable to parse key: %s", r)
    @@ -204,7 +208,7 @@ func (t *parser) key(data map[string]interface{}) (reterr error) {
     			}
     
     			// Now we need to get the value after the ].
    -			list, err = t.listItem(list, i)
    +			list, err = t.listItem(list, i, nestedNameLevel)
     			set(data, kk, list)
     			return err
     		case last == '=':
    @@ -261,18 +265,26 @@ func (t *parser) key(data map[string]interface{}) (reterr error) {
     			set(data, string(k), "")
     			return errors.Errorf("key %q has no value (cannot end with ,)", string(k))
     		case last == '.':
    +			// Check value name is within the maximum nested name level
    +			nestedNameLevel++
    +			if nestedNameLevel > MaxNestedNameLevel {
    +				return fmt.Errorf("value name nested level is greater than maximum supported nested level of %d", MaxNestedNameLevel)
    +			}
    +
     			// First, create or find the target map.
     			inner := map[string]interface{}{}
     			if _, ok := data[string(k)]; ok {
     				inner = data[string(k)].(map[string]interface{})
     			}
     
     			// Recurse
    -			e := t.key(inner)
    -			if len(inner) == 0 {
    +			e := t.key(inner, nestedNameLevel)
    +			if e == nil && len(inner) == 0 {
     				return errors.Errorf("key map %q has no value", string(k))
     			}
    -			set(data, string(k), inner)
    +			if len(inner) != 0 {
    +				set(data, string(k), inner)
    +			}
     			return e
     		}
     	}
    @@ -322,7 +334,7 @@ func (t *parser) keyIndex() (int, error) {
     	return strconv.Atoi(string(v))
     
     }
    -func (t *parser) listItem(list []interface{}, i int) ([]interface{}, error) {
    +func (t *parser) listItem(list []interface{}, i, nestedNameLevel int) ([]interface{}, error) {
     	if i < 0 {
     		return list, fmt.Errorf("negative %d index not allowed", i)
     	}
    @@ -395,7 +407,7 @@ func (t *parser) listItem(list []interface{}, i int) ([]interface{}, error) {
     			}
     		}
     		// Now we need to get the value after the ].
    -		list2, err := t.listItem(crtList, nextI)
    +		list2, err := t.listItem(crtList, nextI, nestedNameLevel)
     		if err != nil {
     			return list, err
     		}
    @@ -414,7 +426,7 @@ func (t *parser) listItem(list []interface{}, i int) ([]interface{}, error) {
     		}
     
     		// Recurse
    -		e := t.key(inner)
    +		e := t.key(inner, nestedNameLevel)
     		if e != nil {
     			return list, e
     		}
    
  • pkg/strvals/parser_test.go+62 0 modified
    @@ -16,6 +16,7 @@ limitations under the License.
     package strvals
     
     import (
    +	"fmt"
     	"testing"
     
     	"sigs.k8s.io/yaml"
    @@ -754,3 +755,64 @@ func TestToYAML(t *testing.T) {
     		t.Errorf("Expected %q, got %q", expect, o)
     	}
     }
    +
    +func TestParseSetNestedLevels(t *testing.T) {
    +	var keyMultipleNestedLevels string
    +	for i := 1; i <= MaxNestedNameLevel+2; i++ {
    +		tmpStr := fmt.Sprintf("name%d", i)
    +		if i <= MaxNestedNameLevel+1 {
    +			tmpStr = tmpStr + "."
    +		}
    +		keyMultipleNestedLevels += tmpStr
    +	}
    +	tests := []struct {
    +		str    string
    +		expect map[string]interface{}
    +		err    bool
    +		errStr string
    +	}{
    +		{
    +			"outer.middle.inner=value",
    +			map[string]interface{}{"outer": map[string]interface{}{"middle": map[string]interface{}{"inner": "value"}}},
    +			false,
    +			"",
    +		},
    +		{
    +			str: keyMultipleNestedLevels + "=value",
    +			err: true,
    +			errStr: fmt.Sprintf("value name nested level is greater than maximum supported nested level of %d",
    +				MaxNestedNameLevel),
    +		},
    +	}
    +
    +	for _, tt := range tests {
    +		got, err := Parse(tt.str)
    +		if err != nil {
    +			if tt.err {
    +				if tt.errStr != "" {
    +					if err.Error() != tt.errStr {
    +						t.Errorf("Expected error: %s. Got error: %s", tt.errStr, err.Error())
    +					}
    +				}
    +				continue
    +			}
    +			t.Fatalf("%s: %s", tt.str, err)
    +		}
    +		if tt.err {
    +			t.Errorf("%s: Expected error. Got nil", tt.str)
    +		}
    +
    +		y1, err := yaml.Marshal(tt.expect)
    +		if err != nil {
    +			t.Fatal(err)
    +		}
    +		y2, err := yaml.Marshal(got)
    +		if err != nil {
    +			t.Fatalf("Error serializing parsed value: %s", err)
    +		}
    +
    +		if string(y1) != string(y2) {
    +			t.Errorf("%s: Expected:\n%s\nGot:\n%s", tt.str, y1, y2)
    +		}
    +	}
    +}
    

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

5

News mentions

0

No linked articles in our index yet.