Commit 7477ea7828e57904213b2717f5d8ede0a8697ded
1 parent
f74b28f0
Add new private method QPDF::processXRefSize
Showing
2 changed files
with
41 additions
and
20 deletions
include/qpdf/QPDF.hh
| @@ -1028,11 +1028,13 @@ class QPDF | @@ -1028,11 +1028,13 @@ class QPDF | ||
| 1028 | qpdf_offset_t read_xrefTable(qpdf_offset_t offset); | 1028 | qpdf_offset_t read_xrefTable(qpdf_offset_t offset); |
| 1029 | qpdf_offset_t read_xrefStream(qpdf_offset_t offset); | 1029 | qpdf_offset_t read_xrefStream(qpdf_offset_t offset); |
| 1030 | qpdf_offset_t processXRefStream(qpdf_offset_t offset, QPDFObjectHandle& xref_stream); | 1030 | qpdf_offset_t processXRefStream(qpdf_offset_t offset, QPDFObjectHandle& xref_stream); |
| 1031 | - std::pair<size_t, std::array<int, 3>> | 1031 | + std::pair<int, std::array<int, 3>> |
| 1032 | processXRefW(QPDFObjectHandle& dict, std::function<QPDFExc(std::string_view)> damaged); | 1032 | processXRefW(QPDFObjectHandle& dict, std::function<QPDFExc(std::string_view)> damaged); |
| 1033 | - std::pair<size_t, std::vector<long long>> processXRefIndex( | 1033 | + int processXRefSize( |
| 1034 | + QPDFObjectHandle& dict, int entry_size, std::function<QPDFExc(std::string_view)> damaged); | ||
| 1035 | + std::pair<int, std::vector<long long>> processXRefIndex( | ||
| 1034 | QPDFObjectHandle& dict, | 1036 | QPDFObjectHandle& dict, |
| 1035 | - size_t entry_size, | 1037 | + int max_num_entries, |
| 1036 | std::function<QPDFExc(std::string_view)> damaged); | 1038 | std::function<QPDFExc(std::string_view)> damaged); |
| 1037 | void insertXrefEntry(int obj, int f0, qpdf_offset_t f1, int f2); | 1039 | void insertXrefEntry(int obj, int f0, qpdf_offset_t f1, int f2); |
| 1038 | void insertFreeXrefEntry(QPDFObjGen); | 1040 | void insertFreeXrefEntry(QPDFObjGen); |
libqpdf/QPDF.cc
| @@ -969,7 +969,7 @@ QPDF::read_xrefStream(qpdf_offset_t xref_offset) | @@ -969,7 +969,7 @@ QPDF::read_xrefStream(qpdf_offset_t xref_offset) | ||
| 969 | } | 969 | } |
| 970 | 970 | ||
| 971 | // Return the entry size of the xref stream and the processed W array. | 971 | // Return the entry size of the xref stream and the processed W array. |
| 972 | -std::pair<size_t, std::array<int, 3>> | 972 | +std::pair<int, std::array<int, 3>> |
| 973 | QPDF::processXRefW(QPDFObjectHandle& dict, std::function<QPDFExc(std::string_view)> damaged) | 973 | QPDF::processXRefW(QPDFObjectHandle& dict, std::function<QPDFExc(std::string_view)> damaged) |
| 974 | { | 974 | { |
| 975 | auto W_obj = dict.getKey("/W"); | 975 | auto W_obj = dict.getKey("/W"); |
| @@ -995,17 +995,39 @@ QPDF::processXRefW(QPDFObjectHandle& dict, std::function<QPDFExc(std::string_vie | @@ -995,17 +995,39 @@ QPDF::processXRefW(QPDFObjectHandle& dict, std::function<QPDFExc(std::string_vie | ||
| 995 | if (entry_size == 0) { | 995 | if (entry_size == 0) { |
| 996 | throw damaged("Cross-reference stream's /W indicates entry size of 0"); | 996 | throw damaged("Cross-reference stream's /W indicates entry size of 0"); |
| 997 | } | 997 | } |
| 998 | - return {toS(entry_size), W}; | 998 | + return {entry_size, W}; |
| 999 | } | 999 | } |
| 1000 | 1000 | ||
| 1001 | -// Return the expected size of the xref stream and the processed Index array. | ||
| 1002 | -std::pair<size_t, std::vector<long long>> | ||
| 1003 | -QPDF::processXRefIndex( | ||
| 1004 | - QPDFObjectHandle& dict, size_t entry_size, std::function<QPDFExc(std::string_view)> damaged) | 1001 | +// Validate Size key and return the maximum number of entries that the xref stream can contain. |
| 1002 | +int | ||
| 1003 | +QPDF::processXRefSize( | ||
| 1004 | + QPDFObjectHandle& dict, int entry_size, std::function<QPDFExc(std::string_view)> damaged) | ||
| 1005 | { | 1005 | { |
| 1006 | - auto max_num_entries = static_cast<unsigned long long>(-1) / entry_size; | 1006 | + // Number of entries is limited by the highest possible object id and stream size. |
| 1007 | + auto max_num_entries = std::numeric_limits<int>::max(); | ||
| 1008 | + if (max_num_entries > (std::numeric_limits<qpdf_offset_t>::max() / entry_size)) { | ||
| 1009 | + max_num_entries = toI(std::numeric_limits<qpdf_offset_t>::max() / entry_size); | ||
| 1010 | + } | ||
| 1007 | 1011 | ||
| 1008 | auto Size_obj = dict.getKey("/Size"); | 1012 | auto Size_obj = dict.getKey("/Size"); |
| 1013 | + long long size; | ||
| 1014 | + if (!dict.getKey("/Size").getValueAsInt(size)) { | ||
| 1015 | + throw damaged("Cross-reference stream does not have a proper /Size key"); | ||
| 1016 | + } else if (size < 0) { | ||
| 1017 | + throw damaged("Cross-reference stream has a negative /Size key"); | ||
| 1018 | + } else if (size >= max_num_entries) { | ||
| 1019 | + throw damaged("Cross-reference stream has an impossibly large /Size key"); | ||
| 1020 | + } | ||
| 1021 | + // We are not validating that Size <= (Size key of parent xref / trailer). | ||
| 1022 | + return max_num_entries; | ||
| 1023 | +} | ||
| 1024 | + | ||
| 1025 | +// Return the number of entries of the xref stream and the processed Index array. | ||
| 1026 | +std::pair<int, std::vector<long long>> | ||
| 1027 | +QPDF::processXRefIndex( | ||
| 1028 | + QPDFObjectHandle& dict, int max_num_entries, std::function<QPDFExc(std::string_view)> damaged) | ||
| 1029 | +{ | ||
| 1030 | + auto size = dict.getKey("/Size").getIntValueAsInt(); | ||
| 1009 | auto Index_obj = dict.getKey("/Index"); | 1031 | auto Index_obj = dict.getKey("/Index"); |
| 1010 | if (!(Index_obj.isArray() || Index_obj.isNull())) { | 1032 | if (!(Index_obj.isArray() || Index_obj.isNull())) { |
| 1011 | throw damaged("Cross-reference stream does not have a proper /Index key"); | 1033 | throw damaged("Cross-reference stream does not have a proper /Index key"); |
| @@ -1030,29 +1052,28 @@ QPDF::processXRefIndex( | @@ -1030,29 +1052,28 @@ QPDF::processXRefIndex( | ||
| 1030 | } else { | 1052 | } else { |
| 1031 | // We have already validated the /Index key. | 1053 | // We have already validated the /Index key. |
| 1032 | QTC::TC("qpdf", "QPDF xref /Index is null"); | 1054 | QTC::TC("qpdf", "QPDF xref /Index is null"); |
| 1033 | - long long size = Size_obj.getIntValue(); | ||
| 1034 | indx.push_back(0); | 1055 | indx.push_back(0); |
| 1035 | indx.push_back(size); | 1056 | indx.push_back(size); |
| 1036 | } | 1057 | } |
| 1037 | 1058 | ||
| 1038 | - size_t num_entries = 0; | 1059 | + int num_entries = 0; |
| 1039 | for (size_t i = 1; i < indx.size(); i += 2) { | 1060 | for (size_t i = 1; i < indx.size(); i += 2) { |
| 1040 | // We are guarding against the possibility of num_entries * entry_size overflowing. | 1061 | // We are guarding against the possibility of num_entries * entry_size overflowing. |
| 1041 | // We are not checking that entries are in ascending order as required by the spec, which | 1062 | // We are not checking that entries are in ascending order as required by the spec, which |
| 1042 | // probably should generate a warning. We are also not checking that for each subsection | 1063 | // probably should generate a warning. We are also not checking that for each subsection |
| 1043 | // first object number + number of entries <= /Size. The spec requires us to ignore object | 1064 | // first object number + number of entries <= /Size. The spec requires us to ignore object |
| 1044 | // number > /Size. | 1065 | // number > /Size. |
| 1045 | - if (indx.at(i) > QIntC::to_longlong(max_num_entries - num_entries)) { | 1066 | + if (indx.at(i) > (max_num_entries - num_entries)) { |
| 1046 | throw damaged( | 1067 | throw damaged( |
| 1047 | "Cross-reference stream claims to contain too many entries: " + | 1068 | "Cross-reference stream claims to contain too many entries: " + |
| 1048 | std::to_string(indx.at(i)) + " " + std::to_string(max_num_entries) + " " + | 1069 | std::to_string(indx.at(i)) + " " + std::to_string(max_num_entries) + " " + |
| 1049 | std::to_string(num_entries)); | 1070 | std::to_string(num_entries)); |
| 1050 | } | 1071 | } |
| 1051 | - num_entries += toS(indx.at(i)); | 1072 | + num_entries += indx.at(i); |
| 1052 | } | 1073 | } |
| 1053 | // entry_size and num_entries have both been validated to ensure that this multiplication does | 1074 | // entry_size and num_entries have both been validated to ensure that this multiplication does |
| 1054 | // not cause an overflow. | 1075 | // not cause an overflow. |
| 1055 | - return {entry_size * num_entries, indx}; | 1076 | + return {num_entries, indx}; |
| 1056 | } | 1077 | } |
| 1057 | 1078 | ||
| 1058 | qpdf_offset_t | 1079 | qpdf_offset_t |
| @@ -1063,16 +1084,14 @@ QPDF::processXRefStream(qpdf_offset_t xref_offset, QPDFObjectHandle& xref_obj) | @@ -1063,16 +1084,14 @@ QPDF::processXRefStream(qpdf_offset_t xref_offset, QPDFObjectHandle& xref_obj) | ||
| 1063 | }; | 1084 | }; |
| 1064 | 1085 | ||
| 1065 | auto dict = xref_obj.getDict(); | 1086 | auto dict = xref_obj.getDict(); |
| 1066 | - auto Size_obj = dict.getKey("/Size"); | ||
| 1067 | - if (!Size_obj.isInteger()) { | ||
| 1068 | - throw damaged("Cross-reference stream does not have a proper /Size key"); | ||
| 1069 | - } | ||
| 1070 | 1087 | ||
| 1071 | auto [entry_size, W] = processXRefW(dict, damaged); | 1088 | auto [entry_size, W] = processXRefW(dict, damaged); |
| 1072 | - auto [expected_size, indx] = processXRefIndex(dict, entry_size, damaged); | 1089 | + int max_num_entries = processXRefSize(dict, entry_size, damaged); |
| 1090 | + auto [num_entries, indx] = processXRefIndex(dict, max_num_entries, damaged); | ||
| 1073 | 1091 | ||
| 1074 | std::shared_ptr<Buffer> bp = xref_obj.getStreamData(qpdf_dl_specialized); | 1092 | std::shared_ptr<Buffer> bp = xref_obj.getStreamData(qpdf_dl_specialized); |
| 1075 | size_t actual_size = bp->getSize(); | 1093 | size_t actual_size = bp->getSize(); |
| 1094 | + auto expected_size = toS(entry_size) * toS(num_entries); | ||
| 1076 | 1095 | ||
| 1077 | if (expected_size != actual_size) { | 1096 | if (expected_size != actual_size) { |
| 1078 | QPDFExc x = damaged( | 1097 | QPDFExc x = damaged( |