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 2013-10-05 Jay Berkenbilt <ejb@ql.org> 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 * Security fix: avoid buffer overrun that could be caused by bogus 8 * Security fix: avoid buffer overrun that could be caused by bogus
4 data in linearization hint streams. The incorrect code could only 9 data in linearization hint streams. The incorrect code could only
5 be triggered when checking linearization data, which must be 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,7 +699,26 @@ QPDF::processXRefStream(qpdf_offset_t xref_offset, QPDFObjectHandle&amp; xref_obj)
699 "Cross-reference stream does not have" 699 "Cross-reference stream does not have"
700 " proper /W and /Index keys"); 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 if (Index_obj.isArray()) 722 if (Index_obj.isArray())
704 { 723 {
705 int n_index = Index_obj.getArrayNItems(); 724 int n_index = Index_obj.getArrayNItems();
@@ -731,25 +750,29 @@ QPDF::processXRefStream(qpdf_offset_t xref_offset, QPDFObjectHandle&amp; xref_obj) @@ -731,25 +750,29 @@ QPDF::processXRefStream(qpdf_offset_t xref_offset, QPDFObjectHandle&amp; xref_obj)
731 else 750 else
732 { 751 {
733 QTC::TC("qpdf", "QPDF xref /Index is null"); 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 indx.push_back(0); 754 indx.push_back(0);
736 indx.push_back(size); 755 indx.push_back(size);
737 } 756 }
738 757
739 - int num_entries = 0; 758 + long long num_entries = 0;
740 for (unsigned int i = 1; i < indx.size(); i += 2) 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 num_entries += indx[i]; 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 size_t expected_size = entry_size * num_entries; 776 size_t expected_size = entry_size * num_entries;
754 777
755 PointerHolder<Buffer> bp = xref_obj.getStreamData(); 778 PointerHolder<Buffer> bp = xref_obj.getStreamData();
@@ -777,6 +800,9 @@ QPDF::processXRefStream(qpdf_offset_t xref_offset, QPDFObjectHandle&amp; xref_obj) @@ -777,6 +800,9 @@ QPDF::processXRefStream(qpdf_offset_t xref_offset, QPDFObjectHandle&amp; xref_obj)
777 800
778 bool saw_first_compressed_object = false; 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 unsigned char const* data = bp->getBuffer(); 806 unsigned char const* data = bp->getBuffer();
781 for (int i = 0; i < num_entries; ++i) 807 for (int i = 0; i < num_entries; ++i)
782 { 808 {