Commit 10bceb552f1cfd2ddae3c8bfd7d9b38a66e710c4

Authored by Jay Berkenbilt
1 parent 3eb4b066

Security: sanitize /W in xref stream

The /W array was not sanitized, possibly causing an integer overflow
in a multiplication. An analysis of the code suggests that there were
no possible exploits based on this since the problems were in checking
expected values but bounds checks were performed on actual values.
Showing 2 changed files with 42 additions and 11 deletions
ChangeLog
1 1 2013-10-05 Jay Berkenbilt <ejb@ql.org>
2 2  
  3 + * Security fix: sanitize /W array in cross reference stream to
  4 + avoid a potential integer overflow in a multiplication. It is
  5 + unlikely that any exploits were possible from this bug as
  6 + additional checks were also performed.
  7 +
3 8 * Security fix: avoid buffer overrun that could be caused by bogus
4 9 data in linearization hint streams. The incorrect code could only
5 10 be triggered when checking linearization data, which must be
... ...
libqpdf/QPDF.cc
... ... @@ -699,7 +699,26 @@ QPDF::processXRefStream(qpdf_offset_t xref_offset, QPDFObjectHandle&amp; xref_obj)
699 699 "Cross-reference stream does not have"
700 700 " proper /W and /Index keys");
701 701 }
702   - std::vector<int> indx;
  702 +
  703 + int W[3];
  704 + size_t entry_size = 0;
  705 + int max_bytes = sizeof(qpdf_offset_t);
  706 + for (int i = 0; i < 3; ++i)
  707 + {
  708 + W[i] = W_obj.getArrayItem(i).getIntValue();
  709 + if (W[i] > max_bytes)
  710 + {
  711 + throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(),
  712 + "xref stream", xref_offset,
  713 + "Cross-reference stream's /W contains"
  714 + " impossibly large values");
  715 + }
  716 + entry_size += W[i];
  717 + }
  718 + long long max_num_entries =
  719 + static_cast<unsigned long long>(-1) / entry_size;
  720 +
  721 + std::vector<long long> indx;
703 722 if (Index_obj.isArray())
704 723 {
705 724 int n_index = Index_obj.getArrayNItems();
... ... @@ -731,25 +750,29 @@ QPDF::processXRefStream(qpdf_offset_t xref_offset, QPDFObjectHandle&amp; xref_obj)
731 750 else
732 751 {
733 752 QTC::TC("qpdf", "QPDF xref /Index is null");
734   - int size = dict.getKey("/Size").getIntValue();
  753 + long long size = dict.getKey("/Size").getIntValue();
735 754 indx.push_back(0);
736 755 indx.push_back(size);
737 756 }
738 757  
739   - int num_entries = 0;
  758 + long long num_entries = 0;
740 759 for (unsigned int i = 1; i < indx.size(); i += 2)
741 760 {
  761 + if (indx[i] > max_num_entries - num_entries)
  762 + {
  763 + throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(),
  764 + "xref stream", xref_offset,
  765 + "Cross-reference stream claims to contain"
  766 + " too many entries: " +
  767 + QUtil::int_to_string(indx[i]) + " " +
  768 + QUtil::int_to_string(max_num_entries) + " " +
  769 + QUtil::int_to_string(num_entries));
  770 + }
742 771 num_entries += indx[i];
743 772 }
744 773  
745   - int W[3];
746   - int entry_size = 0;
747   - for (int i = 0; i < 3; ++i)
748   - {
749   - W[i] = W_obj.getArrayItem(i).getIntValue();
750   - entry_size += W[i];
751   - }
752   -
  774 + // entry_size and num_entries have both been validated to ensure
  775 + // that this multiplication does not cause an overflow.
753 776 size_t expected_size = entry_size * num_entries;
754 777  
755 778 PointerHolder<Buffer> bp = xref_obj.getStreamData();
... ... @@ -777,6 +800,9 @@ QPDF::processXRefStream(qpdf_offset_t xref_offset, QPDFObjectHandle&amp; xref_obj)
777 800  
778 801 bool saw_first_compressed_object = false;
779 802  
  803 + // Actual size vs. expected size check above ensures that we will
  804 + // not overflow any buffers here. We know that entry_size *
  805 + // num_entries is equal to the size of the buffer.
780 806 unsigned char const* data = bp->getBuffer();
781 807 for (int i = 0; i < num_entries; ++i)
782 808 {
... ...