Commit 0bfe9024893ebb1f62108fe6c24410e6ba589c3e

Authored by Jay Berkenbilt
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.
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&lt;int, int&gt; const&amp; object_stream_data) @@ -1466,8 +1484,11 @@ QPDF::calculateLinearizationData(std::map&lt;int, int&gt; const&amp; 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-&gt;runtest(&quot;remove page we don&#39;t have&quot;, @@ -199,7 +199,7 @@ $td-&gt;runtest(&quot;remove page we don&#39;t have&quot;,
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-&gt;runtest(&quot;bounds check linearization data 2&quot;, @@ -537,6 +537,13 @@ $td-&gt;runtest(&quot;bounds check linearization data 2&quot;,
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
  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