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)