Commit 0bfe9024893ebb1f62108fe6c24410e6ba589c3e
1 parent
10bceb55
Security: avoid pre-allocating vectors based on file data
In places where std::vector<T>(size_t) was used, either validate that the size parameter is sane or refactor code to avoid the need to pre-allocate the vector.
Showing
5 changed files
with
55 additions
and
9 deletions
ChangeLog
| 1 | 2013-10-05 Jay Berkenbilt <ejb@ql.org> | 1 | 2013-10-05 Jay Berkenbilt <ejb@ql.org> |
| 2 | 2 | ||
| 3 | + * Security fix: In places where std::vector<T>(size_t) was used, | ||
| 4 | + either validate that the size parameter is sane or refactor code | ||
| 5 | + to avoid the need to pre-allocate the vector. This reduces the | ||
| 6 | + likelihood of allocating a lot of memory in response to invalid | ||
| 7 | + data in linearization hint streams. | ||
| 8 | + | ||
| 3 | * Security fix: sanitize /W array in cross reference stream to | 9 | * Security fix: sanitize /W array in cross reference stream to |
| 4 | avoid a potential integer overflow in a multiplication. It is | 10 | avoid a potential integer overflow in a multiplication. It is |
| 5 | unlikely that any exploits were possible from this bug as | 11 | unlikely that any exploits were possible from this bug as |
libqpdf/QPDF_linearization.cc
| @@ -23,13 +23,22 @@ static void | @@ -23,13 +23,22 @@ static void | ||
| 23 | load_vector_int(BitStream& bit_stream, int nitems, std::vector<T>& vec, | 23 | load_vector_int(BitStream& bit_stream, int nitems, std::vector<T>& vec, |
| 24 | int bits_wanted, int_type T::*field) | 24 | int bits_wanted, int_type T::*field) |
| 25 | { | 25 | { |
| 26 | + bool append = vec.empty(); | ||
| 26 | // nitems times, read bits_wanted from the given bit stream, | 27 | // nitems times, read bits_wanted from the given bit stream, |
| 27 | // storing results in the ith vector entry. | 28 | // storing results in the ith vector entry. |
| 28 | 29 | ||
| 29 | for (int i = 0; i < nitems; ++i) | 30 | for (int i = 0; i < nitems; ++i) |
| 30 | { | 31 | { |
| 32 | + if (append) | ||
| 33 | + { | ||
| 34 | + vec.push_back(T()); | ||
| 35 | + } | ||
| 31 | vec[i].*field = bit_stream.getBits(bits_wanted); | 36 | vec[i].*field = bit_stream.getBits(bits_wanted); |
| 32 | } | 37 | } |
| 38 | + if (static_cast<int>(vec.size()) != nitems) | ||
| 39 | + { | ||
| 40 | + throw std::logic_error("vector has wrong size in load_vector_int"); | ||
| 41 | + } | ||
| 33 | // The PDF spec says that each hint table starts at a byte | 42 | // The PDF spec says that each hint table starts at a byte |
| 34 | // boundary. Each "row" actually must start on a byte boundary. | 43 | // boundary. Each "row" actually must start on a byte boundary. |
| 35 | bit_stream.skipToNextByte(); | 44 | bit_stream.skipToNextByte(); |
| @@ -255,6 +264,17 @@ QPDF::readLinearizationData() | @@ -255,6 +264,17 @@ QPDF::readLinearizationData() | ||
| 255 | 264 | ||
| 256 | // Store linearization parameter data | 265 | // Store linearization parameter data |
| 257 | 266 | ||
| 267 | + // Various places in the code use linp.npages, which is | ||
| 268 | + // initialized from N, to pre-allocate memory, so make sure it's | ||
| 269 | + // accurate and bail right now if it's not. | ||
| 270 | + if (N.getIntValue() != static_cast<long long>(getAllPages().size())) | ||
| 271 | + { | ||
| 272 | + throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(), | ||
| 273 | + "linearization hint table", | ||
| 274 | + this->file->getLastOffset(), | ||
| 275 | + "/N does not match number of pages"); | ||
| 276 | + } | ||
| 277 | + | ||
| 258 | // file_size initialized by isLinearized() | 278 | // file_size initialized by isLinearized() |
| 259 | this->linp.first_page_object = O.getIntValue(); | 279 | this->linp.first_page_object = O.getIntValue(); |
| 260 | this->linp.first_page_end = E.getIntValue(); | 280 | this->linp.first_page_end = E.getIntValue(); |
| @@ -396,10 +416,9 @@ QPDF::readHPageOffset(BitStream h) | @@ -396,10 +416,9 @@ QPDF::readHPageOffset(BitStream h) | ||
| 396 | t.nbits_shared_numerator = h.getBits(16); // 12 | 416 | t.nbits_shared_numerator = h.getBits(16); // 12 |
| 397 | t.shared_denominator = h.getBits(16); // 13 | 417 | t.shared_denominator = h.getBits(16); // 13 |
| 398 | 418 | ||
| 399 | - unsigned int nitems = this->linp.npages; | ||
| 400 | std::vector<HPageOffsetEntry>& entries = t.entries; | 419 | std::vector<HPageOffsetEntry>& entries = t.entries; |
| 401 | - entries = std::vector<HPageOffsetEntry>(nitems); | ||
| 402 | - | 420 | + entries.clear(); |
| 421 | + unsigned int nitems = this->linp.npages; | ||
| 403 | load_vector_int(h, nitems, entries, | 422 | load_vector_int(h, nitems, entries, |
| 404 | t.nbits_delta_nobjects, | 423 | t.nbits_delta_nobjects, |
| 405 | &HPageOffsetEntry::delta_nobjects); | 424 | &HPageOffsetEntry::delta_nobjects); |
| @@ -441,10 +460,9 @@ QPDF::readHSharedObject(BitStream h) | @@ -441,10 +460,9 @@ QPDF::readHSharedObject(BitStream h) | ||
| 441 | QTC::TC("qpdf", "QPDF lin nshared_total > nshared_first_page", | 460 | QTC::TC("qpdf", "QPDF lin nshared_total > nshared_first_page", |
| 442 | (t.nshared_total > t.nshared_first_page) ? 1 : 0); | 461 | (t.nshared_total > t.nshared_first_page) ? 1 : 0); |
| 443 | 462 | ||
| 444 | - int nitems = t.nshared_total; | ||
| 445 | std::vector<HSharedObjectEntry>& entries = t.entries; | 463 | std::vector<HSharedObjectEntry>& entries = t.entries; |
| 446 | - entries = std::vector<HSharedObjectEntry>(nitems); | ||
| 447 | - | 464 | + entries.clear(); |
| 465 | + int nitems = t.nshared_total; | ||
| 448 | load_vector_int(h, nitems, entries, | 466 | load_vector_int(h, nitems, entries, |
| 449 | t.nbits_delta_group_length, | 467 | t.nbits_delta_group_length, |
| 450 | &HSharedObjectEntry::delta_group_length); | 468 | &HSharedObjectEntry::delta_group_length); |
| @@ -1466,8 +1484,11 @@ QPDF::calculateLinearizationData(std::map<int, int> const& object_stream_data) | @@ -1466,8 +1484,11 @@ QPDF::calculateLinearizationData(std::map<int, int> const& object_stream_data) | ||
| 1466 | // validation code can compute them relatively easily given the | 1484 | // validation code can compute them relatively easily given the |
| 1467 | // rest of the information. | 1485 | // rest of the information. |
| 1468 | 1486 | ||
| 1487 | + // npages is the size of the existing pages vector, which has been | ||
| 1488 | + // created by traversing the pages tree, and as such is a | ||
| 1489 | + // reasonable size. | ||
| 1469 | this->c_linp.npages = npages; | 1490 | this->c_linp.npages = npages; |
| 1470 | - this->c_page_offset_data.entries = std::vector<CHPageOffsetEntry>(npages); | 1491 | + this->c_page_offset_data.entries = std::vector<CHPageOffsetEntry>(npages); |
| 1471 | 1492 | ||
| 1472 | // Part 4: open document objects. We don't care about the order. | 1493 | // Part 4: open document objects. We don't care about the order. |
| 1473 | 1494 | ||
| @@ -1861,6 +1882,7 @@ QPDF::calculateHPageOffset( | @@ -1861,6 +1882,7 @@ QPDF::calculateHPageOffset( | ||
| 1861 | 1882 | ||
| 1862 | HPageOffset& ph = this->page_offset_hints; | 1883 | HPageOffset& ph = this->page_offset_hints; |
| 1863 | std::vector<HPageOffsetEntry>& phe = ph.entries; | 1884 | std::vector<HPageOffsetEntry>& phe = ph.entries; |
| 1885 | + // npages is the size of the existing pages array. | ||
| 1864 | phe = std::vector<HPageOffsetEntry>(npages); | 1886 | phe = std::vector<HPageOffsetEntry>(npages); |
| 1865 | 1887 | ||
| 1866 | for (unsigned int i = 0; i < npages; ++i) | 1888 | for (unsigned int i = 0; i < npages; ++i) |
| @@ -1935,7 +1957,7 @@ QPDF::calculateHSharedObject( | @@ -1935,7 +1957,7 @@ QPDF::calculateHSharedObject( | ||
| 1935 | std::vector<CHSharedObjectEntry>& csoe = cso.entries; | 1957 | std::vector<CHSharedObjectEntry>& csoe = cso.entries; |
| 1936 | HSharedObject& so = this->shared_object_hints; | 1958 | HSharedObject& so = this->shared_object_hints; |
| 1937 | std::vector<HSharedObjectEntry>& soe = so.entries; | 1959 | std::vector<HSharedObjectEntry>& soe = so.entries; |
| 1938 | - soe = std::vector<HSharedObjectEntry>(cso.nshared_total); | 1960 | + soe.clear(); |
| 1939 | 1961 | ||
| 1940 | int min_length = outputLengthNextN( | 1962 | int min_length = outputLengthNextN( |
| 1941 | csoe[0].object, 1, lengths, obj_renumber); | 1963 | csoe[0].object, 1, lengths, obj_renumber); |
| @@ -1948,8 +1970,13 @@ QPDF::calculateHSharedObject( | @@ -1948,8 +1970,13 @@ QPDF::calculateHSharedObject( | ||
| 1948 | csoe[i].object, 1, lengths, obj_renumber); | 1970 | csoe[i].object, 1, lengths, obj_renumber); |
| 1949 | min_length = std::min(min_length, length); | 1971 | min_length = std::min(min_length, length); |
| 1950 | max_length = std::max(max_length, length); | 1972 | max_length = std::max(max_length, length); |
| 1973 | + soe.push_back(HSharedObjectEntry()); | ||
| 1951 | soe[i].delta_group_length = length; | 1974 | soe[i].delta_group_length = length; |
| 1952 | } | 1975 | } |
| 1976 | + if (soe.size() != static_cast<size_t>(cso.nshared_total)) | ||
| 1977 | + { | ||
| 1978 | + throw std::logic_error("soe has wrong size after initialization"); | ||
| 1979 | + } | ||
| 1953 | 1980 | ||
| 1954 | so.nshared_total = cso.nshared_total; | 1981 | so.nshared_total = cso.nshared_total; |
| 1955 | so.nshared_first_page = cso.nshared_first_page; | 1982 | so.nshared_first_page = cso.nshared_first_page; |
qpdf/qtest/qpdf.test
| @@ -199,7 +199,7 @@ $td->runtest("remove page we don't have", | @@ -199,7 +199,7 @@ $td->runtest("remove page we don't have", | ||
| 199 | show_ntests(); | 199 | show_ntests(); |
| 200 | # ---------- | 200 | # ---------- |
| 201 | $td->notify("--- Miscellaneous Tests ---"); | 201 | $td->notify("--- Miscellaneous Tests ---"); |
| 202 | -$n_tests += 69; | 202 | +$n_tests += 70; |
| 203 | 203 | ||
| 204 | $td->runtest("qpdf version", | 204 | $td->runtest("qpdf version", |
| 205 | {$td->COMMAND => "qpdf --version"}, | 205 | {$td->COMMAND => "qpdf --version"}, |
| @@ -537,6 +537,13 @@ $td->runtest("bounds check linearization data 2", | @@ -537,6 +537,13 @@ $td->runtest("bounds check linearization data 2", | ||
| 537 | {$td->FILE => "linearization-bounds-2.out", | 537 | {$td->FILE => "linearization-bounds-2.out", |
| 538 | $td->EXIT_STATUS => 2}, | 538 | $td->EXIT_STATUS => 2}, |
| 539 | $td->NORMALIZE_NEWLINES); | 539 | $td->NORMALIZE_NEWLINES); |
| 540 | +# Throws logic error, not bad_alloc | ||
| 541 | +$td->runtest("sanity check array size", | ||
| 542 | + {$td->COMMAND => | ||
| 543 | + "qpdf --check linearization-large-vector-alloc.pdf"}, | ||
| 544 | + {$td->FILE => "linearization-large-vector-alloc.out", | ||
| 545 | + $td->EXIT_STATUS => 2}, | ||
| 546 | + $td->NORMALIZE_NEWLINES); | ||
| 540 | 547 | ||
| 541 | show_ntests(); | 548 | show_ntests(); |
| 542 | # ---------- | 549 | # ---------- |
qpdf/qtest/qpdf/linearization-large-vector-alloc.out
0 → 100644
qpdf/qtest/qpdf/linearization-large-vector-alloc.pdf
0 → 100644
No preview for this file type