mirror of
https://github.com/qpdf/qpdf.git
synced 2024-11-08 14:21:06 +00:00
Merge pull request #1228 from m-holger/fuzz7
Add further sanity and loop detection checks
This commit is contained in:
commit
b45e3420d6
@ -121,6 +121,7 @@ set(CORPUS_OTHER
|
|||||||
69857.fuzz
|
69857.fuzz
|
||||||
69913.fuzz
|
69913.fuzz
|
||||||
69969.fuzz
|
69969.fuzz
|
||||||
|
69977.fuzz
|
||||||
)
|
)
|
||||||
|
|
||||||
set(CORPUS_DIR ${CMAKE_CURRENT_BINARY_DIR}/qpdf_corpus)
|
set(CORPUS_DIR ${CMAKE_CURRENT_BINARY_DIR}/qpdf_corpus)
|
||||||
|
BIN
fuzz/qpdf_extra/69977.fuzz
Normal file
BIN
fuzz/qpdf_extra/69977.fuzz
Normal file
Binary file not shown.
@ -173,11 +173,11 @@ FuzzHelper::doChecks()
|
|||||||
{
|
{
|
||||||
// Get as much coverage as possible in parts of the library that
|
// Get as much coverage as possible in parts of the library that
|
||||||
// might benefit from fuzzing.
|
// might benefit from fuzzing.
|
||||||
std::cout << "starting testWrite\n";
|
std::cerr << "\ninfo: starting testWrite\n";
|
||||||
testWrite();
|
testWrite();
|
||||||
std::cout << "\nstarting testPages\n\n";
|
std::cerr << "\ninfo: starting testPages\n";
|
||||||
testPages();
|
testPages();
|
||||||
std::cout << "\nstarting testOutlines\n\n";
|
std::cerr << "\ninfo: starting testOutlines\n";
|
||||||
testOutlines();
|
testOutlines();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ my @fuzzers = (
|
|||||||
['pngpredictor' => 1],
|
['pngpredictor' => 1],
|
||||||
['runlength' => 6],
|
['runlength' => 6],
|
||||||
['tiffpredictor' => 2],
|
['tiffpredictor' => 2],
|
||||||
['qpdf' => 63], # increment when adding new files
|
['qpdf' => 64], # increment when adding new files
|
||||||
);
|
);
|
||||||
|
|
||||||
my $n_tests = 0;
|
my $n_tests = 0;
|
||||||
|
@ -1502,6 +1502,9 @@ class QPDF
|
|||||||
std::shared_ptr<EncryptionParameters> encp;
|
std::shared_ptr<EncryptionParameters> encp;
|
||||||
std::string pdf_version;
|
std::string pdf_version;
|
||||||
std::map<QPDFObjGen, QPDFXRefEntry> xref_table;
|
std::map<QPDFObjGen, QPDFXRefEntry> xref_table;
|
||||||
|
// Various tables are indexed by object id, with potential size id + 1
|
||||||
|
int xref_table_max_id{std::numeric_limits<int>::max() - 1};
|
||||||
|
qpdf_offset_t xref_table_max_offset{0};
|
||||||
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;
|
std::set<QPDFObjGen> resolving;
|
||||||
|
@ -320,7 +320,7 @@ Pl_DCT::decompress(void* cinfo_p, Buffer* b)
|
|||||||
cinfo->mem->max_memory_to_use = 1'000'000'000;
|
cinfo->mem->max_memory_to_use = 1'000'000'000;
|
||||||
// For some corrupt files the memory used internally by libjpeg stays within the above limits
|
// For some corrupt files the memory used internally by libjpeg stays within the above limits
|
||||||
// even though the size written to the next pipeline is significantly larger.
|
// even though the size written to the next pipeline is significantly larger.
|
||||||
m->corrupt_data_limit = 100'000'000;
|
m->corrupt_data_limit = 10'000'000;
|
||||||
#endif
|
#endif
|
||||||
jpeg_buffer_src(cinfo, b);
|
jpeg_buffer_src(cinfo, b);
|
||||||
|
|
||||||
|
@ -441,6 +441,12 @@ QPDF::parse(char const* password)
|
|||||||
// 30 characters to leave room for the startxref stuff.
|
// 30 characters to leave room for the startxref stuff.
|
||||||
m->file->seek(0, SEEK_END);
|
m->file->seek(0, SEEK_END);
|
||||||
qpdf_offset_t end_offset = m->file->tell();
|
qpdf_offset_t end_offset = m->file->tell();
|
||||||
|
m->xref_table_max_offset = end_offset;
|
||||||
|
// Sanity check on object ids. All objects must appear in xref table / stream. In all realistic
|
||||||
|
// scenarios at least 3 bytes are required.
|
||||||
|
if (m->xref_table_max_id > m->xref_table_max_offset / 3) {
|
||||||
|
m->xref_table_max_id = static_cast<int>(m->xref_table_max_offset / 3);
|
||||||
|
}
|
||||||
qpdf_offset_t start_offset = (end_offset > 1054 ? end_offset - 1054 : 0);
|
qpdf_offset_t start_offset = (end_offset > 1054 ? end_offset - 1054 : 0);
|
||||||
PatternFinder sf(*this, &QPDF::findStartxref);
|
PatternFinder sf(*this, &QPDF::findStartxref);
|
||||||
qpdf_offset_t xref_offset = 0;
|
qpdf_offset_t xref_offset = 0;
|
||||||
@ -494,6 +500,13 @@ QPDF::warn(QPDFExc const& e)
|
|||||||
{
|
{
|
||||||
m->warnings.push_back(e);
|
m->warnings.push_back(e);
|
||||||
if (!m->suppress_warnings) {
|
if (!m->suppress_warnings) {
|
||||||
|
#ifdef QPDF_OSS_FUZZ
|
||||||
|
if (m->warnings.size() > 20) {
|
||||||
|
*m->log->getWarn() << "WARNING: too many warnings - additional warnings surpressed\n";
|
||||||
|
m->suppress_warnings = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
*m->log->getWarn() << "WARNING: " << m->warnings.back().what() << "\n";
|
*m->log->getWarn() << "WARNING: " << m->warnings.back().what() << "\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -547,9 +560,6 @@ QPDF::reconstruct_xref(QPDFExc& e)
|
|||||||
|
|
||||||
m->file->seek(0, SEEK_END);
|
m->file->seek(0, SEEK_END);
|
||||||
qpdf_offset_t eof = m->file->tell();
|
qpdf_offset_t eof = m->file->tell();
|
||||||
// Sanity check on object ids. All objects must appear in xref table / stream. In all realistic
|
|
||||||
// scenarios at leat 3 bytes are required.
|
|
||||||
auto max_obj_id = eof / 3;
|
|
||||||
m->file->seek(0, SEEK_SET);
|
m->file->seek(0, SEEK_SET);
|
||||||
qpdf_offset_t line_start = 0;
|
qpdf_offset_t line_start = 0;
|
||||||
// Don't allow very long tokens here during recovery.
|
// Don't allow very long tokens here during recovery.
|
||||||
@ -567,7 +577,7 @@ QPDF::reconstruct_xref(QPDFExc& e)
|
|||||||
if ((t2.isInteger()) && (readToken(m->file, MAX_LEN).isWord("obj"))) {
|
if ((t2.isInteger()) && (readToken(m->file, MAX_LEN).isWord("obj"))) {
|
||||||
int obj = QUtil::string_to_int(t1.getValue().c_str());
|
int obj = QUtil::string_to_int(t1.getValue().c_str());
|
||||||
int gen = QUtil::string_to_int(t2.getValue().c_str());
|
int gen = QUtil::string_to_int(t2.getValue().c_str());
|
||||||
if (obj <= max_obj_id) {
|
if (obj <= m->xref_table_max_id) {
|
||||||
insertReconstructedXrefEntry(obj, token_start, gen);
|
insertReconstructedXrefEntry(obj, token_start, gen);
|
||||||
} else {
|
} else {
|
||||||
warn(damagedPDF(
|
warn(damagedPDF(
|
||||||
@ -702,7 +712,7 @@ QPDF::read_xref(qpdf_offset_t xref_offset)
|
|||||||
int size = m->trailer.getKey("/Size").getIntValueAsInt();
|
int size = m->trailer.getKey("/Size").getIntValueAsInt();
|
||||||
int max_obj = 0;
|
int max_obj = 0;
|
||||||
if (!m->xref_table.empty()) {
|
if (!m->xref_table.empty()) {
|
||||||
max_obj = (*(m->xref_table.rbegin())).first.getObj();
|
max_obj = m->xref_table.rbegin()->first.getObj();
|
||||||
}
|
}
|
||||||
if (!m->deleted_objects.empty()) {
|
if (!m->deleted_objects.empty()) {
|
||||||
max_obj = std::max(max_obj, *(m->deleted_objects.rbegin()));
|
max_obj = std::max(max_obj, *(m->deleted_objects.rbegin()));
|
||||||
@ -1255,11 +1265,21 @@ QPDF::insertXrefEntry(int obj, int f0, qpdf_offset_t f1, int f2)
|
|||||||
// If there is already an entry for this object and generation in the table, it means that a
|
// If there is already an entry for this object and generation in the table, it means that a
|
||||||
// later xref table has registered this object. Disregard this one.
|
// later xref table has registered this object. Disregard this one.
|
||||||
|
|
||||||
|
if (obj > m->xref_table_max_id) {
|
||||||
|
// ignore impossibly large object ids or object ids > Size.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (m->deleted_objects.count(obj)) {
|
if (m->deleted_objects.count(obj)) {
|
||||||
QTC::TC("qpdf", "QPDF xref deleted object");
|
QTC::TC("qpdf", "QPDF xref deleted object");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (f0 == 2 && static_cast<int>(f1) == obj) {
|
||||||
|
warn(damagedPDF("xref stream", "self-referential object stream " + std::to_string(obj)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto [iter, created] = m->xref_table.try_emplace(QPDFObjGen(obj, (f0 == 2 ? 0 : f2)));
|
auto [iter, created] = m->xref_table.try_emplace(QPDFObjGen(obj, (f0 == 2 ? 0 : f2)));
|
||||||
if (!created) {
|
if (!created) {
|
||||||
QTC::TC("qpdf", "QPDF xref reused object");
|
QTC::TC("qpdf", "QPDF xref reused object");
|
||||||
@ -1296,12 +1316,11 @@ QPDF::insertFreeXrefEntry(QPDFObjGen og)
|
|||||||
void
|
void
|
||||||
QPDF::insertReconstructedXrefEntry(int obj, qpdf_offset_t f1, int f2)
|
QPDF::insertReconstructedXrefEntry(int obj, qpdf_offset_t f1, int f2)
|
||||||
{
|
{
|
||||||
// Various tables are indexed by object id, with potential size id + 1
|
if (!(obj > 0 && obj <= m->xref_table_max_id && 0 <= f2 && f2 < 65535)) {
|
||||||
constexpr static int max_id = std::numeric_limits<int>::max() - 1;
|
|
||||||
if (!(obj > 0 && obj <= max_id && 0 <= f2 && f2 < 65535)) {
|
|
||||||
QTC::TC("qpdf", "QPDF xref overwrite invalid objgen");
|
QTC::TC("qpdf", "QPDF xref overwrite invalid objgen");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QPDFObjGen og(obj, f2);
|
QPDFObjGen og(obj, f2);
|
||||||
if (!m->deleted_objects.count(obj)) {
|
if (!m->deleted_objects.count(obj)) {
|
||||||
// deleted_objects stores the uncompressed objects removed from the xref table at the start
|
// deleted_objects stores the uncompressed objects removed from the xref table at the start
|
||||||
@ -1911,6 +1930,17 @@ QPDF::resolveObjectsInStream(int obj_stream_number)
|
|||||||
|
|
||||||
int num = QUtil::string_to_int(tnum.getValue().c_str());
|
int num = QUtil::string_to_int(tnum.getValue().c_str());
|
||||||
long long offset = QUtil::string_to_int(toffset.getValue().c_str());
|
long long offset = QUtil::string_to_int(toffset.getValue().c_str());
|
||||||
|
if (num > m->xref_table_max_id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (num == obj_stream_number) {
|
||||||
|
warn(damagedPDF(
|
||||||
|
input,
|
||||||
|
m->last_object_description,
|
||||||
|
input->getLastOffset(),
|
||||||
|
"object stream claims to contain itself"));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
offsets[num] = toI(offset + first);
|
offsets[num] = toI(offset + first);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1922,8 +1952,9 @@ QPDF::resolveObjectsInStream(int obj_stream_number)
|
|||||||
m->last_object_description += "object ";
|
m->last_object_description += "object ";
|
||||||
for (auto const& iter: offsets) {
|
for (auto const& iter: offsets) {
|
||||||
QPDFObjGen og(iter.first, 0);
|
QPDFObjGen og(iter.first, 0);
|
||||||
QPDFXRefEntry const& entry = m->xref_table[og];
|
auto entry = m->xref_table.find(og);
|
||||||
if ((entry.getType() == 2) && (entry.getObjStreamNumber() == obj_stream_number)) {
|
if (entry != m->xref_table.end() && entry->second.getType() == 2 &&
|
||||||
|
entry->second.getObjStreamNumber() == obj_stream_number) {
|
||||||
int offset = iter.second;
|
int offset = iter.second;
|
||||||
input->seek(offset, SEEK_SET);
|
input->seek(offset, SEEK_SET);
|
||||||
QPDFObjectHandle oh = readObjectInStream(input, iter.first);
|
QPDFObjectHandle oh = readObjectInStream(input, iter.first);
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
WARNING: issue-118.pdf: can't find PDF header
|
WARNING: issue-118.pdf: can't find PDF header
|
||||||
WARNING: issue-118.pdf (offset 732): loop detected resolving object 2 0
|
WARNING: issue-118.pdf (xref stream, offset 732): self-referential object stream 2
|
||||||
WARNING: issue-118.pdf (xref stream: object 8 0, offset 732): supposed object stream 2 is not a stream
|
|
||||||
issue-118.pdf: unable to find /Root dictionary
|
issue-118.pdf: unable to find /Root dictionary
|
||||||
|
@ -1 +1,2 @@
|
|||||||
|
WARNING: issue-120.pdf (xref stream, offset 712): self-referential object stream 3
|
||||||
qpdf: issue-120.pdf: unable to find page tree
|
qpdf: issue-120.pdf: unable to find page tree
|
||||||
|
@ -3,6 +3,7 @@ WARNING: issue-143.pdf (xref stream: object 3 0, offset 654): stream keyword not
|
|||||||
WARNING: issue-143.pdf (xref stream: object 3 0, offset 607): stream dictionary lacks /Length key
|
WARNING: issue-143.pdf (xref stream: object 3 0, offset 607): stream dictionary lacks /Length key
|
||||||
WARNING: issue-143.pdf (xref stream: object 3 0, offset 654): attempting to recover stream length
|
WARNING: issue-143.pdf (xref stream: object 3 0, offset 654): attempting to recover stream length
|
||||||
WARNING: issue-143.pdf (xref stream: object 3 0, offset 654): recovered stream length: 36
|
WARNING: issue-143.pdf (xref stream: object 3 0, offset 654): recovered stream length: 36
|
||||||
|
WARNING: issue-143.pdf (xref stream, offset 654): self-referential object stream 3
|
||||||
WARNING: issue-143.pdf: file is damaged
|
WARNING: issue-143.pdf: file is damaged
|
||||||
WARNING: issue-143.pdf (object 1 0, offset 48): expected n n obj
|
WARNING: issue-143.pdf (object 1 0, offset 48): expected n n obj
|
||||||
WARNING: issue-143.pdf: Attempting to reconstruct cross-reference table
|
WARNING: issue-143.pdf: Attempting to reconstruct cross-reference table
|
||||||
|
Loading…
Reference in New Issue
Block a user