mirror of
https://github.com/qpdf/qpdf.git
synced 2024-12-22 19:08:59 +00:00
Detect loops in /Pages structure
Pushing inherited objects to pages and getting all pages were both prone to stack overflow infinite loops if there were loops in the Pages dictionary. There is a general weakness in the code in that any part of the code that traverses the Pages structure would be prone to this and would have to implement its own loop detection. A more robust fix may provide some general method for handling the Pages structure, but it's probably not worth doing. Note: addition of *Internal2 private functions was done rather than changing signatures of existing methods to avoid breaking compatibility.
This commit is contained in:
parent
28a9df5119
commit
a11549a566
@ -1,5 +1,9 @@
|
|||||||
2015-02-21 Jay Berkenbilt <ejb@ql.org>
|
2015-02-21 Jay Berkenbilt <ejb@ql.org>
|
||||||
|
|
||||||
|
* Detect loops in Pages structure. Thanks to Gynvael Coldwind and
|
||||||
|
Mateusz Jurczyk of the Google Security Team for providing a sample
|
||||||
|
file with this problem.
|
||||||
|
|
||||||
* Prevent buffer overrun when converting a password to an
|
* Prevent buffer overrun when converting a password to an
|
||||||
encryption key. Thanks to Gynvael Coldwind and Mateusz Jurczyk of
|
encryption key. Thanks to Gynvael Coldwind and Mateusz Jurczyk of
|
||||||
the Google Security Team for providing a sample file with this
|
the Google Security Team for providing a sample file with this
|
||||||
|
3
TODO
3
TODO
@ -1,6 +1,9 @@
|
|||||||
5.2.0
|
5.2.0
|
||||||
=====
|
=====
|
||||||
|
|
||||||
|
* Before release: remember to bump minor shared library version since
|
||||||
|
new methods were added (even though private).
|
||||||
|
|
||||||
* Figure out what about a3576a73593987b26cd3eff346f8f7c11f713cbd
|
* Figure out what about a3576a73593987b26cd3eff346f8f7c11f713cbd
|
||||||
broke binary compatibility.
|
broke binary compatibility.
|
||||||
|
|
||||||
|
@ -668,6 +668,9 @@ class QPDF
|
|||||||
|
|
||||||
void getAllPagesInternal(QPDFObjectHandle cur_pages,
|
void getAllPagesInternal(QPDFObjectHandle cur_pages,
|
||||||
std::vector<QPDFObjectHandle>& result);
|
std::vector<QPDFObjectHandle>& result);
|
||||||
|
void getAllPagesInternal2(QPDFObjectHandle cur_pages,
|
||||||
|
std::vector<QPDFObjectHandle>& result,
|
||||||
|
std::set<QPDFObjGen>& visited);
|
||||||
void insertPage(QPDFObjectHandle newpage, int pos);
|
void insertPage(QPDFObjectHandle newpage, int pos);
|
||||||
int findPage(QPDFObjGen const& og);
|
int findPage(QPDFObjGen const& og);
|
||||||
int findPage(QPDFObjectHandle& page);
|
int findPage(QPDFObjectHandle& page);
|
||||||
@ -1023,6 +1026,12 @@ class QPDF
|
|||||||
std::map<std::string, std::vector<QPDFObjectHandle> >&,
|
std::map<std::string, std::vector<QPDFObjectHandle> >&,
|
||||||
std::vector<QPDFObjectHandle>& all_pages,
|
std::vector<QPDFObjectHandle>& all_pages,
|
||||||
bool allow_changes, bool warn_skipped_keys);
|
bool allow_changes, bool warn_skipped_keys);
|
||||||
|
void pushInheritedAttributesToPageInternal2(
|
||||||
|
QPDFObjectHandle,
|
||||||
|
std::map<std::string, std::vector<QPDFObjectHandle> >&,
|
||||||
|
std::vector<QPDFObjectHandle>& all_pages,
|
||||||
|
bool allow_changes, bool warn_skipped_keys,
|
||||||
|
std::set<QPDFObjGen>& visited);
|
||||||
void updateObjectMaps(ObjUser const& ou, QPDFObjectHandle oh);
|
void updateObjectMaps(ObjUser const& ou, QPDFObjectHandle oh);
|
||||||
void updateObjectMapsInternal(ObjUser const& ou, QPDFObjectHandle oh,
|
void updateObjectMapsInternal(ObjUser const& ou, QPDFObjectHandle oh,
|
||||||
std::set<QPDFObjGen>& visited, bool top);
|
std::set<QPDFObjGen>& visited, bool top);
|
||||||
|
@ -174,6 +174,30 @@ QPDF::pushInheritedAttributesToPageInternal(
|
|||||||
std::vector<QPDFObjectHandle>& pages,
|
std::vector<QPDFObjectHandle>& pages,
|
||||||
bool allow_changes, bool warn_skipped_keys)
|
bool allow_changes, bool warn_skipped_keys)
|
||||||
{
|
{
|
||||||
|
std::set<QPDFObjGen> visited;
|
||||||
|
pushInheritedAttributesToPageInternal2(
|
||||||
|
cur_pages, key_ancestors, pages, allow_changes,
|
||||||
|
warn_skipped_keys, visited);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
QPDF::pushInheritedAttributesToPageInternal2(
|
||||||
|
QPDFObjectHandle cur_pages,
|
||||||
|
std::map<std::string, std::vector<QPDFObjectHandle> >& key_ancestors,
|
||||||
|
std::vector<QPDFObjectHandle>& pages,
|
||||||
|
bool allow_changes, bool warn_skipped_keys,
|
||||||
|
std::set<QPDFObjGen>& visited)
|
||||||
|
{
|
||||||
|
QPDFObjGen this_og = cur_pages.getObjGen();
|
||||||
|
if (visited.count(this_og) > 0)
|
||||||
|
{
|
||||||
|
throw QPDFExc(
|
||||||
|
qpdf_e_pages, this->file->getName(),
|
||||||
|
this->last_object_description, 0,
|
||||||
|
"Loop detected in /Pages structure (inherited attributes)");
|
||||||
|
}
|
||||||
|
visited.insert(this_og);
|
||||||
|
|
||||||
// Extract the underlying dictionary object
|
// Extract the underlying dictionary object
|
||||||
std::string type = cur_pages.getKey("/Type").getName();
|
std::string type = cur_pages.getKey("/Type").getName();
|
||||||
|
|
||||||
@ -259,9 +283,9 @@ QPDF::pushInheritedAttributesToPageInternal(
|
|||||||
int n = kids.getArrayNItems();
|
int n = kids.getArrayNItems();
|
||||||
for (int i = 0; i < n; ++i)
|
for (int i = 0; i < n; ++i)
|
||||||
{
|
{
|
||||||
pushInheritedAttributesToPageInternal(
|
pushInheritedAttributesToPageInternal2(
|
||||||
kids.getArrayItem(i), key_ancestors, pages,
|
kids.getArrayItem(i), key_ancestors, pages,
|
||||||
allow_changes, warn_skipped_keys);
|
allow_changes, warn_skipped_keys, visited);
|
||||||
}
|
}
|
||||||
|
|
||||||
// For each inheritable key, pop the stack. If the stack
|
// For each inheritable key, pop the stack. If the stack
|
||||||
@ -318,6 +342,7 @@ QPDF::pushInheritedAttributesToPageInternal(
|
|||||||
this->file->getLastOffset(),
|
this->file->getLastOffset(),
|
||||||
"invalid Type " + type + " in page tree");
|
"invalid Type " + type + " in page tree");
|
||||||
}
|
}
|
||||||
|
visited.erase(this_og);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -56,6 +56,24 @@ void
|
|||||||
QPDF::getAllPagesInternal(QPDFObjectHandle cur_pages,
|
QPDF::getAllPagesInternal(QPDFObjectHandle cur_pages,
|
||||||
std::vector<QPDFObjectHandle>& result)
|
std::vector<QPDFObjectHandle>& result)
|
||||||
{
|
{
|
||||||
|
std::set<QPDFObjGen> visited;
|
||||||
|
getAllPagesInternal2(cur_pages, result, visited);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
QPDF::getAllPagesInternal2(QPDFObjectHandle cur_pages,
|
||||||
|
std::vector<QPDFObjectHandle>& result,
|
||||||
|
std::set<QPDFObjGen>& visited)
|
||||||
|
{
|
||||||
|
QPDFObjGen this_og = cur_pages.getObjGen();
|
||||||
|
if (visited.count(this_og) > 0)
|
||||||
|
{
|
||||||
|
throw QPDFExc(
|
||||||
|
qpdf_e_pages, this->file->getName(),
|
||||||
|
this->last_object_description, 0,
|
||||||
|
"Loop detected in /Pages structure (getAllPages)");
|
||||||
|
}
|
||||||
|
visited.insert(this_og);
|
||||||
std::string type;
|
std::string type;
|
||||||
QPDFObjectHandle type_key = cur_pages.getKey("/Type");
|
QPDFObjectHandle type_key = cur_pages.getKey("/Type");
|
||||||
if (type_key.isName())
|
if (type_key.isName())
|
||||||
@ -76,7 +94,7 @@ QPDF::getAllPagesInternal(QPDFObjectHandle cur_pages,
|
|||||||
int n = kids.getArrayNItems();
|
int n = kids.getArrayNItems();
|
||||||
for (int i = 0; i < n; ++i)
|
for (int i = 0; i < n; ++i)
|
||||||
{
|
{
|
||||||
getAllPagesInternal(kids.getArrayItem(i), result);
|
getAllPagesInternal2(kids.getArrayItem(i), result, visited);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (type == "/Page")
|
else if (type == "/Page")
|
||||||
@ -90,6 +108,7 @@ QPDF::getAllPagesInternal(QPDFObjectHandle cur_pages,
|
|||||||
this->file->getLastOffset(),
|
this->file->getLastOffset(),
|
||||||
"invalid Type " + type + " in page tree");
|
"invalid Type " + type + " in page tree");
|
||||||
}
|
}
|
||||||
|
visited.erase(this_og);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -199,7 +199,7 @@ $td->runtest("remove page we don't have",
|
|||||||
show_ntests();
|
show_ntests();
|
||||||
# ----------
|
# ----------
|
||||||
$td->notify("--- Miscellaneous Tests ---");
|
$td->notify("--- Miscellaneous Tests ---");
|
||||||
$n_tests += 75;
|
$n_tests += 76;
|
||||||
|
|
||||||
$td->runtest("qpdf version",
|
$td->runtest("qpdf version",
|
||||||
{$td->COMMAND => "qpdf --version"},
|
{$td->COMMAND => "qpdf --version"},
|
||||||
@ -566,6 +566,10 @@ $td->runtest("ensure arguments to R are direct",
|
|||||||
{$td->COMMAND => "qpdf --check indirect-r-arg.pdf"},
|
{$td->COMMAND => "qpdf --check indirect-r-arg.pdf"},
|
||||||
{$td->FILE => "indirect-r-arg.out", $td->EXIT_STATUS => 2},
|
{$td->FILE => "indirect-r-arg.out", $td->EXIT_STATUS => 2},
|
||||||
$td->NORMALIZE_NEWLINES);
|
$td->NORMALIZE_NEWLINES);
|
||||||
|
$td->runtest("detect loops in pages structure",
|
||||||
|
{$td->COMMAND => "qpdf --check pages-loop.pdf"},
|
||||||
|
{$td->FILE => "pages-loop.out", $td->EXIT_STATUS => 2},
|
||||||
|
$td->NORMALIZE_NEWLINES);
|
||||||
|
|
||||||
show_ntests();
|
show_ntests();
|
||||||
# ----------
|
# ----------
|
||||||
|
5
qpdf/qtest/qpdf/pages-loop.out
Normal file
5
qpdf/qtest/qpdf/pages-loop.out
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
checking pages-loop.pdf
|
||||||
|
PDF Version: 1.3
|
||||||
|
File is not encrypted
|
||||||
|
File is not linearized
|
||||||
|
pages-loop.pdf (object 3 0): Loop detected in /Pages structure (getAllPages)
|
102
qpdf/qtest/qpdf/pages-loop.pdf
Normal file
102
qpdf/qtest/qpdf/pages-loop.pdf
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
%PDF-1.3
|
||||||
|
%¿÷¢þ
|
||||||
|
%QDF-1.0
|
||||||
|
|
||||||
|
%% Original object ID: 1 0
|
||||||
|
1 0 obj
|
||||||
|
<<
|
||||||
|
/Pages 2 0 R
|
||||||
|
/Type /Catalog
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
|
||||||
|
%% Original object ID: 2 0
|
||||||
|
2 0 obj
|
||||||
|
<<
|
||||||
|
/Count 1
|
||||||
|
/Kids [
|
||||||
|
3 0 R
|
||||||
|
2 0 R
|
||||||
|
]
|
||||||
|
/Type /Pages
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
|
||||||
|
%% Page 1
|
||||||
|
%% Original object ID: 3 0
|
||||||
|
3 0 obj
|
||||||
|
<<
|
||||||
|
/Contents 4 0 R
|
||||||
|
/MediaBox [
|
||||||
|
0
|
||||||
|
0
|
||||||
|
612
|
||||||
|
792
|
||||||
|
]
|
||||||
|
/Parent 2 0 R
|
||||||
|
/Resources <<
|
||||||
|
/Font <<
|
||||||
|
/F1 6 0 R
|
||||||
|
>>
|
||||||
|
/ProcSet 7 0 R
|
||||||
|
>>
|
||||||
|
/Type /Page
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
|
||||||
|
%% Contents for page 1
|
||||||
|
%% Original object ID: 4 0
|
||||||
|
4 0 obj
|
||||||
|
<<
|
||||||
|
/Length 5 0 R
|
||||||
|
>>
|
||||||
|
stream
|
||||||
|
BT
|
||||||
|
/F1 24 Tf
|
||||||
|
72 720 Td
|
||||||
|
(Potato) Tj
|
||||||
|
ET
|
||||||
|
endstream
|
||||||
|
endobj
|
||||||
|
|
||||||
|
5 0 obj
|
||||||
|
44
|
||||||
|
endobj
|
||||||
|
|
||||||
|
%% Original object ID: 6 0
|
||||||
|
6 0 obj
|
||||||
|
<<
|
||||||
|
/BaseFont /Helvetica
|
||||||
|
/Encoding /WinAnsiEncoding
|
||||||
|
/Name /F1
|
||||||
|
/Subtype /Type1
|
||||||
|
/Type /Font
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
|
||||||
|
%% Original object ID: 5 0
|
||||||
|
7 0 obj
|
||||||
|
[
|
||||||
|
/PDF
|
||||||
|
/Text
|
||||||
|
]
|
||||||
|
endobj
|
||||||
|
|
||||||
|
xref
|
||||||
|
0 8
|
||||||
|
0000000000 65535 f
|
||||||
|
0000000052 00000 n
|
||||||
|
0000000133 00000 n
|
||||||
|
0000000252 00000 n
|
||||||
|
0000000494 00000 n
|
||||||
|
0000000593 00000 n
|
||||||
|
0000000639 00000 n
|
||||||
|
0000000784 00000 n
|
||||||
|
trailer <<
|
||||||
|
/Root 1 0 R
|
||||||
|
/Size 8
|
||||||
|
/ID [<395875d4235973eebbade9c7e9e7f857><395875d4235973eebbade9c7e9e7f857>]
|
||||||
|
>>
|
||||||
|
startxref
|
||||||
|
819
|
||||||
|
%%EOF
|
Loading…
Reference in New Issue
Block a user