Skip to content

embeddedfs/vmdk: limit zlib grain decompression to prevent memory exhaustion#1993

Open
adilburaksen wants to merge 2 commits intogoogle:mainfrom
adilburaksen:fix/vmdk-zlib-decompression-limit
Open

embeddedfs/vmdk: limit zlib grain decompression to prevent memory exhaustion#1993
adilburaksen wants to merge 2 commits intogoogle:mainfrom
adilburaksen:fix/vmdk-zlib-decompression-limit

Conversation

@adilburaksen
Copy link
Copy Markdown

Summary

convertStreamOptimizedExtent calls io.ReadAll on a zlib reader for
compressed grains with no decompression size limit. A crafted
stream-optimized VMDK with a single grain whose payload compresses at
~1000:1 ratio (e.g., zeros) causes ~1.7 GB of heap allocation from a
512 KB input file. The function returns nil on success, making the
memory spike silent.

Root cause (vmdk.go:320, before this fix):
```go
dec, derr := io.ReadAll(zr) // no size limit
```

The else if size < uint32(grainBytes) || size > uint32(grainBytes)
branch is equivalent to size != grainBytes and triggers for all
compressed grains, which is the normal case in stream-optimized VMDKs.

Fix

Replace io.ReadAll(zr) with io.ReadAll(io.LimitReader(zr, grainBytes+1))
and return an error if the decompressed data exceeds the expected grain size.
A valid compressed grain decompresses to exactly grainBytes; anything larger
indicates a malformed or malicious file.

Testing

TestConvertVMDKZlibBombRejected crafts a minimal malicious VMDK in pure Go
(1 MB of zeros compressed to ~1 KB, far exceeding grainBytes = 65536) and
confirms the fixed code returns an error rather than allocating memory.

Before fix: TotalAlloc delta: 1735 MB from a 512 KB file.
After fix: TotalAlloc delta: 0 MB, immediate error returned.

…austion

A stream-optimized VMDK grain whose compressed payload decompresses beyond
one grain size (grainBytes) caused io.ReadAll to allocate unbounded memory.
A 512 KB crafted .vmdk could trigger ~1.7 GB of heap allocation with no
error returned to the caller.

Fix: replace io.ReadAll(zr) with io.ReadAll(io.LimitReader(zr, grainBytes+1))
and return an error if the decompressed output exceeds the expected grain size.

Add TestConvertVMDKZlibBombRejected to confirm the bomb is rejected.
@adilburaksen
Copy link
Copy Markdown
Author

Hi! Just a friendly ping — all CI checks are passing. Happy to address any review feedback whenever you get a chance. Thanks for your time!

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.

1 participant