Skip to content

Cap the amount of data util.Unzip extracts - #4207

Open
4RH1T3CT0R7 wants to merge 2 commits into
micro-editor:masterfrom
4RH1T3CT0R7:fix/4161
Open

Cap the amount of data util.Unzip extracts#4207
4RH1T3CT0R7 wants to merge 2 commits into
micro-editor:masterfrom
4RH1T3CT0R7:fix/4161

Conversation

@4RH1T3CT0R7

@4RH1T3CT0R7 4RH1T3CT0R7 commented Sep 3, 2026

Copy link
Copy Markdown

util.Unzip in internal/util/util.go copies each archive entry with io.Copy and never checks how much it has written, so a plugin that unpacks a downloaded archive can expand a small zip bomb until the disk is full. The function already rejects entries whose path escapes the destination, but had no equivalent check on size.

Each Unzip call now has a 1 GiB budget, and an entry whose declared uncompressed size does not fit fails with file too large: <path> before anything is written; the declared size is trustworthy because archive/zip refuses to read past it. Parent directories are now created with 0755 instead of the entry's file mode, which left them unenterable on Unix. New tests cover a normal archive, an overstated header and an understated one.

Fixes #4161

Unzip copied every archive entry with io.Copy and no bound on the
output, so a small crafted archive handed to a plugin could expand
until the disk filled. The function already rejects entries that
escape the destination directory; this adds the matching check for
size.

Extraction now has a 1 GiB budget per archive. Each entry's declared
uncompressed size is checked against what is left before anything is
written, and the bytes actually written are subtracted afterwards.
archive/zip refuses to read past the size declared in an entry's
header, so the declared size is a real upper bound and no second
limit is needed on the copy. Exceeding the budget returns an error in
the same style as the traversal check.

The new test for a plain archive also showed that Unzip created the
parent directory of a file entry with the file's own mode. Archives
without directory entries, such as ones written by Go's archive/zip,
ended up with an unsearchable 0644 directory on Unix and every file
inside it failed to open. Parent directories now get 0755, the same
as the destination directory.

Fixes micro-editor#4161
Comment thread internal/util/util.go Outdated
@@ -639,6 +642,8 @@ func Unzip(src, dest string) error {

os.MkdirAll(dest, 0755)

@JoeKar JoeKar Sep 3, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of 0755 I'm thinking about a DirMode analogue to:

const FileMode os.FileMode = 0666

This is then valid for the newly introduced 0755 in line 672 too as well as in:

dirPerm := os.FileMode(0755)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, done in ff05d1a: a DirMode constant next to FileMode, used for both MkdirAll calls in Unzip and for dirPerm in the plugin installer.

Unzip and the plugin installer both created directories with a bare
0755, so give it a DirMode constant next to FileMode and use it in all
three places.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants