Commit ba5fb6916446d8bdf79cba25f08d759bc5595aec

Authored by Jay Berkenbilt
1 parent dadf8307

Make popping pipeline stack safer

Use destructors to pop the pipeline stack, and ensure that code that
pops the stack is actually popping the intended thing.
include/qpdf/Pipeline.hh
... ... @@ -70,6 +70,8 @@ class QPDF_DLL_CLASS Pipeline
70 70 virtual void write(unsigned char* data, size_t len) = 0;
71 71 QPDF_DLL
72 72 virtual void finish() = 0;
  73 + QPDF_DLL
  74 + std::string getIdentifier() const;
73 75  
74 76 protected:
75 77 Pipeline* getNext(bool allow_null = false);
... ...
include/qpdf/QPDFWriter.hh
... ... @@ -473,6 +473,34 @@ class QPDFWriter
473 473  
474 474 enum trailer_e { t_normal, t_lin_first, t_lin_second };
475 475  
  476 + // An reference to a PipelinePopper instance is passed into
  477 + // activatePipelineStack. When the PipelinePopper goes out of
  478 + // scope, the pipeline stack is popped. PipelinePopper's
  479 + // destructor calls finish on the current pipeline and pops the
  480 + // pipeline stack until the top of stack is a previous active top
  481 + // of stack, and restores the pipeline to that point. It deletes
  482 + // any pipelines that it pops. If the bp argument is non-null and
  483 + // any of the stack items are of type Pl_Buffer, the buffer is
  484 + // retrieved.
  485 + class PipelinePopper
  486 + {
  487 + friend class QPDFWriter;
  488 + public:
  489 + PipelinePopper(QPDFWriter* qw,
  490 + PointerHolder<Buffer>* bp = 0) :
  491 + qw(qw),
  492 + bp(bp)
  493 + {
  494 + }
  495 + ~PipelinePopper();
  496 +
  497 + private:
  498 + QPDFWriter* qw;
  499 + PointerHolder<Buffer>* bp;
  500 + std::string stack_id;
  501 + };
  502 + friend class PipelinePopper;
  503 +
476 504 unsigned int bytesNeeded(long long n);
477 505 void writeBinary(unsigned long long val, unsigned int bytes);
478 506 void writeString(std::string const& str);
... ... @@ -560,24 +588,17 @@ class QPDFWriter
560 588 int calculateXrefStreamPadding(qpdf_offset_t xref_bytes);
561 589  
562 590 // When filtering subsections, push additional pipelines to the
563   - // stack. When ready to switch, activate the pipeline stack.
564   - // Pipelines passed to pushPipeline are deleted when
565   - // clearPipelineStack is called.
  591 + // stack. When ready to switch, activate the pipeline stack. When
  592 + // the passed in PipelinePopper goes out of scope, the stack is
  593 + // popped.
566 594 Pipeline* pushPipeline(Pipeline*);
567   - void activatePipelineStack();
  595 + void activatePipelineStack(PipelinePopper&);
568 596 void initializePipelineStack(Pipeline *);
569 597  
570   - // Calls finish on the current pipeline and pops the pipeline
571   - // stack until the top of stack is a previous active top of stack,
572   - // and restores the pipeline to that point. Deletes any pipelines
573   - // that it pops. If the bp argument is non-null and any of the
574   - // stack items are of type Pl_Buffer, the buffer is retrieved.
575   - void popPipelineStack(PointerHolder<Buffer>* bp = 0);
576   -
577 598 void adjustAESStreamLength(size_t& length);
578   - void pushEncryptionFilter();
579   - void pushDiscardFilter();
580   - void pushMD5Pipeline();
  599 + void pushEncryptionFilter(PipelinePopper&);
  600 + void pushDiscardFilter(PipelinePopper&);
  601 + void pushMD5Pipeline(PipelinePopper&);
