Moderate severityNVD Advisory· Published Feb 27, 2026· Updated Mar 3, 2026
Manipulated RunLengthDecode streams can exhaust RAM
CVE-2026-28351
Description
pypdf is a free and open-source pure-python PDF library. Prior to version 6.7.4, an attacker who uses this vulnerability can craft a PDF which leads to large memory usage. This requires parsing the content stream using the RunLengthDecode filter. This has been fixed in pypdf 6.7.4. As a workaround, consider applying the changes from PR #3664.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
pypdfPyPI | < 6.7.4 | 6.7.4 |
Affected products
1Patches
1f309c6003746SEC: Allow limiting output length for RunLengthDecode filter (#3664)
3 files changed · +25 −2
docs/user/security.md+1 −0 modified@@ -13,6 +13,7 @@ aware of the possible side effects, you can modify the following constants which * `pypdf.filters.ZLIB_MAX_OUTPUT_LENGTH` for the *FlateDecode* filter (zlib compression) * `pypdf.filters.LZW_MAX_OUTPUT_LENGTH` for the *LZWDecode* filter (LZW compression) +* `pypdf.filters.RUN_LENGTH_MAX_OUTPUT_LENGTH` for the *RunLengthDecode* filter (run-length compression) For JBIG2 images, there is a similar parameter to limit the memory usage during decoding: `pypdf.filters.JBIG2_MAX_OUTPUT_LENGTH` It defaults to 75 MB as well.
pypdf/filters.py+7 −2 modified@@ -73,6 +73,7 @@ JBIG2_MAX_OUTPUT_LENGTH = 75_000_000 LZW_MAX_OUTPUT_LENGTH = 75_000_000 +RUN_LENGTH_MAX_OUTPUT_LENGTH = 75_000_000 ZLIB_MAX_OUTPUT_LENGTH = 75_000_000 ZLIB_MAX_RECOVERY_INPUT_LENGTH = 5_000_000 @@ -408,16 +409,17 @@ def decode( """ lst = [] index = 0 + data_length = len(data) + total_length = 0 while True: - if index >= len(data): + if index >= data_length: logger_warning( "missing EOD in RunLengthDecode, check if output is OK", __name__ ) break # Reached end of string without an EOD length = data[index] index += 1 if length == 128: - data_length = len(data) if index < data_length: # We should first check, if we have an inner stream from a multi-encoded # stream with a faulty trailing newline that we can decode properly. @@ -442,6 +444,9 @@ def decode( length = 257 - length lst.append(bytes((data[index],)) * length) index += 1 + total_length += length + if total_length > RUN_LENGTH_MAX_OUTPUT_LENGTH: + raise LimitReachedError("Limit reached while decompressing.") return b"".join(lst)
tests/test_filters.py+17 −0 modified@@ -1026,3 +1026,20 @@ def test_flatedecode__columns_is_zero(): with pytest.raises(expected_exception=PdfReadError, match=r"^Expected positive number for /Columns, got 0!$"): codec.decode(codec.encode(data), parameters) + + +def test_runlengthdecode__decode_limit(): + uncompressed_size = 76 * 1024 * 1024 # 76 MB target + runs = uncompressed_size // 128 + encoded = (b"\x81A" * runs) + b"\x80" + + with pytest.raises(expected_exception=LimitReachedError, match=r"^Limit reached while decompressing\.$"): + RunLengthDecode.decode(encoded) + + uncompressed_size = 5 * 1024 + runs = uncompressed_size // 128 + encoded = (b"\x81A" * runs) + b"\x80" + + # Use a very low limit for this exact comparison, otherwise *pytest* takes ages to render a failure diff. + with mock.patch("pypdf.filters.RUN_LENGTH_MAX_OUTPUT_LENGTH", uncompressed_size): + assert RunLengthDecode.decode(encoded) == b"A" * uncompressed_size
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-f2v5-7jq9-h8cgghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-28351ghsaADVISORY
- github.com/py-pdf/pypdf/commit/f309c6003746414dc7b5048c19e6d879ff2dc858ghsax_refsource_MISCWEB
- github.com/py-pdf/pypdf/pull/3664ghsax_refsource_MISCWEB
- github.com/py-pdf/pypdf/releases/tag/6.7.4ghsax_refsource_MISCWEB
- github.com/py-pdf/pypdf/security/advisories/GHSA-f2v5-7jq9-h8cgghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.