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.
This commit is contained in:
Jay Berkenbilt 2019-08-27 22:10:11 -04:00
parent dadf8307c8
commit ba5fb69164
4 changed files with 171 additions and 110 deletions

View File

@ -70,6 +70,8 @@ class QPDF_DLL_CLASS Pipeline
virtual void write(unsigned char* data, size_t len) = 0;
QPDF_DLL
virtual void finish() = 0;
QPDF_DLL
std::string getIdentifier() const;
protected:
Pipeline* getNext(bool allow_null = false);

View File

@ -473,6 +473,34 @@ class QPDFWriter
enum trailer_e { t_normal, t_lin_first, t_lin_second };
// An reference to a PipelinePopper instance is passed into
// activatePipelineStack. When the PipelinePopper goes out of
// scope, the pipeline stack is popped. PipelinePopper's
// destructor calls finish on the current pipeline and pops the
// pipeline stack until the top of stack is a previous active top
// of stack, and restores the pipeline to that point. It deletes
// any pipelines that it pops. If the bp argument is non-null and
// any of the stack items are of type Pl_Buffer, the buffer is
// retrieved.
class PipelinePopper
{
friend class QPDFWriter;
public:
PipelinePopper(QPDFWriter* qw,
PointerHolder<Buffer>* bp = 0) :
qw(qw),
bp(bp)
{
}
~PipelinePopper();
private:
QPDFWriter* qw;
PointerHolder<Buffer>* bp;
std::string stack_id;
};
friend class PipelinePopper;
unsigned int bytesNeeded(long long n);
void writeBinary(unsigned long long val, unsigned int bytes);
void writeString(std::string const& str);
@ -560,24 +588,17 @@ class QPDFWriter
int calculateXrefStreamPadding(qpdf_offset_t xref_bytes);
// When filtering subsections, push additional pipelines to the
// stack. When ready to switch, activate the pipeline stack.
// Pipelines passed to pushPipeline are deleted when
// clearPipelineStack is called.
// stack. When ready to switch, activate the pipeline stack. When
// the passed in PipelinePopper goes out of scope, the stack is
// popped.
Pipeline* pushPipeline(Pipeline*);
void activatePipelineStack();
void activatePipelineStack(PipelinePopper&);
void initializePipelineStack(Pipeline *);
// Calls finish on the current pipeline and pops the pipeline
// stack until the top of stack is a previous active top of stack,
// and restores the pipeline to that point. Deletes any pipelines
// that it pops. If the bp argument is non-null and any of the
// stack items are of type Pl_Buffer, the buffer is retrieved.
void popPipelineStack(PointerHolder<Buffer>* bp = 0);
void adjustAESStreamLength(size_t& length);
void pushEncryptionFilter();
void pushDiscardFilter();
void pushMD5Pipeline();
void pushEncryptionFilter(PipelinePopper&);
void pushDiscardFilter(PipelinePopper&);
void pushMD5Pipeline(PipelinePopper&);
void computeDeterministicIDData();
void discardGeneration(std::map<QPDFObjGen, int> const& in,
@ -654,6 +675,7 @@ class QPDFWriter
std::map<QPDFObjGen, int> object_to_object_stream;
std::map<int, std::set<QPDFObjGen> > object_stream_to_objects;
std::list<Pipeline*> pipeline_stack;
unsigned long long next_stack_id;
bool deterministic_id;
Pl_MD5* md5_pipeline;
std::string deterministic_id_data;

View File

@ -31,3 +31,9 @@ Pipeline::getNext(bool allow_null)
}
return this->m->next;
}
std::string
Pipeline::getIdentifier() const
{
return this->identifier;
}

View File