581 602 void computeDeterministicIDData();
582 603  
583 604 void discardGeneration(std::map<QPDFObjGen, int> const& in,
... ... @@ -654,6 +675,7 @@ class QPDFWriter
654 675 std::map<QPDFObjGen, int> object_to_object_stream;
655 676 std::map<int, std::set<QPDFObjGen> > object_stream_to_objects;
656 677 std::list<Pipeline*> pipeline_stack;
  678 + unsigned long long next_stack_id;
657 679 bool deterministic_id;
658 680 Pl_MD5* md5_pipeline;
659 681 std::string deterministic_id_data;
... ...
libqpdf/Pipeline.cc
... ... @@ -31,3 +31,9 @@ Pipeline::getNext(bool allow_null)
31 31 }
32 32 return this->m->next;
33 33 }
  34 +
  35 +std::string
  36 +Pipeline::getIdentifier() const
  37 +{
  38 + return this->identifier;
  39 +}
... ...
libqpdf/QPDFWriter.cc
... ... @@ -63,6 +63,7 @@ QPDFWriter::Members::Members(QPDF&amp; pdf) :
63 63 cur_stream_length(0),
64 64 added_newline(false),
65 65 max_ostream_index(0),
  66 + next_stack_id(0),
66 67 deterministic_id(false),
67 68 md5_pipeline(0),
68 69 did_write_setup(false),
... ... @@ -1049,36 +1050,51 @@ QPDFWriter::pushPipeline(Pipeline* p)
1049 1050 void
1050 1051 QPDFWriter::initializePipelineStack(Pipeline *p)
1051 1052 {
1052   - this->m->pipeline = new Pl_Count("qpdf count", p);
  1053 + this->m->pipeline = new Pl_Count("pipeline stack base", p);
1053 1054 this->m->to_delete.push_back(this->m->pipeline);
1054 1055 this->m->pipeline_stack.push_back(this->m->pipeline);
1055 1056 }
1056 1057  
1057 1058 void
1058   -QPDFWriter::activatePipelineStack()
  1059 +QPDFWriter::activatePipelineStack(PipelinePopper& pp)
1059 1060 {
1060   - Pl_Count* c = new Pl_Count("count", this->m->pipeline_stack.back());
  1061 + std::string stack_id(
  1062 + "stack " + QUtil::uint_to_string(this->m->next_stack_id));
  1063 + Pl_Count* c = new Pl_Count(stack_id.c_str(),
  1064 + this->m->pipeline_stack.back());
  1065 + ++this->m->next_stack_id;
1061 1066 this->m->pipeline_stack.push_back(c);
1062 1067 this->m->pipeline = c;
  1068 + pp.stack_id = stack_id;
1063 1069 }
1064 1070  
1065   -void
1066   -QPDFWriter::popPipelineStack(PointerHolder<Buffer>* bp)
  1071 +QPDFWriter::PipelinePopper::~PipelinePopper()
