Bug summary
The Type1 font parser trusts the declared /Subrs count and allocates a Python list of that size before validating that the following body actually contains that many subroutine definitions. A compact malformed Type1 font can therefore cause avoidable large allocation before parsing fails.
Code for reproduction
import pathlib
import resource
import tempfile
import time
from matplotlib._type1font import Type1Font
def main():
subrs_count = 5_000_000
plaintext = (
b"/FontName /LargeSubrsCount def\n"
b"/FontBBox [0 0 1 1] def\n"
+ f"/Subrs {subrs_count} array\n".encode("ascii")
)
encrypted_hex = Type1Font._encrypt(plaintext, "eexec").hex().encode("ascii")
pfa = (
b"%!PS-AdobeFont-1.0: LargeSubrsCount 001.000\n"
b"eexec\n"
+ encrypted_hex
+ b"\n"
+ (b"0" * 512)
+ b"\ncleartomark\n"
)
font_path = pathlib.Path(tempfile.mkdtemp()) / "large-subrs-count.pfa"
font_path.write_bytes(pfa)
rss_before = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
start = time.time()
try:
Type1Font(str(font_path))
except StopIteration:
parse_result = "body_rejected_after_allocation"
except MemoryError:
parse_result = "memory_error"
else:
raise RuntimeError("malformed font unexpectedly parsed")
elapsed = time.time() - start
rss_after = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
print(f"subrs_count={subrs_count}")
print(f"parse_result={parse_result}")
print(f"elapsed_seconds={elapsed:.3f}")
print(f"rss_before_kb={rss_before}")
print(f"rss_after_kb={rss_after}")
print(f"rss_delta_kb={rss_after - rss_before}")
if __name__ == "__main__":
main()
Actual outcome
In a source-checkout verification run, a compact generated PFA declaring /Subrs 5000000 array caused the parser to allocate based on the declared count before the malformed body was rejected:
subrs_count=5000000
parse_result=body_rejected_after_allocation
elapsed_seconds=0.009
rss_before_kb=45168
rss_after_kb=84208
rss_delta_kb=39040
Expected outcome
Matplotlib should validate the /Subrs body structure before allocating a count-sized list, or enforce a reasonable maximum /Subrs count before allocation.
Additional information
The relevant parser path is lib/matplotlib/_type1font.py:
def _parse_subrs(self, tokens, _data):
count_token = next(tokens)
...
count = count_token.value()
array = [None] * count
next(t for t in tokens if t.is_keyword('array'))
The allocation happens immediately after reading the count token. The parser only confirms the array keyword and the following dup entries after the list has already been allocated.
This report is intended as a robustness bug, not as a security vulnerability report. It requires processing an untrusted Type1 PFA/PFB font.
Operating system
Linux
Matplotlib Version
main branch source checkout; reproduced on commit 5a33910eb7827d8a6188ff42bc788951c015ff48; same parser shape was still present on current main commit 996f4e877f6a7a50a68e00879183b8db8761461d.
Matplotlib Backend
Not backend-specific
Python version
Python 3.12
Jupyter version
Not applicable
Installation
git checkout
Bug summary
The Type1 font parser trusts the declared
/Subrscount and allocates a Python list of that size before validating that the following body actually contains that many subroutine definitions. A compact malformed Type1 font can therefore cause avoidable large allocation before parsing fails.Code for reproduction
Actual outcome
In a source-checkout verification run, a compact generated PFA declaring
/Subrs 5000000 arraycaused the parser to allocate based on the declared count before the malformed body was rejected:Expected outcome
Matplotlib should validate the
/Subrsbody structure before allocating a count-sized list, or enforce a reasonable maximum/Subrscount before allocation.Additional information
The relevant parser path is
lib/matplotlib/_type1font.py:The allocation happens immediately after reading the count token. The parser only confirms the
arraykeyword and the followingdupentries after the list has already been allocated.This report is intended as a robustness bug, not as a security vulnerability report. It requires processing an untrusted Type1 PFA/PFB font.
Operating system
Linux
Matplotlib Version
main branch source checkout; reproduced on commit
5a33910eb7827d8a6188ff42bc788951c015ff48; same parser shape was still present on currentmaincommit996f4e877f6a7a50a68e00879183b8db8761461d.Matplotlib Backend
Not backend-specific
Python version
Python 3.12
Jupyter version
Not applicable
Installation
git checkout