aboutsummaryrefslogtreecommitdiffstats
path: root/libqpdf/QPDFWriter.cc
diff options
context:
space:
mode:
authorJay Berkenbilt <ejb@ql.org>2019-08-28 04:10:11 +0200
committerJay Berkenbilt <ejb@ql.org>2019-08-28 04:27:47 +0200
commitba5fb6916446d8bdf79cba25f08d759bc5595aec (patch)
treeda6691079e5feacc571127cc062482df9ba4399c /libqpdf/QPDFWriter.cc
parentdadf8307c83706c3b097bc4b1fe7b24defbebb8e (diff)
downloadqpdf-ba5fb6916446d8bdf79cba25f08d759bc5595aec.tar.zst
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.
Diffstat (limited to 'libqpdf/QPDFWriter.cc')
-rw-r--r--libqpdf/QPDFWriter.cc217
1 files changed, 124 insertions, 93 deletions
diff --git a/libqpdf/QPDFWriter.cc b/libqpdf/QPDFWriter.cc
index 30bc1fcb..895f98ce 100644
--- a/libqpdf/QPDFWriter.cc
+++ b/libqpdf/QPDFWriter.cc
@@ -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)
- {
- Pipeline* p = this->m->pipeline_stack.back();
- if (dynamic_cast<Pl_MD5*>(p) == this->m->md5_pipeline)
+ if (stack_id.empty())
+ {
+ 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)
+ PointerHolder<Buffer> xref_data;
{
- QPDFXRefEntry& e = this->m->xref[i];
- switch (e.getType())
- {
- case 0:
- writeBinary(0, 1);
- writeBinary(0, f1_size);
- writeBinary(0, f2_size);
- break;
+ 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 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;
+ 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;
- }
+ default:
+ throw std::logic_error("invalid type writing xref stream");
+ break;
+ }
+ }
}
- PointerHolder<Buffer> xref_data;
- popPipelineStack(&xref_data);
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);
}
}