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 | 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 | 9 | * Security fix: sanitize /W array in cross reference stream to |
| 4 | 10 | avoid a potential integer overflow in a multiplication. It is |
| 5 | 11 | unlikely that any exploits were possible from this bug as | ... | ... |
libqpdf/QPDF_linearization.cc
| ... | ... | @@ -23,13 +23,22 @@ static void |
| 23 | 23 | load_vector_int(BitStream& bit_stream, int nitems, std::vector<T>& vec, |
| 24 | 24 | int bits_wanted, int_type T::*field) |
| 25 | 25 | { |
| 26 | + bool append = vec.empty(); | |
| 26 | 27 | // nitems times, read bits_wanted from the given bit stream, |
| 27 | 28 | // storing results in the ith vector entry. |
| 28 | 29 | |
| 29 | 30 | for (int i = 0; i < nitems; ++i) |
| 30 | 31 | { |
| 32 | + if (append) | |
| 33 | + { | |
| 34 | + vec.push_back(T()); | |
| 35 | + } | |
| 31 | 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 | 42 | // The PDF spec says that each hint table starts at a byte |
| 34 | 43 | // boundary. Each "row" actually must start on a byte boundary. |
| 35 | 44 | bit_stream.skipToNextByte(); |
| ... | ... | @@ -255,6 +264,17 @@ QPDF::readLinearizationData() |
| 255 | 264 | |
| 256 | 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 | 278 | // file_size initialized by isLinearized() |
| 259 | 279 | this->linp.first_page_object = O.getIntValue(); |
| 260 | 280 | this->linp.first_page_end = E.getIntValue(); |
| ... | ... | @@ -396,10 +416,9 @@ QPDF::readHPageOffset(BitStream h) |
| 396 | 416 | t.nbits_shared_numerator = h.getBits(16); // 12 |
| 397 | 417 | t.shared_denominator = h.getBits(16); // 13 |
| 398 | 418 | |
| 399 | - unsigned int nitems = this->linp.npages; | |
| 400 | 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 | 422 | load_vector_int(h, nitems, entries, |
| 404 | 423 | t.nbits_delta_nobjects, |
| 405 | 424 | &HPageOffsetEntry::delta_nobjects); |
| ... | ... | @@ -441,10 +460,9 @@ QPDF::readHSharedObject(BitStream h) |
| 441 | 460 | QTC::TC("qpdf", "QPDF lin nshared_total > nshared_first_page", |
| 442 | 461 | (t.nshared_total > t.nshared_first_page) ? 1 : 0); |
| 443 | 462 | |
| 444 | - int nitems = t.nshared_total; | |
| 445 | 463 | std::vector<HSharedObjectEntry>& entries = t.entries; |
| 446 | - entries = std::vector<HSharedObjectEntry>(nitems); | |
| 447 | - | |
| 464 | + entries.clear(); | |
| 465 | + int nitems = t.nshared_total; | |
| 448 | 466 | load_vector_int(h, nitems, entries, |
| 449 | 467 | t.nbits_delta_group_length, |
| 450 | 468 | &HSharedObjectEntry::delta_group_length); |
| ... | ... | @@ -1466,8 +1484,11 @@ QPDF::calculateLinearizationData(std::map<int, int> const& object_stream_data) |
| 1466 | 1484 | // validation code can compute them relatively easily given the |
| 1467 | 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 | 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 | 1493 | // Part 4: open document objects. We don't care about the order. |
| 1473 | 1494 | |
| ... | ... | @@ -1861,6 +1882,7 @@ QPDF::calculateHPageOffset( |
| 1861 | 1882 | |
| 1862 | 1883 | HPageOffset& ph = this->page_offset_hints; |
| 1863 | 1884 | std::vector<HPageOffsetEntry>& phe = ph.entries; |
| 1885 | + // npages is the size of the existing pages array. | |
| 1864 | 1886 | phe = std::vector<HPageOffsetEntry>(npages); |
| 1865 | 1887 | |
| 1866 | 1888 | for (unsigned int i = 0; i < npages; ++i) |
| ... | ... | @@ -1935,7 +1957,7 @@ QPDF::calculateHSharedObject( |
| 1935 | 1957 | std::vector<CHSharedObjectEntry>& csoe = cso.entries; |
| 1936 | 1958 | HSharedObject& so = this->shared_object_hints; |
| 1937 | 1959 | std::vector<HSharedObjectEntry>& soe = so.entries; |
| 1938 | - soe = std::vector<HSharedObjectEntry>(cso.nshared_total); | |
| 1960 | + soe.clear(); | |
| 1939 | 1961 | |
| 1940 | 1962 | int min_length = outputLengthNextN( |
| 1941 | 1963 | csoe[0].object, 1, lengths, obj_renumber); |
| ... | ... | @@ -1948,8 +1970,13 @@ QPDF::calculateHSharedObject( |
| 1948 | 1970 | csoe[i].object, 1, lengths, obj_renumber); |
| 1949 | 1971 | min_length = std::min(min_length, length); |
| 1950 | 1972 | max_length = std::max(max_length, length); |
| 1973 | + soe.push_back(HSharedObjectEntry()); | |
| 1951 | 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 | 1981 | so.nshared_total = cso.nshared_total; |
| 1955 | 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 | 199 | show_ntests(); |
| 200 | 200 | # ---------- |
| 201 | 201 | $td->notify("--- Miscellaneous Tests ---"); |
| 202 | -$n_tests += 69; | |
| 202 | +$n_tests += 70; | |
| 203 | 203 | |
| 204 | 204 | $td->runtest("qpdf version", |
| 205 | 205 | {$td->COMMAND => "qpdf --version"}, |
| ... | ... | @@ -537,6 +537,13 @@ $td->runtest("bounds check linearization data 2", |
| 537 | 537 | {$td->FILE => "linearization-bounds-2.out", |
| 538 | 538 | $td->EXIT_STATUS => 2}, |
| 539 | 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 | 548 | show_ntests(); |
| 542 | 549 | # ---------- | ... | ... |
qpdf/qtest/qpdf/linearization-large-vector-alloc.out
0 → 100644
| 1 | +checking linearization-large-vector-alloc.pdf | |
| 2 | +PDF Version: 1.3 | |
| 3 | +File is not encrypted | |
| 4 | +File is linearized | |
| 5 | +WARNING: linearization-large-vector-alloc.pdf (linearization hint stream: object 62 0, file position 1183): attempting to recover stream length | |
| 6 | +overflow reading bit stream | ... | ... |
qpdf/qtest/qpdf/linearization-large-vector-alloc.pdf
0 → 100644
No preview for this file type