fix(loader): accept a chart archive that exactly fills the size budget

LoadArchiveFiles checks the aggregate decompression budget four times, and three
of them are strict: hd.Size > remainingSize in this same loop, and both
comparisons in BudgetedReader, which enforces the identical
MaxDecompressedChartSize for directory loads. Only the post-copy check used
remainingSize <= 0, so a chart whose contents exactly fill the budget drove
remainingSize to zero and was rejected with "decompressed chart is larger than
the maximum size" - a claim that is false, since it is equal.

The same chart therefore loaded from a directory and failed from a .tgz.

Note that after this change the clause is unreachable: io.LimitReader caps
bytesWritten at remainingSize, so remainingSize can never go below zero. It is
kept as a guard in case that changes.

Signed-off-by: manon <youdie006@users.noreply.github.com>
pull/32635/head
manon 3 days ago
parent fa11636b01
commit c6ddeb2ff8

@ -142,7 +142,7 @@ func LoadArchiveFiles(in io.Reader) ([]*BufferedFile, error) {
// copying early. Here we report that error. This is important if the last file extracted // copying early. Here we report that error. This is important if the last file extracted
// is the one that goes over the limit. It assumes the Size stored in the tar header // is the one that goes over the limit. It assumes the Size stored in the tar header
// is correct, something many applications do. // is correct, something many applications do.
if bytesWritten < hd.Size || remainingSize <= 0 { if bytesWritten < hd.Size || remainingSize < 0 {
return nil, fmt.Errorf("decompressed chart is larger than the maximum size %d", MaxDecompressedChartSize) return nil, fmt.Errorf("decompressed chart is larger than the maximum size %d", MaxDecompressedChartSize)
} }

@ -79,3 +79,50 @@ func TestLoadArchiveFiles(t *testing.T) {
}) })
} }
} }
func TestLoadArchiveFilesAtMaxDecompressedSize(t *testing.T) {
const content = "apiVersion: v2\nname: mychart\nversion: 0.1.0\n"
archiveOf := func(t *testing.T) *bytes.Buffer {
t.Helper()
buf := &bytes.Buffer{}
gzw := gzip.NewWriter(buf)
tw := tar.NewWriter(gzw)
require.NoError(t, tw.WriteHeader(&tar.Header{
Typeflag: tar.TypeReg,
Name: "mychart/Chart.yaml",
Size: int64(len(content)),
}))
_, err := tw.Write([]byte(content))
require.NoError(t, err)
require.NoError(t, tw.Close())
require.NoError(t, gzw.Close())
return buf
}
orig := MaxDecompressedChartSize
t.Cleanup(func() { MaxDecompressedChartSize = orig })
for _, tc := range []struct {
name string
budget int64
wantErr bool
}{
{"one byte over the chart size", int64(len(content)) + 1, false},
// A chart that exactly fills the budget is at the limit, not over it,
// and BudgetedReader accepts it for directory loads.
{"exactly the chart size", int64(len(content)), false},
{"one byte under the chart size", int64(len(content)) - 1, true},
} {
t.Run(tc.name, func(t *testing.T) {
MaxDecompressedChartSize = tc.budget
files, err := LoadArchiveFiles(archiveOf(t))
if tc.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
require.Len(t, files, 1)
})
}
}

Loading…
Cancel
Save