From 160ad076323d3a6641c66f6029c37ad1a029414b Mon Sep 17 00:00:00 2001 From: Vincent Rabaud Date: Tue, 28 Oct 2025 11:22:36 +0100 Subject: [PATCH] Add a fuzzer for WebPDecoderConfig Change-Id: I156304ae361c5f18ec2705adec51dae51ff91f2e --- tests/fuzzer/advanced_api_fuzzer.cc | 34 ++---- tests/fuzzer/dec_fuzzer.cc | 9 +- tests/fuzzer/enc_dec_fuzzer.cc | 162 ++++++++++++++++++++-------- tests/fuzzer/fuzz_utils.h | 127 +++++++++++++++++++++- 4 files changed, 260 insertions(+), 72 deletions(-) diff --git a/tests/fuzzer/advanced_api_fuzzer.cc b/tests/fuzzer/advanced_api_fuzzer.cc index cec5dbf9..bf18cd34 100644 --- a/tests/fuzzer/advanced_api_fuzzer.cc +++ b/tests/fuzzer/advanced_api_fuzzer.cc @@ -17,6 +17,7 @@ #include #include #include +#include #include #include "./fuzz_utils.h" @@ -26,10 +27,9 @@ namespace { -void AdvancedApiTest(std::string_view blob, uint8_t factor_u8, bool flip, - bool bypass_filtering, bool no_fancy_upsampling, - bool use_threads, bool use_cropping, bool use_scaling, - bool use_dithering, int colorspace, bool incremental) { +void AdvancedApiTest(std::string_view blob, uint8_t factor_u8, int colorspace, + bool incremental, + const fuzz_utils::WebPDecoderOptionsCpp& decoder_options) { WebPDecoderConfig config; if (!WebPInitDecoderConfig(&config)) return; const uint8_t* const data = reinterpret_cast(blob.data()); @@ -46,24 +46,14 @@ void AdvancedApiTest(std::string_view blob, uint8_t factor_u8, bool flip, const uint8_t value = fuzz_utils::FuzzHash(data, size); const float factor = factor_u8 / 255.f; // 0-1 - config.options.flip = flip; - config.options.bypass_filtering = bypass_filtering; - config.options.no_fancy_upsampling = no_fancy_upsampling; - config.options.use_threads = use_threads; - if (use_cropping) { - config.options.use_cropping = 1; + std::memcpy(&config.options, &decoder_options, sizeof(decoder_options)); + if (config.options.use_cropping) { config.options.crop_width = (int)(config.input.width * (1 - factor)); config.options.crop_height = (int)(config.input.height * (1 - factor)); config.options.crop_left = config.input.width - config.options.crop_width; config.options.crop_top = config.input.height - config.options.crop_height; } - if (use_dithering) { - int strength = (int)(factor * 100); - config.options.dithering_strength = strength; - config.options.alpha_dithering_strength = 100 - strength; - } - if (use_scaling) { - config.options.use_scaling = 1; + if (config.options.use_scaling) { config.options.scaled_width = (int)(config.input.width * factor * 2); config.options.scaled_height = (int)(config.input.height * factor * 2); } @@ -162,13 +152,6 @@ FUZZ_TEST(AdvancedApi, AdvancedApiTest) .WithDomains(fuzztest::String().WithMaxSize(fuzz_utils::kMaxWebPFileSize + 1), /*factor_u8=*/fuzztest::Arbitrary(), - /*flip=*/fuzztest::Arbitrary(), - /*bypass_filtering=*/fuzztest::Arbitrary(), - /*no_fancy_upsampling=*/fuzztest::Arbitrary(), - /*use_threads=*/fuzztest::Arbitrary(), - /*use_cropping=*/fuzztest::Arbitrary(), - /*use_scaling=*/fuzztest::Arbitrary(), - /*use_dithering=*/fuzztest::Arbitrary(), #if defined(WEBP_REDUCE_CSP) fuzztest::ElementOf({static_cast(MODE_RGBA), static_cast(MODE_BGRA), @@ -177,4 +160,5 @@ FUZZ_TEST(AdvancedApi, AdvancedApiTest) #else fuzztest::InRange(0, static_cast(MODE_LAST) - 1), #endif - /*incremental=*/fuzztest::Arbitrary()); + /*incremental=*/fuzztest::Arbitrary(), + fuzz_utils::ArbitraryValidWebPDecoderOptions()); diff --git a/tests/fuzzer/dec_fuzzer.cc b/tests/fuzzer/dec_fuzzer.cc index 5695374f..20427a41 100644 --- a/tests/fuzzer/dec_fuzzer.cc +++ b/tests/fuzzer/dec_fuzzer.cc @@ -17,6 +17,7 @@ #include #include #include +#include #include #include "./nalloc.h" @@ -25,7 +26,8 @@ namespace { -void DecodeWebP(std::string_view arbitrary_bytes) { +void DecodeWebP(std::string_view arbitrary_bytes, + const fuzz_utils::WebPDecoderOptionsCpp& decoder_options) { WebPDecoderConfig decoder_config; if (!WebPInitDecoderConfig(&decoder_config)) { fprintf(stderr, "WebPInitDecoderConfig failed.\n"); @@ -34,6 +36,8 @@ void DecodeWebP(std::string_view arbitrary_bytes) { nalloc_init(nullptr); nalloc_start(reinterpret_cast(arbitrary_bytes.data()), arbitrary_bytes.size()); + std::memcpy(&decoder_config.options, &decoder_options, + sizeof(decoder_options)); const VP8StatusCode status = WebPDecode(reinterpret_cast(arbitrary_bytes.data()), arbitrary_bytes.size(), &decoder_config); @@ -48,6 +52,7 @@ void DecodeWebP(std::string_view arbitrary_bytes) { FUZZ_TEST(WebPSuite, DecodeWebP) .WithDomains(fuzztest::String().WithMaxSize(fuzz_utils::kMaxWebPFileSize + - 1)); + 1), + fuzz_utils::ArbitraryValidWebPDecoderOptions()); } // namespace diff --git a/tests/fuzzer/enc_dec_fuzzer.cc b/tests/fuzzer/enc_dec_fuzzer.cc index 401955ac..55bf0859 100644 --- a/tests/fuzzer/enc_dec_fuzzer.cc +++ b/tests/fuzzer/enc_dec_fuzzer.cc @@ -18,6 +18,8 @@ #include #include #include +#include +#include #include "./fuzz_utils.h" #include "src/dsp/cpu.h" @@ -28,18 +30,12 @@ namespace { const VP8CPUInfo default_VP8GetCPUInfo = fuzz_utils::VP8GetCPUInfo; -void EncDecTest(bool use_argb, int source_image_index, WebPConfig config, - int optimization_index, - const fuzz_utils::CropOrScaleParams& crop_or_scale_params) { - fuzz_utils::SetOptimization(default_VP8GetCPUInfo, optimization_index); - - // Init the source picture. - WebPPicture pic = fuzz_utils::GetSourcePicture(source_image_index, use_argb); - +void Enc(const fuzz_utils::CropOrScaleParams& crop_or_scale_params, + WebPConfig& config, WebPPicture& pic, + WebPMemoryWriter& memory_writer) { // Crop and scale. if (!fuzz_utils::CropOrScale(&pic, crop_or_scale_params)) { const WebPEncodingError error_code = pic.error_code; - WebPPictureFree(&pic); if (error_code == VP8_ENC_ERROR_OUT_OF_MEMORY) return; fprintf(stderr, "ExtractAndCropOrScale failed. Error code: %d\n", error_code); @@ -64,14 +60,10 @@ void EncDecTest(bool use_argb, int source_image_index, WebPConfig config, } // Encode. - WebPMemoryWriter memory_writer; - WebPMemoryWriterInit(&memory_writer); pic.writer = WebPMemoryWrite; pic.custom_ptr = &memory_writer; if (!WebPEncode(&config, &pic)) { const WebPEncodingError error_code = pic.error_code; - WebPMemoryWriterClear(&memory_writer); - WebPPictureFree(&pic); if (error_code == VP8_ENC_ERROR_OUT_OF_MEMORY || error_code == VP8_ENC_ERROR_BAD_WRITE) { return; @@ -79,69 +71,151 @@ void EncDecTest(bool use_argb, int source_image_index, WebPConfig config, fprintf(stderr, "WebPEncode failed. Error code: %d\n", error_code); std::abort(); } +} + +void EncDecValidTest(bool use_argb, int source_image_index, WebPConfig config, + int optimization_index, + const fuzz_utils::CropOrScaleParams& crop_or_scale_params, + int colorspace, + const fuzz_utils::WebPDecoderOptionsCpp& decoder_options) { + fuzz_utils::SetOptimization(default_VP8GetCPUInfo, optimization_index); + + // Init the source picture. + WebPPicture pic = fuzz_utils::GetSourcePicture(source_image_index, use_argb); + std::unique_ptr pic_owner(&pic); + + WebPMemoryWriter memory_writer; + WebPMemoryWriterInit(&memory_writer); + std::unique_ptr + memory_writer_owner(&memory_writer); + + Enc(crop_or_scale_params, config, pic, memory_writer); // Try decoding the result. const uint8_t* const out_data = memory_writer.mem; const size_t out_size = memory_writer.size; WebPDecoderConfig dec_config; + std::unique_ptr + dec_config_owner(&dec_config); if (!WebPInitDecoderConfig(&dec_config)) { fprintf(stderr, "WebPInitDecoderConfig failed.\n"); - WebPMemoryWriterClear(&memory_writer); - WebPPictureFree(&pic); std::abort(); } dec_config.output.colorspace = MODE_BGRA; - const VP8StatusCode status = WebPDecode(out_data, out_size, &dec_config); + VP8StatusCode status = WebPDecode(out_data, out_size, &dec_config); if ((status != VP8_STATUS_OK && status != VP8_STATUS_OUT_OF_MEMORY && status != VP8_STATUS_USER_ABORT) || (status == VP8_STATUS_OK && (dec_config.output.width != pic.width || dec_config.output.height != pic.height))) { fprintf(stderr, "WebPDecode failed. status: %d.\n", status); - WebPFreeDecBuffer(&dec_config.output); - WebPMemoryWriterClear(&memory_writer); - WebPPictureFree(&pic); std::abort(); } - if (status == VP8_STATUS_OK) { + // Compare the results if exact encoding. + if (status == VP8_STATUS_OK && pic.use_argb && config.lossless && + config.near_lossless == 100) { const uint8_t* const rgba = dec_config.output.u.RGBA.rgba; const int w = dec_config.output.width; const int h = dec_config.output.height; - // Compare the results if exact encoding. - if (pic.use_argb && config.lossless && config.near_lossless == 100) { - const uint32_t* src1 = (const uint32_t*)rgba; - const uint32_t* src2 = pic.argb; - for (int y = 0; y < h; ++y, src1 += w, src2 += pic.argb_stride) { - for (int x = 0; x < w; ++x) { - uint32_t v1 = src1[x], v2 = src2[x]; - if (!config.exact) { - if ((v1 & 0xff000000u) == 0 || (v2 & 0xff000000u) == 0) { - // Only keep alpha for comparison of fully transparent area. - v1 &= 0xff000000u; - v2 &= 0xff000000u; - } - } - if (v1 != v2) { - fprintf(stderr, "Lossless compression failed pixel-exactness.\n"); - WebPFreeDecBuffer(&dec_config.output); - WebPMemoryWriterClear(&memory_writer); - WebPPictureFree(&pic); - std::abort(); + const uint32_t* src1 = (const uint32_t*)rgba; + const uint32_t* src2 = pic.argb; + for (int y = 0; y < h; ++y, src1 += w, src2 += pic.argb_stride) { + for (int x = 0; x < w; ++x) { + uint32_t v1 = src1[x], v2 = src2[x]; + if (!config.exact) { + if ((v1 & 0xff000000u) == 0 || (v2 & 0xff000000u) == 0) { + // Only keep alpha for comparison of fully transparent area. + v1 &= 0xff000000u; + v2 &= 0xff000000u; } } + if (v1 != v2) { + fprintf(stderr, "Lossless compression failed pixel-exactness.\n"); + std::abort(); + } } } } + // Use given decoding options. + if (static_cast(decoder_options.crop_left) + + decoder_options.crop_width > + static_cast(pic.width) || + static_cast(decoder_options.crop_top) + + decoder_options.crop_height > + static_cast(pic.height)) { + return; + } WebPFreeDecBuffer(&dec_config.output); - WebPMemoryWriterClear(&memory_writer); - WebPPictureFree(&pic); + if (!WebPInitDecoderConfig(&dec_config)) { + fprintf(stderr, "WebPInitDecoderConfig failed.\n"); + abort(); + } + + dec_config.output.colorspace = static_cast(colorspace); + std::memcpy(&dec_config.options, &decoder_options, sizeof(decoder_options)); + status = WebPDecode(out_data, out_size, &dec_config); + if (status != VP8_STATUS_OK && status != VP8_STATUS_OUT_OF_MEMORY && + status != VP8_STATUS_USER_ABORT) { + fprintf(stderr, "WebPDecode failed. status: %d.\n", status); + abort(); + } +} + +void EncDecTest(bool use_argb, int source_image_index, WebPConfig config, + int optimization_index, + const fuzz_utils::CropOrScaleParams& crop_or_scale_params, + int colorspace, + const fuzz_utils::WebPDecoderOptionsCpp& decoder_options) { + fuzz_utils::SetOptimization(default_VP8GetCPUInfo, optimization_index); + + // Init the source picture. + WebPPicture pic = fuzz_utils::GetSourcePicture(source_image_index, use_argb); + std::unique_ptr pic_owner(&pic); + WebPMemoryWriter memory_writer; + WebPMemoryWriterInit(&memory_writer); + std::unique_ptr + memory_writer_owner(&memory_writer); + + Enc(crop_or_scale_params, config, pic, memory_writer); + + // Try decoding the result. + const uint8_t* const out_data = memory_writer.mem; + const size_t out_size = memory_writer.size; + WebPDecoderConfig dec_config; + std::unique_ptr + dec_config_owner(&dec_config); + if (!WebPInitDecoderConfig(&dec_config)) { + fprintf(stderr, "WebPInitDecoderConfig failed.\n"); + abort(); + } + + dec_config.output.colorspace = static_cast(colorspace); + std::memcpy(&dec_config.options, &decoder_options, sizeof(decoder_options)); + const VP8StatusCode status = WebPDecode(out_data, out_size, &dec_config); + if (status != VP8_STATUS_OK && status != VP8_STATUS_OUT_OF_MEMORY && + status != VP8_STATUS_USER_ABORT && status != VP8_STATUS_INVALID_PARAM) { + fprintf(stderr, "WebPDecode failed. status: %d.\n", status); + abort(); + } } } // namespace +FUZZ_TEST(EncDec, EncDecValidTest) + .WithDomains(/*use_argb=*/fuzztest::Arbitrary(), + /*source_image_index=*/ + fuzztest::InRange(0, fuzz_utils::kNumSourceImages - 1), + fuzz_utils::ArbitraryWebPConfig(), + /*optimization_index=*/ + fuzztest::InRange(0, + fuzz_utils::kMaxOptimizationIndex), + fuzz_utils::ArbitraryCropOrScaleParams(), + /*colorspace=*/fuzztest::InRange(0, MODE_LAST - 1), + fuzz_utils::ArbitraryValidWebPDecoderOptions()); + FUZZ_TEST(EncDec, EncDecTest) .WithDomains(/*use_argb=*/fuzztest::Arbitrary(), /*source_image_index=*/ @@ -150,4 +224,6 @@ FUZZ_TEST(EncDec, EncDecTest) /*optimization_index=*/ fuzztest::InRange(0, fuzz_utils::kMaxOptimizationIndex), - fuzz_utils::ArbitraryCropOrScaleParams()); + fuzz_utils::ArbitraryCropOrScaleParams(), + /*colorspace=*/fuzztest::Arbitrary(), + fuzz_utils::ArbitraryWebPDecoderOptions()); diff --git a/tests/fuzzer/fuzz_utils.h b/tests/fuzzer/fuzz_utils.h index 7cff43e1..07415ec1 100644 --- a/tests/fuzzer/fuzz_utils.h +++ b/tests/fuzzer/fuzz_utils.h @@ -17,9 +17,12 @@ #ifndef WEBP_TESTS_FUZZER_FUZZ_UTILS_H_ #define WEBP_TESTS_FUZZER_FUZZ_UTILS_H_ +#include +#include #include #include #include +#include #include #include #include @@ -31,6 +34,7 @@ #include "./img_peak.h" #include "fuzztest/fuzztest.h" #include "src/dsp/cpu.h" +#include "src/webp/decode.h" #include "src/webp/encode.h" #include "src/webp/types.h" @@ -79,6 +83,17 @@ constexpr size_t kNumSourceImages = WebPPicture GetSourcePicture(int image_index, bool use_argb); +// Struct to use in a unique_ptr to free the memory. +struct UniquePtrDeleter { + void operator()(WebPMemoryWriter* writer) const { + WebPMemoryWriterClear(writer); + } + void operator()(WebPPicture* pic) const { WebPPictureFree(pic); } + void operator()(WebPDecoderConfig* config) const { + WebPFreeDecBuffer(&config->output); + } +}; + static inline auto ArbitraryWebPConfig() { return fuzztest::Map( [](int lossless, int quality, int method, int image_hint, int segments, @@ -89,7 +104,7 @@ static inline auto ArbitraryWebPConfig() { int thread_level, int low_memory, int near_lossless, int exact, int use_delta_palette, int use_sharp_yuv) -> WebPConfig { WebPConfig config; - if (!WebPConfigInit(&config)) abort(); + if (!WebPConfigInit(&config)) assert(false); config.lossless = lossless; config.quality = quality; config.method = method; @@ -115,7 +130,7 @@ static inline auto ArbitraryWebPConfig() { config.exact = exact; config.use_delta_palette = use_delta_palette; config.use_sharp_yuv = use_sharp_yuv; - if (!WebPValidateConfig(&config)) abort(); + if (!WebPValidateConfig(&config)) assert(false); return config; }, /*lossless=*/fuzztest::InRange(0, 1), @@ -144,6 +159,114 @@ static inline auto ArbitraryWebPConfig() { /*use_sharp_yuv=*/fuzztest::InRange(0, 1)); } +// Like WebPDecoderOptions but with no C array. +// This can be removed once b/294098900 is fixed. +struct WebPDecoderOptionsCpp { + int bypass_filtering; + int no_fancy_upsampling; + int use_cropping; + int crop_left, crop_top; + + int crop_width, crop_height; + int use_scaling; + int scaled_width, scaled_height; + + int use_threads; + int dithering_strength; + int flip; + int alpha_dithering_strength; + + std::array pad; +}; + +static inline auto ArbitraryValidWebPDecoderOptions() { + return fuzztest::Map( + [](int bypass_filtering, int no_fancy_upsampling, int use_cropping, + int crop_left, int crop_top, int crop_width, int crop_height, + int use_scaling, int scaled_width, int scaled_height, int use_threads, + int dithering_strength, int flip, + int alpha_dithering_strength) -> WebPDecoderOptionsCpp { + WebPDecoderOptions options; + options.bypass_filtering = bypass_filtering; + options.no_fancy_upsampling = no_fancy_upsampling; + options.use_cropping = use_cropping; + options.crop_left = crop_left; + options.crop_top = crop_top; + options.crop_width = crop_width; + options.crop_height = crop_height; + options.use_scaling = use_scaling; + options.scaled_width = scaled_width; + options.scaled_height = scaled_height; + options.use_threads = use_threads; + options.dithering_strength = dithering_strength; + options.flip = flip; + options.alpha_dithering_strength = alpha_dithering_strength; + WebPDecoderConfig config; + if (!WebPInitDecoderConfig(&config)) assert(false); + config.options = options; + if (!WebPValidateDecoderConfig(&config)) assert(false); + WebPDecoderOptionsCpp options_cpp; + std::memcpy(&options_cpp, &options, sizeof(options)); + return options_cpp; + }, + /*bypass_filtering=*/fuzztest::InRange(0, 1), + /*no_fancy_upsampling=*/fuzztest::InRange(0, 1), + /*use_cropping=*/fuzztest::InRange(0, 1), + /*crop_left=*/fuzztest::InRange(0, 10), + /*crop_top=*/fuzztest::InRange(0, 10), + /*crop_width=*/fuzztest::InRange(1, 10), + /*crop_height=*/fuzztest::InRange(1, 10), + /*use_scaling=*/fuzztest::InRange(0, 1), + /*scaled_width=*/fuzztest::InRange(1, 10), + /*scaled_height=*/fuzztest::InRange(1, 10), + /*use_threads=*/fuzztest::InRange(0, 1), + /*dithering_strength=*/fuzztest::InRange(0, 100), + /*flip=*/fuzztest::InRange(0, 1), + /*alpha_dithering_strength=*/fuzztest::InRange(0, 100)); +} + +static inline auto ArbitraryWebPDecoderOptions() { + return fuzztest::Map( + [](int bypass_filtering, int no_fancy_upsampling, int use_cropping, + int crop_left, int crop_top, int crop_width, int crop_height, + int use_scaling, int scaled_width, int scaled_height, int use_threads, + int dithering_strength, int flip, + int alpha_dithering_strength) -> WebPDecoderOptionsCpp { + WebPDecoderOptions options; + options.bypass_filtering = bypass_filtering; + options.no_fancy_upsampling = no_fancy_upsampling; + options.use_cropping = use_cropping; + options.crop_left = crop_left; + options.crop_top = crop_top; + options.crop_width = crop_width; + options.crop_height = crop_height; + options.use_scaling = use_scaling; + options.scaled_width = scaled_width; + options.scaled_height = scaled_height; + options.use_threads = use_threads; + options.dithering_strength = dithering_strength; + options.flip = flip; + options.alpha_dithering_strength = alpha_dithering_strength; + WebPDecoderOptionsCpp options_cpp; + std::memcpy(&options_cpp, &options, sizeof(options)); + return options_cpp; + }, + /*bypass_filtering=*/fuzztest::Arbitrary(), + /*no_fancy_upsampling=*/fuzztest::Arbitrary(), + /*use_cropping=*/fuzztest::Arbitrary(), + /*crop_left=*/fuzztest::Arbitrary(), + /*crop_top=*/fuzztest::Arbitrary(), + /*crop_width=*/fuzztest::Arbitrary(), + /*crop_height=*/fuzztest::Arbitrary(), + /*use_scaling=*/fuzztest::Arbitrary(), + /*scaled_width=*/fuzztest::Arbitrary(), + /*scaled_height=*/fuzztest::Arbitrary(), + /*use_threads=*/fuzztest::Arbitrary(), + /*dithering_strength=*/fuzztest::Arbitrary(), + /*flip=*/fuzztest::Arbitrary(), + /*alpha_dithering_strength=*/fuzztest::Arbitrary()); +} + struct CropOrScaleParams { bool alter_input; bool crop_or_scale;