Commit 0e92cf6bf399249c603c3d0212e898fd29e71fcd

Authored by m-holger
Committed by GitHub
2 parents 7d34b89a 477fbd98

Merge pull request #1289 from m-holger/fuzz

Fix bugs found during fuzzing
fuzz/CMakeLists.txt
... ... @@ -142,6 +142,9 @@ set(CORPUS_OTHER
142 142 70306b.fuzz
143 143 71624.fuzz
144 144 71689.fuzz
  145 + 99999a.fuzz
  146 + 99999b.fuzz
  147 + 99999c.fuzz
145 148 )
146 149  
147 150 set(CORPUS_DIR ${CMAKE_CURRENT_BINARY_DIR}/qpdf_corpus)
... ...
fuzz/qpdf_extra/99999a.fuzz 0 → 100644
  1 +%PDF-1.5
  2 +%€€€€
  3 +1 0 obj
  4 +<<
  5 + /Type /Catalog
  6 + /Pages 2 0 R
  7 +>>
  8 +endobj
  9 +2 0 obj
  10 +<<
  11 + /Count 6 Ri
  12 + 0K/ds [3 0 R]
  13 + /Type /Pages
  14 +>>
  15 +endobj
  16 +3 0 obj
  17 +<<
  18 + /Resources <<
  19 + /Font <<
  20 + /F1 5 0 R
  21 + >>
  22 + >>
  23 + /MediaBox [0 0 795 842]
  24 + /Parent 2 0 R
  25 + /Contents 4 0 R
  26 + /Type /Page
  27 +=>
  28 +endobj
  29 +4 0 obj
  30 +<<444444444444444444444444 1 Tr /F1 30 Tf 350 750 Td (foobar) Tj ET
  31 +endstream
  32 +endobj
  33 +5 0 obj
  34 +<<
  35 + /Name /F1
  36 + /BaseFont /Helvetica
  37 + /Type /Font
  38 + /Subtype /Type1
  39 +>>
  40 +e„dobj
  41 +6 0 obj
  42 +<< /Length 6 0 R >>
  43 +stre444444444444444444444444444444<<>>
  44 +endobj
  45 +xref
  46 +0 8
  47 +0000000000 65535 f
  48 +0000000015 00000 n
  49 +0000000066 00000 n
  50 +0000000130 00000 n
  51 +0000000269 00000 n
  52 +0000000362 00000 n
  53 +000000ÎËËÉßÏÏÏ00 n
  54 +0000000500 00000 n
  55 +trailer
  56 +<<
  57 + /Size 713115528178535
  58 + /Root 1 0 R
  59 + /Info 7 0 R
  60 +>>
  61 +startxref
  62 +520
  63 +%%EOF
