]> Cypherpunks repositories - gostls13.git/commitdiff
[release-branch.go1.25] archive/zip: reduce CPU usage in index construction
authorDamien Neil <dneil@google.com>
Wed, 5 Nov 2025 01:00:33 +0000 (17:00 -0800)
committerGopher Robot <gobot@golang.org>
Thu, 15 Jan 2026 18:15:01 +0000 (10:15 -0800)
Constructing the zip index (which is done once when first opening
a file in an archive) can consume large amounts of CPU when
processing deeply-nested directory paths.

Switch to a less inefficient algorithm.

Thanks to Jakub Ciolek for reporting this issue.

goos: darwin
goarch: arm64
pkg: archive/zip
cpu: Apple M4 Pro
                          │  /tmp/bench.0  │            /tmp/bench.1            │
                          │     sec/op     │   sec/op     vs base               │
ReaderOneDeepDir-14         25983.62m ± 2%   46.01m ± 2%  -99.82% (p=0.000 n=8)
ReaderManyDeepDirs-14          16.221 ± 1%    2.763 ± 6%  -82.96% (p=0.000 n=8)
ReaderManyShallowFiles-14      130.3m ± 1%   128.8m ± 2%   -1.20% (p=0.003 n=8)
geomean                         3.801        253.9m       -93.32%

Fixes #77102
Fixes CVE-2025-61728

Change-Id: I2c9c864be01b2a2769eb67fbab1b250aeb8f6c42
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/3060
Reviewed-by: Nicholas Husin <husin@google.com>
Reviewed-by: Neal Patel <nealpatel@google.com>
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/3327
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-on: https://go-review.googlesource.com/c/go/+/736724
TryBot-Bypass: Michael Pratt <mpratt@google.com>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
Auto-Submit: Michael Pratt <mpratt@google.com>

src/archive/zip/reader.go
src/archive/zip/reader_test.go

index 6b57f767fc849d7cd47428fb566f5a922a874aab..b2a4ed6042d5d579bd819093662144d6b13ab6d5 100644 (file)
@@ -834,7 +834,16 @@ func (r *Reader) initFileList() {
                                continue
                        }
 
-                       for dir := path.Dir(name); dir != "."; dir = path.Dir(dir) {
+                       dir := name
+                       for {
+                               if idx := strings.LastIndex(dir, "/"); idx < 0 {
+                                       break
+                               } else {
+                                       dir = dir[:idx]
+                               }
+                               if dirs[dir] {
+                                       break
+                               }
                                dirs[dir] = true
                        }
 
index 410b2d037e44d5671fdb6b35de9a3e1fa61cfbc8..4b1122269e007d7ab67c4b8de82db2af51c0310b 100644 (file)
@@ -9,6 +9,7 @@ import (
        "encoding/binary"
        "encoding/hex"
        "errors"
+       "fmt"
        "internal/obscuretestdata"
        "io"
        "io/fs"
@@ -1876,3 +1877,83 @@ func TestBaseOffsetPlusOverflow(t *testing.T) {
        // as the section reader offset & size were < 0.
        NewReader(bytes.NewReader(data), int64(len(data))+1875)
 }
+
+func BenchmarkReaderOneDeepDir(b *testing.B) {
+       var buf bytes.Buffer
+       zw := NewWriter(&buf)
+
+       for i := range 4000 {
+               name := strings.Repeat("a/", i) + "data"
+               zw.CreateHeader(&FileHeader{
+                       Name:   name,
+                       Method: Store,
+               })
+       }
+
+       if err := zw.Close(); err != nil {
+               b.Fatal(err)
+       }
+       data := buf.Bytes()
+
+       for b.Loop() {
+               zr, err := NewReader(bytes.NewReader(data), int64(len(data)))
+               if err != nil {
+                       b.Fatal(err)
+               }
+               zr.Open("does-not-exist")
+       }
+}
+
+func BenchmarkReaderManyDeepDirs(b *testing.B) {
+       var buf bytes.Buffer
+       zw := NewWriter(&buf)
+
+       for i := range 2850 {
+               name := fmt.Sprintf("%x", i)
+               name = strings.Repeat("/"+name, i+1)[1:]
+
+               zw.CreateHeader(&FileHeader{
+                       Name:   name,
+                       Method: Store,
+               })
+       }
+
+       if err := zw.Close(); err != nil {
+               b.Fatal(err)
+       }
+       data := buf.Bytes()
+
+       for b.Loop() {
+               zr, err := NewReader(bytes.NewReader(data), int64(len(data)))
+               if err != nil {
+                       b.Fatal(err)
+               }
+               zr.Open("does-not-exist")
+       }
+}
+
+func BenchmarkReaderManyShallowFiles(b *testing.B) {
+       var buf bytes.Buffer
+       zw := NewWriter(&buf)
+
+       for i := range 310000 {
+               name := fmt.Sprintf("%v", i)
+               zw.CreateHeader(&FileHeader{
+                       Name:   name,
+                       Method: Store,
+               })
+       }
+
+       if err := zw.Close(); err != nil {
+               b.Fatal(err)
+       }
+       data := buf.Bytes()
+
+       for b.Loop() {
+               zr, err := NewReader(bytes.NewReader(data), int64(len(data)))
+               if err != nil {
+                       b.Fatal(err)
+               }
+               zr.Open("does-not-exist")
+       }
+}