1067 1072 {
1068   - assert(this->m->pipeline_stack.size() >= 2);
1069   - this->m->pipeline->finish();
1070   - assert(dynamic_cast<Pl_Count*>(this->m->pipeline_stack.back()) ==
1071   - this->m->pipeline);
1072   - delete this->m->pipeline_stack.back();
1073   - this->m->pipeline_stack.pop_back();
1074   - while (dynamic_cast<Pl_Count*>(this->m->pipeline_stack.back()) == 0)
1075   - {
1076   - Pipeline* p = this->m->pipeline_stack.back();
1077   - if (dynamic_cast<Pl_MD5*>(p) == this->m->md5_pipeline)
  1073 + if (stack_id.empty())
  1074 + {
  1075 + return;
  1076 + }
  1077 + assert(qw->m->pipeline_stack.size() >= 2);
  1078 + qw->m->pipeline->finish();
  1079 + assert(dynamic_cast<Pl_Count*>(qw->m->pipeline_stack.back()) ==
  1080 + qw->m->pipeline);
  1081 + // It might be possible for this assertion to fail if
  1082 + // writeLinearized exits by exception when deterministic ID, but I
  1083 + // don't think so. As of this writing, this is the only case in
  1084 + // which two dynamically allocated PipelinePopper objects ever
  1085 + // exist at the same time, so the assertion will fail if they get
  1086 + // popped out of order from automatic destruction.
  1087 + assert(qw->m->pipeline->getIdentifier() == stack_id);
  1088 + delete qw->m->pipeline_stack.back();
  1089 + qw->m->pipeline_stack.pop_back();
  1090 + while (dynamic_cast<Pl_Count*>(qw->m->pipeline_stack.back()) == 0)
  1091 + {
  1092 + Pipeline* p = qw->m->pipeline_stack.back();
  1093 + if (dynamic_cast<Pl_MD5*>(p) == qw->m->md5_pipeline)
1078 1094 {
1079   - this->m->md5_pipeline = 0;
  1095 + qw->m->md5_pipeline = 0;
1080 1096 }
1081   - this->m->pipeline_stack.pop_back();
  1097 + qw->m->pipeline_stack.pop_back();
1082 1098 Pl_Buffer* buf = dynamic_cast<Pl_Buffer*>(p);
1083 1099 if (bp && buf)
1084 1100 {
... ... @@ -1086,7 +1102,7 @@ QPDFWriter::popPipelineStack(PointerHolder&lt;Buffer&gt;* bp)
1086 1102 }
1087 1103 delete p;
1088 1104 }
1089   - this->m->pipeline = dynamic_cast<Pl_Count*>(this->m->pipeline_stack.back());
  1105 + qw->m->pipeline = dynamic_cast<Pl_Count*>(qw->m->pipeline_stack.back());
1090 1106 }
1091 1107  
1092 1108 void
... ... @@ -1103,7 +1119,7 @@ QPDFWriter::adjustAESStreamLength(size_t&amp; length)
1103 1119 }
1104 1120  
1105 1121 void
1106   -QPDFWriter::pushEncryptionFilter()
  1122 +QPDFWriter::pushEncryptionFilter(PipelinePopper& pp)
1107 1123 {
1108 1124 if (this->m->encrypted && (! this->m->cur_data_key.empty()))
1109 1125 {
... ... @@ -1125,18 +1141,18 @@ QPDFWriter::pushEncryptionFilter()
1125 1141 }
1126 1142 // Must call this unconditionally so we can call popPipelineStack
1127 1143 // to balance pushEncryptionFilter().
1128   - activatePipelineStack();
  1144 + activatePipelineStack(pp);
1129 1145 }
1130 1146  
1131 1147 void
1132   -QPDFWriter::pushDiscardFilter()
  1148 +QPDFWriter::pushDiscardFilter(PipelinePopper& pp)
1133 1149 {
1134 1150 pushPipeline(new Pl_Discard());
1135   - activatePipelineStack();
  1151 + activatePipelineStack(pp);
1136 1152 }
1137 1153  
1138 1154 void
1139   -QPDFWriter::pushMD5Pipeline()
  1155 +QPDFWriter::pushMD5Pipeline(PipelinePopper& pp)
