mirror of
https://github.com/qpdf/qpdf.git
synced 2024-12-22 10:58:58 +00:00
Detect recursion loops resolving objects (fixes #51)
During parsing of an object, sometimes parts of the object have to be resolved. An example is stream lengths. If such an object directly or indirectly points to the object being parsed, it can cause an infinite loop. Guard against all cases of re-entrant resolution of objects.
This commit is contained in:
parent
afe0242b26
commit
701b518d5c
@ -1,5 +1,10 @@
|
|||||||
2017-07-26 Jay Berkenbilt <ejb@ql.org>
|
2017-07-26 Jay Berkenbilt <ejb@ql.org>
|
||||||
|
|
||||||
|
* Detect infinite loops while resolving objects. This could happen
|
||||||
|
if something inside an object that had to be resolved during
|
||||||
|
parsing, such as a stream length, recursively referenced the
|
||||||
|
object being resolved.
|
||||||
|
|
||||||
* CVE-2017-9208: Handle references to and appearance of object 0
|
* CVE-2017-9208: Handle references to and appearance of object 0
|
||||||
as a special case. Object 0 is not allowed, and qpdf was using it
|
as a special case. Object 0 is not allowed, and qpdf was using it
|
||||||
internally to represent direct objects.
|
internally to represent direct objects.
|
||||||
|
@ -603,6 +603,25 @@ class QPDF
|
|||||||
int gen;
|
int gen;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ResolveRecorder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ResolveRecorder(QPDF* qpdf, QPDFObjGen const& og) :
|
||||||
|
qpdf(qpdf),
|
||||||
|
og(og)
|
||||||
|
{
|
||||||
|
qpdf->resolving.insert(og);
|
||||||
|
}
|
||||||
|
virtual ~ResolveRecorder()
|
||||||
|
{
|
||||||
|
this->qpdf->resolving.erase(og);
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
QPDF* qpdf;
|
||||||
|
QPDFObjGen og;
|
||||||
|
};
|
||||||
|
friend class ResolveRecorder;
|
||||||
|
|
||||||
void parse(char const* password);
|
void parse(char const* password);
|
||||||
void warn(QPDFExc const& e);
|
void warn(QPDFExc const& e);
|
||||||
void setTrailer(QPDFObjectHandle obj);
|
void setTrailer(QPDFObjectHandle obj);
|
||||||
@ -1065,6 +1084,7 @@ class QPDF
|
|||||||
std::map<QPDFObjGen, QPDFXRefEntry> xref_table;
|
std::map<QPDFObjGen, QPDFXRefEntry> xref_table;
|
||||||
std::set<int> deleted_objects;
|
std::set<int> deleted_objects;
|
||||||
std::map<QPDFObjGen, ObjCache> obj_cache;
|
std::map<QPDFObjGen, ObjCache> obj_cache;
|
||||||
|
std::set<QPDFObjGen> resolving;
|
||||||
QPDFObjectHandle trailer;
|
QPDFObjectHandle trailer;
|
||||||
std::vector<QPDFObjectHandle> all_pages;
|
std::vector<QPDFObjectHandle> all_pages;
|
||||||
std::map<QPDFObjGen, int> pageobj_to_pages_pos;
|
std::map<QPDFObjGen, int> pageobj_to_pages_pos;
|
||||||
|
@ -1471,6 +1471,21 @@ QPDF::resolve(int objid, int generation)
|
|||||||
// to insert things into the object cache that don't actually
|
// to insert things into the object cache that don't actually
|
||||||
// exist in the file.
|
// exist in the file.
|
||||||
QPDFObjGen og(objid, generation);
|
QPDFObjGen og(objid, generation);
|
||||||
|
if (this->resolving.count(og))
|
||||||
|
{
|
||||||
|
// This can happen if an object references itself directly or
|
||||||
|
// indirectly in some key that has to be resolved during
|
||||||
|
// object parsing, such as stream length.
|
||||||
|
QTC::TC("qpdf", "QPDF recursion loop in resolve");
|
||||||
|
warn(QPDFExc(qpdf_e_damaged_pdf, this->file->getName(),
|
||||||
|
"", this->file->getLastOffset(),
|
||||||
|
"loop detected resolving object " +
|
||||||
|
QUtil::int_to_string(objid) + " " +
|
||||||
|
QUtil::int_to_string(generation)));
|
||||||
|
return new QPDF_Null;
|
||||||
|
}
|
||||||
|
ResolveRecorder rr(this, og);
|
||||||
|
|
||||||
if (! this->obj_cache.count(og))
|
if (! this->obj_cache.count(og))
|
||||||
{
|
{
|
||||||
if (! this->xref_table.count(og))
|
if (! this->xref_table.count(og))
|
||||||
|
@ -276,3 +276,4 @@ qpdf-c called qpdf_set_deterministic_ID 0
|
|||||||
QPDFObjectHandle indirect with 0 objid 0
|
QPDFObjectHandle indirect with 0 objid 0
|
||||||
QPDF object id 0 0
|
QPDF object id 0 0
|
||||||
QPDF caught recursive xref reconstruction 0
|
QPDF caught recursive xref reconstruction 0
|
||||||
|
QPDF recursion loop in resolve 0
|
||||||
|
@ -206,7 +206,7 @@ $td->runtest("remove page we don't have",
|
|||||||
show_ntests();
|
show_ntests();
|
||||||
# ----------
|
# ----------
|
||||||
$td->notify("--- Miscellaneous Tests ---");
|
$td->notify("--- Miscellaneous Tests ---");
|
||||||
$n_tests += 81;
|
$n_tests += 82;
|
||||||
|
|
||||||
$td->runtest("qpdf version",
|
$td->runtest("qpdf version",
|
||||||
{$td->COMMAND => "qpdf --version"},
|
{$td->COMMAND => "qpdf --version"},
|
||||||
@ -220,6 +220,7 @@ $td->runtest("C API: qpdf version",
|
|||||||
|
|
||||||
# Files to reproduce various bugs
|
# Files to reproduce various bugs
|
||||||
foreach my $d (
|
foreach my $d (
|
||||||
|
["51", "resolve loop"],
|
||||||
["99", "object 0"],
|
["99", "object 0"],
|
||||||
["99b", "object 0"],
|
["99b", "object 0"],
|
||||||
["100","xref reconstruction loop"],
|
["100","xref reconstruction loop"],
|
||||||
|
6
qpdf/qtest/qpdf/issue-51.out
Normal file
6
qpdf/qtest/qpdf/issue-51.out
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
WARNING: issue-51.pdf: reported number of objects (0) inconsistent with actual number of objects (9)
|
||||||
|
WARNING: issue-51.pdf (object 7 0, file position 553): expected endobj
|
||||||
|
WARNING: issue-51.pdf (object 1 0, file position 359): expected endobj
|
||||||
|
WARNING: issue-51.pdf (file position 70): loop detected resolving object 2 0
|
||||||
|
WARNING: issue-51.pdf (object 2 0, file position 71): attempting to recover stream length
|
||||||
|
issue-51.pdf (object 2 0, file position 71): unable to recover stream data
|
22
qpdf/qtest/qpdf/issue-51.pdf
Normal file
22
qpdf/qtest/qpdf/issue-51.pdf
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
%PDF-100000000000002 0 obj
|
||||||
|
<</Length 2 0 R/000000/00000000000>>
|
||||||
|
stream
|
||||||
|
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001 0 obj
|
||||||
|
<</0000000000000000 0 0 R/000000000 0 0 R/00000000[00000000000]/00000<</0/00000000000000000000000000000000>>/00000000 2 0 R>>000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007 0 obj
|
||||||
|
<</0000/0000000/00000 0 0 R
|
||||||
|
/0000000000[1 0 R 0000 null null 0]
|
||||||
|
/0000(00000)
|
||||||
|
>>0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000xref
|
||||||
|
0 9
|
||||||
|
0000000000 00000 f
|
||||||
|
0000000200 00000 n
|
||||||
|
0000000009 00000 n
|
||||||
|
0000000000 00000 n
|
||||||
|
0000000000 00000 n
|
||||||
|
0000000000 00000 n
|
||||||
|
0000000000 00000 n
|
||||||
|
0000000400 00000 n
|
||||||
|
0000000000 00000 n
|
||||||
|
trailer<</Size 0/Root 7 0 R>>startxref
|
||||||
|
740
|
||||||
|
%%EOF
|
Loading…
Reference in New Issue
Block a user