Obscure bug fix copying foreign streams in special cases (fixes #449)

Specifically, if a stream had its stream data replaced and had
indirect /Filter or /DecodeParms, it would result in non-silent loss
of data and/or internal error.
This commit is contained in:
Jay Berkenbilt 2020-10-21 18:50:29 -04:00
parent ad96e1ad74
commit 956c8f6432
9 changed files with 54 additions and 14 deletions

View File

@ -1,5 +1,11 @@
2020-10-21 Jay Berkenbilt <ejb@ql.org> 2020-10-21 Jay Berkenbilt <ejb@ql.org>
* Bug fix: properly handle copying foreign streams that have
indirect /Filter or /DecodeParms keys when stream data has been
replaced. The circumstances leading to this bug are very unusual
but would cause qpdf to either generate an internal error or some
other kind of warning situation if it would occur. Fixes #449.
* Qpdf's build and CI has been migrated from Azure Pipelines * Qpdf's build and CI has been migrated from Azure Pipelines
(Azure Devops) to GitHub Actions. (Azure Devops) to GitHub Actions.

1
TODO
View File

@ -7,7 +7,6 @@ Candidates for upcoming release
* Open "next" issues * Open "next" issues
* bugs * bugs
* #473: zsh completion with directories * #473: zsh completion with directories
* #449: internal error with case to reproduce (from pikepdf)
* #444: concatenated stream/whitespace bug * #444: concatenated stream/whitespace bug
* Non-bugs * Non-bugs
* #446: recognize edited QDF files * #446: recognize edited QDF files

View File

@ -2313,7 +2313,8 @@ QPDF::reserveObjects(QPDFObjectHandle foreign, ObjCopier& obj_copier,
if (foreign.isReserved()) if (foreign.isReserved())
{ {
throw std::logic_error( throw std::logic_error(
"QPDF: attempting to copy a foreign reserved object"); "QPDF: attempting to copy a foreign reserved object: " +
QUtil::int_to_string(foreign.getObjectID()));
} }
if (foreign.isPagesObject()) if (foreign.isPagesObject())
@ -2486,6 +2487,16 @@ QPDF::replaceForeignIndirectObjects(
} }
PointerHolder<Buffer> stream_buffer = PointerHolder<Buffer> stream_buffer =
stream->getStreamDataBuffer(); stream->getStreamDataBuffer();
// Note: at this stage, dictionary keys may still be reserved.
// We have to handle that explicitly if we access anything.
auto get_as_direct = [&old_dict] (std::string const& key) {
QPDFObjectHandle obj = old_dict.getKey(key);
obj.makeDirect();
return obj;
};
QPDFObjectHandle filter = get_as_direct("/Filter");
QPDFObjectHandle decode_parms = get_as_direct("/DecodeParms");
if ((foreign_stream_qpdf->m->immediate_copy_from) && if ((foreign_stream_qpdf->m->immediate_copy_from) &&
(stream_buffer.getPointer() == 0)) (stream_buffer.getPointer() == 0))
{ {
@ -2495,8 +2506,7 @@ QPDF::replaceForeignIndirectObjects(
// have to keep duplicating the memory. // have to keep duplicating the memory.
QTC::TC("qpdf", "QPDF immediate copy stream data"); QTC::TC("qpdf", "QPDF immediate copy stream data");
foreign.replaceStreamData(foreign.getRawStreamData(), foreign.replaceStreamData(foreign.getRawStreamData(),
dict.getKey("/Filter"), filter, decode_parms);
dict.getKey("/DecodeParms"));
stream_buffer = stream->getStreamDataBuffer(); stream_buffer = stream->getStreamDataBuffer();
} }
PointerHolder<QPDFObjectHandle::StreamDataProvider> stream_provider = PointerHolder<QPDFObjectHandle::StreamDataProvider> stream_provider =
@ -2504,9 +2514,7 @@ QPDF::replaceForeignIndirectObjects(
if (stream_buffer.getPointer()) if (stream_buffer.getPointer())
{ {
QTC::TC("qpdf", "QPDF copy foreign stream with buffer"); QTC::TC("qpdf", "QPDF copy foreign stream with buffer");
result.replaceStreamData(stream_buffer, result.replaceStreamData(stream_buffer, filter, decode_parms);
dict.getKey("/Filter"),
dict.getKey("/DecodeParms"));
} }
else if (stream_provider.getPointer()) else if (stream_provider.getPointer())
{ {
@ -2515,8 +2523,7 @@ QPDF::replaceForeignIndirectObjects(
this->m->copied_stream_data_provider->registerForeignStream( this->m->copied_stream_data_provider->registerForeignStream(
local_og, foreign); local_og, foreign);
result.replaceStreamData(this->m->copied_streams, result.replaceStreamData(this->m->copied_streams,
dict.getKey("/Filter"), filter, decode_parms);
dict.getKey("/DecodeParms"));
} }
else else
{ {
@ -2534,8 +2541,7 @@ QPDF::replaceForeignIndirectObjects(
this->m->copied_stream_data_provider->registerForeignStream( this->m->copied_stream_data_provider->registerForeignStream(
local_og, foreign_stream_data); local_og, foreign_stream_data);
result.replaceStreamData(this->m->copied_streams, result.replaceStreamData(this->m->copied_streams,
dict.getKey("/Filter"), filter, decode_parms);
dict.getKey("/DecodeParms"));
} }
} }
else else

View File

@ -66,7 +66,7 @@ endef
# Usage: $(call makelib,objs,library,ldflags,libs,current,revision,age) # Usage: $(call makelib,objs,library,ldflags,libs,current,revision,age)
define makelib define makelib
cl -nologo -O2 -Zi -Gy -EHsc -MD -LD -Fe$(basename $(2))$(shell expr $(5) - $(7)).dll $(1) \ cl -nologo -O2 -Zi -Gy -EHsc -MD -LD -Fe$(basename $(2))$(shell expr $(5) - $(7)).dll $(1) \
-link -SUBSYSTEM:CONSOLE,5.01 -incremental:no \ -link -SUBSYSTEM:CONSOLE -incremental:no \
$(foreach L,$(subst -L,,$(3)),-LIBPATH:$(L)) \ $(foreach L,$(subst -L,,$(3)),-LIBPATH:$(L)) \
$(foreach L,$(subst -l,,$(4)),$(L).lib) $(foreach L,$(subst -l,,$(4)),$(L).lib)
if [ -f $(basename $(2))$(shell expr $(5) - $(7)).dll.manifest ]; then \ if [ -f $(basename $(2))$(shell expr $(5) - $(7)).dll.manifest ]; then \
@ -81,7 +81,7 @@ endef
define makebin define makebin
cl -nologo -O2 -Zi -Gy -EHsc -MD $(1) \ cl -nologo -O2 -Zi -Gy -EHsc -MD $(1) \
$(if $(5),$(5),$(WINDOWS_MAIN_XLINK_FLAGS)) \ $(if $(5),$(5),$(WINDOWS_MAIN_XLINK_FLAGS)) \
-link -SUBSYSTEM:CONSOLE,5.01 -incremental:no -OUT:$(2) \ -link -SUBSYSTEM:CONSOLE -incremental:no -OUT:$(2) \
$(foreach L,$(subst -L,,$(3)),-LIBPATH:$(L)) \ $(foreach L,$(subst -L,,$(3)),-LIBPATH:$(L)) \
$(foreach L,$(subst -l,,$(4)),$(L).lib) $(foreach L,$(subst -l,,$(4)),$(L).lib)
if [ -f $(2).manifest ]; then \ if [ -f $(2).manifest ]; then \

View File

@ -2417,7 +2417,7 @@ $td->runtest("check output",
show_ntests(); show_ntests();
# ---------- # ----------
$td->notify("--- Copy Foreign Objects ---"); $td->notify("--- Copy Foreign Objects ---");
$n_tests += 7; $n_tests += 10;
foreach my $d ([25, 1], [26, 2], [27, 3]) foreach my $d ([25, 1], [26, 2], [27, 3])
{ {
@ -2438,6 +2438,19 @@ $td->runtest("copy objects error",
{$td->FILE => "copy-foreign-objects-errors.out", {$td->FILE => "copy-foreign-objects-errors.out",
$td->EXIT_STATUS => 0}, $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES); $td->NORMALIZE_NEWLINES);
$td->runtest("indirect filters",
{$td->COMMAND => "test_driver 69 indirect-filter.pdf"},
{$td->STRING => "test 69 done\n", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
foreach my $i (0, 1)
{
$td->runtest("check output",
{$td->FILE => "auto-$i.pdf"},
{$td->FILE => "indirect-filter-out-$i.pdf"});
}
show_ntests(); show_ntests();
# ---------- # ----------
$td->notify("--- Error Condition Tests ---"); $td->notify("--- Error Condition Tests ---");

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -2195,6 +2195,22 @@ void runtest(int n, char const* filename1, char const* arg2)
std::cout << "raw stream data okay" << std::endl; std::cout << "raw stream data okay" << std::endl;
} }
} }
else if (n == 69)
{
pdf.setImmediateCopyFrom(true);
auto pages = pdf.getAllPages();
for (size_t i = 0; i < pages.size(); ++i)
{
QPDF out;
out.emptyPDF();
out.addPage(pages.at(i), false);
std::string outname = std::string("auto-") +
QUtil::uint_to_string(i) + ".pdf";
QPDFWriter w(out, outname.c_str());
w.setStaticID(true);
w.write();
}
}
else else
{ {
throw std::runtime_error(std::string("invalid test ") + throw std::runtime_error(std::string("invalid test ") +