fix(chart): format the v3 gzip extra field as an RFC 1952 subfield

internal/chart/v3/util/save.go assigned gzip.Writer.Extra the 41-byte
string "+aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=", which is not a valid
FEXTRA payload. RFC 1952 section 2.3.1.1 requires every subfield to be
SI1, SI2, a 2-byte little-endian LEN, and then LEN bytes of data. Parsed
that way those bytes give SI1='+', SI2='a' and LEN=21064 while only 37
bytes remain, so strict readers reject every archive the v3 writer
produces. Go's compress/gzip does not parse subfields, so nothing in the
tree noticed.

pkg/chart/v2/util/save.go had the identical defect. It was fixed in
#31884 for issue #31844, "helm package produces malformed .tgz", where
the Bazel downloader failed with "Extra subfield lenght exceeds
remaining bytes in extra: 21064 > 37". The v3 writer was never updated.

Use the same header bytes the v2 writer now uses: SI1 and SI2 of 'r',
LEN 0x0028, and the unchanged 40-byte payload. Both writers now emit
byte-identical, well-formed extra fields. Adds the v2 regression test to
the v3 package.

Signed-off-by: Max Freedom Pollard <272618364+MaxFreedomPollard@users.noreply.github.com>
pull/32629/head
Max Freedom Pollard 6 days ago
parent fa11636b01
commit 5e87f7dad4

@ -33,7 +33,11 @@ import (
"helm.sh/helm/v4/pkg/chart/common" "helm.sh/helm/v4/pkg/chart/common"
) )
var headerBytes = []byte("+aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=") // 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. // SaveDir saves a chart as files in a directory.
// //

@ -21,6 +21,7 @@ import (
"bytes" "bytes"
"compress/gzip" "compress/gzip"
"crypto/sha256" "crypto/sha256"
"encoding/binary"
"encoding/hex" "encoding/hex"
"errors" "errors"
"io" "io"
@ -108,6 +109,41 @@ func TestSave(t *testing.T) {
require.Error(t, err, "Expected error saving chart with invalid name") require.Error(t, err, "Expected error saving chart with invalid name")
} }
func TestSavedGzipExtraFieldIsValid(t *testing.T) {
tmp := t.TempDir()
c := &chart.Chart{
Metadata: &chart.Metadata{
APIVersion: chart.APIVersionV3,
Name: "ahab",
Version: "1.2.3",
},
}
where, err := Save(c, tmp)
require.NoError(t, err, "Failed to save")
f, err := os.Open(where)
require.NoError(t, err, "Failed to open saved file")
defer f.Close()
r, err := gzip.NewReader(f)
require.NoError(t, err, "Failed to create gzip reader")
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
extra := r.Extra
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. // Creates a copy with a different schema; does not modify anything.
func withSchema(chart chart.Chart, schema []byte) chart.Chart { func withSchema(chart chart.Chart, schema []byte) chart.Chart {
chart.Schema = schema chart.Schema = schema

Loading…
Cancel
Save