1140 1156 {
1141 1157 if (! this->m->id2.empty())
1142 1158 {
... ... @@ -1153,7 +1169,7 @@ QPDFWriter::pushMD5Pipeline()
1153 1169 // Special case code in popPipelineStack clears this->m->md5_pipeline
1154 1170 // upon deletion.
1155 1171 pushPipeline(this->m->md5_pipeline);
1156   - activatePipelineStack();
  1172 + activatePipelineStack(pp);
1157 1173 }
1158 1174  
1159 1175 void
... ... @@ -1769,8 +1785,8 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level,
1769 1785 for (int attempt = 1; attempt <= 2; ++attempt)
1770 1786 {
1771 1787 pushPipeline(new Pl_Buffer("stream data"));
1772   - activatePipelineStack();
1773   -
  1788 + PipelinePopper pp_stream_data(this, &stream_data);
  1789 + activatePipelineStack(pp_stream_data);
1774 1790 filtered =
1775 1791 object.pipeStreamData(
1776 1792 this->m->pipeline,
... ... @@ -1779,7 +1795,6 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level,
1779 1795 (filter
1780 1796 ? (uncompress ? qpdf_dl_all : this->m->stream_decode_level)
1781 1797 : qpdf_dl_none), false, (attempt == 1));
1782   - popPipelineStack(&stream_data);
1783 1798 if (filter && (! filtered))
1784 1799 {
1785 1800 // Try again
... ... @@ -1808,11 +1823,14 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level,
1808 1823 adjustAESStreamLength(this->m->cur_stream_length);
1809 1824 unparseObject(stream_dict, 0, flags,
1810 1825 this->m->cur_stream_length, compress);
  1826 + unsigned char last_char = '\0';
1811 1827 writeString("\nstream\n");
1812   - pushEncryptionFilter();
1813   - writeBuffer(stream_data);
1814   - unsigned char last_char = this->m->pipeline->getLastChar();
1815   - popPipelineStack();
  1828 + {
  1829 + PipelinePopper pp_enc(this);
  1830 + pushEncryptionFilter(pp_enc);
  1831 + writeBuffer(stream_data);
  1832 + last_char = this->m->pipeline->getLastChar();
  1833 + }
1816 1834  
1817 1835 if (this->m->newline_before_endstream ||
1818 1836 (this->m->qdf_mode && (last_char != '\n')))
... ... @@ -1911,9 +1929,11 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object)
1911 1929 bool compressed = false;
1912 1930 for (int pass = 1; pass <= 2; ++pass)
1913 1931 {
  1932 + // stream_buffer will be initialized only for pass 2
  1933 + PipelinePopper pp_ostream(this, &stream_buffer);
1914 1934 if (pass == 1)
1915 1935 {
1916   - pushDiscardFilter();
  1936 + pushDiscardFilter(pp_ostream);
1917 1937 }
1918 1938 else
1919 1939 {
... ... @@ -1928,10 +1948,12 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object)
1928 1948  
1929 1949 // Take one pass at writing pairs of numbers so we can get
1930 1950 // their size information
1931   - pushDiscardFilter();
1932   - writeObjectStreamOffsets(offsets, first_obj);
1933   - first += this->m->pipeline->getCount();
1934   - popPipelineStack();
  1951 + {
  1952 + PipelinePopper pp_discard(this);
  1953 + pushDiscardFilter(pp_discard);
  1954 + writeObjectStreamOffsets(offsets, first_obj);
  1955 + first += this->m->pipeline->getCount();
  1956 + }
1935 1957  
1936 1958 // Set up a stream to write the stream data into a buffer.
1937 1959 Pipeline* next = pushPipeline(new Pl_Buffer("object stream"));
... ... @@ -1944,7 +1966,7 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object)
1944 1966 new Pl_Flate("compress object stream", next,
1945 1967 Pl_Flate::a_deflate));
1946 1968 }
1947   - activatePipelineStack();
  1969 + activatePipelineStack(pp_ostream);
1948 1970 writeObjectStreamOffsets(offsets, first_obj);
1949 1971 }
1950 1972  
... ... @@ -1994,9 +2016,6 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object)
1994 2016  
1995 2017 this->m->xref[new_obj] = QPDFXRefEntry(2, new_id, count);
1996 2018 }
1997   -
1998   - // stream_buffer will be initialized only for pass 2
1999   - popPipelineStack(&stream_buffer);
2000 2019 }
2001 2020  
2002 2021 // Write the object
... ... @@ -2037,9 +2056,11 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object)
2037 2056 {
2038 2057 QTC::TC("qpdf", "QPDFWriter encrypt object stream");
2039 2058 }
2040   - pushEncryptionFilter();
2041   - writeBuffer(stream_buffer);
2042   - popPipelineStack();
  2059 + {
  2060 + PipelinePopper pp_enc(this);
  2061 + pushEncryptionFilter(pp_enc);
  2062 + writeBuffer(stream_buffer);
  2063 + }
