Skip to content

Don't pre-allocate the Type1 /Subrs array from the declared count#32062

Open
vsaraikin wants to merge 1 commit into
matplotlib:mainfrom
vsaraikin:fix/type1font-subrs-preallocation
Open

Don't pre-allocate the Type1 /Subrs array from the declared count#32062
vsaraikin wants to merge 1 commit into
matplotlib:mainfrom
vsaraikin:fix/type1font-subrs-preallocation

Conversation

@vsaraikin

Copy link
Copy Markdown

Fixes #31962.

When parsing a Type1 font, _parse_subrs read the declared /Subrs count and immediately did array = [None] * count, before reading the body. The count comes straight from the font file, so a malformed font can declare a huge count in a handful of bytes and force a large allocation before anything is validated. _parse_charstrings already avoids this by accumulating into a dict, so this just brings _parse_subrs in line.

The change accumulates the parsed entries into a dict and only allocates the count-sized result list once the body has actually been read.

The regression test builds a tiny font that declares /Subrs 5000000 array with an empty body. On master that allocates ~40 MB ([None] * 5000000) before the parse fails; with the change the parse still fails the same way but peak allocation stays small. The test uses tracemalloc and asserts the peak stays under 5 MB.

_parse_subrs allocated [None] * count from the count declared in the font
before reading the body, so a malformed font declaring a huge count could
force a large allocation before being rejected. Accumulate entries into a
dict and build the list after the body parses, matching _parse_charstrings.
@github-actions

Copy link
Copy Markdown

Thank you for opening your first PR into Matplotlib!

If you have not heard from us in a week or so, please leave a new comment below and that should bring it to our attention. Most of our reviewers are volunteers and sometimes things fall through the cracks. We also ask that you please finish addressing any review comments on this PR and wait for it to be merged (or closed) before opening a new one, as it can be a valuable learning experience to go through the review process.

You can also join us on discourse chat for real-time discussion.

For details on testing, writing docs, and our review process, please see the developer guide.
Please let us know if (and how) you use AI, it will help us give you better feedback on your PR.

We strive to be a welcoming and open project. Please follow our Code of Conduct.

Comment on lines +619 to +620
# force a large allocation before it is rejected; _parse_charstrings
# already avoids this by accumulating into a dict.

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.

Suggested change
# force a large allocation before it is rejected; _parse_charstrings
# already avoids this by accumulating into a dict.
# force a large allocation before it is rejected

This is not relevant here. Also (without being expert in this part of the codebase), _parse_charstrings actually returns a dict, so accumulting in the dict is natural there. Whereas here it an explicit measure to prevent pre-allocation.

path.write_bytes(pfa)

tracemalloc.start()
with pytest.raises(StopIteration):

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.

Am I correct that this StopIteration is raised from one of the next() calls in the for-loop?

Should we catch that and raise a more appropriate error such as

try:
    for _ in range(count):
        ...
except StopIteration:
    raise RuntimeError("Malformed Type1 font file: Incomplete /Subrs")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Type1Font allocates /Subrs array from declared count before validating the body

3 participants