From 42c511198b34b247cb43f0a26b3a518c6e060216 Mon Sep 17 00:00:00 2001 From: m-holger Date: Sun, 30 Jun 2024 13:01:50 +0100 Subject: [PATCH 1/3] Suppress excessive warnings while fuzzing Add extra fuzz test case and amend memory limit for Pl_DCT. --- fuzz/CMakeLists.txt | 1 + fuzz/qpdf_extra/69977.fuzz | Bin 0 -> 28858 bytes fuzz/qpdf_fuzzer.cc | 6 +++--- fuzz/qtest/fuzz.test | 2 +- libqpdf/Pl_DCT.cc | 2 +- libqpdf/QPDF.cc | 7 +++++++ 6 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 fuzz/qpdf_extra/69977.fuzz diff --git a/fuzz/CMakeLists.txt b/fuzz/CMakeLists.txt index d492bfea..98c980d6 100644 --- a/fuzz/CMakeLists.txt +++ b/fuzz/CMakeLists.txt @@ -121,6 +121,7 @@ set(CORPUS_OTHER 69857.fuzz 69913.fuzz 69969.fuzz + 69977.fuzz ) set(CORPUS_DIR ${CMAKE_CURRENT_BINARY_DIR}/qpdf_corpus) diff --git a/fuzz/qpdf_extra/69977.fuzz b/fuzz/qpdf_extra/69977.fuzz new file mode 100644 index 0000000000000000000000000000000000000000..f15c5c985c2a036ab44174ab8caf247ea60a9ac3 GIT binary patch literal 28858 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 1], ['runlength' => 6], ['tiffpredictor' => 2], - ['qpdf' => 63], # increment when adding new files + ['qpdf' => 64], # increment when adding new files ); my $n_tests = 0; diff --git a/libqpdf/Pl_DCT.cc b/libqpdf/Pl_DCT.cc index 5875a0e9..d2544ab0 100644 --- a/libqpdf/Pl_DCT.cc +++ b/libqpdf/Pl_DCT.cc @@ -320,7 +320,7 @@ Pl_DCT::decompress(void* cinfo_p, Buffer* b) 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 // 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 jpeg_buffer_src(cinfo, b); diff --git a/libqpdf/QPDF.cc b/libqpdf/QPDF.cc index f46885a1..dec97420 100644 --- a/libqpdf/QPDF.cc +++ b/libqpdf/QPDF.cc @@ -494,6 +494,13 @@ QPDF::warn(QPDFExc const& e) { m->warnings.push_back(e); 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"; } } From 6d640c569a1aea2e38aeb3cbe2527a586cc864ea Mon Sep 17 00:00:00 2001 From: m-holger Date: Mon, 1 Jul 2024 18:11:51 +0100 Subject: [PATCH 2/3] Add additional object id sanity checks Ensure objects with impossibly large ids are ignored. --- include/qpdf/QPDF.hh | 3 +++ libqpdf/QPDF.cc | 39 ++++++++++++++++++++++++++++------- qpdf/qtest/qpdf/issue-118.out | 3 +-- qpdf/qtest/qpdf/issue-120.out | 1 + qpdf/qtest/qpdf/issue-143.out | 1 + 5 files changed, 37 insertions(+), 10 deletions(-) diff --git a/include/qpdf/QPDF.hh b/include/qpdf/QPDF.hh index a922ae9d..c83bcb0b 100644 --- a/include/qpdf/QPDF.hh +++ b/include/qpdf/QPDF.hh @@ -1502,6 +1502,9 @@ class QPDF std::shared_ptr encp; std::string pdf_version; std::map xref_table; + // Various tables are indexed by object id, with potential size id + 1 + int xref_table_max_id{std::numeric_limits::max() - 1}; + qpdf_offset_t xref_table_max_offset{0}; std::set deleted_objects; std::map obj_cache; std::set resolving; diff --git a/libqpdf/QPDF.cc b/libqpdf/QPDF.cc index dec97420..c83330fe 100644 --- a/libqpdf/QPDF.cc +++ b/libqpdf/QPDF.cc @@ -441,6 +441,12 @@ QPDF::parse(char const* password) // 30 characters to leave room for the startxref stuff. m->file->seek(0, SEEK_END); 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(m->xref_table_max_offset / 3); + } qpdf_offset_t start_offset = (end_offset > 1054 ? end_offset - 1054 : 0); PatternFinder sf(*this, &QPDF::findStartxref); qpdf_offset_t xref_offset = 0; @@ -554,9 +560,6 @@ QPDF::reconstruct_xref(QPDFExc& e) m->file->seek(0, SEEK_END); 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); qpdf_offset_t line_start = 0; // Don't allow very long tokens here during recovery. @@ -574,7 +577,7 @@ QPDF::reconstruct_xref(QPDFExc& e) if ((t2.isInteger()) && (readToken(m->file, MAX_LEN).isWord("obj"))) { int obj = QUtil::string_to_int(t1.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); } else { warn(damagedPDF( @@ -709,7 +712,7 @@ QPDF::read_xref(qpdf_offset_t xref_offset) int size = m->trailer.getKey("/Size").getIntValueAsInt(); int max_obj = 0; 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()) { max_obj = std::max(max_obj, *(m->deleted_objects.rbegin())); @@ -1262,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 // 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)) { QTC::TC("qpdf", "QPDF xref deleted object"); return; } + if (f0 == 2 && static_cast(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))); if (!created) { QTC::TC("qpdf", "QPDF xref reused object"); @@ -1303,12 +1316,11 @@ QPDF::insertFreeXrefEntry(QPDFObjGen og) void QPDF::insertReconstructedXrefEntry(int obj, qpdf_offset_t f1, int f2) { - // Various tables are indexed by object id, with potential size id + 1 - constexpr static int max_id = std::numeric_limits::max() - 1; - if (!(obj > 0 && obj <= max_id && 0 <= f2 && f2 < 65535)) { + if (!(obj > 0 && obj <= m->xref_table_max_id && 0 <= f2 && f2 < 65535)) { QTC::TC("qpdf", "QPDF xref overwrite invalid objgen"); return; } + QPDFObjGen og(obj, f2); if (!m->deleted_objects.count(obj)) { // deleted_objects stores the uncompressed objects removed from the xref table at the start @@ -1918,6 +1930,17 @@ QPDF::resolveObjectsInStream(int obj_stream_number) int num = QUtil::string_to_int(tnum.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); } diff --git a/qpdf/qtest/qpdf/issue-118.out b/qpdf/qtest/qpdf/issue-118.out index 1a5f3f57..2b219b20 100644 --- a/qpdf/qtest/qpdf/issue-118.out +++ b/qpdf/qtest/qpdf/issue-118.out @@ -1,4 +1,3 @@ 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: object 8 0, offset 732): supposed object stream 2 is not a stream +WARNING: issue-118.pdf (xref stream, offset 732): self-referential object stream 2 issue-118.pdf: unable to find /Root dictionary diff --git a/qpdf/qtest/qpdf/issue-120.out b/qpdf/qtest/qpdf/issue-120.out index cc03ccfa..dbef34db 100644 --- a/qpdf/qtest/qpdf/issue-120.out +++ b/qpdf/qtest/qpdf/issue-120.out @@ -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 diff --git a/qpdf/qtest/qpdf/issue-143.out b/qpdf/qtest/qpdf/issue-143.out index 44144b4d..7f787278 100644 --- a/qpdf/qtest/qpdf/issue-143.out +++ b/qpdf/qtest/qpdf/issue-143.out @@ -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 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, offset 654): self-referential object stream 3 WARNING: issue-143.pdf: file is damaged WARNING: issue-143.pdf (object 1 0, offset 48): expected n n obj WARNING: issue-143.pdf: Attempting to reconstruct cross-reference table From a367e56afc8ef1ae02ce3042d429550a46d39bb3 Mon Sep 17 00:00:00 2001 From: m-holger Date: Mon, 1 Jul 2024 23:26:10 +0100 Subject: [PATCH 3/3] In QPDF::resolveObjectsInStream avoid creating xref table entries Invalid entries are created when objects in the stream do not have an existing xref entry. --- libqpdf/QPDF.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libqpdf/QPDF.cc b/libqpdf/QPDF.cc index c83330fe..915518af 100644 --- a/libqpdf/QPDF.cc +++ b/libqpdf/QPDF.cc @@ -1952,8 +1952,9 @@ QPDF::resolveObjectsInStream(int obj_stream_number) m->last_object_description += "object "; for (auto const& iter: offsets) { QPDFObjGen og(iter.first, 0); - QPDFXRefEntry const& entry = m->xref_table[og]; - if ((entry.getType() == 2) && (entry.getObjStreamNumber() == obj_stream_number)) { + auto entry = m->xref_table.find(og); + if (entry != m->xref_table.end() && entry->second.getType() == 2 && + entry->second.getObjStreamNumber() == obj_stream_number) { int offset = iter.second; input->seek(offset, SEEK_SET); QPDFObjectHandle oh = readObjectInStream(input, iter.first);