From d887779b9b0eeb720672a7927479fc627e599c24 Mon Sep 17 00:00:00 2001 From: Will Noble Date: Fri, 27 Feb 2026 07:43:48 -0800 Subject: [PATCH 1/2] Properly format the extra field in gzipped packages Signed-off-by: Will Noble --- pkg/chart/v2/util/save.go | 2 +- pkg/chart/v2/util/save_test.go | 49 ++++++++++++++++++++++++++++++++-- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/pkg/chart/v2/util/save.go b/pkg/chart/v2/util/save.go index e66d86991..09f57da32 100644 --- a/pkg/chart/v2/util/save.go +++ b/pkg/chart/v2/util/save.go @@ -33,7 +33,7 @@ import ( chart "helm.sh/helm/v4/pkg/chart/v2" ) -var headerBytes = []byte("+aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=") +var headerBytes = []byte("rr(\x00aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=") // SaveDir saves a chart as files in a directory. // diff --git a/pkg/chart/v2/util/save_test.go b/pkg/chart/v2/util/save_test.go index 2f2b73efd..aa562725e 100644 --- a/pkg/chart/v2/util/save_test.go +++ b/pkg/chart/v2/util/save_test.go @@ -130,6 +130,51 @@ func TestSave(t *testing.T) { } } +func TestSavedGzipExtraFieldIsValid(t *testing.T) { + tmp := t.TempDir() + c := &chart.Chart{ + Metadata: &chart.Metadata{ + APIVersion: chart.APIVersionV2, + Name: "ahab", + Version: "1.2.3", + }, + } + + where, err := Save(c, tmp) + if err != nil { + t.Fatalf("Failed to save: %s", err) + } + + f, err := os.Open(where) + if err != nil { + t.Fatalf("Failed to open saved file: %s", err) + } + defer f.Close() + + r, err := gzip.NewReader(f) + if err != nil { + t.Fatalf("Failed to create gzip reader: %s", err) + } + defer r.Close() + + // RFC 1952 ยง2.3.1.1: + // Each subfield consists of SI1, SI2 (1 byte each), + // a 2-byte little-endian LEN, and LEN bytes of data. + // https://www.rfc-editor.org/rfc/rfc1952.html#page-8 + // https://github.com/helm/helm/issues/31844 + extra := r.Extra + if len(extra) > 0 { + if len(extra) < 4 { + t.Fatalf("gzip extra field too short to contain a valid subfield: %d byte(s)", len(extra)) + } + dataLen := int(extra[2]) | int(extra[3])<<8 + // Assume a single subfield + if len(extra) != 4+dataLen { + t.Errorf("gzip extra field has malformed subfield: LEN=%d but %d data byte(s) follow the subfield header", dataLen, len(extra)-4) + } + } +} + // Creates a copy with a different schema; does not modify anything. func withSchema(chart chart.Chart, schema []byte) chart.Chart { chart.Schema = schema @@ -299,7 +344,7 @@ func TestRepeatableSave(t *testing.T) { Schema: []byte("{\n \"title\": \"Values\"\n}"), SchemaModTime: modTime, }, - want: "fea2662522317b65c2788ff9e5fc446a9264830038dac618d4449493d99b3257", + want: "63358874b93ea095c857cd66bcf5d0a4464840cf84a07547db744d81d6c5af59", }, { name: "Package 2 files", @@ -321,7 +366,7 @@ func TestRepeatableSave(t *testing.T) { Schema: []byte("{\n \"title\": \"Values\"\n}"), SchemaModTime: modTime, }, - want: "7ae92b2f274bb51ea3f1969e4187d78cc52b5f6f663b44b8fb3b40bcb8ee46f3", + want: "c2a43990053da788ad4e260d3b00d52a0b103ccc67ab9f48278a7b6dcfb2a4bd", }, } for _, test := range tests { From 61bba7c659e3b45ba4db195d58acc668a9c32441 Mon Sep 17 00:00:00 2001 From: Will Noble <4642422+ouillie@users.noreply.github.com> Date: Tue, 23 Jun 2026 00:43:51 +0200 Subject: [PATCH 2/2] Apply suggestions Co-authored-by: George Jenkins Signed-off-by: Will Noble <4642422+ouillie@users.noreply.github.com> --- pkg/chart/v2/util/save.go | 6 +++++- pkg/chart/v2/util/save_test.go | 22 +++++++++++----------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/pkg/chart/v2/util/save.go b/pkg/chart/v2/util/save.go index 09f57da32..19adb8d5f 100644 --- a/pkg/chart/v2/util/save.go +++ b/pkg/chart/v2/util/save.go @@ -33,7 +33,11 @@ import ( chart "helm.sh/helm/v4/pkg/chart/v2" ) -var headerBytes = []byte("rr(\x00aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=") +// RFC 1952 subfield header: +// +---+---+---+---+==================================+ +// |SI1|SI2| LEN |... LEN bytes of subfield data ...| +// +---+---+---+---+==================================+ +var headerBytes = []byte("rr\x28\x00aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=") // SaveDir saves a chart as files in a directory. // diff --git a/pkg/chart/v2/util/save_test.go b/pkg/chart/v2/util/save_test.go index aa562725e..d23f1e8ac 100644 --- a/pkg/chart/v2/util/save_test.go +++ b/pkg/chart/v2/util/save_test.go @@ -21,6 +21,7 @@ import ( "bytes" "compress/gzip" "crypto/sha256" + "encoding/binary" "encoding/hex" "errors" "io" @@ -35,6 +36,8 @@ import ( "helm.sh/helm/v4/pkg/chart/common" chart "helm.sh/helm/v4/pkg/chart/v2" "helm.sh/helm/v4/pkg/chart/v2/loader" + + "github.com/stretchr/testify/require" ) func TestSave(t *testing.T) { @@ -130,6 +133,7 @@ func TestSave(t *testing.T) { } } +// https://github.com/helm/helm/issues/31844 func TestSavedGzipExtraFieldIsValid(t *testing.T) { tmp := t.TempDir() c := &chart.Chart{ @@ -161,18 +165,14 @@ func TestSavedGzipExtraFieldIsValid(t *testing.T) { // Each subfield consists of SI1, SI2 (1 byte each), // a 2-byte little-endian LEN, and LEN bytes of data. // https://www.rfc-editor.org/rfc/rfc1952.html#page-8 - // https://github.com/helm/helm/issues/31844 extra := r.Extra - if len(extra) > 0 { - if len(extra) < 4 { - t.Fatalf("gzip extra field too short to contain a valid subfield: %d byte(s)", len(extra)) - } - dataLen := int(extra[2]) | int(extra[3])<<8 - // Assume a single subfield - if len(extra) != 4+dataLen { - t.Errorf("gzip extra field has malformed subfield: LEN=%d but %d data byte(s) follow the subfield header", dataLen, len(extra)-4) - } - } + + require.NotEmpty(t, extra) + require.GreaterOrEqual(t, len(extra), 4) + + dataLen := int(binary.LittleEndian.Uint16(extra[2:4])) + // Assume a single subfield. + require.Lenf(t, extra, 4+dataLen, "gzip extra field has malformed subfield: LEN=%d but %d data byte(s) follow the subfield header", dataLen, len(extra)-4) } // Creates a copy with a different schema; does not modify anything.