pdfinfo: tolerate PDFs that overflow and underflow the graphics stack
This commit is contained in:
@@ -22,6 +22,7 @@ from enum import Enum
|
||||
from math import hypot, isclose
|
||||
from pathlib import Path
|
||||
from unittest.mock import Mock
|
||||
from warnings import warn
|
||||
import re
|
||||
|
||||
from pikepdf import PdfMatrix
|
||||
@@ -136,6 +137,13 @@ def _interpret_contents(contentstream, initial_shorthand=UNIT_SQUARE):
|
||||
page.
|
||||
|
||||
PDF units suit our needs so we initialize ctm to the identity matrix.
|
||||
|
||||
According to the PDF specification, the maximum stack depth is 32. Other
|
||||
viewers tolerate some amount beyond this. We issue a warning if the
|
||||
stack depth exceeds the spec limit and set a hard limit beyond this to
|
||||
bound our memory requirements. If the stack underflows behavior is
|
||||
undefined in the spec, but we just pretend nothing happened and leave the
|
||||
CTM unchanged.
|
||||
"""
|
||||
|
||||
stack = []
|
||||
@@ -150,18 +158,20 @@ def _interpret_contents(contentstream, initial_shorthand=UNIT_SQUARE):
|
||||
for n, graphobj in enumerate(_normalize_stack(
|
||||
pikepdf.parse_content_stream(contentstream, operator_whitelist))):
|
||||
operands, operator = graphobj
|
||||
|
||||
if operator == 'q':
|
||||
stack.append(ctm)
|
||||
if len(stack) > 32:
|
||||
raise RuntimeError(
|
||||
"PDF graphics stack overflow, operator %i" % n)
|
||||
if len(stack) > 32: # See docstring
|
||||
if len(stack) > 128:
|
||||
raise RuntimeError(
|
||||
"PDF graphics stack overflowed hard limit, operator %i" % n)
|
||||
warn("PDF graphics stack overflowed spec limit")
|
||||
elif operator == 'Q':
|
||||
try:
|
||||
ctm = stack.pop()
|
||||
except IndexError:
|
||||
raise RuntimeError(
|
||||
"PDF graphics stack underflow, operator %i" % n)
|
||||
# Keeping the ctm the same seems to be the only sensible thing
|
||||
# to do. Just pretend nothing happened, keep calm and carry on.
|
||||
warn("PDF graphics stack underflowed - PDF may be malformed")
|
||||
elif operator == 'cm':
|
||||
ctm = PdfMatrix(operands) @ ctm
|
||||
elif operator == 'Do':
|
||||
|
||||
+19
-1
@@ -27,7 +27,6 @@ import shutil
|
||||
import pytest
|
||||
import img2pdf
|
||||
import sys
|
||||
import PyPDF2 as pypdf
|
||||
import pikepdf
|
||||
import pickle
|
||||
|
||||
@@ -196,3 +195,22 @@ def test_corrupt_font_detection(resources, testfile):
|
||||
|
||||
pdf = pdfinfo.PdfInfo(filename, detailed_page_analysis=True)
|
||||
assert pdf[0].has_corrupt_text
|
||||
|
||||
|
||||
def test_stack_abuse():
|
||||
p = pikepdf.Pdf.new()
|
||||
|
||||
stream = pikepdf.Stream(p, b'q ' * 35)
|
||||
with pytest.warns(None) as record:
|
||||
pdfinfo._interpret_contents(stream)
|
||||
assert 'overflowed' in str(record[0].message)
|
||||
|
||||
stream = pikepdf.Stream(p, b'q Q Q Q Q')
|
||||
with pytest.warns(None) as record:
|
||||
pdfinfo._interpret_contents(stream)
|
||||
assert 'underflowed' in str(record[0].message)
|
||||
|
||||
stream = pikepdf.Stream(p, b'q ' * 135)
|
||||
with pytest.warns(None):
|
||||
with pytest.raises(RuntimeError):
|
||||
pdfinfo._interpret_contents(stream)
|
||||
|
||||
Reference in New Issue
Block a user