0 64 \ No newline at end of file
... ...
fuzz/qpdf_extra/99999b.fuzz 0 → 100644
No preview for this file type
fuzz/qpdf_extra/99999c.fuzz 0 → 100644
No preview for this file type
fuzz/qtest/fuzz.test
... ... @@ -11,7 +11,7 @@ my $td = new TestDriver(&#39;fuzz&#39;);
11 11  
12 12 my $qpdf_corpus = $ENV{'QPDF_FUZZ_CORPUS'} || die "must set QPDF_FUZZ_CORPUS";
13 13  
14   -my $n_qpdf_files = 79; # increment when adding new files
  14 +my $n_qpdf_files = 82; # increment when adding new files
15 15  
16 16 my @fuzzers = (
17 17 ['ascii85' => 1],
... ...
libqpdf/QPDF.cc
... ... @@ -832,10 +832,6 @@ std::vector&lt;QPDF::Xref_table::Subsection&gt;
832 832 QPDF::Xref_table::bad_subsections(std::string& line, qpdf_offset_t start)
833 833 {
834 834 std::vector<QPDF::Xref_table::Subsection> result;
835   - qpdf_offset_t f1 = 0;
836   - int f2 = 0;
837   - char type = '\0';
838   -
839 835 file->seek(start, SEEK_SET);
840 836  
841 837 while (true) {
... ... @@ -844,7 +840,7 @@ QPDF::Xref_table::bad_subsections(std::string&amp; line, qpdf_offset_t start)
844 840 auto [obj, num, offset] = result.emplace_back(subsection(line));
845 841 file->seek(offset, SEEK_SET);
846 842 for (qpdf_offset_t i = obj; i - num < obj; ++i) {
847   - if (!read_entry(f1, f2, type)) {
  843 + if (!std::get<0>(read_entry())) {
848 844 QTC::TC("qpdf", "QPDF invalid xref entry");
849 845 throw damaged_table("invalid xref entry (obj=" + std::to_string(i) + ")");
850 846 }
... ... @@ -890,9 +886,13 @@ QPDF::Xref_table::subsections(std::string&amp; line)
890 886 }
891 887 }
892 888  
893   -bool
894   -QPDF::Xref_table::read_bad_entry(qpdf_offset_t& f1, int& f2, char& type)
  889 +// Returns (success, f1, f2, type).
  890 +std::tuple<bool, qpdf_offset_t, int, char>
  891 +QPDF::Xref_table::read_bad_entry()
895 892 {
  893 + qpdf_offset_t f1{0};
  894 + int f2{0};
  895 + char type{'\0'};
896 896 // Reposition after initial read attempt and reread.
897 897 file->seek(file->getLastOffset(), SEEK_SET);
898 898 auto line = file->readLine(30);
... ... @@ -910,7 +910,7 @@ QPDF::Xref_table::read_bad_entry(qpdf_offset_t&amp; f1, int&amp; f2, char&amp; type)
910 910 }
911 911 // Require digit
912 912 if (!QUtil::is_digit(*p)) {
913   - return false;
  913 + return {false, 0, 0, '\0'};
914 914 }
915 915 // Gather digits
916 916 std::string f1_str;
... ... @@ -919,7 +919,7 @@ QPDF::Xref_table::read_bad_entry(qpdf_offset_t&amp; f1, int&amp; f2, char&amp; type)
919 919 }
920 920 // Require space
921 921 if (!QUtil::is_space(*p)) {
922   - return false;
  922 + return {false, 0, 0, '\0'};
923 923 }
924 924 if (QUtil::is_space(*(p + 1))) {
925 925 QTC::TC("qpdf", "QPDF ignore first extra space in xref entry");
... ... @@ -931,7 +931,7 @@ QPDF::Xref_table::read_bad_entry(qpdf_offset_t&amp; f1, int&amp; f2, char&amp; type)
931 931 }
932 932 // Require digit
933 933 if (!QUtil::is_digit(*p)) {
934   - return false;
  934 + return {false, 0, 0, '\0'};
935 935 }
936 936 // Gather digits
937 937 std::string f2_str;
... ... @@ -940,7 +940,7 @@ QPDF::Xref_table::read_bad_entry(qpdf_offset_t&amp; f1, int&amp; f2, char&amp; type)
940 940 }
941 941 // Require space
942 942 if (!QUtil::is_space(*p)) {
943   - return false;
  943 + return {false, 0, 0, '\0'};
944 944 }
945 945 if (QUtil::is_space(*(p + 1))) {
946 946 QTC::TC("qpdf", "QPDF ignore second extra space in xref entry");
... ... @@ -953,7 +953,7 @@ QPDF::Xref_table::read_bad_entry(qpdf_offset_t&amp; f1, int&amp; f2, char&amp; type)
953 953 if ((*p == 'f') || (*p == 'n')) {
954 954 type = *p;
955 955 } else {
956   - return false;
  956 + return {false, 0, 0, '\0'};
957 957 }
958 958 if ((f1_str.length() != 10) || (f2_str.length() != 5)) {
959 959 QTC::TC("qpdf", "QPDF ignore length error xref entry");
... ... @@ -967,18 +967,23 @@ QPDF::Xref_table::read_bad_entry(qpdf_offset_t&amp; f1, int&amp; f2, char&amp; type)
967 967 f1 = QUtil::string_to_ll(f1_str.c_str());
968 968 f2 = QUtil::string_to_int(f2_str.c_str());
969 969  
970   - return true;
  970 + return {true, f1, f2, type};
971 971 }
972 972  
973 973 // Optimistically read and parse xref entry. If entry is bad, call read_bad_xrefEntry and return
974   -// result.
975   -bool
976   -QPDF::Xref_table::read_entry(qpdf_offset_t& f1, int& f2, char& type)
  974 +// result. Returns (success, f1, f2, type).
  975 +std::tuple<bool, qpdf_offset_t, int, char>
  976 +QPDF::Xref_table::read_entry()
977 977 {
  978 + qpdf_offset_t f1{0};
  979 + int f2{0};
  980 + char type{'\0'};
978 981 std::array<char, 21> line;
  982 + f1 = 0;
  983 + f2 = 0;
979 984 if (file->read(line.data(), 20) != 20) {
980 985 // C++20: [[unlikely]]
981   - return false;
  986 + return {false, 0, 0, '\0'};
982 987 }
983 988 line[20] = '\0';
984 989 char const* p = line.data();
... ... @@ -1002,7 +1007,7 @@ QPDF::Xref_table::read_entry(qpdf_offset_t&amp; f1, int&amp; f2, char&amp; type)
1002 1007 if (!QUtil::is_space(*p++)) {
1003 1008 // Entry doesn't start with space or digit.
1004 1009 // C++20: [[unlikely]]
1005   - return false;
  1010 + return {false, 0, 0, '\0'};
1006 1011 }
1007 1012 // Gather digits. NB No risk of overflow as 99'999 < max int.
1008 1013 while (*p == '0') {
... ... @@ -1019,10 +1024,10 @@ QPDF::Xref_table::read_entry(qpdf_offset_t&amp; f1, int&amp; f2, char&amp; type)
1019 1024 // No test for valid line[19].
1020 1025 if (*(++p) && *(++p) && (*p == '\n' || *p == '\r') && f1_len == 10 && f2_len == 5) {
1021 1026 // C++20: [[likely]]
1022   - return true;
  1027 + return {true, f1, f2, type};
1023 1028 }
1024 1029 }
1025   - return read_bad_entry(f1, f2, type);
  1030 + return read_bad_entry();
1026 1031 }
1027 1032  
1028 1033 // Read a single cross-reference table section and associated trailer.
... ... @@ -1052,7 +1057,10 @@ QPDF::Xref_table::process_section(qpdf_offset_t xref_offset)
1052 1057 QTC::TC("qpdf", "QPDF trailer size not integer");
1053 1058 throw qpdf.damagedPDF("trailer", "/Size key in trailer dictionary is not an integer");
1054 1059 }
1055   -
  1060 + if (sz >= static_cast<unsigned int>(max_id_)) {
  1061 + QTC::TC("qpdf", "QPDF trailer size impossibly large");
  1062 + throw qpdf.damagedPDF("trailer", "/Size key in trailer dictionary is impossibly large");
  1063 + }
1056 1064 table.resize(sz);
1057 1065 }
1058 1066  
... ... @@ -1064,10 +1072,8 @@ QPDF::Xref_table::process_section(qpdf_offset_t xref_offset)
1064 1072 first_item_offset_ = file->tell();
1065 1073 }
1066 1074 // For xref_table, these will always be small enough to be ints
1067   - qpdf_offset_t f1 = 0;
1068   - int f2 = 0;
1069   - char type = '\0';
1070   - if (!read_entry(f1, f2, type)) {
  1075 + auto [success, f1, f2, type] = read_entry();
  1076 + if (!success) {
1071 1077 throw damaged_table("invalid xref entry (obj=" + std::to_string(i) + ")");
1072 1078 }
1073 1079 if (type == 'f') {
... ... @@ -1585,8 +1591,7 @@ QPDF::Xref_table::read_trailer()
1585 1591 {
1586 1592 qpdf_offset_t offset = file->tell();
1587 1593 bool empty = false;
1588   - auto object =
1589   - QPDFParser(*file, "trailer", tokenizer, nullptr, &qpdf, true).parse(empty, false);
  1594 + auto object = QPDFParser(*file, "trailer", tokenizer, nullptr, &qpdf, true).parse(empty, false);
1590 1595 if (empty) {
1591 1596 // Nothing in the PDF spec appears to allow empty objects, but they have been encountered in
1592 1597 // actual PDF files and Adobe Reader appears to ignore them.
... ...
libqpdf/qpdf/QPDF_private.hh
... ... @@ -292,8 +292,8 @@ class QPDF::Xref_table
292 292 std::vector<Subsection> subsections(std::string& line);
293 293 std::vector<Subsection> bad_subsections(std::string& line, qpdf_offset_t offset);
294 294 Subsection subsection(std::string const& line);
295   - bool read_entry(qpdf_offset_t& f1, int& f2, char& type);
296   - bool read_bad_entry(qpdf_offset_t& f1, int& f2, char& type);
  295 + std::tuple<bool, qpdf_offset_t, int, char> read_entry();
  296 + std::tuple<bool, qpdf_offset_t, int, char> read_bad_entry();
297 297  
298 298 // Methods to parse streams
299 299 qpdf_offset_t read_stream(qpdf_offset_t offset);
... ...
qpdf/qpdf.testcov
... ... @@ -55,6 +55,7 @@ QPDF invalid xref entry 0
55 55 QPDF missing trailer 0
56 56 QPDF trailer lacks size 0
57 57 QPDF trailer size not integer 0
  58 +QPDF trailer size impossibly large 0
58 59 QPDF trailer prev not integer 0
59 60 QPDFParser bad brace 0
60 61 QPDFParser bad brace in parseRemainder 0
... ...
qpdf/qtest/qpdf/issue-fuzz.out 0 → 100644
  1 +WARNING: issue-fuzz.pdf: can't find PDF header
  2 +WARNING: issue-fuzz.pdf (xref table, offset 19): accepting invalid xref table entry
  3 +WARNING: issue-fuzz.pdf (trailer, offset 36): unknown token while reading object; treating as string
  4 +WARNING: issue-fuzz.pdf (trailer, offset 53): unexpected >
  5 +WARNING: issue-fuzz.pdf (trailer, offset 54): unknown token while reading object; treating as string
  6 +WARNING: issue-fuzz.pdf (trailer, offset 58): unknown token while reading object; treating as string
  7 +WARNING: issue-fuzz.pdf (trailer, offset 72): unknown token while reading object; treating as string
  8 +WARNING: issue-fuzz.pdf (trailer, offset 36): dictionary ended prematurely; using null as value for last key
  9 +WARNING: issue-fuzz.pdf (trailer, offset 36): expected dictionary key but found non-name object; inserting key /QPDFFake1
  10 +WARNING: issue-fuzz.pdf (trailer, offset 36): expected dictionary key but found non-name object; inserting key /QPDFFake2
  11 +WARNING: issue-fuzz.pdf (trailer, offset 36): expected dictionary key but found non-name object; inserting key /QPDFFake3
  12 +WARNING: issue-fuzz.pdf (trailer, offset 36): expected dictionary key but found non-name object; inserting key /QPDFFake4
  13 +WARNING: issue-fuzz.pdf (trailer, offset 36): expected dictionary key but found non-name object; inserting key /QPDFFake5
  14 +WARNING: issue-fuzz.pdf (trailer, offset 36): expected dictionary key but found non-name object; inserting key /QPDFFake6
  15 +WARNING: issue-fuzz.pdf (trailer, offset 36): expected dictionary key but found non-name object; inserting key /QPDFFake7
  16 +WARNING: issue-fuzz.pdf: file is damaged
  17 +WARNING: issue-fuzz.pdf (trailer, offset 32): /Size key in trailer dictionary is impossibly large
  18 +WARNING: issue-fuzz.pdf: Attempting to reconstruct cross-reference table
  19 +qpdf: issue-fuzz.pdf: unable to find /Root dictionary
... ...
qpdf/qtest/qpdf/issue-fuzz.pdf 0 → 100644
No preview for this file type
qpdf/qtest/specific-bugs.test
... ... @@ -38,6 +38,7 @@ my @bug_tests = (
38 38 ["263", "empty xref stream", 2],
39 39 ["335a", "ozz-fuzz-12152", 2],
40 40 ["335b", "ozz-fuzz-14845", 2],
  41 + ["fuzz", "impossibly large trailer /Size"],
41 42 # ["fuzz-16214", "stream in object stream", 3, "--preserve-unreferenced"],
42 43 # When adding to this list, consider adding to CORPUS_FROM_TEST in
43 44 # fuzz/CMakeLists.txt and updating the count in
... ...