2043 2064 if (this->m->newline_before_endstream)
2044 2065 {
2045 2066 writeString("\n");
... ... @@ -2779,10 +2800,13 @@ QPDFWriter::writeHintStream(int hint_id)
2779 2800 {
2780 2801 QTC::TC("qpdf", "QPDFWriter encrypted hint stream");
2781 2802 }
2782   - pushEncryptionFilter();
2783   - writeBuffer(hint_buffer);
2784   - unsigned char last_char = this->m->pipeline->getLastChar();
2785   - popPipelineStack();
  2803 + unsigned char last_char = '\0';
  2804 + {
  2805 + PipelinePopper pp_enc(this);
  2806 + pushEncryptionFilter(pp_enc);
  2807 + writeBuffer(hint_buffer);
  2808 + last_char = this->m->pipeline->getLastChar();
  2809 + }
2786 2810  
2787 2811 if (last_char != '\n')
2788 2812 {
... ... @@ -2896,46 +2920,48 @@ QPDFWriter::writeXRefStream(int xref_id, int max_id, qpdf_offset_t max_offset,
2896 2920 new Pl_PNGFilter(
2897 2921 "pngify xref", p, Pl_PNGFilter::a_encode, esize));
2898 2922 }
2899   - activatePipelineStack();
2900   - for (int i = first; i <= last; ++i)
  2923 + PointerHolder<Buffer> xref_data;
2901 2924 {
2902   - QPDFXRefEntry& e = this->m->xref[i];
2903   - switch (e.getType())
2904   - {
2905   - case 0:
2906   - writeBinary(0, 1);
2907   - writeBinary(0, f1_size);
2908   - writeBinary(0, f2_size);
2909   - break;
  2925 + PipelinePopper pp_xref(this, &xref_data);
  2926 + activatePipelineStack(pp_xref);
  2927 + for (int i = first; i <= last; ++i)
  2928 + {
  2929 + QPDFXRefEntry& e = this->m->xref[i];
  2930 + switch (e.getType())
  2931 + {
  2932 + case 0:
  2933 + writeBinary(0, 1);
  2934 + writeBinary(0, f1_size);
  2935 + writeBinary(0, f2_size);
  2936 + break;
2910 2937  
2911   - case 1:
2912   - {
2913   - qpdf_offset_t offset = e.getOffset();
2914   - if ((hint_id != 0) &&
2915   - (i != hint_id) &&
2916   - (offset >= hint_offset))
2917   - {
2918   - offset += hint_length;
2919   - }
2920   - writeBinary(1, 1);
2921   - writeBinary(QIntC::to_ulonglong(offset), f1_size);
2922   - writeBinary(0, f2_size);
2923   - }
2924   - break;
  2938 + case 1:
  2939 + {
  2940 + qpdf_offset_t offset = e.getOffset();
  2941 + if ((hint_id != 0) &&
  2942 + (i != hint_id) &&
  2943 + (offset >= hint_offset))
  2944 + {
  2945 + offset += hint_length;
  2946 + }
  2947 + writeBinary(1, 1);
  2948 + writeBinary(QIntC::to_ulonglong(offset), f1_size);
  2949 + writeBinary(0, f2_size);
  2950 + }
  2951 + break;
2925 2952  
2926   - case 2:
2927   - writeBinary(2, 1);
2928   - writeBinary(QIntC::to_ulonglong(e.getObjStreamNumber()), f1_size);
2929   - writeBinary(QIntC::to_ulonglong(e.getObjStreamIndex()), f2_size);
2930   - break;
  2953 + case 2:
  2954 + writeBinary(2, 1);
  2955 + writeBinary(QIntC::to_ulonglong(e.getObjStreamNumber()), f1_size);
  2956 + writeBinary(QIntC::to_ulonglong(e.getObjStreamIndex()), f2_size);
  2957 + break;
2931 2958  
2932   - default:
2933   - throw std::logic_error("invalid type writing xref stream");
2934   - break;
2935   - }
  2959 + default:
  2960 + throw std::logic_error("invalid type writing xref stream");
  2961 + break;
  2962 + }
  2963 + }
2936 2964 }
2937   - PointerHolder<Buffer> xref_data;
2938   - popPipelineStack(&xref_data);
2939 2965  
2940 2966 openObject(xref_id);
2941 2967 writeString("<<");
... ... @@ -3156,6 +3182,8 @@ QPDFWriter::writeLinearized()
3156 3182 // Write file in two passes. Part numbers refer to PDF spec 1.4.
3157 3183  
3158 3184 FILE* lin_pass1_file = 0;
  3185 + PointerHolder<PipelinePopper> pp_pass1 = new PipelinePopper(this);
  3186 + PointerHolder<PipelinePopper> pp_md5 = new PipelinePopper(this);
3159 3187 for (int pass = 1; pass <= 2; ++pass)
3160 3188 {
3161 3189 if (pass == 1)
... ... @@ -3167,15 +3195,15 @@ QPDFWriter::writeLinearized()
3167 3195 this->m->lin_pass1_filename.c_str(), "wb");
3168 3196 pushPipeline(
3169 3197 new Pl_StdioFile("linearization pass1", lin_pass1_file));
3170   - activatePipelineStack();
  3198 + activatePipelineStack(*pp_pass1);
3171 3199 }
3172 3200 else
3173 3201 {
3174   - pushDiscardFilter();
  3202 + pushDiscardFilter(*pp_pass1);
3175 3203 }
3176 3204 if (this->m->deterministic_id)
3177 3205 {
3178   - pushMD5Pipeline();
  3206 + pushMD5Pipeline(*pp_md5);
3179 3207 }
3180 3208 }
3181 3209  
... ... @@ -3392,23 +3420,25 @@ QPDFWriter::writeLinearized()
3392 3420 QTC::TC("qpdf", "QPDFWriter linearized deterministic ID",
3393 3421 need_xref_stream ? 0 : 1);
3394 3422 computeDeterministicIDData();
3395   - popPipelineStack();
  3423 + pp_md5 = 0;
