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 | 1028 | qpdf_offset_t read_xrefTable(qpdf_offset_t offset); |
| 1029 | 1029 | qpdf_offset_t read_xrefStream(qpdf_offset_t offset); |
| 1030 | 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 | 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 | 1036 | QPDFObjectHandle& dict, |
| 1035 | - size_t entry_size, | |
| 1037 | + int max_num_entries, | |
| 1036 | 1038 | std::function<QPDFExc(std::string_view)> damaged); |
| 1037 | 1039 | void insertXrefEntry(int obj, int f0, qpdf_offset_t f1, int f2); |
| 1038 | 1040 | void insertFreeXrefEntry(QPDFObjGen); | ... | ... |
libqpdf/QPDF.cc
| ... | ... | @@ -969,7 +969,7 @@ QPDF::read_xrefStream(qpdf_offset_t xref_offset) |
| 969 | 969 | } |
| 970 | 970 | |
| 971 | 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 | 973 | QPDF::processXRefW(QPDFObjectHandle& dict, std::function<QPDFExc(std::string_view)> damaged) |
| 974 | 974 | { |
| 975 | 975 | auto W_obj = dict.getKey("/W"); |
| ... | ... | @@ -995,17 +995,39 @@ QPDF::processXRefW(QPDFObjectHandle& dict, std::function<QPDFExc(std::string_vie |
| 995 | 995 | if (entry_size == 0) { |
| 996 | 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 | 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 | 1031 | auto Index_obj = dict.getKey("/Index"); |
| 1010 | 1032 | if (!(Index_obj.isArray() || Index_obj.isNull())) { |
| 1011 | 1033 | throw damaged("Cross-reference stream does not have a proper /Index key"); |
| ... | ... | @@ -1030,29 +1052,28 @@ QPDF::processXRefIndex( |
| 1030 | 1052 | } else { |
| 1031 | 1053 | // We have already validated the /Index key. |
| 1032 | 1054 | QTC::TC("qpdf", "QPDF xref /Index is null"); |
| 1033 | - long long size = Size_obj.getIntValue(); | |
| 1034 | 1055 | indx.push_back(0); |
| 1035 | 1056 | indx.push_back(size); |
| 1036 | 1057 | } |
| 1037 | 1058 | |
| 1038 | - size_t num_entries = 0; | |
| 1059 | + int num_entries = 0; | |
| 1039 | 1060 | for (size_t i = 1; i < indx.size(); i += 2) { |
| 1040 | 1061 | // We are guarding against the possibility of num_entries * entry_size overflowing. |
| 1041 | 1062 | // We are not checking that entries are in ascending order as required by the spec, which |
| 1042 | 1063 | // probably should generate a warning. We are also not checking that for each subsection |
| 1043 | 1064 | // first object number + number of entries <= /Size. The spec requires us to ignore object |
| 1044 | 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 | 1067 | throw damaged( |
| 1047 | 1068 | "Cross-reference stream claims to contain too many entries: " + |
| 1048 | 1069 | std::to_string(indx.at(i)) + " " + std::to_string(max_num_entries) + " " + |
| 1049 | 1070 | std::to_string(num_entries)); |
| 1050 | 1071 | } |
| 1051 | - num_entries += toS(indx.at(i)); | |
| 1072 | + num_entries += indx.at(i); | |
| 1052 | 1073 | } |
| 1053 | 1074 | // entry_size and num_entries have both been validated to ensure that this multiplication does |
| 1054 | 1075 | // not cause an overflow. |
| 1055 | - return {entry_size * num_entries, indx}; | |
| 1076 | + return {num_entries, indx}; | |
| 1056 | 1077 | } |
| 1057 | 1078 | |
| 1058 | 1079 | qpdf_offset_t |
| ... | ... | @@ -1063,16 +1084,14 @@ QPDF::processXRefStream(qpdf_offset_t xref_offset, QPDFObjectHandle& xref_obj) |
| 1063 | 1084 | }; |
| 1064 | 1085 | |
| 1065 | 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 | 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 | 1092 | std::shared_ptr<Buffer> bp = xref_obj.getStreamData(qpdf_dl_specialized); |
| 1075 | 1093 | size_t actual_size = bp->getSize(); |
| 1094 | + auto expected_size = toS(entry_size) * toS(num_entries); | |
| 1076 | 1095 | |
| 1077 | 1096 | if (expected_size != actual_size) { |
| 1078 | 1097 | QPDFExc x = damaged( | ... | ... |