Skip to content
Permalink
Browse files

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.
  • Loading branch information...
jberkenbilt committed Jul 26, 2017
1 parent afe0242 commit 701b518d5c56a1449825a3a37a716c58e05e1c3e
@@ -1,5 +1,10 @@
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
as a special case. Object 0 is not allowed, and qpdf was using it
internally to represent direct objects.
@@ -603,6 +603,25 @@ class QPDF
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 warn(QPDFExc const& e);
void setTrailer(QPDFObjectHandle obj);
@@ -1065,6 +1084,7 @@ class QPDF
std::map<QPDFObjGen, QPDFXRefEntry> xref_table;
std::set<int> deleted_objects;
std::map<QPDFObjGen, ObjCache> obj_cache;
std::set<QPDFObjGen> resolving;
QPDFObjectHandle trailer;
std::vector<QPDFObjectHandle> all_pages;
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
// exist in the file.
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->xref_table.count(og))
@@ -276,3 +276,4 @@ qpdf-c called qpdf_set_deterministic_ID 0
QPDFObjectHandle indirect with 0 objid 0
QPDF object id 0 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();
# ----------
$td->notify("--- Miscellaneous Tests ---");
$n_tests += 81;
$n_tests += 82;

$td->runtest("qpdf version",
{$td->COMMAND => "qpdf --version"},
@@ -220,6 +220,7 @@ $td->runtest("C API: qpdf version",

# Files to reproduce various bugs
foreach my $d (
["51", "resolve loop"],
["99", "object 0"],
["99b", "object 0"],
["100","xref reconstruction loop"],
@@ -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
Binary file not shown.

0 comments on commit 701b518

Please sign in to comment.
You can’t perform that action at this time.