diff --git a/cli/context/store/store.go b/cli/context/store/store.go index 2b8b5c311478..0832d4ecf795 100644 --- a/cli/context/store/store.go +++ b/cli/context/store/store.go @@ -474,17 +474,7 @@ func importZip(name string, s Writer, reader io.Reader) error { } importedMetaFile = true } else if strings.HasPrefix(zf.Name, "tls/") { - f, err := zf.Open() - if err != nil { - return err - } - data, err := io.ReadAll(f) - defer f.Close() - if err != nil { - return err - } - err = importEndpointTLS(&tlsData, zf.Name, data) - if err != nil { + if err := importTLSEntry(zf, &tlsData); err != nil { return err } } @@ -495,6 +485,26 @@ func importZip(name string, s Writer, reader io.Reader) error { return s.ResetTLSMaterial(name, &tlsData) } +func importTLSEntry(zf *zip.File, tlsData *ContextTLSData) error { + // Reject entries whose advertised uncompressed size exceeds + // the per-file cap without decompressing, to avoid allocating + // gigabytes for a zip bomb (see #6917). + if zf.UncompressedSize64 > uint64(maxAllowedFileSizeToImport) { + return invalidParameter(fmt.Errorf("%s: tls file exceeds maximum allowed size", zf.Name)) + } + f, err := zf.Open() + if err != nil { + return err + } + defer f.Close() + // Defense in depth in case the zip header is spoofed. + data, err := io.ReadAll(&limitedReader{R: f, N: maxAllowedFileSizeToImport}) + if err != nil { + return err + } + return importEndpointTLS(tlsData, zf.Name, data) +} + func parseMetadata(data []byte, name string) (Metadata, error) { var meta Metadata if err := json.Unmarshal(data, &meta); err != nil { diff --git a/cli/context/store/store_test.go b/cli/context/store/store_test.go index 20d0ef6463f6..b508c47cbe2b 100644 --- a/cli/context/store/store_test.go +++ b/cli/context/store/store_test.go @@ -211,6 +211,41 @@ func TestImportZip(t *testing.T) { assert.NilError(t, err) } +// TestImportZipTLSTooLarge verifies that a TLS entry whose uncompressed +// size exceeds the per-file limit is rejected instead of being read into +// memory unbounded (zip-bomb protection, see issue #6917). +func TestImportZipTLSTooLarge(t *testing.T) { + meta, err := json.Marshal(Metadata{ + Endpoints: map[string]any{ + "ep1": endpoint{Foo: "bar"}, + }, + Metadata: context{Bar: "baz"}, + Name: "source", + }) + assert.NilError(t, err) + + buf := new(bytes.Buffer) + w := zip.NewWriter(buf) + + mf, err := w.Create("meta.json") + assert.NilError(t, err) + _, err = mf.Write(meta) + assert.NilError(t, err) + + tf, err := w.Create(path.Join("tls", "docker", "ca.pem")) + assert.NilError(t, err) + // Write well over the per-file cap; zeros compress to a tiny archive + // so the outer archive-size cap is not hit first. + oversized := make([]byte, 2*maxAllowedFileSizeToImport) + _, err = tf.Write(oversized) + assert.NilError(t, err) + assert.NilError(t, w.Close()) + + s := New(t.TempDir(), testCfg) + err = Import("zipBomb", s, bytes.NewReader(buf.Bytes())) + assert.ErrorContains(t, err, "tls file exceeds maximum allowed size") +} + func TestImportZipInvalid(t *testing.T) { testDir := t.TempDir() zf := path.Join(testDir, "test.zip")