Skip to content

[BUG] util.Unzip copies archive entries without an output-size limit #4161

Description

@GG-Feng

Description

Description

util.Unzip rejects traversal entries with a destination path boundary check, but it copies each archive entry with io.Copy and does not appear to enforce a per-entry or total output-size limit.

Relevant code in internal/util/util.go:

path := filepath.Join(dest, f.Name)

if !strings.HasPrefix(path, filepath.Clean(dest)+string(os.PathSeparator)) {
    return fmt.Errorf("illegal file path: %s", path)
}

_, err = io.Copy(f, rc)

Local validation output:

=== RUN   TestReproH12_UnzipBombAndTraversalMitigation
    repro_high_test.go:57:   (a) traversal rejected by HasPrefix: illegal file path: /tmp/TestReproH12_UnzipBombAndTraversalMitigation3808501357/outside.txt
    repro_high_test.go:76:   (b) 2182-byte zip -> 2097152 bytes via io.Copy with NO LimitReader
    repro_high_test.go:77: H12 = SPLIT: traversal mitigated (HasPrefix, FP), zip bomb real (io.Copy no cap, TP)
--- PASS: TestReproH12_UnzipBombAndTraversalMitigation (0.01s)

Expected behavior:

Unzip should enforce a reasonable per-entry and/or total extracted-size limit, or allow callers to provide one.

Actual behavior:

Each entry is copied until EOF, so extracted output size is controlled entirely by the archive contents.

Suggested fix:

Add an extraction size budget, such as a maximum bytes-per-entry and total extracted bytes. The copy path could use a bounded reader/writer and return an error once the configured limit is exceeded.

Environment

Environment

  • Version: tested at commit 77de728
  • OS: Linux / Docker
  • Terminal: N/A

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions