From b8814a57f0e010969b0d9352d86fa30b855d3fa0 Mon Sep 17 00:00:00 2001 From: Vincent Rabaud Date: Fri, 27 Mar 2026 14:49:51 +0100 Subject: [PATCH] Add a fuzzer for ReadAnimatedImage Bug: 496629074 Change-Id: Ie984f0eab67e8e6eda44abeedf9c13aa213dd340 --- CMakeLists.txt | 13 ++++++ examples/Makefile.am | 13 +++++- examples/anim_util.c | 68 ++++++++++++++++++++++++++------ examples/anim_util.h | 7 ++++ tests/fuzzer/CMakeLists.txt | 4 ++ tests/fuzzer/anim_util_fuzzer.cc | 49 +++++++++++++++++++++++ 6 files changed, 141 insertions(+), 13 deletions(-) create mode 100644 tests/fuzzer/anim_util_fuzzer.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index c4db2b3e..96c7de95 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -752,12 +752,24 @@ if(WEBP_BUILD_WEBP_JS) endif() if(WEBP_BUILD_ANIM_UTILS) + # anim_util + parse_makefile_am(${CMAKE_CURRENT_SOURCE_DIR}/examples "ANIM_UTIL_SRCS" + "anim_util_[^ ]*") + add_library(anim_util STATIC ${ANIM_UTIL_SRCS}) + target_include_directories( + anim_util + PUBLIC + $ + $) + target_link_libraries(anim_util PUBLIC webp GIF::GIF) + # anim_diff parse_makefile_am(${CMAKE_CURRENT_SOURCE_DIR}/examples "ANIM_DIFF_SRCS" "anim_diff") add_executable(anim_diff ${ANIM_DIFF_SRCS}) target_link_libraries( anim_diff + anim_util exampleutil imagedec imageenc @@ -773,6 +785,7 @@ if(WEBP_BUILD_ANIM_UTILS) add_executable(anim_dump ${ANIM_DUMP_SRCS}) target_link_libraries( anim_dump + anim_util exampleutil imagedec imageenc diff --git a/examples/Makefile.am b/examples/Makefile.am index b8c669c6..de6413ce 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -24,23 +24,32 @@ if BUILD_WEBPINFO endif noinst_LTLIBRARIES = libexample_util.la +noinst_LTLIBRARIES += libanim_util.la libexample_util_la_SOURCES = example_util.c example_util.h libexample_util_la_LIBADD = ../src/libwebp.la -anim_diff_SOURCES = anim_diff.c anim_util.c anim_util.h gifdec.c gifdec.h +libanim_util_la_SOURCES = anim_util.c anim_util.h gifdec.c gifdec.h +libanim_util_la_LIBADD = +libanim_util_la_LIBADD += ../src/libwebp.la +libanim_util_la_LIBADD += ../src/demux/libwebpdemux.la +libanim_util_la_LIBADD += $(GIF_LIBS) + +anim_diff_SOURCES = anim_diff.c anim_diff_CPPFLAGS = $(AM_CPPFLAGS) $(GIF_INCLUDES) anim_diff_LDADD = anim_diff_LDADD += ../src/demux/libwebpdemux.la +anim_diff_LDADD += libanim_util.la anim_diff_LDADD += libexample_util.la anim_diff_LDADD += ../imageio/libimageio_util.la anim_diff_LDADD += $(GIF_LIBS) -lm -anim_dump_SOURCES = anim_dump.c anim_util.c anim_util.h gifdec.c gifdec.h +anim_dump_SOURCES = anim_dump.c anim_dump_CPPFLAGS = $(AM_CPPFLAGS) $(PNG_INCLUDES) anim_dump_CPPFLAGS += $(GIF_INCLUDES) anim_dump_LDADD = anim_dump_LDADD += ../src/demux/libwebpdemux.la +anim_dump_LDADD += libanim_util.la anim_dump_LDADD += libexample_util.la anim_dump_LDADD += ../imageio/libimageio_util.la anim_dump_LDADD += ../imageio/libimageenc.la diff --git a/examples/anim_util.c b/examples/anim_util.c index 5759dbed..086fa355 100644 --- a/examples/anim_util.c +++ b/examples/anim_util.c @@ -23,7 +23,6 @@ #include "../imageio/imageio_util.h" #include "./gifdec.h" #include "./unicode.h" -#include "./unicode_gif.h" #include "webp/decode.h" #include "webp/demux.h" #include "webp/format_constants.h" @@ -294,6 +293,24 @@ End: #if defined(WEBP_HAVE_GIF) +typedef struct { + const uint8_t* data; + size_t size; + size_t offset; +} GifBufferContext; + +static int MemoryReadGIF(GifFileType* gif, GifByteType* dest, int len) { + GifBufferContext* const ctx = (GifBufferContext*)gif->UserData; + if (ctx->offset + len > ctx->size) { + len = (int)(ctx->size - ctx->offset); + } + if (len > 0) { + memcpy(dest, ctx->data + ctx->offset, len); + ctx->offset += len; + } + return len; +} + // Returns true if this is a valid GIF bitstream. static int IsGIF(const WebPData* const data) { return data->size > GIF_STAMP_LEN && @@ -504,18 +521,25 @@ static int ReadFrameGIF(const SavedImage* const gif_image, return 1; } -// Read animated GIF bitstream from 'filename' into 'AnimatedImage' struct. -static int ReadAnimatedGIF(const char filename[], AnimatedImage* const image, - int dump_frames, const char dump_folder[]) { +// Read animated GIF bitstream from 'gif_data' into 'AnimatedImage' struct. +static int ReadAnimatedGIF(const char filename[], + const WebPData* const gif_data, + AnimatedImage* const image, int dump_frames, + const char dump_folder[]) { uint32_t frame_count; uint32_t canvas_width, canvas_height; uint32_t i; int gif_error; GifFileType* gif; + GifBufferContext ctx; - gif = DGifOpenFileUnicode((const W_CHAR*)filename, NULL); + ctx.data = gif_data->bytes; + ctx.size = gif_data->size; + ctx.offset = 0; + gif = DGifOpen(&ctx, MemoryReadGIF, &gif_error); if (gif == NULL) { - WFPRINTF(stderr, "Could not read file: %s.\n", (const W_CHAR*)filename); + WFPRINTF(stderr, "Could not read GIF from memory: %s.\n", + (const W_CHAR*)filename); return 0; } @@ -684,9 +708,12 @@ static int IsGIF(const WebPData* const data) { return 0; } -static int ReadAnimatedGIF(const char filename[], AnimatedImage* const image, - int dump_frames, const char dump_folder[]) { +static int ReadAnimatedGIF(const char filename[], + const WebPData* const gif_data, + AnimatedImage* const image, int dump_frames, + const char dump_folder[]) { (void)filename; + (void)gif_data; (void)image; (void)dump_frames; (void)dump_folder; @@ -706,18 +733,38 @@ int ReadAnimatedImage(const char filename[], AnimatedImage* const image, WebPData webp_data; WebPDataInit(&webp_data); - memset(image, 0, sizeof(*image)); if (!ImgIoUtilReadFile(filename, &webp_data.bytes, &webp_data.size)) { WFPRINTF(stderr, "Error reading file: %s\n", (const W_CHAR*)filename); return 0; } + ok = ReadAnimatedImageFromMemory(filename, webp_data.bytes, webp_data.size, + image, dump_frames, dump_folder); + if (!ok) { + WFPRINTF(stderr, "Error parsing image: %s\n", (const W_CHAR*)filename); + } + + WebPDataClear(&webp_data); + return ok; +} + +int ReadAnimatedImageFromMemory(const char filename[], + const uint8_t* const data, size_t size, + AnimatedImage* const image, int dump_frames, + const char dump_folder[]) { + int ok = 0; + WebPData webp_data; + + webp_data.bytes = data; + webp_data.size = size; + memset(image, 0, sizeof(*image)); + if (IsWebP(&webp_data)) { ok = ReadAnimatedWebP(filename, &webp_data, image, dump_frames, dump_folder); } else if (IsGIF(&webp_data)) { - ok = ReadAnimatedGIF(filename, image, dump_frames, dump_folder); + ok = ReadAnimatedGIF(filename, &webp_data, image, dump_frames, dump_folder); } else { WFPRINTF(stderr, "Unknown file type: %s. Supported file types are WebP and GIF\n", @@ -725,7 +772,6 @@ int ReadAnimatedImage(const char filename[], AnimatedImage* const image, ok = 0; } if (!ok) ClearAnimatedImage(image); - WebPDataClear(&webp_data); return ok; } diff --git a/examples/anim_util.h b/examples/anim_util.h index c9d24be3..c5c4c9f8 100644 --- a/examples/anim_util.h +++ b/examples/anim_util.h @@ -52,6 +52,13 @@ void ClearAnimatedImage(AnimatedImage* const image); int ReadAnimatedImage(const char filename[], AnimatedImage* const image, int dump_frames, const char dump_folder[]); +// Same as 'ReadAnimatedImage', but from a memory buffer. +// filename is only used for log messages. +int ReadAnimatedImageFromMemory(const char filename[], + const uint8_t* const data, size_t size, + AnimatedImage* const image, int dump_frames, + const char dump_folder[]); + // Given two RGBA buffers, calculate max pixel difference and PSNR. // If 'premultiply' is true, R/G/B values will be pre-multiplied by the // transparency before comparison. diff --git a/tests/fuzzer/CMakeLists.txt b/tests/fuzzer/CMakeLists.txt index 8965cf95..fa3cfbdf 100644 --- a/tests/fuzzer/CMakeLists.txt +++ b/tests/fuzzer/CMakeLists.txt @@ -59,6 +59,10 @@ add_webp_fuzztest(huffman_fuzzer webpdecode webpdspdecode webputilsdecode) add_webp_fuzztest(imageio_fuzzer imagedec) add_webp_fuzztest(simple_api_fuzzer) +if (WEBP_BUILD_ANIM_UTILS) + add_webp_fuzztest(anim_util_fuzzer anim_util imageioutil webpdemux) +endif() + if(WEBP_BUILD_LIBWEBPMUX) add_webp_fuzztest(animation_api_fuzzer webpdemux) add_webp_fuzztest(animdecoder_fuzzer imageioutil webpdemux) diff --git a/tests/fuzzer/anim_util_fuzzer.cc b/tests/fuzzer/anim_util_fuzzer.cc new file mode 100644 index 00000000..b5b5394c --- /dev/null +++ b/tests/fuzzer/anim_util_fuzzer.cc @@ -0,0 +1,49 @@ +// Copyright 2026 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + +#include "./fuzz_utils.h" +#include "./nalloc.h" +#include "examples/anim_util.h" + +namespace { + +void ReadAnimatedImageTest(std::string_view blob) { + const uint8_t* const data = reinterpret_cast(blob.data()); + const size_t size = blob.size(); + if (fuzz_utils::IsImageTooBig(data, size)) return; + + nalloc_init(nullptr); + nalloc_start(data, size); + + AnimatedImage image; + if (ReadAnimatedImageFromMemory("random_file", data, size, &image, + /*dump_frames=*/0, /*dump_folder=*/nullptr)) { + ClearAnimatedImage(&image); + } + + nalloc_end(); +} + +} // namespace + +FUZZ_TEST(ReadAnimatedImage, ReadAnimatedImageTest) + .WithDomains(fuzztest::String().WithMaxSize(fuzz_utils::kMaxWebPFileSize + + 1));