From 1b84aa7bbaa4735c5a292b773d86ad0fc04dd92e Mon Sep 17 00:00:00 2001 From: uditDewan Date: Sun, 12 Jul 2026 02:12:07 -0400 Subject: [PATCH] cli/context/store: limit decompressed size of TLS files on zip import The meta.json entry of an imported zip archive was read through limitedReader, but TLS entries were read with a bare io.ReadAll. Only the compressed archive size was capped, so a crafted zip entry could decompress to many times the allowed import size and exhaust memory. Apply the same size cap to TLS entries, and make limitedReader return its limit error instead of a clean EOF when a read lands exactly on the limit while more data remains, so oversized content fails instead of being silently truncated. Fixes #6917 Signed-off-by: uditDewan --- cli/context/store/io_utils.go | 3 --- cli/context/store/io_utils_test.go | 7 ++++++ cli/context/store/store.go | 2 +- cli/context/store/store_test.go | 40 ++++++++++++++++++++++++++++++ 4 files changed, 48 insertions(+), 4 deletions(-) diff --git a/cli/context/store/io_utils.go b/cli/context/store/io_utils.go index 097443d03174..58a4fef4d67f 100644 --- a/cli/context/store/io_utils.go +++ b/cli/context/store/io_utils.go @@ -16,9 +16,6 @@ func (l *limitedReader) Read(p []byte) (n int, err error) { if l.N < 0 { return 0, errors.New("read exceeds the defined limit") } - if l.N == 0 { - return 0, io.EOF - } // have to cap N + 1 otherwise we won't hit limit err if int64(len(p)) > l.N+1 { p = p[0 : l.N+1] diff --git a/cli/context/store/io_utils_test.go b/cli/context/store/io_utils_test.go index 72cb406f853a..4d558c4a9950 100644 --- a/cli/context/store/io_utils_test.go +++ b/cli/context/store/io_utils_test.go @@ -4,6 +4,7 @@ import ( "io" "strings" "testing" + "testing/iotest" "gotest.tools/v3/assert" ) @@ -21,4 +22,10 @@ func TestLimitReaderReadAll(t *testing.T) { r = strings.NewReader("Test") _, err = io.ReadAll(&limitedReader{R: r, N: 2}) assert.Error(t, err, "read exceeds the defined limit") + + // exceeding the limit must error even when a read lands exactly on + // the limit before the remaining data is reached + r = strings.NewReader("Test") + _, err = io.ReadAll(&limitedReader{R: iotest.OneByteReader(r), N: 2}) + assert.Error(t, err, "read exceeds the defined limit") } diff --git a/cli/context/store/store.go b/cli/context/store/store.go index 2b8b5c311478..625acab48602 100644 --- a/cli/context/store/store.go +++ b/cli/context/store/store.go @@ -478,7 +478,7 @@ func importZip(name string, s Writer, reader io.Reader) error { if err != nil { return err } - data, err := io.ReadAll(f) + data, err := io.ReadAll(&limitedReader{R: f, N: maxAllowedFileSizeToImport}) defer f.Close() if err != nil { return err diff --git a/cli/context/store/store_test.go b/cli/context/store/store_test.go index 20d0ef6463f6..3ad3ab6d9eea 100644 --- a/cli/context/store/store_test.go +++ b/cli/context/store/store_test.go @@ -236,6 +236,46 @@ func TestImportZipInvalid(t *testing.T) { assert.ErrorContains(t, err, "unexpected context file") } +func TestImportZipTLSDataTooLarge(t *testing.T) { + testDir := t.TempDir() + zf := path.Join(testDir, "test.zip") + + f, err := os.Create(zf) + assert.NilError(t, err) + defer f.Close() + w := zip.NewWriter(f) + + meta, err := json.Marshal(Metadata{ + Endpoints: map[string]any{ + "ep1": endpoint{Foo: "bar"}, + }, + Metadata: context{Bar: "baz"}, + Name: "source", + }) + assert.NilError(t, err) + mf, err := w.Create("meta.json") + assert.NilError(t, err) + _, err = mf.Write(meta) + assert.NilError(t, err) + + // a highly compressible TLS entry that inflates beyond the allowed + // import size, even though the zip file itself stays well under it + tf, err := w.Create(path.Join("tls", "docker", "ca.pem")) + assert.NilError(t, err) + _, err = tf.Write(make([]byte, 2*maxAllowedFileSizeToImport)) + assert.NilError(t, err) + err = w.Close() + assert.NilError(t, err) + + source, err := os.Open(zf) + assert.NilError(t, err) + defer source.Close() + var r io.Reader = source + s := New(testDir, testCfg) + err = Import("zipTLSTooLarge", s, r) + assert.ErrorContains(t, err, "exceeds the defined limit") +} + func TestCorruptMetadata(t *testing.T) { tempDir := t.TempDir() s := New(tempDir, testCfg)