From 4993b95f532fdfc1996809189aa7e24ee839d983 Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Thu, 8 Dec 2016 11:54:04 -0500 Subject: [PATCH] Do not create SkGifCodec if true size is not known If there is enough data in the stream to read the reported canvas size, but not enough to read the first image's header, we do not know the true canvas size, since we may expand it to fit the first frame. In that case, return nullptr from NewFromStream. Add a test. SkGifCodec.cpp: Correct a comment - parse returns false if there is a fatal error. parse() returning true does not guarantee that the size was found. Instead of checking the width and height, check to see whether the first frame exists and has its header defined. If not, we do not yet know the true canvas size. Assert that the canvas size is non-zero, which is a fatal error from parse. SkGifImageReader.cpp: Move the code to set the header defined before the SkGIFSizeQuery exit condition. This allows SkGifCodec to check the first frame's header to determine whether the size is known. GifTest.cpp: Add a test which truncates the file just before the image header (and after the global header). Prior to the other changes, this would create an SkCodec. For an image that needs its canvas size expanded, the SkCodec would have an incorrect size. CodecPartialTest.cpp: randPixels.gif now needs more than half of its data to create an SkCodec, so set a minimum for test_partial. Change-Id: I40482f524128b2f1fe59b8f27dd64c7cbe793079 Reviewed-on: https://skia-review.googlesource.com/5701 Reviewed-by: Derek Sollenberger Commit-Queue: Leon Scroggins --- src/codec/SkGifCodec.cpp | 9 +++++++-- tests/CodecPartialTest.cpp | 6 +++--- tests/GifTest.cpp | 12 ++++++++++++ third_party/gif/SkGifImageReader.cpp | 8 ++++---- 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/codec/SkGifCodec.cpp b/src/codec/SkGifCodec.cpp index 7fc3c98..d13f97a 100644 --- a/src/codec/SkGifCodec.cpp +++ b/src/codec/SkGifCodec.cpp @@ -74,14 +74,19 @@ static SkCodec::Result gif_error(const char* msg, SkCodec::Result result = SkCod SkCodec* SkGifCodec::NewFromStream(SkStream* stream) { std::unique_ptr reader(new SkGifImageReader(stream)); if (!reader->parse(SkGifImageReader::SkGIFSizeQuery)) { - // Not enough data to determine the size. + // Fatal error occurred. return nullptr; } - if (0 == reader->screenWidth() || 0 == reader->screenHeight()) { + // If no images are in the data, or the first header is not yet defined, we cannot + // create a codec. In either case, the width and height are not yet known. + if (0 == reader->imagesCount() || !reader->frameContext(0)->isHeaderDefined()) { return nullptr; } + // isHeaderDefined() will not return true if the screen size is empty. + SkASSERT(reader->screenHeight() > 0 && reader->screenWidth() > 0); + const auto alpha = reader->firstFrameHasAlpha() ? SkEncodedInfo::kBinary_Alpha : SkEncodedInfo::kOpaque_Alpha; // Use kPalette since Gifs are encoded with a color table. diff --git a/tests/CodecPartialTest.cpp b/tests/CodecPartialTest.cpp index ba1a51f..9611d09 100644 --- a/tests/CodecPartialTest.cpp +++ b/tests/CodecPartialTest.cpp @@ -90,7 +90,7 @@ private: SkMemoryStream fStream; }; -static void test_partial(skiatest::Reporter* r, const char* name) { +static void test_partial(skiatest::Reporter* r, const char* name, size_t minBytes = 0) { sk_sp file = make_from_resource(name); if (!file) { SkDebugf("missing resource %s\n", name); @@ -104,7 +104,7 @@ static void test_partial(skiatest::Reporter* r, const char* name) { } // Now decode part of the file - HaltingStream* stream = new HaltingStream(file, file->size() / 2); + HaltingStream* stream = new HaltingStream(file, SkTMax(file->size() / 2, minBytes)); // Note that we cheat and hold on to a pointer to stream, though it is owned by // partialCodec. @@ -174,7 +174,7 @@ DEF_TEST(Codec_partial, r) { test_partial(r, "baby_tux.png"); test_partial(r, "box.gif"); - test_partial(r, "randPixels.gif"); + test_partial(r, "randPixels.gif", 215); test_partial(r, "color_wheel.gif"); } diff --git a/tests/GifTest.cpp b/tests/GifTest.cpp index b06d3ea..17dce92 100644 --- a/tests/GifTest.cpp +++ b/tests/GifTest.cpp @@ -227,3 +227,15 @@ DEF_TEST(Gif_Sampled, r) { bm.rowBytes(), &options); REPORTER_ASSERT(r, result == SkCodec::kSuccess); } + +// If a GIF file is truncated before the header for the first image is defined, +// we should not create an SkCodec. +DEF_TEST(Codec_GifTruncated, r) { + SkString path = GetResourcePath("test640x479.gif"); + sk_sp data(SkData::MakeFromFileName(path.c_str())); + + // This is right before the header for the first image. + data = SkData::MakeSubset(data.get(), 0, 446); + std::unique_ptr codec(SkCodec::NewFromData(data)); + REPORTER_ASSERT(r, !codec); +} diff --git a/third_party/gif/SkGifImageReader.cpp b/third_party/gif/SkGifImageReader.cpp index 14aa1f1..9a14018 100644 --- a/third_party/gif/SkGifImageReader.cpp +++ b/third_party/gif/SkGifImageReader.cpp @@ -781,6 +781,10 @@ bool SkGifImageReader::parse(SkGifImageReader::SkGIFParseQuery query) } } + addFrameIfNecessary(); + SkGIFFrameContext* currentFrame = m_frames.back().get(); + currentFrame->setHeaderDefined(); + if (query == SkGIFSizeQuery) { // The decoder needs to stop, so we return here, before // flushing the buffer. Next time through, we'll be in the same @@ -789,10 +793,6 @@ bool SkGifImageReader::parse(SkGifImageReader::SkGIFParseQuery query) return true; } - addFrameIfNecessary(); - SkGIFFrameContext* currentFrame = m_frames.back().get(); - - currentFrame->setHeaderDefined(); currentFrame->setRect(xOffset, yOffset, width, height); currentFrame->setInterlaced(SkToBool(currentComponent[8] & 0x40)); -- 2.7.4