3396 3424 assert(this->m->md5_pipeline == 0);
3397 3425 }
3398 3426  
3399 3427 // Close first pass pipeline
3400 3428 file_size = this->m->pipeline->getCount();
3401   - popPipelineStack();
  3429 + pp_pass1 = 0;
3402 3430  
3403 3431 // Save hint offset since it will be set to zero by
3404 3432 // calling openObject.
3405 3433 qpdf_offset_t hint_offset = this->m->xref[hint_id].getOffset();
3406 3434  
3407 3435 // Write hint stream to a buffer
3408   - pushPipeline(new Pl_Buffer("hint buffer"));
3409   - activatePipelineStack();
3410   - writeHintStream(hint_id);
3411   - popPipelineStack(&hint_buffer);
  3436 + {
  3437 + pushPipeline(new Pl_Buffer("hint buffer"));
  3438 + PipelinePopper pp_hint(this, &hint_buffer);
  3439 + activatePipelineStack(pp_hint);
  3440 + writeHintStream(hint_id);
  3441 + }
3412 3442 hint_length = QIntC::to_offset(hint_buffer->getSize());
3413 3443  
3414 3444 // Restore hint offset
... ... @@ -3541,9 +3571,10 @@ QPDFWriter::registerProgressReporter(PointerHolder&lt;ProgressReporter&gt; pr)
3541 3571 void
3542 3572 QPDFWriter::writeStandard()
3543 3573 {
  3574 + PointerHolder<PipelinePopper> pp_md5 = new PipelinePopper(this);
3544 3575 if (this->m->deterministic_id)
3545 3576 {
3546   - pushMD5Pipeline();
  3577 + pushMD5Pipeline(*pp_md5);
3547 3578 }
3548 3579  
3549 3580 // Start writing
... ... @@ -3597,7 +3628,7 @@ QPDFWriter::writeStandard()
3597 3628 {
3598 3629 QTC::TC("qpdf", "QPDFWriter standard deterministic ID",
3599 3630 this->m->object_stream_to_objects.empty() ? 0 : 1);
3600   - popPipelineStack();
  3631 + pp_md5 = 0;
3601 3632 assert(this->m->md5_pipeline == 0);
3602 3633 }
3603 3634 }
... ...