fix(usvg): limit SVGZ decompression size to prevent decompression bombs - #1139
Open
akashchamp wants to merge 1 commit into
Open
akashchamp wants to merge 1 commit into
akashchamp wants to merge 1 commit into
Conversation
`decompress_svgz` (used by `Tree::from_data`) called `GzDecoder::read_to_end` with no bound on the output size. Since GZip allows very high compression ratios, a tiny, maliciously crafted `.svgz` file can decompress into gigabytes of data and exhaust all available memory. A ~500 KiB crafted file was enough to produce 500 MiB in memory in under a second locally. Cap decompression at 1 GiB and return a new `Error::SvgzDecompressionLimitReached` (and the matching `RESVG_ERROR_SVGZ_DECOMPRESSION_LIMIT_REACHED` C API value) when a file would decompress past that limit, instead of reading to completion unconditionally. Legitimate SVGZ files, which are XML text and stay well under this size, are unaffected. Fixes linebender#1138
akashchamp
force-pushed
the
fix/svgz-decompression-bomb
branch
from
September 25, 2026 18:18
fe52ed9 to
ff20ac8
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
usvg::Tree::from_datadecompresses.svgz(GZip-compressed SVG) input viadecompress_svgz, which calledGzDecoder::read_to_endwith no bound on thedecompressed size:
GZip supports very high compression ratios, so a small, maliciously crafted
.svgzfile can decompress into an enormous amount of data, exhaustingavailable memory (a "decompression bomb" / zip-bomb style DoS). Any
application that calls
Tree::from_dataon untrusted input is affected.Publicly disclosed in #1138 (write-up:
https://fereidani.com/usvg-svgz-decompression-bomb-in-treefromdata).
I confirmed it locally: a ~498 KiB crafted
.svgzfile (highly compressible,repeated zero bytes) decompressed to 500 MiB in ~0.3s / ~580 MiB RSS with the
unpatched code. A larger crafted input reaches gigabytes just as easily.
Fix
Cap the number of bytes
decompress_svgzwill read out of the decoder at1 GiB. If more compressed data remains after hitting the cap, return a new
Error::SvgzDecompressionLimitReachedinstead of continuing to decompressunboundedly. The 1 GiB ceiling is generous for legitimate SVG (plain XML
text), so real-world
.svgzfiles are unaffected.Also added the corresponding C API error value
(
RESVG_ERROR_SVGZ_DECOMPRESSION_LIMIT_REACHED) incrates/c-api/lib.rs,resvg.h, andResvgQt.h, appended after the existing values to avoidchanging the numeric value of any existing
resvg_errorvariant (ABIstability for C/C++ consumers).
Testing
crates/usvg/src/parser/mod.rs::svgz_tests, unit-testing the newlimiting logic directly (with a small
limitparameter so the tests stayfast/cheap rather than allocating a real 1 GiB buffer): normal input under
the limit decodes correctly, input landing exactly at the limit is
accepted, and a crafted bomb exceeding the limit is rejected with
Error::SvgzDecompressionLimitReached.~500 KiB input, as described above) and confirmed the fix rejects a
crafted ~1.5 MiB input that would otherwise decompress to ~1.5 GiB, with
peak memory now capped at ~1 GiB instead of growing unbounded.
.svgzfixture(
crates/resvg/tests/resources/image.svgz) still decompresses correctly.cargo test --all --release(full workspace, matching CI'sTeststep):1731 + 3 + 28 + 31 + 1 tests, all passing.
cargo fmt --all --check,bash .github/copyright.sh: both pass.cargo build/cargo build --no-default-featuresincrates/c-api, andcargo check --no-default-featuresincrates/resvgandcrates/usvg(matching CI's other build steps): all pass.
Scope
Only touches
decompress_svgzand the error type it can now return(propagated through the existing
usvg::Errorenum and the C API'sresvg_errorenum), plus the new tests. No unrelated changes.Fixes #1138