@ -63,6 +63,7 @@ QPDFWriter::Members::Members(QPDF& pdf) :
cur_stream_length(0),
added_newline(false),
max_ostream_index(0),
next_stack_id(0),
deterministic_id(false),
md5_pipeline(0),
did_write_setup(false),
@ -1049,36 +1050,51 @@ QPDFWriter::pushPipeline(Pipeline* p)
void
QPDFWriter::initializePipelineStack(Pipeline *p)
{
this->m->pipeline = new Pl_Count("qpdf count", p);
this->m->pipeline = new Pl_Count("pipeline stack base", p);
this->m->to_delete.push_back(this->m->pipeline);
this->m->pipeline_stack.push_back(this->m->pipeline);
}
void
QPDFWriter::activatePipelineStack()
QPDFWriter::activatePipelineStack(PipelinePopper& pp)
{
Pl_Count* c = new Pl_Count("count", this->m->pipeline_stack.back());
std::string stack_id(
"stack " + QUtil::uint_to_string(this->m->next_stack_id));
Pl_Count* c = new Pl_Count(stack_id.c_str(),
this->m->pipeline_stack.back());
++this->m->next_stack_id;
this->m->pipeline_stack.push_back(c);
this->m->pipeline = c;
pp.stack_id = stack_id;
}
void
QPDFWriter::popPipelineStack(PointerHolder<Buffer>* bp)
QPDFWriter::PipelinePopper::~PipelinePopper()
{
assert(this->m->pipeline_stack.size() >= 2);
this->m->pipeline->finish();
assert(dynamic_cast<Pl_Count*>(this->m->pipeline_stack.back()) ==
this->m->pipeline);
delete this->m->pipeline_stack.back();
this->m->pipeline_stack.pop_back();
while (dynamic_cast<Pl_Count*>(this->m->pipeline_stack.back()) == 0)
if (stack_id.empty())
{
Pipeline* p = this->m->pipeline_stack.back();
if (dynamic_cast<Pl_MD5*>(p) == this->m->md5_pipeline)
return;
}
assert(qw->m->pipeline_stack.size() >= 2);
qw->m->pipeline->finish();
assert(dynamic_cast<Pl_Count*>(qw->m->pipeline_stack.back()) ==
qw->m->pipeline);
// It might be possible for this assertion to fail if
// writeLinearized exits by exception when deterministic ID, but I
// don't think so. As of this writing, this is the only case in
// which two dynamically allocated PipelinePopper objects ever
// exist at the same time, so the assertion will fail if they get
// popped out of order from automatic destruction.
assert(qw->m->pipeline->getIdentifier() == stack_id);
delete qw->m->pipeline_stack.back();
qw->m->pipeline_stack.pop_back();
while (dynamic_cast<Pl_Count*>(qw->m->pipeline_stack.back()) == 0)
{
Pipeline* p = qw->m->pipeline_stack.back();
if (dynamic_cast<Pl_MD5*>(p) == qw->m->md5_pipeline)
{
this->m->md5_pipeline = 0;
qw->m->md5_pipeline = 0;
}
this->m->pipeline_stack.pop_back();
qw->m->pipeline_stack.pop_back();
Pl_Buffer* buf = dynamic_cast<Pl_Buffer*>(p);
if (bp && buf)
{
@ -1086,7 +1102,7 @@ QPDFWriter::popPipelineStack(PointerHolder<Buffer>* bp)
}
delete p;
}
this->m->pipeline = dynamic_cast<Pl_Count*>(this->m->pipeline_stack.back());
qw->m->pipeline = dynamic_cast<Pl_Count*>(qw->m->pipeline_stack.back());
}
void
@ -1103,7 +1119,7 @@ QPDFWriter::adjustAESStreamLength(size_t& length)
}
void
QPDFWriter::pushEncryptionFilter()
QPDFWriter::pushEncryptionFilter(PipelinePopper& pp)
{
if (this->m->encrypted && (! this->m->cur_data_key.empty()))
{
@ -1125,18 +1141,18 @@ QPDFWriter::pushEncryptionFilter()
}
// Must call this unconditionally so we can call popPipelineStack
// to balance pushEncryptionFilter().
activatePipelineStack();
activatePipelineStack(pp);
}
void
QPDFWriter::pushDiscardFilter()
QPDFWriter::pushDiscardFilter(PipelinePopper& pp)
{
pushPipeline(new Pl_Discard());
activatePipelineStack();
activatePipelineStack(pp);
}
void
QPDFWriter::pushMD5Pipeline()
QPDFWriter::pushMD5Pipeline(PipelinePopper& pp)
{
if (! this->m->id2.empty())
{
@ -1153,7 +1169,7 @@ QPDFWriter::pushMD5Pipeline()
// Special case code in popPipelineStack clears this->m->md5_pipeline
// upon deletion.
pushPipeline(this->m->md5_pipeline);
activatePipelineStack();
activatePipelineStack(pp);
}
void
@ -1769,8 +1785,8 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level,
for (int attempt = 1; attempt <= 2; ++attempt)
{
pushPipeline(new Pl_Buffer("stream data"));
activatePipelineStack();
PipelinePopper pp_stream_data(this, &stream_data);
activatePipelineStack(pp_stream_data);
filtered =
object.pipeStreamData(
this->m->pipeline,
@ -1779,7 +1795,6 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level,
(filter
? (uncompress ? qpdf_dl_all : this->m->stream_decode_level)
: qpdf_dl_none), false, (attempt == 1));
popPipelineStack(&stream_data);
if (filter && (! filtered))
{
// Try again
@ -1808,11 +1823,14 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level,
adjustAESStreamLength(this->m->cur_stream_length);
unparseObject(stream_dict, 0, flags,
this->m->cur_stream_length, compress);
unsigned char last_char = '\0';
writeString("\nstream\n");
pushEncryptionFilter();
writeBuffer(stream_data);
unsigned char last_char = this->m->pipeline->getLastChar();
popPipelineStack();
{
PipelinePopper pp_enc(this);
pushEncryptionFilter(pp_enc);
writeBuffer(stream_data);
last_char = this->m->pipeline->getLastChar();
}
if (this->m->newline_before_endstream ||
(this->m->qdf_mode && (last_char != '\n')))
@ -1911,9 +1929,11 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object)
bool compressed = false;
for (int pass = 1; pass <= 2; ++pass)
{
// stream_buffer will be initialized only for pass 2
PipelinePopper pp_ostream(this, &stream_buffer);
if (pass == 1)
{
pushDiscardFilter();
pushDiscardFilter(pp_ostream);
}
else
{
@ -1928,10 +1948,12 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object)
// Take one pass at writing pairs of numbers so we can get
// their size information
pushDiscardFilter();
writeObjectStreamOffsets(offsets, first_obj);
first += this->m->pipeline->getCount();
popPipelineStack();
{
PipelinePopper pp_discard(this);
pushDiscardFilter(pp_discard);
writeObjectStreamOffsets(offsets, first_obj);
first += this->m->pipeline->getCount();
}
// Set up a stream to write the stream data into a buffer.
Pipeline* next = pushPipeline(new Pl_Buffer("object stream"));
@ -1944,7 +1966,7 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object)
new Pl_Flate("compress object stream", next,
Pl_Flate::a_deflate));
}
activatePipelineStack();
activatePipelineStack(pp_ostream);
writeObjectStreamOffsets(offsets, first_obj);
}
@ -1994,9 +2016,6 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object)
this->m->xref[new_obj] = QPDFXRefEntry(2, new_id, count);
}
// stream_buffer will be initialized only for pass 2
popPipelineStack(&stream_buffer);
}
// Write the object
@ -2037,9 +2056,11 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object)
{
QTC::TC("qpdf", "QPDFWriter encrypt object stream");
}
pushEncryptionFilter();
writeBuffer(stream_buffer);
popPipelineStack();
{
PipelinePopper pp_enc(this);
pushEncryptionFilter(pp_enc);
writeBuffer(stream_buffer);
}
if (this->m->newline_before_endstream)
{
writeString("\n");
@ -2779,10 +2800,13 @@ QPDFWriter::writeHintStream(int hint_id)
{
QTC::TC("qpdf", "QPDFWriter encrypted hint stream");
}
pushEncryptionFilter();
writeBuffer(hint_buffer);
unsigned char last_char = this->m->pipeline->getLastChar();
popPipelineStack();
unsigned char last_char = '\0';
{
PipelinePopper pp_enc(this);
pushEncryptionFilter(pp_enc);
writeBuffer(hint_buffer);
last_char = this->m->pipeline->getLastChar();
}
if (last_char != '\n')
{
@ -2896,46 +2920,48 @@ QPDFWriter::writeXRefStream(int xref_id, int max_id, qpdf_offset_t max_offset,
new Pl_PNGFilter(
"pngify xref", p, Pl_PNGFilter::a_encode, esize));
}
activatePipelineStack();
for (int i = first; i <= last; ++i)
{
QPDFXRefEntry& e = this->m->xref[i];
switch (e.getType())
{
case 0:
writeBinary(0, 1);
writeBinary(0, f1_size);
writeBinary(0, f2_size);
break;
case 1:
{
qpdf_offset_t offset = e.getOffset();
if ((hint_id != 0) &&
(i != hint_id) &&
(offset >= hint_offset))
{
offset += hint_length;
}
writeBinary(1, 1);
writeBinary(QIntC::to_ulonglong(offset), f1_size);
writeBinary(0, f2_size);
}
break;
case 2:
writeBinary(2, 1);
writeBinary(QIntC::to_ulonglong(e.getObjStreamNumber()), f1_size);
writeBinary(QIntC::to_ulonglong(e.getObjStreamIndex()), f2_size);
break;
default:
throw std::logic_error("invalid type writing xref stream");
break;
}
}
PointerHolder<Buffer> xref_data;
popPipelineStack(&xref_data);
{
PipelinePopper pp_xref(this, &xref_data);
activatePipelineStack(pp_xref);
for (int i = first; i <= last; ++i)
{
QPDFXRefEntry& e = this->m->xref[i];
switch (e.getType())
{
case 0:
writeBinary(0, 1);
writeBinary(0, f1_size);
writeBinary(0, f2_size);
break;
case 1:
{
qpdf_offset_t offset = e.getOffset();
if ((hint_id != 0) &&
(i != hint_id) &&
(offset >= hint_offset))
{
offset += hint_length;
}
writeBinary(1, 1);
writeBinary(QIntC::to_ulonglong(offset), f1_size);
writeBinary(0, f2_size);
}
break;
case 2:
writeBinary(2, 1);
writeBinary(QIntC::to_ulonglong(e.getObjStreamNumber()), f1_size);
writeBinary(QIntC::to_ulonglong(e.getObjStreamIndex()), f2_size);
break;
default:
throw std::logic_error("invalid type writing xref stream");
break;
}
}
}
openObject(xref_id);
writeString("<<");
@ -3156,6 +3182,8 @@ QPDFWriter::writeLinearized()
// Write file in two passes. Part numbers refer to PDF spec 1.4.
FILE* lin_pass1_file = 0;
PointerHolder<PipelinePopper> pp_pass1 = new PipelinePopper(this);
PointerHolder<PipelinePopper> pp_md5 = new PipelinePopper(this);
for (int pass = 1; pass <= 2; ++pass)
{
if (pass == 1)
@ -3167,15 +3195,15 @@ QPDFWriter::writeLinearized()
this->m->lin_pass1_filename.c_str(), "wb");
pushPipeline(
new Pl_StdioFile("linearization pass1", lin_pass1_file));
activatePipelineStack();
activatePipelineStack(*pp_pass1);
}
else
{
pushDiscardFilter();
pushDiscardFilter(*pp_pass1);
}
if (this->m->deterministic_id)
{
pushMD5Pipeline();
pushMD5Pipeline(*pp_md5);
}
}
@ -3392,23 +3420,25 @@ QPDFWriter::writeLinearized()
QTC::TC("qpdf", "QPDFWriter linearized deterministic ID",
need_xref_stream ? 0 : 1);
computeDeterministicIDData();
popPipelineStack();
pp_md5 = 0;
assert(this->m->md5_pipeline == 0);
}
// Close first pass pipeline
file_size = this->m->pipeline->getCount();
popPipelineStack();
pp_pass1 = 0;
// Save hint offset since it will be set to zero by
// calling openObject.
qpdf_offset_t hint_offset = this->m->xref[hint_id].getOffset();
// Write hint stream to a buffer
pushPipeline(new Pl_Buffer("hint buffer"));
activatePipelineStack();
writeHintStream(hint_id);
popPipelineStack(&hint_buffer);
{
pushPipeline(new Pl_Buffer("hint buffer"));
PipelinePopper pp_hint(this, &hint_buffer);
activatePipelineStack(pp_hint);
writeHintStream(hint_id);
}
hint_length = QIntC::to_offset(hint_buffer->getSize());
// Restore hint offset
@ -3541,9 +3571,10 @@ QPDFWriter::registerProgressReporter(PointerHolder<ProgressReporter> pr)
void
QPDFWriter::writeStandard()
{
PointerHolder<PipelinePopper> pp_md5 = new PipelinePopper(this);
if (this->m->deterministic_id)
{
pushMD5Pipeline();
pushMD5Pipeline(*pp_md5);
}
// Start writing
@ -3597,7 +3628,7 @@ QPDFWriter::writeStandard()
{
QTC::TC("qpdf", "QPDFWriter standard deterministic ID",
this->m->object_stream_to_objects.empty() ? 0 : 1);
popPipelineStack();
pp_md5 = 0;
assert(this->m->md5_pipeline == 0);
}
}